@quanticjs/core 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/dist/bootstrap/bootstrapService.d.ts +8 -0
  2. package/dist/bootstrap/bootstrapService.js +58 -0
  3. package/dist/cqrs/PipelineExecutor.d.ts +31 -0
  4. package/dist/cqrs/PipelineExecutor.js +81 -0
  5. package/dist/cqrs/behaviors/CacheBehavior.d.ts +9 -0
  6. package/dist/cqrs/behaviors/CacheBehavior.js +68 -0
  7. package/dist/cqrs/behaviors/DistributedLockBehavior.d.ts +12 -0
  8. package/dist/cqrs/behaviors/DistributedLockBehavior.js +90 -0
  9. package/dist/cqrs/behaviors/FeatureFlagBehavior.d.ts +8 -0
  10. package/dist/cqrs/behaviors/FeatureFlagBehavior.js +58 -0
  11. package/dist/cqrs/behaviors/InvalidateCacheBehavior.d.ts +9 -0
  12. package/dist/cqrs/behaviors/InvalidateCacheBehavior.js +61 -0
  13. package/dist/cqrs/behaviors/LogBehavior.d.ts +9 -0
  14. package/dist/cqrs/behaviors/LogBehavior.js +125 -0
  15. package/dist/cqrs/behaviors/PerformanceBehavior.d.ts +12 -0
  16. package/dist/cqrs/behaviors/PerformanceBehavior.js +56 -0
  17. package/dist/cqrs/behaviors/TransactionalBehavior.d.ts +18 -0
  18. package/dist/cqrs/behaviors/TransactionalBehavior.js +77 -0
  19. package/dist/cqrs/behaviors/ValidationBehavior.d.ts +4 -0
  20. package/dist/cqrs/behaviors/ValidationBehavior.js +33 -0
  21. package/dist/cqrs/behaviors/WorkflowBehavior.d.ts +8 -0
  22. package/dist/cqrs/behaviors/WorkflowBehavior.js +61 -0
  23. package/dist/cqrs/constants.d.ts +2 -0
  24. package/dist/cqrs/constants.js +5 -0
  25. package/dist/cqrs/decorators/Cache.decorator.d.ts +13 -0
  26. package/dist/cqrs/decorators/Cache.decorator.js +18 -0
  27. package/dist/cqrs/decorators/DistributedLock.decorator.d.ts +15 -0
  28. package/dist/cqrs/decorators/DistributedLock.decorator.js +23 -0
  29. package/dist/cqrs/decorators/FeatureFlag.decorator.d.ts +9 -0
  30. package/dist/cqrs/decorators/FeatureFlag.decorator.js +14 -0
  31. package/dist/cqrs/decorators/InvalidateCache.decorator.d.ts +6 -0
  32. package/dist/cqrs/decorators/InvalidateCache.decorator.js +14 -0
  33. package/dist/cqrs/decorators/IsolatedTransaction.decorator.d.ts +14 -0
  34. package/dist/cqrs/decorators/IsolatedTransaction.decorator.js +25 -0
  35. package/dist/cqrs/decorators/Log.decorator.d.ts +11 -0
  36. package/dist/cqrs/decorators/Log.decorator.js +18 -0
  37. package/dist/cqrs/decorators/Validate.decorator.d.ts +24 -0
  38. package/dist/cqrs/decorators/Validate.decorator.js +37 -0
  39. package/dist/cqrs/decorators/Workflow.decorator.d.ts +8 -0
  40. package/dist/cqrs/decorators/Workflow.decorator.js +14 -0
  41. package/dist/cqrs/interfaces/WorkflowEngine.d.ts +14 -0
  42. package/dist/cqrs/interfaces/WorkflowEngine.js +4 -0
  43. package/dist/cqrs/pipeline/QuanticCommandBus.d.ts +37 -0
  44. package/dist/cqrs/pipeline/QuanticCommandBus.js +99 -0
  45. package/dist/cqrs/pipeline/QuanticQueryBus.d.ts +28 -0
  46. package/dist/cqrs/pipeline/QuanticQueryBus.js +78 -0
  47. package/dist/cqrs/pipeline/runPipeline.d.ts +3 -0
  48. package/dist/cqrs/pipeline/runPipeline.js +12 -0
  49. package/dist/cqrs/transaction/TransactionContext.d.ts +18 -0
  50. package/dist/cqrs/transaction/TransactionContext.js +26 -0
  51. package/dist/cqrs/transaction/getTransactionalRepo.d.ts +16 -0
  52. package/dist/cqrs/transaction/getTransactionalRepo.js +22 -0
  53. package/dist/cqrs/validation/ICommandValidator.d.ts +48 -0
  54. package/dist/cqrs/validation/ICommandValidator.js +21 -0
  55. package/dist/entities/BaseEntity.d.ts +5 -0
  56. package/dist/entities/BaseEntity.js +31 -0
  57. package/dist/entities/TenantBaseEntity.d.ts +4 -0
  58. package/dist/entities/TenantBaseEntity.js +22 -0
  59. package/dist/events/DomainEvent.d.ts +14 -0
  60. package/dist/events/DomainEvent.js +27 -0
  61. package/dist/events/OutboxEvent.entity.d.ts +18 -0
  62. package/dist/events/OutboxEvent.entity.js +87 -0
  63. package/dist/events/OutboxPublisherService.d.ts +14 -0
  64. package/dist/events/OutboxPublisherService.js +104 -0
  65. package/dist/events/RedisStreamConsumer.d.ts +43 -0
  66. package/dist/events/RedisStreamConsumer.js +158 -0
  67. package/dist/events/RedisStreamPublisher.d.ts +9 -0
  68. package/dist/events/RedisStreamPublisher.js +60 -0
  69. package/dist/filters/GlobalExceptionFilter.d.ts +11 -0
  70. package/dist/filters/GlobalExceptionFilter.js +102 -0
  71. package/dist/guards/JwtAuthGuard.d.ts +10 -0
  72. package/dist/guards/JwtAuthGuard.js +46 -0
  73. package/dist/guards/JwtStrategy.d.ts +22 -0
  74. package/dist/guards/JwtStrategy.js +47 -0
  75. package/dist/guards/RolesGuard.d.ts +8 -0
  76. package/dist/guards/RolesGuard.js +52 -0
  77. package/dist/index.d.ts +69 -0
  78. package/dist/index.js +146 -0
  79. package/dist/interceptors/ResultInterceptor.d.ts +7 -0
  80. package/dist/interceptors/ResultInterceptor.js +88 -0
  81. package/dist/lifecycle/GracefulShutdownService.d.ts +24 -0
  82. package/dist/lifecycle/GracefulShutdownService.js +93 -0
  83. package/dist/logging/pino-config.d.ts +35 -0
  84. package/dist/logging/pino-config.js +79 -0
  85. package/dist/metrics/MetricsController.d.ts +7 -0
  86. package/dist/metrics/MetricsController.js +42 -0
  87. package/dist/metrics/MetricsService.d.ts +13 -0
  88. package/dist/metrics/MetricsService.js +58 -0
  89. package/dist/middleware/CorrelationIdMiddleware.d.ts +4 -0
  90. package/dist/middleware/CorrelationIdMiddleware.js +33 -0
  91. package/dist/middleware/CorrelationStore.d.ts +11 -0
  92. package/dist/middleware/CorrelationStore.js +9 -0
  93. package/dist/middleware/TenantContextMiddleware.d.ts +7 -0
  94. package/dist/middleware/TenantContextMiddleware.js +27 -0
  95. package/dist/middleware/TenantStore.d.ts +9 -0
  96. package/dist/middleware/TenantStore.js +9 -0
  97. package/dist/redis/redis.module.d.ts +8 -0
  98. package/dist/redis/redis.module.js +49 -0
  99. package/dist/resilience/CircuitBreakerFactory.d.ts +12 -0
  100. package/dist/resilience/CircuitBreakerFactory.js +22 -0
  101. package/dist/result/Result.d.ts +26 -0
  102. package/dist/result/Result.js +62 -0
  103. package/dist/shared-kernel.module.d.ts +13 -0
  104. package/dist/shared-kernel.module.js +87 -0
  105. package/dist/subscribers/TenantSubscriber.d.ts +20 -0
  106. package/dist/subscribers/TenantSubscriber.js +52 -0
  107. package/dist/testing/TestingModuleFactory.d.ts +23 -0
  108. package/dist/testing/TestingModuleFactory.js +63 -0
  109. package/dist/testing/index.d.ts +2 -0
  110. package/dist/testing/index.js +7 -0
  111. package/dist/testing/mocks.d.ts +34 -0
  112. package/dist/testing/mocks.js +62 -0
  113. package/dist/unleash/initial-flags.d.ts +7 -0
  114. package/dist/unleash/initial-flags.js +9 -0
  115. package/dist/unleash/unleash.module.d.ts +9 -0
  116. package/dist/unleash/unleash.module.js +47 -0
  117. package/package.json +140 -0
  118. package/src/bootstrap/bootstrapService.ts +72 -0
  119. package/src/cqrs/behaviors/CacheBehavior.spec.ts +63 -0
  120. package/src/cqrs/behaviors/CacheBehavior.ts +54 -0
  121. package/src/cqrs/behaviors/DistributedLockBehavior.ts +88 -0
  122. package/src/cqrs/behaviors/FeatureFlagBehavior.ts +46 -0
  123. package/src/cqrs/behaviors/InvalidateCacheBehavior.spec.ts +89 -0
  124. package/src/cqrs/behaviors/InvalidateCacheBehavior.ts +50 -0
  125. package/src/cqrs/behaviors/LogBehavior.spec.ts +55 -0
  126. package/src/cqrs/behaviors/LogBehavior.ts +121 -0
  127. package/src/cqrs/behaviors/PerformanceBehavior.spec.ts +48 -0
  128. package/src/cqrs/behaviors/PerformanceBehavior.ts +43 -0
  129. package/src/cqrs/behaviors/TransactionalBehavior.ts +64 -0
  130. package/src/cqrs/behaviors/ValidationBehavior.spec.ts +114 -0
  131. package/src/cqrs/behaviors/ValidationBehavior.ts +29 -0
  132. package/src/cqrs/behaviors/WorkflowBehavior.spec.ts +97 -0
  133. package/src/cqrs/behaviors/WorkflowBehavior.ts +62 -0
  134. package/src/cqrs/constants.ts +2 -0
  135. package/src/cqrs/decorators/Cache.decorator.ts +24 -0
  136. package/src/cqrs/decorators/DistributedLock.decorator.ts +34 -0
  137. package/src/cqrs/decorators/FeatureFlag.decorator.ts +23 -0
  138. package/src/cqrs/decorators/InvalidateCache.decorator.spec.ts +20 -0
  139. package/src/cqrs/decorators/InvalidateCache.decorator.ts +17 -0
  140. package/src/cqrs/decorators/IsolatedTransaction.decorator.ts +24 -0
  141. package/src/cqrs/decorators/Log.decorator.ts +22 -0
  142. package/src/cqrs/decorators/Validate.decorator.ts +39 -0
  143. package/src/cqrs/decorators/Workflow.decorator.ts +22 -0
  144. package/src/cqrs/interfaces/WorkflowEngine.ts +19 -0
  145. package/src/cqrs/pipeline/QuanticCommandBus.ts +69 -0
  146. package/src/cqrs/pipeline/QuanticQueryBus.ts +56 -0
  147. package/src/cqrs/pipeline/runPipeline.ts +22 -0
  148. package/src/cqrs/transaction/TransactionContext.ts +26 -0
  149. package/src/cqrs/transaction/getTransactionalRepo.ts +23 -0
  150. package/src/cqrs/validation/ICommandValidator.ts +55 -0
  151. package/src/entities/BaseEntity.ts +16 -0
  152. package/src/entities/TenantBaseEntity.ts +7 -0
  153. package/src/events/DomainEvent.ts +27 -0
  154. package/src/events/OutboxEvent.entity.ts +56 -0
  155. package/src/events/OutboxPublisherService.ts +94 -0
  156. package/src/events/RedisStreamConsumer.ts +172 -0
  157. package/src/events/RedisStreamPublisher.ts +54 -0
  158. package/src/filters/GlobalExceptionFilter.ts +125 -0
  159. package/src/guards/JwtAuthGuard.ts +29 -0
  160. package/src/guards/JwtStrategy.ts +41 -0
  161. package/src/guards/RolesGuard.ts +39 -0
  162. package/src/index.ts +118 -0
  163. package/src/interceptors/ResultInterceptor.ts +93 -0
  164. package/src/lifecycle/GracefulShutdownService.ts +77 -0
  165. package/src/logging/pino-config.ts +80 -0
  166. package/src/metrics/MetricsController.ts +17 -0
  167. package/src/metrics/MetricsService.ts +55 -0
  168. package/src/middleware/CorrelationIdMiddleware.ts +27 -0
  169. package/src/middleware/CorrelationStore.ts +13 -0
  170. package/src/middleware/TenantContextMiddleware.ts +21 -0
  171. package/src/middleware/TenantStore.ts +11 -0
  172. package/src/redis/redis.module.ts +41 -0
  173. package/src/resilience/CircuitBreakerFactory.ts +33 -0
  174. package/src/result/Result.ts +66 -0
  175. package/src/shared-kernel.module.ts +87 -0
  176. package/src/subscribers/TenantSubscriber.ts +47 -0
  177. package/src/testing/TestingModuleFactory.ts +78 -0
  178. package/src/testing/index.ts +2 -0
  179. package/src/testing/mocks.ts +59 -0
  180. package/src/unleash/unleash.module.ts +45 -0
  181. package/tsconfig.json +22 -0
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TestingModuleFactory = exports.createMockRedisClient = exports.createMockRepository = void 0;
4
+ const testing_1 = require("@nestjs/testing");
5
+ const cqrs_1 = require("@nestjs/cqrs");
6
+ const typeorm_1 = require("@nestjs/typeorm");
7
+ const common_1 = require("@nestjs/common");
8
+ const QuanticCommandBus_1 = require("../cqrs/pipeline/QuanticCommandBus");
9
+ const QuanticQueryBus_1 = require("../cqrs/pipeline/QuanticQueryBus");
10
+ const LogBehavior_1 = require("../cqrs/behaviors/LogBehavior");
11
+ const ValidationBehavior_1 = require("../cqrs/behaviors/ValidationBehavior");
12
+ const CacheBehavior_1 = require("../cqrs/behaviors/CacheBehavior");
13
+ const DistributedLockBehavior_1 = require("../cqrs/behaviors/DistributedLockBehavior");
14
+ const TransactionalBehavior_1 = require("../cqrs/behaviors/TransactionalBehavior");
15
+ const PerformanceBehavior_1 = require("../cqrs/behaviors/PerformanceBehavior");
16
+ const MetricsService_1 = require("../metrics/MetricsService");
17
+ const constants_1 = require("../cqrs/constants");
18
+ const mocks_1 = require("./mocks");
19
+ Object.defineProperty(exports, "createMockRepository", { enumerable: true, get: function () { return mocks_1.createMockRepository; } });
20
+ Object.defineProperty(exports, "createMockRedisClient", { enumerable: true, get: function () { return mocks_1.createMockRedisClient; } });
21
+ /**
22
+ * Factory for bootstrapping a NestJS TestingModule preconfigured
23
+ * with mocked repositories, Redis, and optionally the CQRS pipeline.
24
+ */
25
+ class TestingModuleFactory {
26
+ static create(options = {}) {
27
+ const { providers = [], entities = [], overrides = [], withPipeline = false } = options;
28
+ const mockRepos = entities.map((entity) => ({
29
+ provide: (0, typeorm_1.getRepositoryToken)(entity),
30
+ useValue: (0, mocks_1.createMockRepository)(),
31
+ }));
32
+ const mockRedis = { provide: constants_1.REDIS_CLIENT, useValue: (0, mocks_1.createMockRedisClient)() };
33
+ const pipelineProviders = withPipeline
34
+ ? [
35
+ LogBehavior_1.LogBehavior,
36
+ ValidationBehavior_1.ValidationBehavior,
37
+ CacheBehavior_1.CacheBehavior,
38
+ DistributedLockBehavior_1.DistributedLockBehavior,
39
+ TransactionalBehavior_1.TransactionalBehavior,
40
+ PerformanceBehavior_1.PerformanceBehavior,
41
+ MetricsService_1.MetricsService,
42
+ QuanticCommandBus_1.QuanticCommandBus,
43
+ QuanticQueryBus_1.QuanticQueryBus,
44
+ ]
45
+ : [];
46
+ let builder = testing_1.Test.createTestingModule({
47
+ imports: [cqrs_1.CqrsModule.forRoot()],
48
+ providers: [
49
+ ...mockRepos,
50
+ mockRedis,
51
+ ...pipelineProviders,
52
+ ...providers,
53
+ ],
54
+ });
55
+ for (const override of overrides) {
56
+ builder = builder.overrideProvider(override.provide).useValue(override.useValue);
57
+ }
58
+ // Silence NestJS logs during tests
59
+ common_1.Logger.overrideLogger(['error']);
60
+ return builder;
61
+ }
62
+ }
63
+ exports.TestingModuleFactory = TestingModuleFactory;
@@ -0,0 +1,2 @@
1
+ export { TestingModuleFactory, createMockRepository, createMockRedisClient } from './TestingModuleFactory';
2
+ export type { TestingModuleOptions } from './TestingModuleFactory';
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createMockRedisClient = exports.createMockRepository = exports.TestingModuleFactory = void 0;
4
+ var TestingModuleFactory_1 = require("./TestingModuleFactory");
5
+ Object.defineProperty(exports, "TestingModuleFactory", { enumerable: true, get: function () { return TestingModuleFactory_1.TestingModuleFactory; } });
6
+ Object.defineProperty(exports, "createMockRepository", { enumerable: true, get: function () { return TestingModuleFactory_1.createMockRepository; } });
7
+ Object.defineProperty(exports, "createMockRedisClient", { enumerable: true, get: function () { return TestingModuleFactory_1.createMockRedisClient; } });
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Creates a mock repository with common TypeORM repository methods stubbed.
3
+ */
4
+ export declare function createMockRepository<T = any>(): {
5
+ find: jest.Mock<any, any, any>;
6
+ findOne: jest.Mock<any, any, any>;
7
+ findOneBy: jest.Mock<any, any, any>;
8
+ findAndCount: jest.Mock<any, any, any>;
9
+ save: jest.Mock<any, any, any>;
10
+ create: jest.Mock<any, any, any>;
11
+ update: jest.Mock<any, any, any>;
12
+ delete: jest.Mock<any, any, any>;
13
+ remove: jest.Mock<any, any, any>;
14
+ count: jest.Mock<any, any, any>;
15
+ createQueryBuilder: jest.Mock<{
16
+ where: jest.Mock<any, any, any>;
17
+ andWhere: jest.Mock<any, any, any>;
18
+ orderBy: jest.Mock<any, any, any>;
19
+ addOrderBy: jest.Mock<any, any, any>;
20
+ take: jest.Mock<any, any, any>;
21
+ skip: jest.Mock<any, any, any>;
22
+ leftJoinAndSelect: jest.Mock<any, any, any>;
23
+ getMany: jest.Mock<any, any, any>;
24
+ getOne: jest.Mock<any, any, any>;
25
+ getManyAndCount: jest.Mock<any, any, any>;
26
+ }, [], any>;
27
+ manager: {
28
+ transaction: jest.Mock<any, any, any>;
29
+ };
30
+ };
31
+ /**
32
+ * Creates a mock Redis client with common ioredis methods stubbed.
33
+ */
34
+ export declare function createMockRedisClient(): Record<string, jest.Mock<any, any, any>>;
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createMockRepository = createMockRepository;
4
+ exports.createMockRedisClient = createMockRedisClient;
5
+ /**
6
+ * Creates a mock repository with common TypeORM repository methods stubbed.
7
+ */
8
+ function createMockRepository() {
9
+ return {
10
+ find: jest.fn(),
11
+ findOne: jest.fn(),
12
+ findOneBy: jest.fn(),
13
+ findAndCount: jest.fn(),
14
+ save: jest.fn().mockImplementation((entity) => Promise.resolve(entity)),
15
+ create: jest.fn().mockImplementation((dto) => dto),
16
+ update: jest.fn(),
17
+ delete: jest.fn(),
18
+ remove: jest.fn(),
19
+ count: jest.fn(),
20
+ createQueryBuilder: jest.fn(() => ({
21
+ where: jest.fn().mockReturnThis(),
22
+ andWhere: jest.fn().mockReturnThis(),
23
+ orderBy: jest.fn().mockReturnThis(),
24
+ addOrderBy: jest.fn().mockReturnThis(),
25
+ take: jest.fn().mockReturnThis(),
26
+ skip: jest.fn().mockReturnThis(),
27
+ leftJoinAndSelect: jest.fn().mockReturnThis(),
28
+ getMany: jest.fn().mockResolvedValue([]),
29
+ getOne: jest.fn().mockResolvedValue(null),
30
+ getManyAndCount: jest.fn().mockResolvedValue([[], 0]),
31
+ })),
32
+ manager: {
33
+ transaction: jest.fn().mockImplementation((cb) => cb({
34
+ save: jest.fn().mockImplementation((entity) => Promise.resolve(entity)),
35
+ findOne: jest.fn(),
36
+ })),
37
+ },
38
+ };
39
+ }
40
+ /**
41
+ * Creates a mock Redis client with common ioredis methods stubbed.
42
+ */
43
+ function createMockRedisClient() {
44
+ const client = {
45
+ get: jest.fn().mockResolvedValue(null),
46
+ set: jest.fn().mockResolvedValue('OK'),
47
+ del: jest.fn().mockResolvedValue(1),
48
+ setex: jest.fn().mockResolvedValue('OK'),
49
+ exists: jest.fn().mockResolvedValue(0),
50
+ xadd: jest.fn().mockResolvedValue('1-0'),
51
+ xreadgroup: jest.fn().mockResolvedValue(null),
52
+ xack: jest.fn().mockResolvedValue(1),
53
+ xgroup: jest.fn().mockResolvedValue('OK'),
54
+ quit: jest.fn().mockResolvedValue('OK'),
55
+ disconnect: jest.fn(),
56
+ on: jest.fn().mockReturnThis(),
57
+ duplicate: jest.fn(),
58
+ };
59
+ // duplicate() returns a fresh mock with the same shape
60
+ client.duplicate.mockImplementation(() => createMockRedisClient());
61
+ return client;
62
+ }
@@ -0,0 +1,7 @@
1
+ export declare const FEATURE_FLAGS: {
2
+ readonly ENABLE_PREMIUM_MODE: "enable-premium-mode";
3
+ readonly ENABLE_AGENCY_PLAN: "enable-agency-plan";
4
+ readonly ENABLE_BACKLOG_GENERATION: "enable-backlog-generation";
5
+ };
6
+ export type FeatureFlagName = (typeof FEATURE_FLAGS)[keyof typeof FEATURE_FLAGS];
7
+ //# sourceMappingURL=initial-flags.d.ts.map
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FEATURE_FLAGS = void 0;
4
+ exports.FEATURE_FLAGS = {
5
+ ENABLE_PREMIUM_MODE: 'enable-premium-mode',
6
+ ENABLE_AGENCY_PLAN: 'enable-agency-plan',
7
+ ENABLE_BACKLOG_GENERATION: 'enable-backlog-generation',
8
+ };
9
+ //# sourceMappingURL=initial-flags.js.map
@@ -0,0 +1,9 @@
1
+ import { DynamicModule } from '@nestjs/common';
2
+ export interface UnleashModuleOptions {
3
+ url?: string;
4
+ appName?: string;
5
+ customHeaders?: Record<string, string>;
6
+ }
7
+ export declare class UnleashModule {
8
+ static forRoot(options?: UnleashModuleOptions): DynamicModule;
9
+ }
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var UnleashModule_1;
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.UnleashModule = void 0;
11
+ const common_1 = require("@nestjs/common");
12
+ const unleash_client_1 = require("unleash-client");
13
+ const FeatureFlagBehavior_1 = require("../cqrs/behaviors/FeatureFlagBehavior");
14
+ let UnleashModule = UnleashModule_1 = class UnleashModule {
15
+ static forRoot(options) {
16
+ const unleashProvider = {
17
+ provide: unleash_client_1.Unleash,
18
+ useFactory: () => {
19
+ const logger = new common_1.Logger('UnleashModule');
20
+ const url = options?.url || process.env['UNLEASH_URL'] || 'http://localhost:4242/api';
21
+ const appName = options?.appName || process.env['UNLEASH_APP_NAME'] || 'arex';
22
+ const token = options?.customHeaders?.['Authorization'] ||
23
+ process.env['UNLEASH_API_TOKEN'] ||
24
+ '*:*.unleash-insecure-api-token';
25
+ logger.log(`Connecting to Unleash at ${url}`);
26
+ return (0, unleash_client_1.initialize)({
27
+ url,
28
+ appName,
29
+ customHeaders: {
30
+ Authorization: token,
31
+ },
32
+ refreshInterval: 10000,
33
+ });
34
+ },
35
+ };
36
+ return {
37
+ module: UnleashModule_1,
38
+ providers: [unleashProvider, FeatureFlagBehavior_1.FeatureFlagBehavior],
39
+ exports: [unleash_client_1.Unleash, FeatureFlagBehavior_1.FeatureFlagBehavior],
40
+ };
41
+ }
42
+ };
43
+ exports.UnleashModule = UnleashModule;
44
+ exports.UnleashModule = UnleashModule = UnleashModule_1 = __decorate([
45
+ (0, common_1.Global)(),
46
+ (0, common_1.Module)({})
47
+ ], UnleashModule);
package/package.json ADDED
@@ -0,0 +1,140 @@
1
+ {
2
+ "name": "@quanticjs/core",
3
+ "version": "1.1.1",
4
+ "description": "NestJS CQRS framework — Result<T>, pipeline behaviors, base entities, multi-tenancy, Redis Streams, observability",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist",
9
+ "src",
10
+ "tsconfig.json"
11
+ ],
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "https://github.com/quanticjs/quanticjs-backend.git"
15
+ },
16
+ "publishConfig": {
17
+ "registry": "https://registry.npmjs.org",
18
+ "access": "public"
19
+ },
20
+ "license": "MIT",
21
+ "scripts": {
22
+ "build": "tsc -p tsconfig.json",
23
+ "lint": "eslint 'src/**/*.ts'",
24
+ "test": "jest --passWithNoTests"
25
+ },
26
+ "dependencies": {
27
+ "cockatiel": "^3.2.1",
28
+ "uuid": "^11.1.0",
29
+ "zod": "^3.25.76"
30
+ },
31
+ "peerDependencies": {
32
+ "@nestjs/common": "^11.0.0",
33
+ "@nestjs/passport": "^11.0.0",
34
+ "@nestjs/swagger": "^11.0.0",
35
+ "@nestjs/terminus": "^11.0.0",
36
+ "@nestjs/throttler": "^6.0.0",
37
+ "helmet": "^8.0.0",
38
+ "ioredis": "^5.0.0",
39
+ "jwks-rsa": "^4.0.0",
40
+ "nestjs-pino": "^4.0.0",
41
+ "passport": "^0.7.0",
42
+ "passport-jwt": "^4.0.0",
43
+ "pino": "^10.0.0",
44
+ "pino-http": "^11.0.0",
45
+ "pino-seq": "^3.0.0 || ^4.0.0",
46
+ "prom-client": "^15.0.0",
47
+ "typeorm": "^0.3.20",
48
+ "unleash-client": "^6.0.0"
49
+ },
50
+ "peerDependenciesMeta": {
51
+ "@nestjs/common": {
52
+ "optional": false
53
+ },
54
+ "@nestjs/passport": {
55
+ "optional": false
56
+ },
57
+ "@nestjs/swagger": {
58
+ "optional": false
59
+ },
60
+ "@nestjs/terminus": {
61
+ "optional": false
62
+ },
63
+ "@nestjs/throttler": {
64
+ "optional": false
65
+ },
66
+ "helmet": {
67
+ "optional": false
68
+ },
69
+ "ioredis": {
70
+ "optional": false
71
+ },
72
+ "jwks-rsa": {
73
+ "optional": false
74
+ },
75
+ "nestjs-pino": {
76
+ "optional": false
77
+ },
78
+ "passport": {
79
+ "optional": false
80
+ },
81
+ "passport-jwt": {
82
+ "optional": false
83
+ },
84
+ "pino": {
85
+ "optional": false
86
+ },
87
+ "pino-http": {
88
+ "optional": false
89
+ },
90
+ "pino-seq": {
91
+ "optional": false
92
+ },
93
+ "prom-client": {
94
+ "optional": false
95
+ },
96
+ "typeorm": {
97
+ "optional": false
98
+ },
99
+ "unleash-client": {
100
+ "optional": false
101
+ }
102
+ },
103
+ "devDependencies": {
104
+ "@nestjs/common": "^11.1.19",
105
+ "@nestjs/core": "^11.1.19",
106
+ "@nestjs/cqrs": "^11.0.3",
107
+ "@nestjs/swagger": "^11.4.2",
108
+ "@nestjs/terminus": "^11.1.1",
109
+ "@nestjs/testing": "^11.1.19",
110
+ "@nestjs/throttler": "^6.5.0",
111
+ "@nestjs/typeorm": "^11.0.1",
112
+ "@types/cookie-parser": "^1.4.10",
113
+ "@types/express": "^5.0.6",
114
+ "@types/jest": "^29.5.0",
115
+ "@types/passport-jwt": "^4.0.1",
116
+ "@types/node": "^22.0.0",
117
+ "@types/uuid": "^10.0.0",
118
+ "cookie-parser": "^1.4.7",
119
+ "@nestjs/passport": "^11.0.5",
120
+ "eslint": "^9.0.0",
121
+ "express": "^5.2.1",
122
+ "helmet": "^8.1.0",
123
+ "ioredis": "^5.10.1",
124
+ "jest": "^29.7.0",
125
+ "jwks-rsa": "^4.0.1",
126
+ "passport": "^0.7.0",
127
+ "passport-jwt": "^4.0.1",
128
+ "nestjs-pino": "^4.6.1",
129
+ "pino": "^10.3.1",
130
+ "pino-http": "^11.0.0",
131
+ "pino-seq": "^4.0.0",
132
+ "prom-client": "^15.1.3",
133
+ "reflect-metadata": "^0.2.2",
134
+ "rxjs": "^7.8.2",
135
+ "ts-jest": "^29.2.0",
136
+ "typeorm": "^0.3.29",
137
+ "typescript": "^5.7.0",
138
+ "unleash-client": "^6.10.1"
139
+ }
140
+ }
@@ -0,0 +1,72 @@
1
+ import { INestApplication, ValidationPipe } from '@nestjs/common';
2
+ import { NestFactory } from '@nestjs/core';
3
+ import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
4
+ import { Logger } from 'nestjs-pino';
5
+ import helmet from 'helmet';
6
+ import cookieParser from 'cookie-parser';
7
+ import { GlobalExceptionFilter } from '../filters/GlobalExceptionFilter';
8
+
9
+ export interface BootstrapOptions {
10
+ module: any;
11
+ port: number;
12
+ serviceName: string;
13
+ rawBody?: boolean;
14
+ }
15
+
16
+ export async function bootstrapService(options: BootstrapOptions): Promise<INestApplication> {
17
+ const { module, port, serviceName, rawBody } = options;
18
+
19
+ const app = await NestFactory.create(module, {
20
+ bufferLogs: true,
21
+ ...(rawBody ? { rawBody: true } : {}),
22
+ });
23
+
24
+ app.useLogger(app.get(Logger));
25
+
26
+ // Security headers
27
+ app.use(helmet());
28
+
29
+ // Cookie parsing (required for OAuth flows)
30
+ app.use(cookieParser());
31
+
32
+ // CORS
33
+ const allowedOrigins = (process.env.CORS_ORIGINS || 'http://localhost:5173').split(',');
34
+ app.enableCors({
35
+ origin: allowedOrigins,
36
+ credentials: true,
37
+ methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
38
+ allowedHeaders: ['Content-Type', 'Authorization', 'X-Tenant-ID', 'X-Correlation-ID'],
39
+ exposedHeaders: ['X-Correlation-ID'],
40
+ maxAge: 86400,
41
+ });
42
+
43
+ // Global exception filter
44
+ app.useGlobalFilters(new GlobalExceptionFilter());
45
+
46
+ // Global validation pipe
47
+ app.useGlobalPipes(
48
+ new ValidationPipe({
49
+ whitelist: true,
50
+ forbidNonWhitelisted: true,
51
+ transform: true,
52
+ }),
53
+ );
54
+
55
+ // Swagger — disabled in production
56
+ if (process.env.NODE_ENV !== 'production') {
57
+ const config = new DocumentBuilder()
58
+ .setTitle(`AREX — ${serviceName}`)
59
+ .setDescription(`OpenAPI spec for ${serviceName}`)
60
+ .setVersion('1.0.0')
61
+ .addBearerAuth()
62
+ .build();
63
+ const document = SwaggerModule.createDocument(app, config);
64
+ SwaggerModule.setup('swagger', app, document);
65
+ }
66
+
67
+ // Enable graceful shutdown hooks (SIGTERM, SIGINT)
68
+ app.enableShutdownHooks();
69
+
70
+ await app.listen(port);
71
+ return app;
72
+ }
@@ -0,0 +1,63 @@
1
+ import { CacheBehavior } from './CacheBehavior';
2
+ import { Result } from '../../result/Result';
3
+ import { Cache } from '../decorators/Cache.decorator';
4
+
5
+ @Cache('test:{id}', { ttlSeconds: 60 })
6
+ class CachedQuery {
7
+ constructor(public readonly id: string) {}
8
+ }
9
+
10
+ class UncachedQuery {
11
+ constructor(public readonly id: string) {}
12
+ }
13
+
14
+ describe('CacheBehavior', () => {
15
+ let redis: { get: jest.Mock; set: jest.Mock };
16
+ let behavior: CacheBehavior;
17
+ const next = jest.fn();
18
+
19
+ beforeEach(() => {
20
+ redis = {
21
+ get: jest.fn().mockResolvedValue(null),
22
+ set: jest.fn().mockResolvedValue('OK'),
23
+ };
24
+ behavior = new CacheBehavior(redis as any);
25
+ next.mockReset().mockResolvedValue(Result.success({ data: 'fresh' }));
26
+ });
27
+
28
+ it('should skip cache for commands without @Cache decorator', async () => {
29
+ const result = await behavior.execute(new UncachedQuery('1'), next);
30
+
31
+ expect(result.isSuccess).toBe(true);
32
+ expect(redis.get).not.toHaveBeenCalled();
33
+ expect(next).toHaveBeenCalledTimes(1);
34
+ });
35
+
36
+ it('should return cached value on cache hit', async () => {
37
+ redis.get.mockResolvedValue(JSON.stringify({ data: 'cached' }));
38
+
39
+ const result = await behavior.execute(new CachedQuery('abc'), next);
40
+
41
+ expect(result.isSuccess).toBe(true);
42
+ expect(result.value).toEqual({ data: 'cached' });
43
+ expect(next).not.toHaveBeenCalled();
44
+ expect(redis.get).toHaveBeenCalledWith('test:abc');
45
+ });
46
+
47
+ it('should call handler and cache result on cache miss', async () => {
48
+ const result = await behavior.execute(new CachedQuery('abc'), next);
49
+
50
+ expect(result.isSuccess).toBe(true);
51
+ expect(result.value).toEqual({ data: 'fresh' });
52
+ expect(next).toHaveBeenCalledTimes(1);
53
+ expect(redis.set).toHaveBeenCalledWith('test:abc', JSON.stringify({ data: 'fresh' }), 'EX', 60);
54
+ });
55
+
56
+ it('should skip cache when redis is not available', async () => {
57
+ const noCacheBehavior = new CacheBehavior(undefined);
58
+ const result = await noCacheBehavior.execute(new CachedQuery('1'), next);
59
+
60
+ expect(result.isSuccess).toBe(true);
61
+ expect(next).toHaveBeenCalledTimes(1);
62
+ });
63
+ });
@@ -0,0 +1,54 @@
1
+ import { Injectable, Logger, Optional, Inject } from '@nestjs/common';
2
+ import { getCacheMetadata } from '../decorators/Cache.decorator';
3
+ import { Result } from '../../result/Result';
4
+ import { REDIS_CLIENT } from '../constants';
5
+ import type { Redis } from 'ioredis';
6
+
7
+ @Injectable()
8
+ export class CacheBehavior {
9
+ private readonly logger = new Logger(CacheBehavior.name);
10
+
11
+ constructor(
12
+ @Optional() @Inject(REDIS_CLIENT) private readonly redis?: Redis,
13
+ ) {}
14
+
15
+ async execute<T>(command: object, next: () => Promise<Result<T>>): Promise<Result<T>> {
16
+ const metadata = getCacheMetadata(command.constructor);
17
+
18
+ if (!metadata || !this.redis) {
19
+ return next();
20
+ }
21
+
22
+ const cacheKey = this.interpolateKey(metadata.key, command);
23
+
24
+ try {
25
+ const cached = await this.redis.get(cacheKey);
26
+ if (cached) {
27
+ this.logger.debug(`Cache hit: ${cacheKey}`);
28
+ return Result.success(JSON.parse(cached) as T);
29
+ }
30
+ } catch {
31
+ this.logger.warn(`Cache read failed for key: ${cacheKey}`);
32
+ }
33
+
34
+ const result = await next();
35
+
36
+ if (result.isSuccess && result.value !== undefined) {
37
+ try {
38
+ await this.redis.set(cacheKey, JSON.stringify(result.value), 'EX', metadata.ttlSeconds!);
39
+ this.logger.debug(`Cache set: ${cacheKey} (TTL: ${metadata.ttlSeconds}s)`);
40
+ } catch {
41
+ this.logger.warn(`Cache write failed for key: ${cacheKey}`);
42
+ }
43
+ }
44
+
45
+ return result;
46
+ }
47
+
48
+ private interpolateKey(template: string, command: object): string {
49
+ return template.replace(/\{(\w+)\}/g, (_, prop) => {
50
+ const value = (command as Record<string, unknown>)[prop];
51
+ return value != null ? String(value) : '';
52
+ });
53
+ }
54
+ }
@@ -0,0 +1,88 @@
1
+ import { Injectable, Logger, Optional, Inject } from '@nestjs/common';
2
+ import { getDistributedLockMetadata } from '../decorators/DistributedLock.decorator';
3
+ import { Result, ErrorType } from '../../result/Result';
4
+ import { REDIS_CLIENT } from '../constants';
5
+ import type { Redis } from 'ioredis';
6
+ import { v4 as uuidv4 } from 'uuid';
7
+
8
+ @Injectable()
9
+ export class DistributedLockBehavior {
10
+ private readonly logger = new Logger(DistributedLockBehavior.name);
11
+
12
+ constructor(
13
+ @Optional() @Inject(REDIS_CLIENT) private readonly redis?: Redis,
14
+ ) {}
15
+
16
+ async execute<T>(command: object, next: () => Promise<Result<T>>): Promise<Result<T>> {
17
+ const metadata = getDistributedLockMetadata(command.constructor);
18
+
19
+ if (!metadata || !this.redis) {
20
+ return next();
21
+ }
22
+
23
+ const lockKey = `lock:${this.interpolateKey(metadata.key, command)}`;
24
+ const lockValue = uuidv4();
25
+ const acquired = await this.tryAcquire(
26
+ lockKey,
27
+ lockValue,
28
+ metadata.lockTtlSeconds!,
29
+ metadata.acquireTimeoutSeconds!,
30
+ );
31
+
32
+ if (!acquired) {
33
+ this.logger.warn(`Failed to acquire lock: ${lockKey}`);
34
+ return Result.failure<T>(ErrorType.Conflict, `Resource is locked: ${metadata.key}`);
35
+ }
36
+
37
+ try {
38
+ return await next();
39
+ } finally {
40
+ await this.release(lockKey, lockValue);
41
+ }
42
+ }
43
+
44
+ private async tryAcquire(
45
+ key: string,
46
+ value: string,
47
+ ttlSeconds: number,
48
+ timeoutSeconds: number,
49
+ ): Promise<boolean> {
50
+ const deadline = Date.now() + timeoutSeconds * 1000;
51
+ const retryDelay = 50;
52
+
53
+ while (Date.now() < deadline) {
54
+ const result = await this.redis!.set(key, value, 'EX', ttlSeconds, 'NX');
55
+ if (result === 'OK') return true;
56
+ await this.sleep(retryDelay);
57
+ }
58
+
59
+ return false;
60
+ }
61
+
62
+ private async release(key: string, value: string): Promise<void> {
63
+ // Lua script ensures only the owner releases the lock
64
+ const script = `
65
+ if redis.call("get", KEYS[1]) == ARGV[1] then
66
+ return redis.call("del", KEYS[1])
67
+ else
68
+ return 0
69
+ end
70
+ `;
71
+ try {
72
+ await this.redis!.eval(script, 1, key, value);
73
+ } catch {
74
+ this.logger.warn(`Failed to release lock: ${key}`);
75
+ }
76
+ }
77
+
78
+ private sleep(ms: number): Promise<void> {
79
+ return new Promise((resolve) => setTimeout(resolve, ms));
80
+ }
81
+
82
+ private interpolateKey(template: string, command: object): string {
83
+ return template.replace(/\{(\w+)\}/g, (_, prop) => {
84
+ const value = (command as Record<string, unknown>)[prop];
85
+ return value != null ? String(value) : '';
86
+ });
87
+ }
88
+ }