@martel/calyx 1.11.0 → 1.12.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/CHANGELOG.md +7 -0
- package/package.json +1 -1
- package/src/cache/cache.interceptor.ts +4 -2
- package/src/cache/decorators.ts +4 -0
- package/src/cache/index.ts +1 -0
- package/src/core/container.ts +242 -9
- package/src/core/index.ts +2 -0
- package/src/core/lazy-module-loader.ts +29 -0
- package/src/core/metadata.ts +6 -1
- package/src/core/testing-module.ts +119 -0
- package/src/cqrs/cqrs.ts +175 -0
- package/src/graphql/decorators.ts +16 -0
- package/src/graphql/graphql.module.ts +16 -0
- package/src/http/application.ts +128 -17
- package/src/http/decorators.ts +4 -0
- package/src/index.ts +2 -0
- package/src/microservices/clients.module.ts +47 -0
- package/src/microservices/index.ts +1 -0
- package/src/microservices/microservice.ts +1 -1
- package/src/schedule/decorators.ts +10 -6
- package/src/schedule/index.ts +1 -0
- package/src/schedule/schedule.module.ts +3 -2
- package/src/schedule/scheduler-registry.ts +50 -0
- package/src/security/index.ts +1 -0
- package/src/security/throttler.module.ts +108 -0
- package/src/terminus/terminus.ts +61 -0
- package/src/validation/http-pipes.ts +128 -0
- package/src/validation/index.ts +1 -0
- package/src/websockets/decorators.ts +12 -2
- package/tests/nestjs-parity.test.ts +272 -0
package/src/cqrs/cqrs.ts
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { Module, Injectable } from '../core/decorators.ts';
|
|
2
|
+
import { Type } from '../core/metadata.ts';
|
|
3
|
+
import { ModuleRef } from '../core/module-ref.ts';
|
|
4
|
+
|
|
5
|
+
export interface ICommand {}
|
|
6
|
+
export interface ICommandHandler<TCommand extends ICommand = any, TResult = any> {
|
|
7
|
+
execute(command: TCommand): Promise<TResult>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface IQuery {}
|
|
11
|
+
export interface IQueryHandler<TQuery extends IQuery = any, TResult = any> {
|
|
12
|
+
execute(query: TQuery): Promise<TResult>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface IEvent {}
|
|
16
|
+
export interface IEventHandler<TEvent extends IEvent = any> {
|
|
17
|
+
handle(event: TEvent): any;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
import { METADATA_KEYS } from '../core/metadata.ts';
|
|
21
|
+
|
|
22
|
+
export const COMMAND_HANDLER_METADATA = 'cqrs:command_handler';
|
|
23
|
+
export const QUERY_HANDLER_METADATA = 'cqrs:query_handler';
|
|
24
|
+
export const EVENT_HANDLER_METADATA = 'cqrs:event_handler';
|
|
25
|
+
|
|
26
|
+
export const CommandHandler = (command: Type<ICommand>): ClassDecorator => {
|
|
27
|
+
return (target) => {
|
|
28
|
+
Reflect.defineMetadata(COMMAND_HANDLER_METADATA, command, target);
|
|
29
|
+
Reflect.defineMetadata(METADATA_KEYS.INJECTABLE, true, target);
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const QueryHandler = (query: Type<IQuery>): ClassDecorator => {
|
|
34
|
+
return (target) => {
|
|
35
|
+
Reflect.defineMetadata(QUERY_HANDLER_METADATA, query, target);
|
|
36
|
+
Reflect.defineMetadata(METADATA_KEYS.INJECTABLE, true, target);
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const EventsHandler = (...events: Type<IEvent>[]): ClassDecorator => {
|
|
41
|
+
return (target) => {
|
|
42
|
+
Reflect.defineMetadata(EVENT_HANDLER_METADATA, events, target);
|
|
43
|
+
Reflect.defineMetadata(METADATA_KEYS.INJECTABLE, true, target);
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
@Injectable()
|
|
48
|
+
export class CommandBus {
|
|
49
|
+
private handlers = new Map<any, ICommandHandler>();
|
|
50
|
+
|
|
51
|
+
register(command: any, handler: ICommandHandler) {
|
|
52
|
+
this.handlers.set(command, handler);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async execute<T extends ICommand, R = any>(command: T): Promise<R> {
|
|
56
|
+
const commandClass = command.constructor;
|
|
57
|
+
const handler = this.handlers.get(commandClass);
|
|
58
|
+
if (!handler) {
|
|
59
|
+
throw new Error(`CommandHandler not found for command: ${commandClass.name}`);
|
|
60
|
+
}
|
|
61
|
+
return (await handler.execute(command)) as R;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@Injectable()
|
|
66
|
+
export class QueryBus {
|
|
67
|
+
private handlers = new Map<any, IQueryHandler>();
|
|
68
|
+
|
|
69
|
+
register(query: any, handler: IQueryHandler) {
|
|
70
|
+
this.handlers.set(query, handler);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async execute<T extends IQuery, R = any>(query: T): Promise<R> {
|
|
74
|
+
const queryClass = query.constructor;
|
|
75
|
+
const handler = this.handlers.get(queryClass);
|
|
76
|
+
if (!handler) {
|
|
77
|
+
throw new Error(`QueryHandler not found for query: ${queryClass.name}`);
|
|
78
|
+
}
|
|
79
|
+
return (await handler.execute(query)) as R;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
@Injectable()
|
|
84
|
+
export class EventBus {
|
|
85
|
+
private handlers = new Map<any, IEventHandler[]>();
|
|
86
|
+
|
|
87
|
+
register(event: any, handler: IEventHandler) {
|
|
88
|
+
const existing = this.handlers.get(event) || [];
|
|
89
|
+
existing.push(handler);
|
|
90
|
+
this.handlers.set(event, existing);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
publish<T extends IEvent>(event: T) {
|
|
94
|
+
const eventClass = event.constructor;
|
|
95
|
+
const eventHandlers = this.handlers.get(eventClass) || [];
|
|
96
|
+
for (const handler of eventHandlers) {
|
|
97
|
+
try {
|
|
98
|
+
handler.handle(event);
|
|
99
|
+
} catch (err) {
|
|
100
|
+
console.error(`Error in event handler:`, err);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
@Injectable()
|
|
107
|
+
export class EventPublisher {
|
|
108
|
+
constructor(private readonly eventBus: EventBus) {}
|
|
109
|
+
|
|
110
|
+
mergeClassContext<T extends Type<any>>(metatype: T): T {
|
|
111
|
+
const eventBus = this.eventBus;
|
|
112
|
+
return class extends metatype {
|
|
113
|
+
publish(event: IEvent) {
|
|
114
|
+
eventBus.publish(event);
|
|
115
|
+
}
|
|
116
|
+
publishAll(events: IEvent[]) {
|
|
117
|
+
for (const event of events) {
|
|
118
|
+
eventBus.publish(event);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
mergeObjectContext<T extends Object>(object: T): T {
|
|
125
|
+
const eventBus = this.eventBus;
|
|
126
|
+
return Object.assign(object, {
|
|
127
|
+
publish: (event: IEvent) => eventBus.publish(event),
|
|
128
|
+
publishAll: (events: IEvent[]) => {
|
|
129
|
+
for (const event of events) {
|
|
130
|
+
eventBus.publish(event);
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
@Module({
|
|
138
|
+
providers: [CommandBus, QueryBus, EventBus, EventPublisher],
|
|
139
|
+
exports: [CommandBus, QueryBus, EventBus, EventPublisher],
|
|
140
|
+
})
|
|
141
|
+
export class CqrsModule {
|
|
142
|
+
constructor(
|
|
143
|
+
private readonly moduleRef: ModuleRef,
|
|
144
|
+
private readonly commandBus: CommandBus,
|
|
145
|
+
private readonly queryBus: QueryBus,
|
|
146
|
+
private readonly eventBus: EventBus
|
|
147
|
+
) {}
|
|
148
|
+
|
|
149
|
+
onModuleInit() {
|
|
150
|
+
const container = (this.moduleRef as any).container;
|
|
151
|
+
if (!container) return;
|
|
152
|
+
|
|
153
|
+
const instances = container.getProviderAndControllerInstances();
|
|
154
|
+
for (const inst of instances) {
|
|
155
|
+
if (!inst || !inst.constructor) continue;
|
|
156
|
+
|
|
157
|
+
const command = Reflect.getMetadata(COMMAND_HANDLER_METADATA, inst.constructor);
|
|
158
|
+
if (command) {
|
|
159
|
+
this.commandBus.register(command, inst);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const query = Reflect.getMetadata(QUERY_HANDLER_METADATA, inst.constructor);
|
|
163
|
+
if (query) {
|
|
164
|
+
this.queryBus.register(query, inst);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const events = Reflect.getMetadata(EVENT_HANDLER_METADATA, inst.constructor);
|
|
168
|
+
if (events && Array.isArray(events)) {
|
|
169
|
+
for (const event of events) {
|
|
170
|
+
this.eventBus.register(event, inst);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -47,6 +47,22 @@ export function Parent(): ParameterDecorator {
|
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
export function Context(name?: string): ParameterDecorator {
|
|
51
|
+
return (target, propertyKey, parameterIndex) => {
|
|
52
|
+
const contextParams = Reflect.getOwnMetadata('calyx:context', target, propertyKey!) || [];
|
|
53
|
+
contextParams.push({ parameterIndex, name });
|
|
54
|
+
Reflect.defineMetadata('calyx:context', contextParams, target, propertyKey!);
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function Info(): ParameterDecorator {
|
|
59
|
+
return (target, propertyKey, parameterIndex) => {
|
|
60
|
+
const infoParams = Reflect.getOwnMetadata('calyx:info', target, propertyKey!) || [];
|
|
61
|
+
infoParams.push(parameterIndex);
|
|
62
|
+
Reflect.defineMetadata('calyx:info', infoParams, target, propertyKey!);
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
50
66
|
export function ObjectType(): ClassDecorator {
|
|
51
67
|
return (target) => {
|
|
52
68
|
Reflect.defineMetadata('calyx:object_type', true, target);
|
|
@@ -313,6 +313,14 @@ export class GraphQLModule {
|
|
|
313
313
|
for (const idx of parentParams) {
|
|
314
314
|
params[idx] = parent;
|
|
315
315
|
}
|
|
316
|
+
const contextParams = Reflect.getMetadata('calyx:context', resolverInstance, fieldMeta.propertyKey) || [];
|
|
317
|
+
for (const item of contextParams) {
|
|
318
|
+
params[item.parameterIndex] = item.name ? context?.[item.name] : context;
|
|
319
|
+
}
|
|
320
|
+
const infoParams = Reflect.getMetadata('calyx:info', resolverInstance, fieldMeta.propertyKey) || [];
|
|
321
|
+
for (const idx of infoParams) {
|
|
322
|
+
params[idx] = info;
|
|
323
|
+
}
|
|
316
324
|
return resolverInstance[fieldMeta.propertyKey](...params);
|
|
317
325
|
};
|
|
318
326
|
|
|
@@ -473,6 +481,14 @@ export class GraphQLModule {
|
|
|
473
481
|
for (const idx of parentParams) {
|
|
474
482
|
params[idx] = parent;
|
|
475
483
|
}
|
|
484
|
+
const contextParams = Reflect.getMetadata('calyx:context', resolverInstance, fieldRes.propertyKey) || [];
|
|
485
|
+
for (const item of contextParams) {
|
|
486
|
+
params[item.parameterIndex] = item.name ? context?.[item.name] : context;
|
|
487
|
+
}
|
|
488
|
+
const infoParams = Reflect.getMetadata('calyx:info', resolverInstance, fieldRes.propertyKey) || [];
|
|
489
|
+
for (const idx of infoParams) {
|
|
490
|
+
params[idx] = info;
|
|
491
|
+
}
|
|
476
492
|
return resolverInstance[fieldRes.propertyKey](...params);
|
|
477
493
|
};
|
|
478
494
|
|
package/src/http/application.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { EventEmitter } from '../event-emitter/event-emitter.ts';
|
|
|
10
10
|
import { cors, CorsOptions } from '../security/cors.middleware.ts';
|
|
11
11
|
import { helmet, HelmetOptions } from '../security/helmet.middleware.ts';
|
|
12
12
|
import { CronMatcher } from '../schedule/cron.matcher.ts';
|
|
13
|
+
import { SchedulerRegistry } from '../schedule/scheduler-registry.ts';
|
|
13
14
|
import { SerializationCompiler } from '../validation/compiler.ts';
|
|
14
15
|
import { VersioningOptions, VersioningType, VersionExtractor, VERSION_METADATA_KEY } from '../versioning/versioning.ts';
|
|
15
16
|
import { QueueManager, PROCESSOR_METADATA_KEY } from '../queue/queue.module.ts';
|
|
@@ -66,6 +67,41 @@ export class CalyxResponse {
|
|
|
66
67
|
this.cookiesList.push(formatCookie(name, value, options));
|
|
67
68
|
return this;
|
|
68
69
|
}
|
|
70
|
+
|
|
71
|
+
header(name: string, value: string) {
|
|
72
|
+
return this.set(name, value);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
type(contentType: string) {
|
|
76
|
+
return this.set('content-type', contentType);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
redirect(url: string, status?: number) {
|
|
80
|
+
this.statusCode = status ?? 302;
|
|
81
|
+
this.set('location', url);
|
|
82
|
+
this.sent = true;
|
|
83
|
+
return this;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
end() {
|
|
87
|
+
this.sent = true;
|
|
88
|
+
return this;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
get(name: string): string | undefined {
|
|
92
|
+
return this.headers[name.toLowerCase()];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
append(name: string, value: string) {
|
|
96
|
+
const key = name.toLowerCase();
|
|
97
|
+
const existing = this.headers[key];
|
|
98
|
+
this.headers[key] = existing ? `${existing}, ${value}` : value;
|
|
99
|
+
return this;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
clearCookie(name: string, options?: any) {
|
|
103
|
+
return this.cookie(name, '', { ...options, expires: new Date(0) });
|
|
104
|
+
}
|
|
69
105
|
}
|
|
70
106
|
|
|
71
107
|
interface CompiledLifecycleItem {
|
|
@@ -186,7 +222,7 @@ export class CalyxApplication {
|
|
|
186
222
|
this.isInitialized = true;
|
|
187
223
|
|
|
188
224
|
// Bootstrap the dependency injection container
|
|
189
|
-
this.container.bootstrap(this.rootModule);
|
|
225
|
+
await this.container.bootstrap(this.rootModule);
|
|
190
226
|
|
|
191
227
|
// Resolve registered modules middlewares
|
|
192
228
|
this.resolveMiddleware();
|
|
@@ -685,6 +721,10 @@ export class CalyxApplication {
|
|
|
685
721
|
case 'query': val = config.name ? query?.[config.name] : query; break;
|
|
686
722
|
case 'body': val = config.name ? body?.[config.name] : body; break;
|
|
687
723
|
case 'headers': val = config.name ? req.headers.get(config.name) : Object.fromEntries(req.headers.entries()); break;
|
|
724
|
+
case 'session': val = (req as any).session; break;
|
|
725
|
+
case 'ip': val = this.server ? (this.server.requestIP(req)?.address ?? req.headers.get('x-forwarded-for') ?? '') : (req.headers.get('x-forwarded-for') ?? ''); break;
|
|
726
|
+
case 'hostparam': val = config.name ? req.headers.get('host') : req.headers.get('host'); break;
|
|
727
|
+
case 'next': val = next; break;
|
|
688
728
|
case 'custom': val = config.factory ? config.factory(config.name, context) : undefined; break;
|
|
689
729
|
}
|
|
690
730
|
|
|
@@ -939,6 +979,18 @@ export class CalyxApplication {
|
|
|
939
979
|
case 'headers':
|
|
940
980
|
val = config.name ? req.headers.get(config.name) : Object.fromEntries(req.headers.entries());
|
|
941
981
|
break;
|
|
982
|
+
case 'session':
|
|
983
|
+
val = (req as any).session;
|
|
984
|
+
break;
|
|
985
|
+
case 'ip':
|
|
986
|
+
val = this.server ? (this.server.requestIP(req)?.address ?? req.headers.get('x-forwarded-for') ?? '') : (req.headers.get('x-forwarded-for') ?? '');
|
|
987
|
+
break;
|
|
988
|
+
case 'hostparam':
|
|
989
|
+
val = config.name ? req.headers.get('host') : req.headers.get('host');
|
|
990
|
+
break;
|
|
991
|
+
case 'next':
|
|
992
|
+
val = () => {};
|
|
993
|
+
break;
|
|
942
994
|
case 'custom':
|
|
943
995
|
val = config.factory ? config.factory(config.name, context!) : undefined;
|
|
944
996
|
break;
|
|
@@ -1554,11 +1606,18 @@ export class CalyxApplication {
|
|
|
1554
1606
|
return;
|
|
1555
1607
|
}
|
|
1556
1608
|
|
|
1609
|
+
let registry: SchedulerRegistry | null = null;
|
|
1610
|
+
try {
|
|
1611
|
+
registry = this.container.getGlobalOrAnyInstance(SchedulerRegistry);
|
|
1612
|
+
} catch {
|
|
1613
|
+
// ignore
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1557
1616
|
const instances = this.container.getProviderAndControllerInstances();
|
|
1558
1617
|
for (const instance of instances) {
|
|
1559
1618
|
if (!instance || !instance.constructor) continue;
|
|
1560
1619
|
|
|
1561
|
-
const crons: { expression: string; propertyKey: string | symbol }[] =
|
|
1620
|
+
const crons: { expression: string; propertyKey: string | symbol; name?: string }[] =
|
|
1562
1621
|
Reflect.getMetadata('calyx:cron', instance.constructor) || [];
|
|
1563
1622
|
for (const cron of crons) {
|
|
1564
1623
|
const parts = cron.expression.split(' ');
|
|
@@ -1584,9 +1643,16 @@ export class CalyxApplication {
|
|
|
1584
1643
|
const intervalMs = isSecondLevel ? 1000 : 20000;
|
|
1585
1644
|
const timer = setInterval(tick, intervalMs);
|
|
1586
1645
|
this.cleanupListeners.push(() => clearInterval(timer));
|
|
1646
|
+
|
|
1647
|
+
if (registry && cron.name) {
|
|
1648
|
+
registry.addCronJob(cron.name, {
|
|
1649
|
+
start: () => {},
|
|
1650
|
+
stop: () => clearInterval(timer),
|
|
1651
|
+
});
|
|
1652
|
+
}
|
|
1587
1653
|
}
|
|
1588
1654
|
|
|
1589
|
-
const intervals: { ms: number; propertyKey: string | symbol }[] =
|
|
1655
|
+
const intervals: { ms: number; propertyKey: string | symbol; name?: string }[] =
|
|
1590
1656
|
Reflect.getMetadata('calyx:interval', instance.constructor) || [];
|
|
1591
1657
|
for (const interval of intervals) {
|
|
1592
1658
|
const timer = setInterval(() => {
|
|
@@ -1597,9 +1663,13 @@ export class CalyxApplication {
|
|
|
1597
1663
|
}
|
|
1598
1664
|
}, interval.ms);
|
|
1599
1665
|
this.cleanupListeners.push(() => clearInterval(timer));
|
|
1666
|
+
|
|
1667
|
+
if (registry && interval.name) {
|
|
1668
|
+
registry.addInterval(interval.name, timer);
|
|
1669
|
+
}
|
|
1600
1670
|
}
|
|
1601
1671
|
|
|
1602
|
-
const timeouts: { ms: number; propertyKey: string | symbol }[] =
|
|
1672
|
+
const timeouts: { ms: number; propertyKey: string | symbol; name?: string }[] =
|
|
1603
1673
|
Reflect.getMetadata('calyx:timeout', instance.constructor) || [];
|
|
1604
1674
|
for (const timeout of timeouts) {
|
|
1605
1675
|
const timer = setTimeout(() => {
|
|
@@ -1610,6 +1680,10 @@ export class CalyxApplication {
|
|
|
1610
1680
|
}
|
|
1611
1681
|
}, timeout.ms);
|
|
1612
1682
|
this.cleanupListeners.push(() => clearTimeout(timer));
|
|
1683
|
+
|
|
1684
|
+
if (registry && timeout.name) {
|
|
1685
|
+
registry.addTimeout(timeout.name, timer);
|
|
1686
|
+
}
|
|
1613
1687
|
}
|
|
1614
1688
|
}
|
|
1615
1689
|
}
|
|
@@ -1635,14 +1709,24 @@ export class CalyxApplication {
|
|
|
1635
1709
|
for (const sub of subMessages) {
|
|
1636
1710
|
const paramMapping: any[] = [];
|
|
1637
1711
|
|
|
1712
|
+
const classPipes = Reflect.getMetadata(METADATA_KEYS.PIPES, instance.constructor) || [];
|
|
1713
|
+
const methodPipes = Reflect.getMetadata(METADATA_KEYS.PIPES, instance.constructor.prototype, sub.propertyKey) || [];
|
|
1714
|
+
const methodPipesCompiled = this.compileLifecycleItems(this.rootModule, [...this.globalPipes, ...classPipes, ...methodPipes]);
|
|
1715
|
+
|
|
1638
1716
|
for (const bp of bodyParams) {
|
|
1639
1717
|
if (bp.propertyKey === sub.propertyKey) {
|
|
1640
|
-
paramMapping[bp.parameterIndex] =
|
|
1718
|
+
paramMapping[bp.parameterIndex] = {
|
|
1719
|
+
type: 'body',
|
|
1720
|
+
name: bp.name,
|
|
1721
|
+
pipes: [...methodPipesCompiled, ...this.compileLifecycleItems(this.rootModule, bp.pipes || [])],
|
|
1722
|
+
};
|
|
1641
1723
|
}
|
|
1642
1724
|
}
|
|
1643
1725
|
for (const sp of socketParams) {
|
|
1644
1726
|
if (sp.propertyKey === sub.propertyKey) {
|
|
1645
|
-
paramMapping[sp.parameterIndex] =
|
|
1727
|
+
paramMapping[sp.parameterIndex] = {
|
|
1728
|
+
type: 'socket',
|
|
1729
|
+
};
|
|
1646
1730
|
}
|
|
1647
1731
|
}
|
|
1648
1732
|
|
|
@@ -1654,11 +1738,16 @@ export class CalyxApplication {
|
|
|
1654
1738
|
const methodInterceptors = Reflect.getMetadata(METADATA_KEYS.INTERCEPTORS, instance.constructor.prototype, sub.propertyKey) || [];
|
|
1655
1739
|
const interceptors = this.compileLifecycleItems(this.rootModule, [...this.globalInterceptors, ...classInterceptors, ...methodInterceptors]);
|
|
1656
1740
|
|
|
1741
|
+
const classFilters = Reflect.getMetadata(METADATA_KEYS.FILTERS, instance.constructor) || [];
|
|
1742
|
+
const methodFilters = Reflect.getMetadata(METADATA_KEYS.FILTERS, instance.constructor.prototype, sub.propertyKey) || [];
|
|
1743
|
+
const filters = this.compileLifecycleItems(this.rootModule, [...this.globalFilters, ...classFilters, ...methodFilters]);
|
|
1744
|
+
|
|
1657
1745
|
handlers.set(sub.event, {
|
|
1658
1746
|
propertyKey: sub.propertyKey,
|
|
1659
1747
|
paramMapping,
|
|
1660
1748
|
guards,
|
|
1661
1749
|
interceptors,
|
|
1750
|
+
filters,
|
|
1662
1751
|
gatewayClass: instance.constructor,
|
|
1663
1752
|
});
|
|
1664
1753
|
}
|
|
@@ -1738,16 +1827,6 @@ export class CalyxApplication {
|
|
|
1738
1827
|
|
|
1739
1828
|
const handlerInfo = gateway.handlers.get(event);
|
|
1740
1829
|
if (handlerInfo) {
|
|
1741
|
-
const args: any[] = [];
|
|
1742
|
-
for (let i = 0; i < handlerInfo.paramMapping.length; i++) {
|
|
1743
|
-
const type = handlerInfo.paramMapping[i];
|
|
1744
|
-
if (type === 'body') {
|
|
1745
|
-
args[i] = data;
|
|
1746
|
-
} else if (type === 'socket') {
|
|
1747
|
-
args[i] = ws;
|
|
1748
|
-
}
|
|
1749
|
-
}
|
|
1750
|
-
|
|
1751
1830
|
const context = this.contextPool.acquire();
|
|
1752
1831
|
context.resetContextWs(ws, data, handlerInfo.gatewayClass, gateway.instance[handlerInfo.propertyKey]);
|
|
1753
1832
|
|
|
@@ -1759,6 +1838,26 @@ export class CalyxApplication {
|
|
|
1759
1838
|
}
|
|
1760
1839
|
}
|
|
1761
1840
|
|
|
1841
|
+
const args: any[] = [];
|
|
1842
|
+
for (let i = 0; i < handlerInfo.paramMapping.length; i++) {
|
|
1843
|
+
const mapping = handlerInfo.paramMapping[i];
|
|
1844
|
+
if (!mapping) continue;
|
|
1845
|
+
if (mapping.type === 'body') {
|
|
1846
|
+
let val = mapping.name ? data?.[mapping.name] : data;
|
|
1847
|
+
for (const pipe of mapping.pipes || []) {
|
|
1848
|
+
const transformed = pipe.instance.transform(val, {
|
|
1849
|
+
type: 'body',
|
|
1850
|
+
metatype: undefined,
|
|
1851
|
+
data: mapping.name,
|
|
1852
|
+
});
|
|
1853
|
+
val = transformed instanceof Promise ? await transformed : transformed;
|
|
1854
|
+
}
|
|
1855
|
+
args[i] = val;
|
|
1856
|
+
} else if (mapping.type === 'socket') {
|
|
1857
|
+
args[i] = ws;
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1762
1861
|
const nextCall = {
|
|
1763
1862
|
handle: async () => {
|
|
1764
1863
|
return gateway.instance[handlerInfo.propertyKey](...args);
|
|
@@ -1782,7 +1881,19 @@ export class CalyxApplication {
|
|
|
1782
1881
|
ws.send(JSON.stringify(result));
|
|
1783
1882
|
}
|
|
1784
1883
|
} catch (err: any) {
|
|
1785
|
-
|
|
1884
|
+
if (handlerInfo.filters && handlerInfo.filters.length > 0) {
|
|
1885
|
+
for (const filter of handlerInfo.filters) {
|
|
1886
|
+
const catchException = Reflect.getMetadata('calyx:catch', filter.token) || [];
|
|
1887
|
+
if (catchException.length === 0 || catchException.some((exc: any) => err instanceof exc)) {
|
|
1888
|
+
const filterResult = filter.instance.catch(err, context);
|
|
1889
|
+
if (filterResult !== undefined) {
|
|
1890
|
+
ws.send(JSON.stringify(filterResult));
|
|
1891
|
+
return;
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
ws.send(JSON.stringify({ event: 'error', data: err.message || 'Internal error' }));
|
|
1786
1897
|
} finally {
|
|
1787
1898
|
context.clearContext();
|
|
1788
1899
|
this.contextPool.release(context);
|
package/src/http/decorators.ts
CHANGED
|
@@ -82,6 +82,10 @@ export const Headers = (first?: any, ...pipes: any[]) => {
|
|
|
82
82
|
return createHttpParamDecorator('headers', name, parsedPipes);
|
|
83
83
|
};
|
|
84
84
|
|
|
85
|
+
export const Ip = () => createHttpParamDecorator('ip');
|
|
86
|
+
export const HostParam = (name?: string) => createHttpParamDecorator('hostparam', name);
|
|
87
|
+
export const Next = () => createHttpParamDecorator('next');
|
|
88
|
+
|
|
85
89
|
export function HttpCode(code: number): MethodDecorator {
|
|
86
90
|
return (target, propertyKey) => {
|
|
87
91
|
Reflect.defineMetadata(METADATA_KEYS.HTTP_CODE, code, target, propertyKey);
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Module, DynamicModule } from '../core/decorators.ts';
|
|
2
|
+
import { ClientTcp } from './client-tcp.ts';
|
|
3
|
+
|
|
4
|
+
export function Client(options: any): PropertyDecorator {
|
|
5
|
+
return (target: any, propertyKey: string | symbol) => {
|
|
6
|
+
let clientInstance: any = null;
|
|
7
|
+
Object.defineProperty(target, propertyKey, {
|
|
8
|
+
get() {
|
|
9
|
+
if (!clientInstance) {
|
|
10
|
+
clientInstance = new ClientTcp(options?.options || options || {});
|
|
11
|
+
}
|
|
12
|
+
return clientInstance;
|
|
13
|
+
},
|
|
14
|
+
configurable: true,
|
|
15
|
+
enumerable: true,
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ClientProviderConfig {
|
|
21
|
+
name: string;
|
|
22
|
+
transport?: any;
|
|
23
|
+
options?: {
|
|
24
|
+
host?: string;
|
|
25
|
+
port?: number;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@Module({})
|
|
30
|
+
export class ClientsModule {
|
|
31
|
+
static register(clients: ClientProviderConfig[]): DynamicModule {
|
|
32
|
+
const providers = clients.map((client) => {
|
|
33
|
+
return {
|
|
34
|
+
provide: client.name,
|
|
35
|
+
useFactory: () => {
|
|
36
|
+
return new ClientTcp(client.options || {});
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
module: ClientsModule,
|
|
43
|
+
providers,
|
|
44
|
+
exports: clients.map((c) => c.name),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -31,7 +31,7 @@ export class CalyxMicroservice {
|
|
|
31
31
|
async listen(): Promise<any> {
|
|
32
32
|
if (this.isListening) return;
|
|
33
33
|
|
|
34
|
-
this.container.bootstrap(this.rootModule);
|
|
34
|
+
await this.container.bootstrap(this.rootModule);
|
|
35
35
|
this.server.registerHandlers(this.container, this.globalGuards, this.globalInterceptors);
|
|
36
36
|
|
|
37
37
|
const hostInfo = await this.server.listen();
|
|
@@ -1,28 +1,32 @@
|
|
|
1
1
|
import 'reflect-metadata';
|
|
2
2
|
|
|
3
|
-
export function Cron(expression: string): MethodDecorator {
|
|
3
|
+
export function Cron(expression: string, options?: { name?: string }): MethodDecorator {
|
|
4
4
|
return (target, propertyKey) => {
|
|
5
5
|
const constructor = target.constructor;
|
|
6
6
|
const existing = Reflect.getOwnMetadata('calyx:cron', constructor) || [];
|
|
7
|
-
existing.push({ expression, propertyKey });
|
|
7
|
+
existing.push({ expression, propertyKey, name: options?.name });
|
|
8
8
|
Reflect.defineMetadata('calyx:cron', existing, constructor);
|
|
9
9
|
};
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
export function Interval(
|
|
12
|
+
export function Interval(nameOrMs: string | number, ms?: number): MethodDecorator {
|
|
13
13
|
return (target, propertyKey) => {
|
|
14
14
|
const constructor = target.constructor;
|
|
15
|
+
const name = typeof nameOrMs === 'string' ? nameOrMs : undefined;
|
|
16
|
+
const intervalMs = typeof nameOrMs === 'number' ? nameOrMs : ms!;
|
|
15
17
|
const existing = Reflect.getOwnMetadata('calyx:interval', constructor) || [];
|
|
16
|
-
existing.push({ ms, propertyKey });
|
|
18
|
+
existing.push({ ms: intervalMs, propertyKey, name });
|
|
17
19
|
Reflect.defineMetadata('calyx:interval', existing, constructor);
|
|
18
20
|
};
|
|
19
21
|
}
|
|
20
22
|
|
|
21
|
-
export function Timeout(
|
|
23
|
+
export function Timeout(nameOrMs: string | number, ms?: number): MethodDecorator {
|
|
22
24
|
return (target, propertyKey) => {
|
|
23
25
|
const constructor = target.constructor;
|
|
26
|
+
const name = typeof nameOrMs === 'string' ? nameOrMs : undefined;
|
|
27
|
+
const timeoutMs = typeof nameOrMs === 'number' ? nameOrMs : ms!;
|
|
24
28
|
const existing = Reflect.getOwnMetadata('calyx:timeout', constructor) || [];
|
|
25
|
-
existing.push({ ms, propertyKey });
|
|
29
|
+
existing.push({ ms: timeoutMs, propertyKey, name });
|
|
26
30
|
Reflect.defineMetadata('calyx:timeout', existing, constructor);
|
|
27
31
|
};
|
|
28
32
|
}
|
package/src/schedule/index.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { Module, DynamicModule } from '../core/decorators.ts';
|
|
2
|
+
import { SchedulerRegistry } from './scheduler-registry.ts';
|
|
2
3
|
|
|
3
4
|
@Module({})
|
|
4
5
|
export class ScheduleModule {
|
|
5
6
|
static forRoot(): DynamicModule {
|
|
6
7
|
return {
|
|
7
8
|
module: ScheduleModule,
|
|
8
|
-
providers: [],
|
|
9
|
-
exports: [],
|
|
9
|
+
providers: [SchedulerRegistry],
|
|
10
|
+
exports: [SchedulerRegistry],
|
|
10
11
|
global: true,
|
|
11
12
|
};
|
|
12
13
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Injectable } from '../core/decorators.ts';
|
|
2
|
+
|
|
3
|
+
@Injectable()
|
|
4
|
+
export class SchedulerRegistry {
|
|
5
|
+
private cronJobs = new Map<string, any>();
|
|
6
|
+
private intervals = new Map<string, any>();
|
|
7
|
+
private timeouts = new Map<string, any>();
|
|
8
|
+
|
|
9
|
+
// Cron
|
|
10
|
+
getCronJob(name: string) {
|
|
11
|
+
const job = this.cronJobs.get(name);
|
|
12
|
+
if (!job) throw new Error(`No Cron Job was found with the given name (${name})`);
|
|
13
|
+
return job;
|
|
14
|
+
}
|
|
15
|
+
getCronJobs(): Map<string, any> { return this.cronJobs; }
|
|
16
|
+
addCronJob(name: string, job: any) { this.cronJobs.set(name, job); }
|
|
17
|
+
deleteCronJob(name: string) {
|
|
18
|
+
const job = this.cronJobs.get(name);
|
|
19
|
+
if (job && typeof job.stop === 'function') job.stop();
|
|
20
|
+
this.cronJobs.delete(name);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Interval
|
|
24
|
+
getInterval(name: string) {
|
|
25
|
+
const interval = this.intervals.get(name);
|
|
26
|
+
if (!interval) throw new Error(`No Interval was found with the given name (${name})`);
|
|
27
|
+
return interval;
|
|
28
|
+
}
|
|
29
|
+
getIntervals(): string[] { return Array.from(this.intervals.keys()); }
|
|
30
|
+
addInterval(name: string, intervalId: any) { this.intervals.set(name, intervalId); }
|
|
31
|
+
deleteInterval(name: string) {
|
|
32
|
+
const interval = this.intervals.get(name);
|
|
33
|
+
if (interval) clearInterval(interval);
|
|
34
|
+
this.intervals.delete(name);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Timeout
|
|
38
|
+
getTimeout(name: string) {
|
|
39
|
+
const timeout = this.timeouts.get(name);
|
|
40
|
+
if (!timeout) throw new Error(`No Timeout was found with the given name (${name})`);
|
|
41
|
+
return timeout;
|
|
42
|
+
}
|
|
43
|
+
getTimeouts(): string[] { return Array.from(this.timeouts.keys()); }
|
|
44
|
+
addTimeout(name: string, timeoutId: any) { this.timeouts.set(name, timeoutId); }
|
|
45
|
+
deleteTimeout(name: string) {
|
|
46
|
+
const timeout = this.timeouts.get(name);
|
|
47
|
+
if (timeout) clearTimeout(timeout);
|
|
48
|
+
this.timeouts.delete(name);
|
|
49
|
+
}
|
|
50
|
+
}
|