@common-stack/store-mongo 8.0.2-alpha.0 → 8.1.1-alpha.13
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/LICENSE +34 -21
- package/lib/containers/container.d.ts +14 -0
- package/lib/containers/container.js +17 -0
- package/lib/containers/container.js.map +1 -0
- package/lib/containers/index.d.ts +1 -0
- package/lib/dataloaders/bulk-dataloader-v2.d.ts +40 -0
- package/lib/dataloaders/bulk-dataloader-v2.js +118 -0
- package/lib/dataloaders/bulk-dataloader-v2.js.map +1 -0
- package/lib/dataloaders/bulk-dataloader-v2.test.d.ts +1 -0
- package/lib/dataloaders/bulk-dataloader.d.ts +1 -1
- package/lib/dataloaders/bulk-dataloader.js.map +1 -1
- package/lib/dataloaders/bulk-dataloader.test.d.ts +1 -0
- package/lib/dataloaders/index.d.ts +1 -0
- package/lib/graphql/schema/base-services.graphql +238 -0
- package/lib/helpers/mongoose-connection.js.map +1 -1
- package/lib/index.d.ts +2 -1
- package/lib/index.js +1 -1
- package/lib/interfaces/base-repository.d.ts +1 -5
- package/lib/interfaces/base-service.d.ts +1 -1
- package/lib/interfaces/getAllArgs.d.ts +135 -0
- package/lib/interfaces/getAllArgs.js +39 -0
- package/lib/interfaces/getAllArgs.js.map +1 -0
- package/lib/interfaces/index.d.ts +1 -4
- package/lib/interfaces/index.old.d.ts +3 -0
- package/lib/mixins/BaseServiceMixin.d.ts +45 -0
- package/lib/mixins/BaseServiceMixin.js +212 -0
- package/lib/mixins/BaseServiceMixin.js.map +1 -0
- package/lib/mixins/__tests__/BaseServiceMixin.test.d.ts +1 -0
- package/lib/mixins/base-service-mixin.js.map +1 -1
- package/lib/mixins/index.d.ts +1 -0
- package/lib/module.d.ts +2 -0
- package/lib/module.js +4 -0
- package/lib/module.js.map +1 -0
- package/lib/services/BaseProxyService.d.ts +28 -0
- package/lib/services/BaseProxyService.js +52 -0
- package/lib/services/BaseProxyService.js.map +1 -0
- package/lib/services/BaseService.d.ts +23 -0
- package/lib/services/BaseService.js +65 -0
- package/lib/services/BaseService.js.map +1 -0
- package/lib/services/BaseService.test.d.ts +1 -0
- package/lib/services/ConnectionPoolManager.d.ts +54 -0
- package/lib/services/ConnectionPoolManager.js +163 -0
- package/lib/services/ConnectionPoolManager.js.map +1 -0
- package/lib/services/base-proxy-service.d.ts +2 -1
- package/lib/services/base-proxy-service.js.map +1 -1
- package/lib/services/base-service.d.ts +2 -1
- package/lib/services/base-service.js.map +1 -1
- package/lib/services/index.d.ts +3 -0
- package/lib/store/models/common-options-v2.d.ts +16 -0
- package/lib/store/models/common-options-v2.js +40 -0
- package/lib/store/models/common-options-v2.js.map +1 -0
- package/lib/store/models/common-options.js.map +1 -1
- package/lib/store/models/index.d.ts +1 -0
- package/lib/store/repositories/BaseMongoRepository.d.ts +72 -0
- package/lib/store/repositories/BaseMongoRepository.js +423 -0
- package/lib/store/repositories/BaseMongoRepository.js.map +1 -0
- package/lib/store/repositories/BaseMongoRepository.test.d.ts +1 -0
- package/lib/store/repositories/CustomIdRepository.test.d.ts +1 -0
- package/lib/store/repositories/base-repository.d.ts +2 -1
- package/lib/store/repositories/base-repository.js +1 -1
- package/lib/store/repositories/base-repository.js.map +1 -1
- package/lib/store/repositories/index.d.ts +1 -0
- package/lib/templates/constants/SERVER_TYPES.ts.template +7 -0
- package/lib/templates/repositories/DataLoader.ts.template +211 -0
- package/lib/templates/repositories/DatabaseMigration.ts.template +13 -0
- package/lib/templates/repositories/IBaseMongoRepository.ts.template +284 -0
- package/lib/templates/repositories/IBaseService.ts.template +231 -0
- package/lib/templates/repositories/IBaseServiceMixin.ts.template +477 -0
- package/lib/templates/repositories/dbCommonTypes.ts.template +140 -0
- package/lib/templates/repositories/moleculerEventHandler.ts.template.toberemoved +118 -0
- package/lib/templates/repositories/mongoCommonTypes.ts.template +21 -0
- package/lib/templates/repositories/typedMoleculerService.ts.template.toberemoved +1188 -0
- package/lib/templates/repositories/zodToMoleculer.ts.template.toberemoved +133 -0
- package/package.json +22 -5
- package/lib/interfaces/base-repository.js +0 -5
- package/lib/interfaces/base-repository.js.map +0 -1
- package/lib/interfaces/get-all-args.d.ts +0 -9
|
@@ -0,0 +1,1188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file typed-moleculer-service.ts
|
|
3
|
+
* @description Provides type-safe base class for Moleculer services with ENFORCED
|
|
4
|
+
* compile-time verification AND automatic action generation from service methods.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/* eslint-disable @typescript-eslint/no-namespace */
|
|
8
|
+
import type { ServiceSchema } from 'moleculer';
|
|
9
|
+
import { Moleculer } from './moleculerEventHandler';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Exclude base service CRUD methods and lifecycle methods
|
|
13
|
+
*/
|
|
14
|
+
type ExcludedMethods =
|
|
15
|
+
| 'dispose'
|
|
16
|
+
| 'get'
|
|
17
|
+
| 'getAll'
|
|
18
|
+
| 'bulkDelete'
|
|
19
|
+
| 'delete'
|
|
20
|
+
| 'count'
|
|
21
|
+
| 'getByName'
|
|
22
|
+
| 'getByIds'
|
|
23
|
+
| 'getAllWithCount'
|
|
24
|
+
| 'create'
|
|
25
|
+
| 'update'
|
|
26
|
+
| 'bulkCreate'
|
|
27
|
+
| 'insert';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Extract all method names from a service interface (excluding base methods)
|
|
31
|
+
*/
|
|
32
|
+
export type ServiceMethods<T> = {
|
|
33
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
34
|
+
[K in keyof T]: T[K] extends Function ? (K extends ExcludedMethods ? never : K) : never;
|
|
35
|
+
}[keyof T];
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Required actions type - forces all service methods to have actions
|
|
39
|
+
*/
|
|
40
|
+
export type RequiredServiceActions<TService> = {
|
|
41
|
+
[K in ServiceMethods<TService>]: {
|
|
42
|
+
params?: Record<string, unknown>;
|
|
43
|
+
handler: (...args: unknown[]) => unknown;
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Service schema with REQUIRED typed actions - TypeScript will error if any are missing
|
|
49
|
+
*/
|
|
50
|
+
export interface EnforcedServiceSchema<TService> extends Omit<ServiceSchema, 'actions'> {
|
|
51
|
+
name: string; // Required by Moleculer
|
|
52
|
+
actions: RequiredServiceActions<TService> & Record<string, unknown>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Infer Moleculer param type from TypeScript function parameter types
|
|
57
|
+
* Maps TypeScript types to Moleculer validation strings
|
|
58
|
+
*
|
|
59
|
+
* @internal Reserved for future advanced type inference
|
|
60
|
+
*/
|
|
61
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
62
|
+
type InferMoleculerParamType<T> =
|
|
63
|
+
T extends string ? 'string' :
|
|
64
|
+
T extends number ? 'number' :
|
|
65
|
+
T extends boolean ? 'boolean' :
|
|
66
|
+
T extends unknown[] ? 'array' :
|
|
67
|
+
T extends object ? 'object' :
|
|
68
|
+
'any';
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Extract parameter types from a function signature
|
|
72
|
+
*
|
|
73
|
+
* @internal Reserved for future advanced type inference
|
|
74
|
+
*/
|
|
75
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
76
|
+
type FunctionParams<T> = T extends (...args: infer P) => unknown ? P : never;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Split parameters by comma, respecting nested structures
|
|
80
|
+
*/
|
|
81
|
+
function splitParams(paramsStr: string): string[] {
|
|
82
|
+
const params: string[] = [];
|
|
83
|
+
let current = '';
|
|
84
|
+
let depth = 0;
|
|
85
|
+
|
|
86
|
+
for (let i = 0; i < paramsStr.length; i++) {
|
|
87
|
+
const char = paramsStr[i];
|
|
88
|
+
|
|
89
|
+
if (char === '<' || char === '{' || char === '[' || char === '(') {
|
|
90
|
+
depth++;
|
|
91
|
+
current += char;
|
|
92
|
+
} else if (char === '>' || char === '}' || char === ']' || char === ')') {
|
|
93
|
+
depth--;
|
|
94
|
+
current += char;
|
|
95
|
+
} else if (char === ',' && depth === 0) {
|
|
96
|
+
params.push(current);
|
|
97
|
+
current = '';
|
|
98
|
+
} else {
|
|
99
|
+
current += char;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (current) {
|
|
104
|
+
params.push(current);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return params;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Infer Moleculer validator type from TypeScript type annotation
|
|
112
|
+
*/
|
|
113
|
+
function inferMoleculerType(typeAnnotation: string): string {
|
|
114
|
+
const type = typeAnnotation.trim();
|
|
115
|
+
|
|
116
|
+
// Check for array types
|
|
117
|
+
if (type.endsWith('[]') || type.startsWith('Array<') || type.startsWith('ReadonlyArray<')) {
|
|
118
|
+
return 'array';
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Check for primitive types
|
|
122
|
+
if (type === 'string' || type === 'String') return 'string';
|
|
123
|
+
if (type === 'number' || type === 'Number') return 'number';
|
|
124
|
+
if (type === 'boolean' || type === 'Boolean') return 'boolean';
|
|
125
|
+
|
|
126
|
+
// Check for common built-in types
|
|
127
|
+
if (type === 'Date') return 'date';
|
|
128
|
+
if (type === 'RegExp') return 'object';
|
|
129
|
+
|
|
130
|
+
// Check for union types with primitive
|
|
131
|
+
if (type.includes('|')) {
|
|
132
|
+
const types = type.split('|').map(t => t.trim());
|
|
133
|
+
// If all types are same primitive, return that
|
|
134
|
+
const nonNullTypes = types.filter(t => t !== 'null' && t !== 'undefined');
|
|
135
|
+
if (nonNullTypes.length === 1) {
|
|
136
|
+
return inferMoleculerType(nonNullTypes[0]);
|
|
137
|
+
}
|
|
138
|
+
// If contains string, number, or boolean, try to infer
|
|
139
|
+
if (nonNullTypes.some(t => t === 'string')) return 'string';
|
|
140
|
+
if (nonNullTypes.some(t => t === 'number')) return 'number';
|
|
141
|
+
if (nonNullTypes.some(t => t === 'boolean')) return 'boolean';
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Check for enum types (assume string enum)
|
|
145
|
+
if (type.includes('Enum') || type.includes('Type')) {
|
|
146
|
+
return 'string';
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Default to object for interfaces, types, classes, etc.
|
|
150
|
+
return 'object';
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Infer type from default value
|
|
155
|
+
*/
|
|
156
|
+
function inferTypeFromDefault(defaultValue: string): string {
|
|
157
|
+
// String defaults
|
|
158
|
+
if (defaultValue.startsWith("'") || defaultValue.startsWith('"') || defaultValue.startsWith('`')) {
|
|
159
|
+
return 'string';
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Number defaults
|
|
163
|
+
if (/^-?\d+(\.\d+)?$/.test(defaultValue)) {
|
|
164
|
+
return 'number';
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Boolean defaults
|
|
168
|
+
if (defaultValue === 'true' || defaultValue === 'false') {
|
|
169
|
+
return 'boolean';
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Array defaults
|
|
173
|
+
if (defaultValue.startsWith('[')) {
|
|
174
|
+
return 'array';
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Object defaults
|
|
178
|
+
if (defaultValue.startsWith('{')) {
|
|
179
|
+
return 'object';
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return 'object';
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Extract parameter names and infer types from a function's string representation
|
|
187
|
+
* Returns array of { name, type } where type is the inferred Moleculer validator
|
|
188
|
+
*/
|
|
189
|
+
function extractParamsWithTypes(funcStr: string): Array<{ name: string; type: string; optional: boolean }> {
|
|
190
|
+
// Match function parameters: function(a, b) or (a, b) => or async (a, b) =>
|
|
191
|
+
const match = funcStr.match(/(?:function\s*)?(?:\w+\s*)?\(([^)]*)\)/);
|
|
192
|
+
if (!match || !match[1]) return [];
|
|
193
|
+
|
|
194
|
+
const paramsStr = match[1].trim();
|
|
195
|
+
if (!paramsStr) return [];
|
|
196
|
+
|
|
197
|
+
const params: Array<{ name: string; type: string; optional: boolean }> = [];
|
|
198
|
+
|
|
199
|
+
// Split by comma, but handle nested objects/arrays
|
|
200
|
+
const paramParts = splitParams(paramsStr);
|
|
201
|
+
|
|
202
|
+
paramParts.forEach(param => {
|
|
203
|
+
const trimmed = param.trim();
|
|
204
|
+
if (!trimmed) return;
|
|
205
|
+
|
|
206
|
+
// Check if optional (has ?)
|
|
207
|
+
const optional = trimmed.includes('?');
|
|
208
|
+
|
|
209
|
+
// Extract parameter name and type annotation
|
|
210
|
+
// Patterns: "name", "name?", "name: Type", "name?: Type", "name = default"
|
|
211
|
+
const paramMatch = trimmed.match(/^(\w+)\??(?:\s*:\s*([^=]+))?(?:\s*=\s*(.+))?/);
|
|
212
|
+
if (!paramMatch) return;
|
|
213
|
+
|
|
214
|
+
const name = paramMatch[1];
|
|
215
|
+
const typeAnnotation = paramMatch[2]?.trim();
|
|
216
|
+
const defaultValue = paramMatch[3]?.trim();
|
|
217
|
+
|
|
218
|
+
// Infer Moleculer type from TypeScript annotation or default value
|
|
219
|
+
let moleculerType = 'object'; // Safe default
|
|
220
|
+
|
|
221
|
+
if (typeAnnotation) {
|
|
222
|
+
moleculerType = inferMoleculerType(typeAnnotation);
|
|
223
|
+
} else if (defaultValue) {
|
|
224
|
+
moleculerType = inferTypeFromDefault(defaultValue);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
params.push({
|
|
228
|
+
name,
|
|
229
|
+
type: moleculerType,
|
|
230
|
+
optional: optional || !!defaultValue,
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
return params;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Extract parameter names from a function's string representation (legacy)
|
|
239
|
+
* @deprecated Use extractParamsWithTypes instead
|
|
240
|
+
*/
|
|
241
|
+
function extractParamNames(funcStr: string): string[] {
|
|
242
|
+
const params = extractParamsWithTypes(funcStr);
|
|
243
|
+
return params.map(p => p.name);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* ADVANCED AUTO-GENERATED configuration with full control
|
|
248
|
+
* Params are inferred from TypeScript function signatures with optional fine-grained control
|
|
249
|
+
*/
|
|
250
|
+
export type AutoInferredActionConfig<TService> = {
|
|
251
|
+
/**
|
|
252
|
+
* Optional: Override param schemas for specific methods
|
|
253
|
+
* Use this when you need custom Moleculer validation beyond the default 'object' type
|
|
254
|
+
*
|
|
255
|
+
* @example
|
|
256
|
+
* ```typescript
|
|
257
|
+
* paramOverrides: {
|
|
258
|
+
* findAccountById: { id: 'string' }, // Override inferred 'object' with 'string'
|
|
259
|
+
* getUsers: { where: { type: 'object', optional: true } } // Add optional flag
|
|
260
|
+
* }
|
|
261
|
+
* ```
|
|
262
|
+
*/
|
|
263
|
+
paramOverrides?: Partial<{
|
|
264
|
+
[K in ServiceMethods<TService>]: Record<string, unknown>;
|
|
265
|
+
}>;
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Optional: Only include specific methods (whitelist approach)
|
|
269
|
+
* If specified, ONLY these methods will be auto-generated
|
|
270
|
+
* Takes precedence over exclude
|
|
271
|
+
*
|
|
272
|
+
* @example
|
|
273
|
+
* ```typescript
|
|
274
|
+
* include: ['findAccountById', 'createAccount', 'updateAccount']
|
|
275
|
+
* ```
|
|
276
|
+
*/
|
|
277
|
+
include?: Array<ServiceMethods<TService>>;
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Optional: Exclude specific methods from auto-generation (blacklist approach)
|
|
281
|
+
* Ignored if 'include' is specified
|
|
282
|
+
*
|
|
283
|
+
* @example
|
|
284
|
+
* ```typescript
|
|
285
|
+
* exclude: ['deleteAccount', 'internalMethod']
|
|
286
|
+
* ```
|
|
287
|
+
*/
|
|
288
|
+
exclude?: Array<ServiceMethods<TService>>;
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Optional: Map action names to different method names
|
|
292
|
+
* Use this when your Moleculer action name differs from the service method name
|
|
293
|
+
*
|
|
294
|
+
* @example
|
|
295
|
+
* ```typescript
|
|
296
|
+
* actionToMethodMap: {
|
|
297
|
+
* 'accounts.find': 'findAccountById',
|
|
298
|
+
* 'accounts.create': 'createAccount'
|
|
299
|
+
* }
|
|
300
|
+
* ```
|
|
301
|
+
*/
|
|
302
|
+
actionToMethodMap?: Record<string, string>;
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Optional: Add custom Moleculer action properties for specific methods
|
|
306
|
+
* Allows setting visibility, caching, rate limiting, etc.
|
|
307
|
+
*
|
|
308
|
+
* @example
|
|
309
|
+
* ```typescript
|
|
310
|
+
* actionConfig: {
|
|
311
|
+
* findAccountById: {
|
|
312
|
+
* cache: true,
|
|
313
|
+
* visibility: 'public'
|
|
314
|
+
* },
|
|
315
|
+
* deleteAccount: {
|
|
316
|
+
* visibility: 'protected'
|
|
317
|
+
* }
|
|
318
|
+
* }
|
|
319
|
+
* ```
|
|
320
|
+
*/
|
|
321
|
+
actionConfig?: Partial<{
|
|
322
|
+
[K in ServiceMethods<TService>]: {
|
|
323
|
+
cache?: boolean | object;
|
|
324
|
+
visibility?: 'public' | 'protected' | 'private';
|
|
325
|
+
[key: string]: unknown;
|
|
326
|
+
};
|
|
327
|
+
}>;
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Helper function to get all method names from an object, including inherited methods
|
|
332
|
+
* from the entire prototype chain (excluding Object.prototype methods)
|
|
333
|
+
*
|
|
334
|
+
* @param obj - The object to get methods from
|
|
335
|
+
* @returns Array of method names
|
|
336
|
+
*/
|
|
337
|
+
function getAllMethodNames(obj: unknown): string[] {
|
|
338
|
+
const methods = new Set<string>();
|
|
339
|
+
let current = obj;
|
|
340
|
+
|
|
341
|
+
// Traverse up the prototype chain
|
|
342
|
+
while (current && current !== Object.prototype) {
|
|
343
|
+
const prototype = Object.getPrototypeOf(current);
|
|
344
|
+
if (prototype && prototype !== Object.prototype) {
|
|
345
|
+
Object.getOwnPropertyNames(prototype).forEach(name => {
|
|
346
|
+
// Only add functions, not constructors or private methods
|
|
347
|
+
if (name !== 'constructor' && !name.startsWith('_')) {
|
|
348
|
+
const descriptor = Object.getOwnPropertyDescriptor(prototype, name);
|
|
349
|
+
if (descriptor && typeof descriptor.value === 'function') {
|
|
350
|
+
methods.add(name);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
current = prototype;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return Array.from(methods);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* ADVANCED AUTO-GENERATED: Generate actions with full control over inclusion, exclusion, and overrides.
|
|
363
|
+
* Params are inferred from TypeScript function signatures with optional fine-grained control.
|
|
364
|
+
*
|
|
365
|
+
* @param service - The service instance to wrap
|
|
366
|
+
* @param config - Advanced configuration for fine-grained control
|
|
367
|
+
* @returns Actions object with handlers for selected service methods
|
|
368
|
+
*
|
|
369
|
+
* @example
|
|
370
|
+
* ```typescript
|
|
371
|
+
* // Basic: Auto-generate all methods
|
|
372
|
+
* const actions = generateAutoInferredServiceActions<IAccountService>(accountService);
|
|
373
|
+
*
|
|
374
|
+
* // Advanced: Include only specific methods
|
|
375
|
+
* const actions = generateAutoInferredServiceActions<IAccountService>(accountService, {
|
|
376
|
+
* include: ['findAccountById', 'createAccount', 'updateAccount']
|
|
377
|
+
* });
|
|
378
|
+
*
|
|
379
|
+
* // Advanced: Exclude specific methods
|
|
380
|
+
* const actions = generateAutoInferredServiceActions<IAccountService>(accountService, {
|
|
381
|
+
* exclude: ['deleteAccount', 'internalMethod']
|
|
382
|
+
* });
|
|
383
|
+
*
|
|
384
|
+
* // Advanced: Override param schemas for specific methods
|
|
385
|
+
* const actions = generateAutoInferredServiceActions<IAccountService>(accountService, {
|
|
386
|
+
* paramOverrides: {
|
|
387
|
+
* findAccountById: { id: 'string' },
|
|
388
|
+
* getUsers: { where: { type: 'object', optional: true } }
|
|
389
|
+
* }
|
|
390
|
+
* });
|
|
391
|
+
*
|
|
392
|
+
* // Advanced: Add custom action configuration
|
|
393
|
+
* const actions = generateAutoInferredServiceActions<IAccountService>(accountService, {
|
|
394
|
+
* actionConfig: {
|
|
395
|
+
* findAccountById: { cache: true, visibility: 'public' },
|
|
396
|
+
* deleteAccount: { visibility: 'protected' }
|
|
397
|
+
* }
|
|
398
|
+
* });
|
|
399
|
+
*
|
|
400
|
+
* // Advanced: Combine multiple options
|
|
401
|
+
* const actions = generateAutoInferredServiceActions<IAccountService>(accountService, {
|
|
402
|
+
* include: ['findAccountById', 'createAccount'],
|
|
403
|
+
* paramOverrides: {
|
|
404
|
+
* findAccountById: { id: 'string' }
|
|
405
|
+
* },
|
|
406
|
+
* actionConfig: {
|
|
407
|
+
* findAccountById: { cache: true }
|
|
408
|
+
* }
|
|
409
|
+
* });
|
|
410
|
+
* ```
|
|
411
|
+
*/
|
|
412
|
+
export function generateAutoInferredServiceActions<TService>(
|
|
413
|
+
service: TService,
|
|
414
|
+
config: AutoInferredActionConfig<TService> = {},
|
|
415
|
+
): Record<string, { params?: Record<string, unknown>; handler: (ctx: unknown) => unknown }> {
|
|
416
|
+
const {
|
|
417
|
+
paramOverrides = {},
|
|
418
|
+
include,
|
|
419
|
+
exclude = [],
|
|
420
|
+
actionToMethodMap = {},
|
|
421
|
+
actionConfig = {}
|
|
422
|
+
} = config;
|
|
423
|
+
const actions: Record<string, { params?: Record<string, unknown>; handler: (ctx: unknown) => unknown }> = {};
|
|
424
|
+
|
|
425
|
+
// Get all methods from the service, including inherited methods from parent classes
|
|
426
|
+
const allKeys = getAllMethodNames(service);
|
|
427
|
+
|
|
428
|
+
allKeys.forEach((key) => {
|
|
429
|
+
// Skip constructor and private methods
|
|
430
|
+
if (key === 'constructor' || key.startsWith('_')) {
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Apply include filter (whitelist) - takes precedence
|
|
435
|
+
if (include && include.length > 0) {
|
|
436
|
+
if (!include.includes(key as ServiceMethods<TService>)) {
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
// Apply exclude filter (blacklist) - only if include is not specified
|
|
441
|
+
else if (exclude.includes(key as ServiceMethods<TService>)) {
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const method = (service as Record<string, unknown>)[key];
|
|
446
|
+
if (typeof method !== 'function') {
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const methodName = actionToMethodMap[key] || key;
|
|
451
|
+
const serviceMethod = (service as Record<string, unknown>)[methodName];
|
|
452
|
+
|
|
453
|
+
if (typeof serviceMethod !== 'function') {
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Use runtime reflection to infer params and types from function signature
|
|
458
|
+
const funcStr = serviceMethod.toString();
|
|
459
|
+
const paramsWithTypes = extractParamsWithTypes(funcStr);
|
|
460
|
+
|
|
461
|
+
// Build param schema
|
|
462
|
+
let paramSchema: Record<string, unknown> | undefined;
|
|
463
|
+
|
|
464
|
+
// Use override if provided
|
|
465
|
+
if (paramOverrides[key as keyof typeof paramOverrides]) {
|
|
466
|
+
paramSchema = paramOverrides[key as keyof typeof paramOverrides] as Record<string, unknown>;
|
|
467
|
+
} else if (paramsWithTypes.length > 0) {
|
|
468
|
+
// Auto-generate schema from inferred types
|
|
469
|
+
paramSchema = paramsWithTypes.reduce((acc, param) => {
|
|
470
|
+
if (param.optional) {
|
|
471
|
+
acc[param.name] = { type: param.type, optional: true };
|
|
472
|
+
} else {
|
|
473
|
+
acc[param.name] = param.type;
|
|
474
|
+
}
|
|
475
|
+
return acc;
|
|
476
|
+
}, {} as Record<string, unknown>);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Get custom action configuration if provided
|
|
480
|
+
const customActionConfig = actionConfig[key as keyof typeof actionConfig];
|
|
481
|
+
|
|
482
|
+
// Build the action object
|
|
483
|
+
const action: { params?: Record<string, unknown>; handler: (ctx: unknown) => unknown; [key: string]: unknown } = {
|
|
484
|
+
...(paramSchema ? { params: paramSchema } : {}),
|
|
485
|
+
handler: (ctx: { params: Record<string, unknown> }) => {
|
|
486
|
+
const method = (service as Record<string, (...args: unknown[]) => unknown>)[methodName];
|
|
487
|
+
const paramsObj = ctx.params || {};
|
|
488
|
+
const values = Object.values(paramsObj);
|
|
489
|
+
return method.apply(service, values.length > 0 ? values : [paramsObj]);
|
|
490
|
+
},
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
// Merge custom action config if provided (cache, visibility, etc.)
|
|
494
|
+
if (customActionConfig) {
|
|
495
|
+
Object.assign(action, customActionConfig);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
actions[key] = action;
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
return actions;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Generate both actions AND events from a service instance
|
|
506
|
+
* - Actions: Generated from regular service methods
|
|
507
|
+
* - Events: Generated from methods marked with @MoleculerEventHandler decorator
|
|
508
|
+
*
|
|
509
|
+
* @param service - The service instance to wrap
|
|
510
|
+
* @param config - Configuration for actions and events
|
|
511
|
+
* @returns Object with both actions and events
|
|
512
|
+
*
|
|
513
|
+
* @example
|
|
514
|
+
* ```typescript
|
|
515
|
+
* // Service with decorator
|
|
516
|
+
* class OrganizationService {
|
|
517
|
+
* // Regular method becomes action
|
|
518
|
+
* async createOrganization(org: Org): Promise<Org> { }
|
|
519
|
+
*
|
|
520
|
+
* // Decorated method becomes event handler
|
|
521
|
+
* @MoleculerEventHandler(UserBroadcasterAction.OnUserCreated)
|
|
522
|
+
* async onUserCreated(event: UserEvent): Promise<void> { }
|
|
523
|
+
* }
|
|
524
|
+
*
|
|
525
|
+
* // Generate both
|
|
526
|
+
* const { actions, events } = generateServiceActionsAndEvents(service);
|
|
527
|
+
* ```
|
|
528
|
+
*/
|
|
529
|
+
export function generateServiceActionsAndEvents<TService>(
|
|
530
|
+
service: TService,
|
|
531
|
+
config: AutoInferredActionConfig<TService> = {},
|
|
532
|
+
): {
|
|
533
|
+
actions: Record<string, { params?: Record<string, unknown>; handler: (ctx: unknown) => unknown; [key: string]: unknown }>;
|
|
534
|
+
events: Record<string, { handler: (ctx: unknown) => unknown; group?: string }>;
|
|
535
|
+
} {
|
|
536
|
+
// Get event handlers from decorator metadata
|
|
537
|
+
const eventHandlers = Moleculer.getEventHandlers(service as object);
|
|
538
|
+
const eventHandlerMethods = new Set(eventHandlers.map(h => h.methodName));
|
|
539
|
+
|
|
540
|
+
// Generate actions (excluding event handler methods)
|
|
541
|
+
const actionsConfig: AutoInferredActionConfig<TService> = {
|
|
542
|
+
...config,
|
|
543
|
+
exclude: [
|
|
544
|
+
...(config.exclude || []),
|
|
545
|
+
...Array.from(eventHandlerMethods) as Array<ServiceMethods<TService>>,
|
|
546
|
+
],
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
const actions = generateAutoInferredServiceActions<TService>(service, actionsConfig);
|
|
550
|
+
|
|
551
|
+
// Generate events from decorated methods
|
|
552
|
+
const events: Record<string, { handler: (ctx: unknown) => unknown; group?: string }> = {};
|
|
553
|
+
|
|
554
|
+
eventHandlers.forEach((metadata: Moleculer.EventHandlerMetadata) => {
|
|
555
|
+
const method = (service as Record<string, unknown>)[metadata.methodName];
|
|
556
|
+
|
|
557
|
+
if (typeof method === 'function') {
|
|
558
|
+
events[metadata.eventName] = {
|
|
559
|
+
handler: async (ctx: { params: { event?: unknown } }) => {
|
|
560
|
+
// Event payload is typically wrapped in ctx.params.event
|
|
561
|
+
const eventData = ctx.params.event || ctx.params;
|
|
562
|
+
return (method as (...args: unknown[]) => unknown).call(service, eventData);
|
|
563
|
+
},
|
|
564
|
+
...(metadata.group ? { group: metadata.group } : {}),
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
return { actions, events };
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
export interface AutoActionConfig {
|
|
573
|
+
/**
|
|
574
|
+
* Custom parameter validation schema for specific actions
|
|
575
|
+
* Key is the action/method name, value is Moleculer params schema
|
|
576
|
+
*/
|
|
577
|
+
params?: Record<string, Record<string, unknown>>;
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Actions to exclude from auto-generation (use manual handlers instead)
|
|
581
|
+
*/
|
|
582
|
+
exclude?: string[];
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Action name mappings (if action name differs from method name)
|
|
586
|
+
* Key is the action name, value is the method name
|
|
587
|
+
*/
|
|
588
|
+
actionToMethodMap?: Record<string, string>;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* Type-safe configuration that REQUIRES params for ALL service methods
|
|
593
|
+
* TypeScript will show compile error if any method is missing from params
|
|
594
|
+
*/
|
|
595
|
+
export type TypeSafeActionConfig<TService> = {
|
|
596
|
+
/**
|
|
597
|
+
* REQUIRED: Parameter validation schema for ALL service methods
|
|
598
|
+
* TypeScript will error if any method is missing
|
|
599
|
+
*/
|
|
600
|
+
params: {
|
|
601
|
+
[K in ServiceMethods<TService>]: Record<string, unknown>;
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Optional: Actions to exclude from auto-generation
|
|
606
|
+
*/
|
|
607
|
+
exclude?: string[];
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Optional: Action name mappings
|
|
611
|
+
*/
|
|
612
|
+
actionToMethodMap?: Record<string, string>;
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Automatically generate Moleculer actions from a service instance.
|
|
617
|
+
*
|
|
618
|
+
* This eliminates the need to write repetitive action handlers - just pass your service
|
|
619
|
+
* and it will create handlers that forward all calls automatically.
|
|
620
|
+
*
|
|
621
|
+
* @param service - The service instance to wrap
|
|
622
|
+
* @param config - Optional configuration for customizing actions
|
|
623
|
+
* @returns Actions object with handlers for all service methods
|
|
624
|
+
*
|
|
625
|
+
* @example
|
|
626
|
+
* ```typescript
|
|
627
|
+
* const actions = generateServiceActions(accountService, {
|
|
628
|
+
* params: {
|
|
629
|
+
* findAccountById: { id: 'string' },
|
|
630
|
+
* createAccount: { account: 'object' }
|
|
631
|
+
* },
|
|
632
|
+
* exclude: ['get', 'getAll'] // Handle these manually
|
|
633
|
+
* });
|
|
634
|
+
* ```
|
|
635
|
+
*/
|
|
636
|
+
export function generateServiceActions<TService extends Record<string, unknown>>(
|
|
637
|
+
service: TService,
|
|
638
|
+
config: AutoActionConfig = {},
|
|
639
|
+
): Record<string, { params?: Record<string, unknown>; handler: (ctx: unknown) => unknown }> {
|
|
640
|
+
const { params = {}, exclude = [], actionToMethodMap = {} } = config;
|
|
641
|
+
const actions: Record<string, { params?: Record<string, unknown>; handler: (ctx: unknown) => unknown }> = {};
|
|
642
|
+
|
|
643
|
+
// Get all methods from the service
|
|
644
|
+
const allKeys = Object.getOwnPropertyNames(Object.getPrototypeOf(service));
|
|
645
|
+
const generatedMethods: string[] = [];
|
|
646
|
+
|
|
647
|
+
allKeys.forEach((key) => {
|
|
648
|
+
// Skip excluded methods, constructors, and private methods
|
|
649
|
+
if (exclude.includes(key) || key === 'constructor' || key.startsWith('_')) {
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
const method = (service as Record<string, unknown>)[key];
|
|
654
|
+
|
|
655
|
+
// Only process functions
|
|
656
|
+
if (typeof method !== 'function') {
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// Determine the method name to call (in case of mapping)
|
|
661
|
+
const methodName = actionToMethodMap[key] || key;
|
|
662
|
+
const serviceMethod = (service as Record<string, unknown>)[methodName];
|
|
663
|
+
|
|
664
|
+
if (typeof serviceMethod !== 'function') {
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
generatedMethods.push(key);
|
|
669
|
+
|
|
670
|
+
// Create the action handler
|
|
671
|
+
actions[key] = {
|
|
672
|
+
...(params[key] ? { params: params[key] } : {}),
|
|
673
|
+
handler: (ctx: { params: Record<string, unknown> }) => {
|
|
674
|
+
// Call the service method with ctx.params spread as arguments
|
|
675
|
+
const method = (service as Record<string, (...args: unknown[]) => unknown>)[methodName];
|
|
676
|
+
|
|
677
|
+
// If params is an object with known keys, extract them in order
|
|
678
|
+
// Otherwise, pass the whole params object
|
|
679
|
+
const paramsObj = ctx.params || {};
|
|
680
|
+
const values = Object.values(paramsObj);
|
|
681
|
+
|
|
682
|
+
return method.apply(service, values.length > 0 ? values : [paramsObj]);
|
|
683
|
+
},
|
|
684
|
+
};
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
// In development, log methods that don't have param validation
|
|
688
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
689
|
+
const methodsWithoutParams = generatedMethods.filter(method => !params[method]);
|
|
690
|
+
if (methodsWithoutParams.length > 0) {
|
|
691
|
+
console.warn(
|
|
692
|
+
`⚠️ [generateServiceActions] The following methods don't have param validation defined:`,
|
|
693
|
+
methodsWithoutParams
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
return actions;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/**
|
|
702
|
+
* TYPE-SAFE version: Generate Moleculer actions with COMPILE-TIME verification.
|
|
703
|
+
* TypeScript will error if any service method is missing from params.
|
|
704
|
+
*
|
|
705
|
+
* @param service - The service instance to wrap
|
|
706
|
+
* @param config - Type-safe configuration requiring ALL methods to have params
|
|
707
|
+
* @returns Actions object with handlers for all service methods
|
|
708
|
+
*
|
|
709
|
+
* @example
|
|
710
|
+
* ```typescript
|
|
711
|
+
* const actions = generateTypeSafeServiceActions<IAccountService>(accountService, {
|
|
712
|
+
* params: {
|
|
713
|
+
* // TypeScript enforces ALL IAccountService methods are here!
|
|
714
|
+
* findAccountById: { id: 'string' },
|
|
715
|
+
* createAccount: { account: 'object' },
|
|
716
|
+
* // ... if you forget any method, TypeScript will error
|
|
717
|
+
* }
|
|
718
|
+
* });
|
|
719
|
+
* ```
|
|
720
|
+
*/
|
|
721
|
+
export function generateTypeSafeServiceActions<TService>(
|
|
722
|
+
service: TService,
|
|
723
|
+
config: TypeSafeActionConfig<TService>,
|
|
724
|
+
): Record<string, { params?: Record<string, unknown>; handler: (ctx: unknown) => unknown }> {
|
|
725
|
+
// Delegate to the regular function - type checking happens at compile time
|
|
726
|
+
return generateServiceActions(service as unknown as Record<string, unknown>, config as AutoActionConfig);
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* Verify that all service methods have param validation defined.
|
|
731
|
+
* Throws an error if any methods are missing params.
|
|
732
|
+
*
|
|
733
|
+
* Use this in development to ensure you haven't forgotten any param schemas.
|
|
734
|
+
*
|
|
735
|
+
* @param service - The service instance
|
|
736
|
+
* @param config - The config used for generateServiceActions
|
|
737
|
+
* @throws Error if any methods are missing param validation
|
|
738
|
+
*
|
|
739
|
+
* @example
|
|
740
|
+
* ```typescript
|
|
741
|
+
* const config = { params: { ... }, exclude: [...] };
|
|
742
|
+
* verifyAllParamsDefined(accountService, config); // Throws if params missing
|
|
743
|
+
* const actions = generateServiceActions(accountService, config);
|
|
744
|
+
* ```
|
|
745
|
+
*/
|
|
746
|
+
export function verifyAllParamsDefined<TService extends Record<string, unknown>>(
|
|
747
|
+
service: TService,
|
|
748
|
+
config: AutoActionConfig = {},
|
|
749
|
+
): void {
|
|
750
|
+
const { params = {}, exclude = [] } = config;
|
|
751
|
+
const allKeys = Object.getOwnPropertyNames(Object.getPrototypeOf(service));
|
|
752
|
+
const missingParams: string[] = [];
|
|
753
|
+
|
|
754
|
+
allKeys.forEach((key) => {
|
|
755
|
+
// Skip excluded methods, constructors, and private methods
|
|
756
|
+
if (exclude.includes(key) || key === 'constructor' || key.startsWith('_')) {
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
const method = (service as Record<string, unknown>)[key];
|
|
761
|
+
|
|
762
|
+
// Only check functions
|
|
763
|
+
if (typeof method !== 'function') {
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// Check if params defined
|
|
768
|
+
if (!params[key]) {
|
|
769
|
+
missingParams.push(key);
|
|
770
|
+
}
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
if (missingParams.length > 0) {
|
|
774
|
+
const errorMessage = [
|
|
775
|
+
`Missing param validation for methods: ${missingParams.join(', ')}`,
|
|
776
|
+
'',
|
|
777
|
+
'Add these to your params config:',
|
|
778
|
+
...missingParams.map(method => ` ${method}: { /* your params */ },`),
|
|
779
|
+
].join('\n');
|
|
780
|
+
|
|
781
|
+
throw new Error(errorMessage);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* Type-safe Moleculer Service base class with ENFORCED action verification.
|
|
787
|
+
*
|
|
788
|
+
* @deprecated Use generateServiceActionsAndEvents() or generateAutoInferredServiceActions() instead
|
|
789
|
+
* This class requires importing Moleculer Service which causes build issues.
|
|
790
|
+
*
|
|
791
|
+
* Usage with auto-generated actions:
|
|
792
|
+
* ```typescript
|
|
793
|
+
* export class AccountMoleculerService extends Service {
|
|
794
|
+
* constructor(broker: ServiceBroker, { container }: { container: Container }) {
|
|
795
|
+
* super(broker);
|
|
796
|
+
* const accountService = container.get<IAccountService>(SERVER_TYPES.IAccountService);
|
|
797
|
+
*
|
|
798
|
+
* // Automatically generate ALL actions from the service!
|
|
799
|
+
* const autoActions = generateAutoInferredServiceActions(accountService);
|
|
800
|
+
*
|
|
801
|
+
* this.parseServiceSchema({
|
|
802
|
+
* name: MoleculerServiceName.AccountUser,
|
|
803
|
+
* actions: autoActions,
|
|
804
|
+
* });
|
|
805
|
+
* }
|
|
806
|
+
* }
|
|
807
|
+
* ```
|
|
808
|
+
*/
|
|
809
|
+
// export class TypedMoleculerService<TService> extends Service {
|
|
810
|
+
// constructor(broker: ServiceBroker) {
|
|
811
|
+
// super(broker);
|
|
812
|
+
// }
|
|
813
|
+
|
|
814
|
+
// protected generateActions<T extends Record<string, unknown>>(
|
|
815
|
+
// service: T,
|
|
816
|
+
// config?: AutoActionConfig,
|
|
817
|
+
// ): Record<string, { params?: Record<string, unknown>; handler: (ctx: unknown) => unknown }> {
|
|
818
|
+
// return generateServiceActions(service, config);
|
|
819
|
+
// }
|
|
820
|
+
|
|
821
|
+
// protected parseTypedServiceSchema<T = TService>(schema: EnforcedServiceSchema<T>): void {
|
|
822
|
+
// this.parseServiceSchema(schema as ServiceSchema);
|
|
823
|
+
// }
|
|
824
|
+
|
|
825
|
+
// protected parsePartialServiceSchema(schema: ServiceSchema): void {
|
|
826
|
+
// this.parseServiceSchema(schema);
|
|
827
|
+
// }
|
|
828
|
+
// }
|
|
829
|
+
|
|
830
|
+
/**
|
|
831
|
+
* Placeholder for TypedMoleculerService for backward compatibility
|
|
832
|
+
* @deprecated Use generateServiceActionsAndEvents() instead
|
|
833
|
+
*/
|
|
834
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
835
|
+
export type TypedMoleculerService<TService> = unknown;
|
|
836
|
+
|
|
837
|
+
/**
|
|
838
|
+
* Helper type for explicit verification - use in const to get better error messages
|
|
839
|
+
*/
|
|
840
|
+
export type VerifyAllActionsImplemented<TService> = {
|
|
841
|
+
[K in ServiceMethods<TService>]: true;
|
|
842
|
+
};
|
|
843
|
+
|
|
844
|
+
/**
|
|
845
|
+
* Typed action handler with parameter and return type safety
|
|
846
|
+
*/
|
|
847
|
+
export interface TypedActionHandler<TParams = unknown, TReturn = unknown> {
|
|
848
|
+
params?: Record<string, unknown>;
|
|
849
|
+
handler: (ctx: { params: TParams }) => TReturn | Promise<TReturn>;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
/**
|
|
853
|
+
* Helper to create a typed action handler
|
|
854
|
+
*/
|
|
855
|
+
export function createTypedAction<TParams, TReturn>(
|
|
856
|
+
action: TypedActionHandler<TParams, TReturn>,
|
|
857
|
+
): TypedActionHandler<TParams, TReturn> {
|
|
858
|
+
return action;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
/**
|
|
862
|
+
* Service schema type with better action type hints
|
|
863
|
+
*/
|
|
864
|
+
export interface TypedServiceSchema extends ServiceSchema {
|
|
865
|
+
actions?: Record<string, TypedActionHandler>;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
/**
|
|
869
|
+
* Extend Moleculer namespace with action generation utilities
|
|
870
|
+
*/
|
|
871
|
+
declare module './moleculerEventHandler' {
|
|
872
|
+
export namespace Moleculer {
|
|
873
|
+
/**
|
|
874
|
+
* ADVANCED AUTO-GENERATED configuration with full control
|
|
875
|
+
*/
|
|
876
|
+
export type AutoInferredActionConfig<TService> = {
|
|
877
|
+
paramOverrides?: Partial<{
|
|
878
|
+
[K in ServiceMethods<TService>]: Record<string, unknown>;
|
|
879
|
+
}>;
|
|
880
|
+
include?: Array<ServiceMethods<TService>>;
|
|
881
|
+
exclude?: Array<ServiceMethods<TService>>;
|
|
882
|
+
actionToMethodMap?: Record<string, string>;
|
|
883
|
+
actionConfig?: Partial<{
|
|
884
|
+
[K in ServiceMethods<TService>]: {
|
|
885
|
+
cache?: boolean | object;
|
|
886
|
+
visibility?: 'public' | 'protected' | 'private';
|
|
887
|
+
[key: string]: unknown;
|
|
888
|
+
};
|
|
889
|
+
}>;
|
|
890
|
+
};
|
|
891
|
+
|
|
892
|
+
/**
|
|
893
|
+
* Generate actions with full control over inclusion, exclusion, and overrides.
|
|
894
|
+
* Params are inferred from TypeScript function signatures.
|
|
895
|
+
*/
|
|
896
|
+
export function generateAutoInferredActions<TService>(
|
|
897
|
+
service: TService,
|
|
898
|
+
config?: AutoInferredActionConfig<TService>,
|
|
899
|
+
): Record<string, { params?: Record<string, unknown>; handler: (ctx: unknown) => unknown }>;
|
|
900
|
+
|
|
901
|
+
/**
|
|
902
|
+
* Generate both actions AND events from a service instance
|
|
903
|
+
*/
|
|
904
|
+
export function generateActionsAndEvents<TService>(
|
|
905
|
+
service: TService,
|
|
906
|
+
config?: AutoInferredActionConfig<TService>,
|
|
907
|
+
): {
|
|
908
|
+
actions: Record<string, { params?: Record<string, unknown>; handler: (ctx: unknown) => unknown; [key: string]: unknown }>;
|
|
909
|
+
events: Record<string, { handler: (ctx: unknown) => unknown; group?: string }>;
|
|
910
|
+
};
|
|
911
|
+
|
|
912
|
+
/**
|
|
913
|
+
* DEBUG UTILITY: Extract parameter information for all service methods
|
|
914
|
+
* Returns detailed information about parameters, schemas, and configurations
|
|
915
|
+
*/
|
|
916
|
+
export function debugServiceParams<TService>(
|
|
917
|
+
service: TService,
|
|
918
|
+
config?: AutoInferredActionConfig<TService>,
|
|
919
|
+
): Record<string, {
|
|
920
|
+
methodName: string;
|
|
921
|
+
paramNames: string[];
|
|
922
|
+
functionSignature: string;
|
|
923
|
+
inferredSchema: Record<string, unknown> | undefined;
|
|
924
|
+
overrideSchema: Record<string, unknown> | undefined;
|
|
925
|
+
isExcluded: boolean;
|
|
926
|
+
isEventHandler: boolean;
|
|
927
|
+
}>;
|
|
928
|
+
|
|
929
|
+
/**
|
|
930
|
+
* DEBUG UTILITY: Pretty print service parameter information to console
|
|
931
|
+
* Useful for understanding auto-generation and identifying methods needing overrides
|
|
932
|
+
*/
|
|
933
|
+
export function printServiceParams<TService>(
|
|
934
|
+
service: TService,
|
|
935
|
+
config?: AutoInferredActionConfig<TService>,
|
|
936
|
+
options?: {
|
|
937
|
+
onlyMissingOverrides?: boolean;
|
|
938
|
+
onlyIncluded?: boolean;
|
|
939
|
+
showExcluded?: boolean;
|
|
940
|
+
},
|
|
941
|
+
): void;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
/**
|
|
946
|
+
* DEBUG UTILITY: Dumps parameter information for all service methods
|
|
947
|
+
*
|
|
948
|
+
* This function extracts and logs parameter names, types, and inferred schemas
|
|
949
|
+
* for debugging auto-generation issues. Use during development to understand
|
|
950
|
+
* what the auto-generation sees and optimize paramOverrides.
|
|
951
|
+
*
|
|
952
|
+
* @param service - The service instance to analyze
|
|
953
|
+
* @param config - Optional configuration to see what would be generated
|
|
954
|
+
* @returns Object with detailed parameter information for each method
|
|
955
|
+
*
|
|
956
|
+
* @example
|
|
957
|
+
* ```typescript
|
|
958
|
+
* // In development/debug mode
|
|
959
|
+
* const paramInfo = Moleculer.debugServiceParams(accountService);
|
|
960
|
+
* console.log(JSON.stringify(paramInfo, null, 2));
|
|
961
|
+
*
|
|
962
|
+
* // With config to see filtered results
|
|
963
|
+
* const paramInfo = Moleculer.debugServiceParams(accountService, {
|
|
964
|
+
* exclude: ['dispose', 'createUserToken']
|
|
965
|
+
* });
|
|
966
|
+
* ```
|
|
967
|
+
*/
|
|
968
|
+
export function debugServiceParams<TService>(
|
|
969
|
+
service: TService,
|
|
970
|
+
config: AutoInferredActionConfig<TService> = {},
|
|
971
|
+
): Record<string, {
|
|
972
|
+
methodName: string;
|
|
973
|
+
paramNames: string[];
|
|
974
|
+
functionSignature: string;
|
|
975
|
+
inferredSchema: Record<string, unknown> | undefined;
|
|
976
|
+
overrideSchema: Record<string, unknown> | undefined;
|
|
977
|
+
isExcluded: boolean;
|
|
978
|
+
isEventHandler: boolean;
|
|
979
|
+
}> {
|
|
980
|
+
const {
|
|
981
|
+
paramOverrides = {},
|
|
982
|
+
include,
|
|
983
|
+
exclude = [],
|
|
984
|
+
} = config;
|
|
985
|
+
|
|
986
|
+
const result: Record<string, {
|
|
987
|
+
methodName: string;
|
|
988
|
+
paramNames: string[];
|
|
989
|
+
functionSignature: string;
|
|
990
|
+
inferredSchema: Record<string, unknown> | undefined;
|
|
991
|
+
overrideSchema: Record<string, unknown> | undefined;
|
|
992
|
+
isExcluded: boolean;
|
|
993
|
+
isEventHandler: boolean;
|
|
994
|
+
}> = {};
|
|
995
|
+
|
|
996
|
+
// Get all methods from the service, including inherited methods
|
|
997
|
+
const allKeys = getAllMethodNames(service);
|
|
998
|
+
|
|
999
|
+
// Get event handler methods
|
|
1000
|
+
const eventHandlers = Moleculer.getEventHandlers(service as object);
|
|
1001
|
+
const eventHandlerMethods = new Set(eventHandlers.map(h => h.methodName));
|
|
1002
|
+
|
|
1003
|
+
allKeys.forEach((key) => {
|
|
1004
|
+
// Skip constructor and private methods
|
|
1005
|
+
if (key === 'constructor' || key.startsWith('_')) {
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
const method = (service as Record<string, unknown>)[key];
|
|
1010
|
+
if (typeof method !== 'function') {
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
// Determine if this method would be excluded
|
|
1015
|
+
const isEventHandler = eventHandlerMethods.has(key);
|
|
1016
|
+
let isExcluded = false;
|
|
1017
|
+
|
|
1018
|
+
if (include && include.length > 0) {
|
|
1019
|
+
isExcluded = !include.includes(key as ServiceMethods<TService>);
|
|
1020
|
+
} else {
|
|
1021
|
+
isExcluded = exclude.includes(key as ServiceMethods<TService>) || isEventHandler;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
// Get function signature
|
|
1025
|
+
const funcStr = method.toString();
|
|
1026
|
+
const paramsWithTypes = extractParamsWithTypes(funcStr);
|
|
1027
|
+
|
|
1028
|
+
// Extract just the function signature (first line with params)
|
|
1029
|
+
const signatureMatch = funcStr.match(/^(?:async\s+)?(?:function\s+)?(?:\w+\s*)?\(([^)]*)\)/);
|
|
1030
|
+
const signature = signatureMatch ? signatureMatch[0] : funcStr.split('{')[0].trim();
|
|
1031
|
+
|
|
1032
|
+
// Build inferred schema using new type inference
|
|
1033
|
+
let inferredSchema: Record<string, unknown> | undefined;
|
|
1034
|
+
if (paramsWithTypes.length > 0) {
|
|
1035
|
+
inferredSchema = paramsWithTypes.reduce((acc, param) => {
|
|
1036
|
+
if (param.optional) {
|
|
1037
|
+
acc[param.name] = { type: param.type, optional: true };
|
|
1038
|
+
} else {
|
|
1039
|
+
acc[param.name] = param.type;
|
|
1040
|
+
}
|
|
1041
|
+
return acc;
|
|
1042
|
+
}, {} as Record<string, unknown>);
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
// Get override schema if provided
|
|
1046
|
+
const overrideSchema = paramOverrides[key as keyof typeof paramOverrides] as Record<string, unknown> | undefined;
|
|
1047
|
+
|
|
1048
|
+
result[key] = {
|
|
1049
|
+
methodName: key,
|
|
1050
|
+
paramNames: paramsWithTypes.map(p => p.name),
|
|
1051
|
+
functionSignature: signature,
|
|
1052
|
+
inferredSchema,
|
|
1053
|
+
overrideSchema,
|
|
1054
|
+
isExcluded,
|
|
1055
|
+
isEventHandler,
|
|
1056
|
+
};
|
|
1057
|
+
});
|
|
1058
|
+
|
|
1059
|
+
return result;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
/**
|
|
1063
|
+
* DEBUG UTILITY: Pretty prints service parameter information to console
|
|
1064
|
+
*
|
|
1065
|
+
* Formats and logs the output of debugServiceParams() in a readable way.
|
|
1066
|
+
* Shows which methods need paramOverrides and what types are inferred.
|
|
1067
|
+
*
|
|
1068
|
+
* @param service - The service instance to analyze
|
|
1069
|
+
* @param config - Optional configuration
|
|
1070
|
+
* @param options - Display options
|
|
1071
|
+
*
|
|
1072
|
+
* @example
|
|
1073
|
+
* ```typescript
|
|
1074
|
+
* // Print all methods
|
|
1075
|
+
* Moleculer.printServiceParams(accountService);
|
|
1076
|
+
*
|
|
1077
|
+
* // Print only methods without overrides
|
|
1078
|
+
* Moleculer.printServiceParams(accountService, config, { onlyMissingOverrides: true });
|
|
1079
|
+
*
|
|
1080
|
+
* // Print only included methods
|
|
1081
|
+
* Moleculer.printServiceParams(accountService, config, { onlyIncluded: true });
|
|
1082
|
+
* ```
|
|
1083
|
+
*/
|
|
1084
|
+
export function printServiceParams<TService>(
|
|
1085
|
+
service: TService,
|
|
1086
|
+
config: AutoInferredActionConfig<TService> = {},
|
|
1087
|
+
options: {
|
|
1088
|
+
onlyMissingOverrides?: boolean;
|
|
1089
|
+
onlyIncluded?: boolean;
|
|
1090
|
+
showExcluded?: boolean;
|
|
1091
|
+
} = {},
|
|
1092
|
+
): void {
|
|
1093
|
+
const paramInfo = debugServiceParams(service, config);
|
|
1094
|
+
const methods = Object.values(paramInfo);
|
|
1095
|
+
|
|
1096
|
+
const separator = '='.repeat(80);
|
|
1097
|
+
const dashedLine = '-'.repeat(80);
|
|
1098
|
+
|
|
1099
|
+
console.log(`\n${separator}`);
|
|
1100
|
+
console.log('SERVICE PARAMETER DEBUG INFORMATION');
|
|
1101
|
+
console.log(`${separator}\n`);
|
|
1102
|
+
|
|
1103
|
+
// Summary statistics
|
|
1104
|
+
const totalMethods = methods.length;
|
|
1105
|
+
const includedMethods = methods.filter(m => !m.isExcluded);
|
|
1106
|
+
const eventHandlers = methods.filter(m => m.isEventHandler);
|
|
1107
|
+
const withOverrides = methods.filter(m => m.overrideSchema);
|
|
1108
|
+
const withoutOverrides = includedMethods.filter(m => !m.overrideSchema && !m.isExcluded);
|
|
1109
|
+
|
|
1110
|
+
console.log('📊 SUMMARY:');
|
|
1111
|
+
console.log(` Total methods found: ${totalMethods}`);
|
|
1112
|
+
console.log(` Included in actions: ${includedMethods.length}`);
|
|
1113
|
+
console.log(` Event handlers (excluded from actions): ${eventHandlers.length}`);
|
|
1114
|
+
console.log(` With param overrides: ${withOverrides.length}`);
|
|
1115
|
+
console.log(` Without param overrides: ${withoutOverrides.length}`);
|
|
1116
|
+
console.log(`\n${dashedLine}\n`);
|
|
1117
|
+
|
|
1118
|
+
// Filter methods based on options
|
|
1119
|
+
let displayMethods = methods;
|
|
1120
|
+
if (options.onlyMissingOverrides) {
|
|
1121
|
+
displayMethods = displayMethods.filter(m => !m.overrideSchema && !m.isExcluded);
|
|
1122
|
+
}
|
|
1123
|
+
if (options.onlyIncluded) {
|
|
1124
|
+
displayMethods = displayMethods.filter(m => !m.isExcluded);
|
|
1125
|
+
}
|
|
1126
|
+
if (!options.showExcluded) {
|
|
1127
|
+
displayMethods = displayMethods.filter(m => !m.isExcluded);
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
// Print each method
|
|
1131
|
+
displayMethods.forEach((info, index) => {
|
|
1132
|
+
let status: string;
|
|
1133
|
+
if (info.isExcluded) {
|
|
1134
|
+
status = '❌ EXCLUDED';
|
|
1135
|
+
} else if (info.isEventHandler) {
|
|
1136
|
+
status = '📡 EVENT HANDLER';
|
|
1137
|
+
} else if (info.overrideSchema) {
|
|
1138
|
+
status = '✅ HAS OVERRIDE';
|
|
1139
|
+
} else {
|
|
1140
|
+
status = '⚠️ NEEDS OVERRIDE';
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
console.log(`${index + 1}. ${info.methodName} ${status}`);
|
|
1144
|
+
console.log(` Signature: ${info.functionSignature}`);
|
|
1145
|
+
console.log(` Parameters: [${info.paramNames.join(', ')}]`);
|
|
1146
|
+
|
|
1147
|
+
if (info.inferredSchema) {
|
|
1148
|
+
console.log(` Inferred: ${JSON.stringify(info.inferredSchema)}`);
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
if (info.overrideSchema) {
|
|
1152
|
+
console.log(` Override: ${JSON.stringify(info.overrideSchema)}`);
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
console.log('');
|
|
1156
|
+
});
|
|
1157
|
+
|
|
1158
|
+
// Suggestions
|
|
1159
|
+
if (withoutOverrides.length > 0 && !options.onlyMissingOverrides) {
|
|
1160
|
+
console.log(`\n${separator}`);
|
|
1161
|
+
console.log('💡 SUGGESTIONS:');
|
|
1162
|
+
console.log(`${separator}\n`);
|
|
1163
|
+
console.log('The following methods use default "object" validation:');
|
|
1164
|
+
console.log('Consider adding paramOverrides for better type safety:\n');
|
|
1165
|
+
|
|
1166
|
+
const suggestions: string[] = [];
|
|
1167
|
+
withoutOverrides.forEach(info => {
|
|
1168
|
+
const params = info.paramNames.map(p => `${p}: 'string'`).join(', ');
|
|
1169
|
+
suggestions.push(` ${info.methodName}: { ${params} },`);
|
|
1170
|
+
});
|
|
1171
|
+
|
|
1172
|
+
console.log('paramOverrides: {');
|
|
1173
|
+
console.log(suggestions.join('\n'));
|
|
1174
|
+
console.log('}');
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
console.log(`\n${separator}\n`);
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
// Implement the namespace extensions
|
|
1181
|
+
Moleculer.generateAutoInferredActions = generateAutoInferredServiceActions;
|
|
1182
|
+
Moleculer.generateActionsAndEvents = generateServiceActionsAndEvents;
|
|
1183
|
+
Moleculer.debugServiceParams = debugServiceParams;
|
|
1184
|
+
Moleculer.printServiceParams = printServiceParams;
|
|
1185
|
+
|
|
1186
|
+
|
|
1187
|
+
|
|
1188
|
+
|