@quanticjs/core 1.1.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/QuanticCoreModule.d.ts +4 -0
- package/dist/QuanticCoreModule.js +53 -0
- package/dist/cqrs/behaviors/LogBehavior.d.ts +4 -1
- package/dist/cqrs/behaviors/LogBehavior.js +3 -0
- package/dist/cqrs/behaviors/TransactionalBehavior.d.ts +4 -11
- package/dist/cqrs/behaviors/TransactionalBehavior.js +3 -12
- package/dist/cqrs/behaviors/ValidationBehavior.d.ts +4 -1
- package/dist/cqrs/behaviors/ValidationBehavior.js +3 -0
- package/dist/cqrs/pipeline/QuanticCommandBus.d.ts +3 -28
- package/dist/cqrs/pipeline/QuanticCommandBus.js +11 -59
- package/dist/cqrs/pipeline/QuanticQueryBus.d.ts +3 -19
- package/dist/cqrs/pipeline/QuanticQueryBus.js +11 -38
- package/dist/cqrs/pipeline/behavior-order.d.ts +11 -0
- package/dist/cqrs/pipeline/behavior-order.js +14 -0
- package/dist/cqrs/pipeline/types.d.ts +8 -0
- package/dist/cqrs/pipeline/types.js +4 -0
- package/dist/index.d.ts +6 -22
- package/dist/index.js +13 -44
- package/package.json +26 -80
- package/dist/cqrs/PipelineExecutor.d.ts +0 -31
- package/dist/cqrs/PipelineExecutor.js +0 -81
- package/dist/cqrs/behaviors/CacheBehavior.d.ts +0 -9
- package/dist/cqrs/behaviors/CacheBehavior.js +0 -68
- package/dist/cqrs/behaviors/DistributedLockBehavior.d.ts +0 -12
- package/dist/cqrs/behaviors/DistributedLockBehavior.js +0 -90
- package/dist/cqrs/behaviors/FeatureFlagBehavior.d.ts +0 -8
- package/dist/cqrs/behaviors/FeatureFlagBehavior.js +0 -58
- package/dist/cqrs/behaviors/InvalidateCacheBehavior.d.ts +0 -9
- package/dist/cqrs/behaviors/InvalidateCacheBehavior.js +0 -61
- package/dist/cqrs/behaviors/PerformanceBehavior.d.ts +0 -12
- package/dist/cqrs/behaviors/PerformanceBehavior.js +0 -56
- package/dist/cqrs/behaviors/WorkflowBehavior.d.ts +0 -8
- package/dist/cqrs/behaviors/WorkflowBehavior.js +0 -61
- package/dist/events/DomainEvent.d.ts +0 -14
- package/dist/events/DomainEvent.js +0 -27
- package/dist/events/OutboxEvent.entity.d.ts +0 -18
- package/dist/events/OutboxEvent.entity.js +0 -87
- package/dist/events/OutboxPublisherService.d.ts +0 -14
- package/dist/events/OutboxPublisherService.js +0 -104
- package/dist/events/RedisStreamConsumer.d.ts +0 -43
- package/dist/events/RedisStreamConsumer.js +0 -158
- package/dist/events/RedisStreamPublisher.d.ts +0 -9
- package/dist/events/RedisStreamPublisher.js +0 -60
- package/dist/metrics/MetricsController.d.ts +0 -7
- package/dist/metrics/MetricsController.js +0 -42
- package/dist/metrics/MetricsService.d.ts +0 -13
- package/dist/metrics/MetricsService.js +0 -58
- package/dist/redis/redis.module.d.ts +0 -8
- package/dist/redis/redis.module.js +0 -49
- package/dist/shared-kernel.module.d.ts +0 -13
- package/dist/shared-kernel.module.js +0 -87
- package/dist/testing/TestingModuleFactory.d.ts +0 -23
- package/dist/testing/TestingModuleFactory.js +0 -63
- package/dist/testing/index.d.ts +0 -2
- package/dist/testing/index.js +0 -7
- package/dist/unleash/initial-flags.d.ts +0 -7
- package/dist/unleash/initial-flags.js +0 -9
- package/dist/unleash/unleash.module.d.ts +0 -9
- package/dist/unleash/unleash.module.js +0 -47
- package/src/bootstrap/bootstrapService.ts +0 -72
- package/src/cqrs/behaviors/CacheBehavior.spec.ts +0 -63
- package/src/cqrs/behaviors/CacheBehavior.ts +0 -54
- package/src/cqrs/behaviors/DistributedLockBehavior.ts +0 -88
- package/src/cqrs/behaviors/FeatureFlagBehavior.ts +0 -46
- package/src/cqrs/behaviors/InvalidateCacheBehavior.spec.ts +0 -89
- package/src/cqrs/behaviors/InvalidateCacheBehavior.ts +0 -50
- package/src/cqrs/behaviors/LogBehavior.spec.ts +0 -55
- package/src/cqrs/behaviors/LogBehavior.ts +0 -121
- package/src/cqrs/behaviors/PerformanceBehavior.spec.ts +0 -48
- package/src/cqrs/behaviors/PerformanceBehavior.ts +0 -43
- package/src/cqrs/behaviors/TransactionalBehavior.ts +0 -64
- package/src/cqrs/behaviors/ValidationBehavior.spec.ts +0 -114
- package/src/cqrs/behaviors/ValidationBehavior.ts +0 -29
- package/src/cqrs/behaviors/WorkflowBehavior.spec.ts +0 -97
- package/src/cqrs/behaviors/WorkflowBehavior.ts +0 -62
- package/src/cqrs/constants.ts +0 -2
- package/src/cqrs/decorators/Cache.decorator.ts +0 -24
- package/src/cqrs/decorators/DistributedLock.decorator.ts +0 -34
- package/src/cqrs/decorators/FeatureFlag.decorator.ts +0 -23
- package/src/cqrs/decorators/InvalidateCache.decorator.spec.ts +0 -20
- package/src/cqrs/decorators/InvalidateCache.decorator.ts +0 -17
- package/src/cqrs/decorators/IsolatedTransaction.decorator.ts +0 -24
- package/src/cqrs/decorators/Log.decorator.ts +0 -22
- package/src/cqrs/decorators/Validate.decorator.ts +0 -39
- package/src/cqrs/decorators/Workflow.decorator.ts +0 -22
- package/src/cqrs/interfaces/WorkflowEngine.ts +0 -19
- package/src/cqrs/pipeline/QuanticCommandBus.ts +0 -69
- package/src/cqrs/pipeline/QuanticQueryBus.ts +0 -56
- package/src/cqrs/pipeline/runPipeline.ts +0 -22
- package/src/cqrs/transaction/TransactionContext.ts +0 -26
- package/src/cqrs/transaction/getTransactionalRepo.ts +0 -23
- package/src/cqrs/validation/ICommandValidator.ts +0 -55
- package/src/entities/BaseEntity.ts +0 -16
- package/src/entities/TenantBaseEntity.ts +0 -7
- package/src/events/DomainEvent.ts +0 -27
- package/src/events/OutboxEvent.entity.ts +0 -56
- package/src/events/OutboxPublisherService.ts +0 -94
- package/src/events/RedisStreamConsumer.ts +0 -172
- package/src/events/RedisStreamPublisher.ts +0 -54
- package/src/filters/GlobalExceptionFilter.ts +0 -125
- package/src/guards/JwtAuthGuard.ts +0 -29
- package/src/guards/JwtStrategy.ts +0 -41
- package/src/guards/RolesGuard.ts +0 -39
- package/src/index.ts +0 -118
- package/src/interceptors/ResultInterceptor.ts +0 -93
- package/src/lifecycle/GracefulShutdownService.ts +0 -77
- package/src/logging/pino-config.ts +0 -80
- package/src/metrics/MetricsController.ts +0 -17
- package/src/metrics/MetricsService.ts +0 -55
- package/src/middleware/CorrelationIdMiddleware.ts +0 -27
- package/src/middleware/CorrelationStore.ts +0 -13
- package/src/middleware/TenantContextMiddleware.ts +0 -21
- package/src/middleware/TenantStore.ts +0 -11
- package/src/redis/redis.module.ts +0 -41
- package/src/resilience/CircuitBreakerFactory.ts +0 -33
- package/src/result/Result.ts +0 -66
- package/src/shared-kernel.module.ts +0 -87
- package/src/subscribers/TenantSubscriber.ts +0 -47
- package/src/testing/TestingModuleFactory.ts +0 -78
- package/src/testing/index.ts +0 -2
- package/src/testing/mocks.ts +0 -59
- package/src/unleash/unleash.module.ts +0 -45
- package/tsconfig.json +0 -22
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { Injectable, NestMiddleware } from '@nestjs/common';
|
|
2
|
-
import { v4 as uuidv4 } from 'uuid';
|
|
3
|
-
import { correlationStore } from './CorrelationStore';
|
|
4
|
-
|
|
5
|
-
const CORRELATION_ID_HEADER = 'X-Correlation-ID';
|
|
6
|
-
|
|
7
|
-
@Injectable()
|
|
8
|
-
export class CorrelationIdMiddleware implements NestMiddleware {
|
|
9
|
-
use(req: any, res: any, next: () => void): void {
|
|
10
|
-
const correlationId =
|
|
11
|
-
(req.headers[CORRELATION_ID_HEADER.toLowerCase()] as string) || uuidv4();
|
|
12
|
-
|
|
13
|
-
req.correlationId = correlationId;
|
|
14
|
-
req.headers[CORRELATION_ID_HEADER.toLowerCase()] = correlationId;
|
|
15
|
-
res.setHeader(CORRELATION_ID_HEADER, correlationId);
|
|
16
|
-
|
|
17
|
-
const storeData = {
|
|
18
|
-
correlationId,
|
|
19
|
-
userId: req.user?.keycloakId,
|
|
20
|
-
organizationId: req.user?.organizationId,
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
correlationStore.run(storeData, () => {
|
|
24
|
-
next();
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { AsyncLocalStorage } from 'async_hooks';
|
|
2
|
-
|
|
3
|
-
export interface CorrelationStoreData {
|
|
4
|
-
correlationId: string;
|
|
5
|
-
userId?: string;
|
|
6
|
-
organizationId?: string;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Request-scoped async local storage for correlation context.
|
|
11
|
-
* Set by CorrelationIdMiddleware, read by LogBehavior and error filters.
|
|
12
|
-
*/
|
|
13
|
-
export const correlationStore = new AsyncLocalStorage<CorrelationStoreData>();
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { Injectable, NestMiddleware } from '@nestjs/common';
|
|
2
|
-
import { tenantStore } from './TenantStore';
|
|
3
|
-
|
|
4
|
-
export interface TenantContext {
|
|
5
|
-
organizationId: string | null;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
@Injectable()
|
|
9
|
-
export class TenantContextMiddleware implements NestMiddleware {
|
|
10
|
-
use(req: any, _res: any, next: () => void): void {
|
|
11
|
-
// Extract org_id from JWT payload (set by JwtAuthGuard)
|
|
12
|
-
const orgId = req.user?.organizationId || req.headers['x-tenant-id'] || null;
|
|
13
|
-
req.tenantId = orgId;
|
|
14
|
-
req.tenantContext = { organizationId: orgId } as TenantContext;
|
|
15
|
-
|
|
16
|
-
// Store in AsyncLocalStorage so TenantSubscriber can access it
|
|
17
|
-
tenantStore.run({ organizationId: orgId }, () => {
|
|
18
|
-
next();
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { AsyncLocalStorage } from 'async_hooks';
|
|
2
|
-
|
|
3
|
-
export interface TenantStoreData {
|
|
4
|
-
organizationId: string | null;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Request-scoped async local storage for tenant context.
|
|
9
|
-
* Set by TenantContextMiddleware, read by TenantSubscriber.
|
|
10
|
-
*/
|
|
11
|
-
export const tenantStore = new AsyncLocalStorage<TenantStoreData>();
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { DynamicModule, Global, Logger, Module } from '@nestjs/common';
|
|
2
|
-
import Redis from 'ioredis';
|
|
3
|
-
import { REDIS_CLIENT } from '../cqrs/constants';
|
|
4
|
-
|
|
5
|
-
export interface RedisModuleOptions {
|
|
6
|
-
url?: string;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
@Global()
|
|
10
|
-
@Module({})
|
|
11
|
-
export class RedisModule {
|
|
12
|
-
private static readonly logger = new Logger('RedisModule');
|
|
13
|
-
|
|
14
|
-
static forRoot(options: RedisModuleOptions = {}): DynamicModule {
|
|
15
|
-
const redisProvider = {
|
|
16
|
-
provide: REDIS_CLIENT,
|
|
17
|
-
useFactory: () => {
|
|
18
|
-
const url = options.url || process.env.REDIS_URL || 'redis://localhost:6379';
|
|
19
|
-
const client = new Redis(url, {
|
|
20
|
-
maxRetriesPerRequest: 3,
|
|
21
|
-
retryStrategy(times: number) {
|
|
22
|
-
const delay = Math.min(times * 200, 5000);
|
|
23
|
-
RedisModule.logger.warn(`Redis reconnecting (attempt ${times}, delay ${delay}ms)`);
|
|
24
|
-
return delay;
|
|
25
|
-
},
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
client.on('connect', () => RedisModule.logger.log('Redis connected'));
|
|
29
|
-
client.on('error', (err) => RedisModule.logger.error('Redis error', err.message));
|
|
30
|
-
|
|
31
|
-
return client;
|
|
32
|
-
},
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
return {
|
|
36
|
-
module: RedisModule,
|
|
37
|
-
providers: [redisProvider],
|
|
38
|
-
exports: [REDIS_CLIENT],
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { circuitBreaker, ConsecutiveBreaker, handleAll, retry, wrap, ExponentialBackoff } from 'cockatiel';
|
|
2
|
-
|
|
3
|
-
export interface CircuitBreakerOptions {
|
|
4
|
-
halfOpenAfterMs?: number;
|
|
5
|
-
consecutiveFailures?: number;
|
|
6
|
-
maxRetries?: number;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Creates a resilience policy combining retry + circuit breaker.
|
|
11
|
-
* Usage:
|
|
12
|
-
* const policy = createCircuitBreaker({ consecutiveFailures: 5 });
|
|
13
|
-
* const result = await policy.execute(() => httpCall());
|
|
14
|
-
*/
|
|
15
|
-
export function createCircuitBreaker(options: CircuitBreakerOptions = {}) {
|
|
16
|
-
const {
|
|
17
|
-
halfOpenAfterMs = 30_000,
|
|
18
|
-
consecutiveFailures = 5,
|
|
19
|
-
maxRetries = 2,
|
|
20
|
-
} = options;
|
|
21
|
-
|
|
22
|
-
const retryPolicy = retry(handleAll, {
|
|
23
|
-
maxAttempts: maxRetries,
|
|
24
|
-
backoff: new ExponentialBackoff(),
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
const breaker = circuitBreaker(handleAll, {
|
|
28
|
-
halfOpenAfter: halfOpenAfterMs,
|
|
29
|
-
breaker: new ConsecutiveBreaker(consecutiveFailures),
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
return wrap(retryPolicy, breaker);
|
|
33
|
-
}
|
package/src/result/Result.ts
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
export enum ErrorType {
|
|
2
|
-
NotFound = 'NOT_FOUND',
|
|
3
|
-
Forbidden = 'FORBIDDEN',
|
|
4
|
-
Conflict = 'CONFLICT',
|
|
5
|
-
ValidationError = 'VALIDATION_ERROR',
|
|
6
|
-
InternalError = 'INTERNAL_ERROR',
|
|
7
|
-
Unauthorized = 'UNAUTHORIZED',
|
|
8
|
-
UnprocessableEntity = 'UNPROCESSABLE_ENTITY',
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export class Result<T> {
|
|
12
|
-
private constructor(
|
|
13
|
-
public readonly isSuccess: boolean,
|
|
14
|
-
public readonly value?: T,
|
|
15
|
-
public readonly errorType?: ErrorType,
|
|
16
|
-
public readonly errorMessage?: string,
|
|
17
|
-
) {}
|
|
18
|
-
|
|
19
|
-
static success<T>(value: T): Result<T> {
|
|
20
|
-
return new Result<T>(true, value);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
static failure<T>(errorType: ErrorType, message: string): Result<T> {
|
|
24
|
-
return new Result<T>(false, undefined, errorType, message);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
static notFound<T>(message: string): Result<T> {
|
|
28
|
-
return Result.failure<T>(ErrorType.NotFound, message);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
static forbidden<T>(message: string): Result<T> {
|
|
32
|
-
return Result.failure<T>(ErrorType.Forbidden, message);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
static conflict<T>(message: string): Result<T> {
|
|
36
|
-
return Result.failure<T>(ErrorType.Conflict, message);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
static validationError<T>(message: string): Result<T> {
|
|
40
|
-
return Result.failure<T>(ErrorType.ValidationError, message);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
static unauthorized<T>(message: string): Result<T> {
|
|
44
|
-
return Result.failure<T>(ErrorType.Unauthorized, message);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
static unprocessableEntity<T>(message: string): Result<T> {
|
|
48
|
-
return Result.failure<T>(ErrorType.UnprocessableEntity, message);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
unwrap(): T {
|
|
52
|
-
if (!this.isSuccess || this.value === undefined) {
|
|
53
|
-
throw new Error(
|
|
54
|
-
`Cannot unwrap failed Result: ${this.errorType} - ${this.errorMessage}`,
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
return this.value;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
map<U>(fn: (value: T) => U): Result<U> {
|
|
61
|
-
if (this.isSuccess && this.value !== undefined) {
|
|
62
|
-
return Result.success(fn(this.value));
|
|
63
|
-
}
|
|
64
|
-
return Result.failure<U>(this.errorType!, this.errorMessage!);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { DynamicModule, Global, Module } from '@nestjs/common';
|
|
2
|
-
import { CqrsModule } from '@nestjs/cqrs';
|
|
3
|
-
import { QuanticCommandBus } from './cqrs/pipeline/QuanticCommandBus';
|
|
4
|
-
import { QuanticQueryBus } from './cqrs/pipeline/QuanticQueryBus';
|
|
5
|
-
import { LogBehavior } from './cqrs/behaviors/LogBehavior';
|
|
6
|
-
import { ValidationBehavior } from './cqrs/behaviors/ValidationBehavior';
|
|
7
|
-
import { CacheBehavior } from './cqrs/behaviors/CacheBehavior';
|
|
8
|
-
import { DistributedLockBehavior } from './cqrs/behaviors/DistributedLockBehavior';
|
|
9
|
-
import { TransactionalBehavior } from './cqrs/behaviors/TransactionalBehavior';
|
|
10
|
-
import { PerformanceBehavior } from './cqrs/behaviors/PerformanceBehavior';
|
|
11
|
-
import { WorkflowBehavior } from './cqrs/behaviors/WorkflowBehavior';
|
|
12
|
-
import { InvalidateCacheBehavior } from './cqrs/behaviors/InvalidateCacheBehavior';
|
|
13
|
-
import { MetricsService } from './metrics/MetricsService';
|
|
14
|
-
import { MetricsController } from './metrics/MetricsController';
|
|
15
|
-
import { GracefulShutdownService } from './lifecycle/GracefulShutdownService';
|
|
16
|
-
import { RedisStreamPublisher } from './events/RedisStreamPublisher';
|
|
17
|
-
import { RedisModule, type RedisModuleOptions } from './redis/redis.module';
|
|
18
|
-
import { UnleashModule, type UnleashModuleOptions } from './unleash/unleash.module';
|
|
19
|
-
|
|
20
|
-
export type SharedKernelModuleOptions = QuanticModuleOptions;
|
|
21
|
-
|
|
22
|
-
export interface QuanticModuleOptions {
|
|
23
|
-
redis?: RedisModuleOptions;
|
|
24
|
-
unleash?: UnleashModuleOptions | false;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
@Global()
|
|
28
|
-
@Module({})
|
|
29
|
-
export class QuanticModule {
|
|
30
|
-
static forRoot(options: QuanticModuleOptions = {}): DynamicModule {
|
|
31
|
-
const imports: Array<DynamicModule> = [
|
|
32
|
-
CqrsModule.forRoot(),
|
|
33
|
-
RedisModule.forRoot(options.redis),
|
|
34
|
-
];
|
|
35
|
-
|
|
36
|
-
if (options.unleash !== false) {
|
|
37
|
-
imports.push(UnleashModule.forRoot(options.unleash));
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const exports: any[] = [
|
|
41
|
-
CqrsModule,
|
|
42
|
-
RedisModule,
|
|
43
|
-
RedisStreamPublisher,
|
|
44
|
-
LogBehavior,
|
|
45
|
-
ValidationBehavior,
|
|
46
|
-
CacheBehavior,
|
|
47
|
-
InvalidateCacheBehavior,
|
|
48
|
-
DistributedLockBehavior,
|
|
49
|
-
TransactionalBehavior,
|
|
50
|
-
PerformanceBehavior,
|
|
51
|
-
WorkflowBehavior,
|
|
52
|
-
MetricsService,
|
|
53
|
-
GracefulShutdownService,
|
|
54
|
-
QuanticCommandBus,
|
|
55
|
-
QuanticQueryBus,
|
|
56
|
-
];
|
|
57
|
-
|
|
58
|
-
if (options.unleash !== false) {
|
|
59
|
-
exports.push(UnleashModule);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return {
|
|
63
|
-
module: QuanticModule,
|
|
64
|
-
imports,
|
|
65
|
-
controllers: [MetricsController],
|
|
66
|
-
providers: [
|
|
67
|
-
LogBehavior,
|
|
68
|
-
ValidationBehavior,
|
|
69
|
-
CacheBehavior,
|
|
70
|
-
InvalidateCacheBehavior,
|
|
71
|
-
DistributedLockBehavior,
|
|
72
|
-
TransactionalBehavior,
|
|
73
|
-
PerformanceBehavior,
|
|
74
|
-
WorkflowBehavior,
|
|
75
|
-
MetricsService,
|
|
76
|
-
GracefulShutdownService,
|
|
77
|
-
RedisStreamPublisher,
|
|
78
|
-
QuanticCommandBus,
|
|
79
|
-
QuanticQueryBus,
|
|
80
|
-
],
|
|
81
|
-
exports,
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/** @deprecated Use QuanticModule instead */
|
|
87
|
-
export const SharedKernelModule = QuanticModule;
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { EventSubscriber, EntitySubscriberInterface, InsertEvent, UpdateEvent, RemoveEvent } from 'typeorm';
|
|
2
|
-
import { TenantBaseEntity } from '../entities/TenantBaseEntity';
|
|
3
|
-
import { tenantStore } from '../middleware/TenantStore';
|
|
4
|
-
|
|
5
|
-
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* TypeORM subscriber that sets PostgreSQL session variable `app.current_org_id`
|
|
9
|
-
* before write operations on tenant-scoped entities. This enables RLS policies.
|
|
10
|
-
*
|
|
11
|
-
* For SELECT queries, RLS is enforced via the session variable set by the
|
|
12
|
-
* TransactionalBehavior or manually via `SET LOCAL app.current_org_id`.
|
|
13
|
-
*
|
|
14
|
-
* Register this subscriber in each service's TypeORM DataSource config:
|
|
15
|
-
* subscribers: [TenantSubscriber]
|
|
16
|
-
*/
|
|
17
|
-
@EventSubscriber()
|
|
18
|
-
export class TenantSubscriber implements EntitySubscriberInterface<TenantBaseEntity> {
|
|
19
|
-
listenTo() {
|
|
20
|
-
return TenantBaseEntity;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async beforeInsert(event: InsertEvent<TenantBaseEntity>): Promise<void> {
|
|
24
|
-
const orgId = event.entity?.organizationId ?? this.getOrgIdFromStore();
|
|
25
|
-
await this.setTenantId(event.queryRunner, orgId);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
async beforeUpdate(event: UpdateEvent<TenantBaseEntity>): Promise<void> {
|
|
29
|
-
const orgId = event.entity?.organizationId ?? this.getOrgIdFromStore();
|
|
30
|
-
await this.setTenantId(event.queryRunner, orgId);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
async beforeRemove(event: RemoveEvent<TenantBaseEntity>): Promise<void> {
|
|
34
|
-
const orgId = event.entity?.organizationId ?? this.getOrgIdFromStore();
|
|
35
|
-
await this.setTenantId(event.queryRunner, orgId);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
private getOrgIdFromStore(): string | null {
|
|
39
|
-
return tenantStore.getStore()?.organizationId ?? null;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
private async setTenantId(queryRunner: any, organizationId?: string | null): Promise<void> {
|
|
43
|
-
if (organizationId && UUID_REGEX.test(organizationId)) {
|
|
44
|
-
await queryRunner.query(`SET LOCAL app.current_org_id = '${organizationId}'`);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { Test, TestingModuleBuilder } from '@nestjs/testing';
|
|
2
|
-
import { CqrsModule } from '@nestjs/cqrs';
|
|
3
|
-
import { getRepositoryToken } from '@nestjs/typeorm';
|
|
4
|
-
import { Logger } from '@nestjs/common';
|
|
5
|
-
import { QuanticCommandBus } from '../cqrs/pipeline/QuanticCommandBus';
|
|
6
|
-
import { QuanticQueryBus } from '../cqrs/pipeline/QuanticQueryBus';
|
|
7
|
-
import { LogBehavior } from '../cqrs/behaviors/LogBehavior';
|
|
8
|
-
import { ValidationBehavior } from '../cqrs/behaviors/ValidationBehavior';
|
|
9
|
-
import { CacheBehavior } from '../cqrs/behaviors/CacheBehavior';
|
|
10
|
-
import { DistributedLockBehavior } from '../cqrs/behaviors/DistributedLockBehavior';
|
|
11
|
-
import { TransactionalBehavior } from '../cqrs/behaviors/TransactionalBehavior';
|
|
12
|
-
import { PerformanceBehavior } from '../cqrs/behaviors/PerformanceBehavior';
|
|
13
|
-
import { MetricsService } from '../metrics/MetricsService';
|
|
14
|
-
import { REDIS_CLIENT } from '../cqrs/constants';
|
|
15
|
-
import { createMockRepository, createMockRedisClient } from './mocks';
|
|
16
|
-
|
|
17
|
-
export { createMockRepository, createMockRedisClient };
|
|
18
|
-
|
|
19
|
-
export interface TestingModuleOptions {
|
|
20
|
-
/** Providers to register (handlers, services, etc.) */
|
|
21
|
-
providers?: any[];
|
|
22
|
-
/** Entity classes to auto-mock their repositories */
|
|
23
|
-
entities?: any[];
|
|
24
|
-
/** Additional overrides to apply to the TestingModule builder */
|
|
25
|
-
overrides?: Array<{ provide: any; useValue: any }>;
|
|
26
|
-
/** Whether to include the full CQRS pipeline behaviors (default: false) */
|
|
27
|
-
withPipeline?: boolean;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Factory for bootstrapping a NestJS TestingModule preconfigured
|
|
32
|
-
* with mocked repositories, Redis, and optionally the CQRS pipeline.
|
|
33
|
-
*/
|
|
34
|
-
export class TestingModuleFactory {
|
|
35
|
-
static create(options: TestingModuleOptions = {}): TestingModuleBuilder {
|
|
36
|
-
const { providers = [], entities = [], overrides = [], withPipeline = false } = options;
|
|
37
|
-
|
|
38
|
-
const mockRepos = entities.map((entity) => ({
|
|
39
|
-
provide: getRepositoryToken(entity),
|
|
40
|
-
useValue: createMockRepository(),
|
|
41
|
-
}));
|
|
42
|
-
|
|
43
|
-
const mockRedis = { provide: REDIS_CLIENT, useValue: createMockRedisClient() };
|
|
44
|
-
|
|
45
|
-
const pipelineProviders = withPipeline
|
|
46
|
-
? [
|
|
47
|
-
LogBehavior,
|
|
48
|
-
ValidationBehavior,
|
|
49
|
-
CacheBehavior,
|
|
50
|
-
DistributedLockBehavior,
|
|
51
|
-
TransactionalBehavior,
|
|
52
|
-
PerformanceBehavior,
|
|
53
|
-
MetricsService,
|
|
54
|
-
QuanticCommandBus,
|
|
55
|
-
QuanticQueryBus,
|
|
56
|
-
]
|
|
57
|
-
: [];
|
|
58
|
-
|
|
59
|
-
let builder = Test.createTestingModule({
|
|
60
|
-
imports: [CqrsModule.forRoot()],
|
|
61
|
-
providers: [
|
|
62
|
-
...mockRepos,
|
|
63
|
-
mockRedis,
|
|
64
|
-
...pipelineProviders,
|
|
65
|
-
...providers,
|
|
66
|
-
],
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
for (const override of overrides) {
|
|
70
|
-
builder = builder.overrideProvider(override.provide).useValue(override.useValue);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Silence NestJS logs during tests
|
|
74
|
-
Logger.overrideLogger(['error']);
|
|
75
|
-
|
|
76
|
-
return builder;
|
|
77
|
-
}
|
|
78
|
-
}
|
package/src/testing/index.ts
DELETED
package/src/testing/mocks.ts
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Creates a mock repository with common TypeORM repository methods stubbed.
|
|
3
|
-
*/
|
|
4
|
-
export function createMockRepository<T = any>() {
|
|
5
|
-
return {
|
|
6
|
-
find: jest.fn(),
|
|
7
|
-
findOne: jest.fn(),
|
|
8
|
-
findOneBy: jest.fn(),
|
|
9
|
-
findAndCount: jest.fn(),
|
|
10
|
-
save: jest.fn().mockImplementation((entity: T) => Promise.resolve(entity)),
|
|
11
|
-
create: jest.fn().mockImplementation((dto: any) => dto),
|
|
12
|
-
update: jest.fn(),
|
|
13
|
-
delete: jest.fn(),
|
|
14
|
-
remove: jest.fn(),
|
|
15
|
-
count: jest.fn(),
|
|
16
|
-
createQueryBuilder: jest.fn(() => ({
|
|
17
|
-
where: jest.fn().mockReturnThis(),
|
|
18
|
-
andWhere: jest.fn().mockReturnThis(),
|
|
19
|
-
orderBy: jest.fn().mockReturnThis(),
|
|
20
|
-
addOrderBy: jest.fn().mockReturnThis(),
|
|
21
|
-
take: jest.fn().mockReturnThis(),
|
|
22
|
-
skip: jest.fn().mockReturnThis(),
|
|
23
|
-
leftJoinAndSelect: jest.fn().mockReturnThis(),
|
|
24
|
-
getMany: jest.fn().mockResolvedValue([]),
|
|
25
|
-
getOne: jest.fn().mockResolvedValue(null),
|
|
26
|
-
getManyAndCount: jest.fn().mockResolvedValue([[], 0]),
|
|
27
|
-
})),
|
|
28
|
-
manager: {
|
|
29
|
-
transaction: jest.fn().mockImplementation((cb: any) => cb({
|
|
30
|
-
save: jest.fn().mockImplementation((entity: any) => Promise.resolve(entity)),
|
|
31
|
-
findOne: jest.fn(),
|
|
32
|
-
})),
|
|
33
|
-
},
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Creates a mock Redis client with common ioredis methods stubbed.
|
|
39
|
-
*/
|
|
40
|
-
export function createMockRedisClient() {
|
|
41
|
-
const client: Record<string, jest.Mock> = {
|
|
42
|
-
get: jest.fn().mockResolvedValue(null),
|
|
43
|
-
set: jest.fn().mockResolvedValue('OK'),
|
|
44
|
-
del: jest.fn().mockResolvedValue(1),
|
|
45
|
-
setex: jest.fn().mockResolvedValue('OK'),
|
|
46
|
-
exists: jest.fn().mockResolvedValue(0),
|
|
47
|
-
xadd: jest.fn().mockResolvedValue('1-0'),
|
|
48
|
-
xreadgroup: jest.fn().mockResolvedValue(null),
|
|
49
|
-
xack: jest.fn().mockResolvedValue(1),
|
|
50
|
-
xgroup: jest.fn().mockResolvedValue('OK'),
|
|
51
|
-
quit: jest.fn().mockResolvedValue('OK'),
|
|
52
|
-
disconnect: jest.fn(),
|
|
53
|
-
on: jest.fn().mockReturnThis(),
|
|
54
|
-
duplicate: jest.fn(),
|
|
55
|
-
};
|
|
56
|
-
// duplicate() returns a fresh mock with the same shape
|
|
57
|
-
client.duplicate.mockImplementation(() => createMockRedisClient());
|
|
58
|
-
return client;
|
|
59
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { Module, Global, DynamicModule, Logger } from '@nestjs/common';
|
|
2
|
-
import { initialize, Unleash } from 'unleash-client';
|
|
3
|
-
import { FeatureFlagBehavior } from '../cqrs/behaviors/FeatureFlagBehavior';
|
|
4
|
-
|
|
5
|
-
export interface UnleashModuleOptions {
|
|
6
|
-
url?: string;
|
|
7
|
-
appName?: string;
|
|
8
|
-
customHeaders?: Record<string, string>;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
@Global()
|
|
12
|
-
@Module({})
|
|
13
|
-
export class UnleashModule {
|
|
14
|
-
static forRoot(options?: UnleashModuleOptions): DynamicModule {
|
|
15
|
-
const unleashProvider = {
|
|
16
|
-
provide: Unleash,
|
|
17
|
-
useFactory: () => {
|
|
18
|
-
const logger = new Logger('UnleashModule');
|
|
19
|
-
const url = options?.url || process.env['UNLEASH_URL'] || 'http://localhost:4242/api';
|
|
20
|
-
const appName = options?.appName || process.env['UNLEASH_APP_NAME'] || 'arex';
|
|
21
|
-
const token =
|
|
22
|
-
options?.customHeaders?.['Authorization'] ||
|
|
23
|
-
process.env['UNLEASH_API_TOKEN'] ||
|
|
24
|
-
'*:*.unleash-insecure-api-token';
|
|
25
|
-
|
|
26
|
-
logger.log(`Connecting to Unleash at ${url}`);
|
|
27
|
-
|
|
28
|
-
return initialize({
|
|
29
|
-
url,
|
|
30
|
-
appName,
|
|
31
|
-
customHeaders: {
|
|
32
|
-
Authorization: token,
|
|
33
|
-
},
|
|
34
|
-
refreshInterval: 10000,
|
|
35
|
-
});
|
|
36
|
-
},
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
return {
|
|
40
|
-
module: UnleashModule,
|
|
41
|
-
providers: [unleashProvider, FeatureFlagBehavior],
|
|
42
|
-
exports: [Unleash, FeatureFlagBehavior],
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "commonjs",
|
|
5
|
-
"lib": ["ES2022"],
|
|
6
|
-
"outDir": "./dist",
|
|
7
|
-
"rootDir": "./src",
|
|
8
|
-
"strict": true,
|
|
9
|
-
"esModuleInterop": true,
|
|
10
|
-
"skipLibCheck": true,
|
|
11
|
-
"forceConsistentCasingInFileNames": true,
|
|
12
|
-
"declaration": true,
|
|
13
|
-
"declarationMap": false,
|
|
14
|
-
"sourceMap": false,
|
|
15
|
-
"moduleResolution": "node",
|
|
16
|
-
"resolveJsonModule": true,
|
|
17
|
-
"emitDecoratorMetadata": true,
|
|
18
|
-
"experimentalDecorators": true
|
|
19
|
-
},
|
|
20
|
-
"include": ["src/**/*"],
|
|
21
|
-
"exclude": ["node_modules", "dist", "**/*.spec.ts"]
|
|
22
|
-
}
|