@common-stack/server-core 7.2.1-alpha.49 → 7.2.1-alpha.50
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/lib/index.d.ts +0 -1
- package/lib/index.js +1 -1
- package/package.json +2 -2
- package/lib/moleculer-generation/generateAllServiceSchemas.cjs +0 -628
- package/lib/moleculer-generation/moleculerEventHandler.js +0 -91
- package/lib/moleculer-generation/moleculerEventHandler.js.map +0 -1
- package/lib/moleculer-generation/serviceGenerationUtils.js +0 -156
- package/lib/moleculer-generation/serviceGenerationUtils.js.map +0 -1
- package/lib/moleculer-generation/typedMoleculerService.js +0 -685
- package/lib/moleculer-generation/typedMoleculerService.js.map +0 -1
- package/lib/moleculer-generation/typedProxyService.js +0 -226
- package/lib/moleculer-generation/typedProxyService.js.map +0 -1
- package/lib/moleculer-generation/zodToMoleculer.js +0 -120
- package/lib/moleculer-generation/zodToMoleculer.js.map +0 -1
package/lib/index.d.ts
CHANGED
package/lib/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export{Feature,featureCatalog}from'./connector/connector.js';export{GraphqlRootType}from'./interfaces/rules.js';export{TYPES}from'./constants/types.js';export{CACHE_CONTROL_DIRECTIVE}from'./constants/directives.js';export{logger}from'./logger/logger.js';export{getCurrentPreferences,transformPrefsToArray}from'./utils/preferences.js';export{generateQueryCacheKey}from'./utils/generate-query-cache-key.js';export{extractTenantId}from'./utils/extract-tenant-id.js';export{getDirectiveArgsFromSchema}from'./utils/get-directive-args-from-schema.js'
|
|
1
|
+
export{Feature,featureCatalog}from'./connector/connector.js';export{GraphqlRootType}from'./interfaces/rules.js';export{TYPES}from'./constants/types.js';export{CACHE_CONTROL_DIRECTIVE}from'./constants/directives.js';export{logger}from'./logger/logger.js';export{getCurrentPreferences,transformPrefsToArray}from'./utils/preferences.js';export{generateQueryCacheKey}from'./utils/generate-query-cache-key.js';export{extractTenantId}from'./utils/extract-tenant-id.js';export{getDirectiveArgsFromSchema}from'./utils/get-directive-args-from-schema.js';//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@common-stack/server-core",
|
|
3
|
-
"version": "7.2.1-alpha.
|
|
3
|
+
"version": "7.2.1-alpha.50",
|
|
4
4
|
"description": "common core for higher packages to depend on",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"author": "CDMBase LLC",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"publishConfig": {
|
|
35
35
|
"access": "public"
|
|
36
36
|
},
|
|
37
|
-
"gitHead": "
|
|
37
|
+
"gitHead": "86248ff51544d199dfe9b57471665d8c26d2817e",
|
|
38
38
|
"typescript": {
|
|
39
39
|
"definition": "lib/index.d.ts"
|
|
40
40
|
}
|
|
@@ -1,628 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Generate Zod schemas for ALL service interfaces (CommonJS version)
|
|
4
|
-
*
|
|
5
|
-
* This script can be run from:
|
|
6
|
-
* 1. Local project: node scripts/generateAllServiceSchemas.cjs
|
|
7
|
-
* 2. As npm script: Add to package.json scripts and run via npm/yarn
|
|
8
|
-
* 3. From node_modules: npx @common-stack/server-core generateAllServiceSchemas
|
|
9
|
-
*
|
|
10
|
-
* Configuration is loaded from cdecode-config.json in the project root.
|
|
11
|
-
* All paths in the config are relative to the project root.
|
|
12
|
-
*
|
|
13
|
-
* @example package.json script
|
|
14
|
-
* "scripts": {
|
|
15
|
-
* "generateSchemas": "node node_modules/@common-stack/server-core/lib/moleculer-generation/generateAllServiceSchemas.cjs"
|
|
16
|
-
* }
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
const fs = require('fs');
|
|
20
|
-
const path = require('path');
|
|
21
|
-
const ts = require('typescript');
|
|
22
|
-
|
|
23
|
-
// Determine the project root - use current working directory (where the command is run)
|
|
24
|
-
// This works whether the script is in local scripts/ or node_modules/
|
|
25
|
-
const projectRoot = process.cwd();
|
|
26
|
-
|
|
27
|
-
// Load configuration from cdecode-config.json in the project root
|
|
28
|
-
const configPath = path.join(projectRoot, 'cdecode-config.json');
|
|
29
|
-
let serviceConfig = {};
|
|
30
|
-
|
|
31
|
-
if (fs.existsSync(configPath)) {
|
|
32
|
-
try {
|
|
33
|
-
const fullConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
34
|
-
serviceConfig = fullConfig.serviceSchemas || {};
|
|
35
|
-
console.log('✅ Loaded configuration from cdecode-config.json');
|
|
36
|
-
} catch (error) {
|
|
37
|
-
console.warn('⚠️ Could not parse cdecode-config.json, using defaults');
|
|
38
|
-
}
|
|
39
|
-
} else {
|
|
40
|
-
console.warn('⚠️ cdecode-config.json not found in project root, using defaults');
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Apply configuration with defaults (all paths relative to project root)
|
|
44
|
-
const SERVICES_DIR = path.join(projectRoot, serviceConfig.servicesDir || 'packages/common/src/services');
|
|
45
|
-
const OUTPUT_FILE = path.join(
|
|
46
|
-
projectRoot,
|
|
47
|
-
serviceConfig.outputFile || 'packages/common/src/generated/service-schemas.ts',
|
|
48
|
-
);
|
|
49
|
-
|
|
50
|
-
// Validate that SERVICES_DIR exists
|
|
51
|
-
if (!fs.existsSync(SERVICES_DIR)) {
|
|
52
|
-
console.error(`❌ Error: Services directory not found: ${SERVICES_DIR}`);
|
|
53
|
-
console.error(` Current working directory: ${projectRoot}`);
|
|
54
|
-
console.error(` Expected path (from config): ${serviceConfig.servicesDir || 'packages/common/src/services'}`);
|
|
55
|
-
console.error('\n Make sure:');
|
|
56
|
-
console.error(' 1. You are running this script from the project root');
|
|
57
|
-
console.error(' 2. The servicesDir path in cdecode-config.json is correct');
|
|
58
|
-
process.exit(1);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const SKIP_SERVICES = serviceConfig.skipServices || [
|
|
62
|
-
'AuthBackendClient',
|
|
63
|
-
'PubSub',
|
|
64
|
-
'RedisCacheManager',
|
|
65
|
-
'InngestClient',
|
|
66
|
-
'JSONContributionRegistry',
|
|
67
|
-
'PageStore',
|
|
68
|
-
'PublisherEventService',
|
|
69
|
-
];
|
|
70
|
-
const KNOWN_ENUMS = serviceConfig.knownEnums || [];
|
|
71
|
-
const CONST_ENUM_SCHEMAS = serviceConfig.constEnumSchemas || ['ConfigurationTarget', 'ConfigurationScope'];
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Calculate relative path from outputFile to graphqlSchemasPath
|
|
75
|
-
*/
|
|
76
|
-
function calculateRelativeImportPath() {
|
|
77
|
-
if (!serviceConfig.outputFile || !serviceConfig.graphqlSchemasPath) {
|
|
78
|
-
return './generated-zod-schemas';
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const outputDir = path.dirname(path.join(projectRoot, serviceConfig.outputFile));
|
|
82
|
-
const graphqlSchemaPath = path.join(projectRoot, serviceConfig.graphqlSchemasPath);
|
|
83
|
-
|
|
84
|
-
// Calculate relative path
|
|
85
|
-
const relativePath = path.relative(outputDir, graphqlSchemaPath);
|
|
86
|
-
|
|
87
|
-
// Remove .ts extension and ensure it starts with ./
|
|
88
|
-
const withoutExt = relativePath.replace(/\.ts$/, '');
|
|
89
|
-
return withoutExt.startsWith('.') ? withoutExt : `./${withoutExt}`;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const GRAPHQL_SCHEMAS_IMPORT_PATH = calculateRelativeImportPath();
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Dynamically discover all available GraphQL Zod schemas from graphql-zod-schemas.ts
|
|
96
|
-
* This scans for exported schema functions and extracts their type names
|
|
97
|
-
*/
|
|
98
|
-
function discoverGraphQLZodTypes() {
|
|
99
|
-
const graphqlSchemaFile = serviceConfig.graphqlSchemasPath
|
|
100
|
-
? path.join(projectRoot, serviceConfig.graphqlSchemasPath)
|
|
101
|
-
: path.join(projectRoot, 'packages/common/src/generated/generated-zod-schemas.ts');
|
|
102
|
-
|
|
103
|
-
// Check if file exists
|
|
104
|
-
if (!fs.existsSync(graphqlSchemaFile)) {
|
|
105
|
-
console.warn('⚠️ graphql-zod-schemas.ts not found, using empty set for GraphQL types');
|
|
106
|
-
return new Set();
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const content = fs.readFileSync(graphqlSchemaFile, 'utf8');
|
|
110
|
-
const graphqlTypes = new Set();
|
|
111
|
-
|
|
112
|
-
// Match all exported schema function names
|
|
113
|
-
// Pattern: export function SomeTypeInputSchema() or export function SomeTypeSchema()
|
|
114
|
-
const schemaFunctionPattern = /export function ([A-Z][a-zA-Z0-9]+)Schema\(\)/g;
|
|
115
|
-
let match;
|
|
116
|
-
|
|
117
|
-
while ((match = schemaFunctionPattern.exec(content)) !== null) {
|
|
118
|
-
const schemaName = match[1];
|
|
119
|
-
|
|
120
|
-
// Convert schema function name back to interface name
|
|
121
|
-
// Examples:
|
|
122
|
-
// - UserTokenInputSchema -> IUserTokenInput
|
|
123
|
-
// - UserAuth0UpdateFieldsSchema -> IUserAuth0UpdateFields
|
|
124
|
-
// - ConfigurationNodeInputSchema -> IConfigurationNodeInput
|
|
125
|
-
|
|
126
|
-
// Skip enum schemas (they don't have 'Input' suffix typically and are in CONST_ENUM_SCHEMAS)
|
|
127
|
-
if (CONST_ENUM_SCHEMAS.includes(schemaName)) {
|
|
128
|
-
continue;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Add 'I' prefix to convert to interface name
|
|
132
|
-
const interfaceName = 'I' + schemaName;
|
|
133
|
-
graphqlTypes.add(interfaceName);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
return graphqlTypes;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Dynamically discover available GraphQL Zod types
|
|
140
|
-
const GRAPHQL_ZOD_TYPES = discoverGraphQLZodTypes();
|
|
141
|
-
|
|
142
|
-
function findServiceInterfaces() {
|
|
143
|
-
const files = fs.readdirSync(SERVICES_DIR).filter((f) => f.endsWith('.ts') && f !== 'index.ts');
|
|
144
|
-
const services = [];
|
|
145
|
-
|
|
146
|
-
files.forEach((file) => {
|
|
147
|
-
const content = fs.readFileSync(path.join(SERVICES_DIR, file), 'utf8');
|
|
148
|
-
const interfaceMatch = content.match(/export interface (I[A-Z][a-zA-Z0-9]*Service)/);
|
|
149
|
-
|
|
150
|
-
if (interfaceMatch) {
|
|
151
|
-
const serviceName = interfaceMatch[1];
|
|
152
|
-
if (!SKIP_SERVICES.includes(serviceName.replace(/^I/, ''))) {
|
|
153
|
-
services.push({
|
|
154
|
-
name: serviceName,
|
|
155
|
-
file: file,
|
|
156
|
-
path: path.join(SERVICES_DIR, file),
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
return services;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Parse a TypeScript source file and extract method signatures from an interface
|
|
167
|
-
* Now includes support for resolving inherited interface methods
|
|
168
|
-
*/
|
|
169
|
-
function parseServiceInterface(filePath, interfaceName) {
|
|
170
|
-
const sourceCode = fs.readFileSync(filePath, 'utf8');
|
|
171
|
-
const sourceFile = ts.createSourceFile(filePath, sourceCode, ts.ScriptTarget.Latest, true);
|
|
172
|
-
|
|
173
|
-
const methods = [];
|
|
174
|
-
const usedEnums = new Set();
|
|
175
|
-
const parsedInterfaces = new Set(); // Track parsed interfaces to avoid infinite recursion
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Extract methods from an interface node
|
|
179
|
-
*/
|
|
180
|
-
function extractMethodsFromInterface(node, currentSourceFile) {
|
|
181
|
-
node.members.forEach((member) => {
|
|
182
|
-
if (ts.isMethodSignature(member) && member.name) {
|
|
183
|
-
const methodName = member.name.getText(currentSourceFile);
|
|
184
|
-
const parameters = [];
|
|
185
|
-
|
|
186
|
-
if (member.parameters) {
|
|
187
|
-
member.parameters.forEach((param) => {
|
|
188
|
-
const paramName = param.name.getText(currentSourceFile);
|
|
189
|
-
const paramType = param.type ? param.type.getText(currentSourceFile) : 'any';
|
|
190
|
-
const isOptional = !!param.questionToken;
|
|
191
|
-
|
|
192
|
-
// Skip destructured parameters - treat the whole param as passthrough object
|
|
193
|
-
if (ts.isObjectBindingPattern(param.name)) {
|
|
194
|
-
// This is a destructured parameter like { user, accessToken }
|
|
195
|
-
// Use the type instead
|
|
196
|
-
parameters.push({
|
|
197
|
-
name: '_destructured',
|
|
198
|
-
type: paramType,
|
|
199
|
-
isOptional,
|
|
200
|
-
isDestructured: true,
|
|
201
|
-
});
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Check if type is a known enum or const enum
|
|
206
|
-
KNOWN_ENUMS.forEach((enumName) => {
|
|
207
|
-
if (paramType.includes(enumName)) {
|
|
208
|
-
usedEnums.add(enumName);
|
|
209
|
-
}
|
|
210
|
-
});
|
|
211
|
-
CONST_ENUM_SCHEMAS.forEach((enumName) => {
|
|
212
|
-
if (paramType.includes(enumName)) {
|
|
213
|
-
usedEnums.add(enumName);
|
|
214
|
-
}
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
parameters.push({
|
|
218
|
-
name: paramName,
|
|
219
|
-
type: paramType,
|
|
220
|
-
isOptional,
|
|
221
|
-
});
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// Only add if method doesn't already exist (child interface overrides parent)
|
|
226
|
-
if (!methods.find((m) => m.name === methodName)) {
|
|
227
|
-
methods.push({
|
|
228
|
-
name: methodName,
|
|
229
|
-
parameters,
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Resolve and parse extended interfaces
|
|
238
|
-
*/
|
|
239
|
-
function resolveExtendedInterfaces(node, currentSourceFile) {
|
|
240
|
-
if (node.heritageClauses) {
|
|
241
|
-
node.heritageClauses.forEach((heritage) => {
|
|
242
|
-
heritage.types.forEach((type) => {
|
|
243
|
-
const baseInterfaceName = type.expression.getText(currentSourceFile);
|
|
244
|
-
|
|
245
|
-
// Skip if already parsed (prevent infinite recursion)
|
|
246
|
-
if (parsedInterfaces.has(baseInterfaceName)) {
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
parsedInterfaces.add(baseInterfaceName);
|
|
250
|
-
|
|
251
|
-
// Try to find the base interface in common locations
|
|
252
|
-
// projectRoot is process.cwd() - where the command is run from (project root)
|
|
253
|
-
const possiblePaths = [
|
|
254
|
-
// Most common: IBaseService in project's packages/common/src/repositories/
|
|
255
|
-
path.join(projectRoot, 'packages/common/src/repositories/IBaseService.ts'),
|
|
256
|
-
// Alternative: in store-mongo package
|
|
257
|
-
path.join(projectRoot, 'packages/common-store-mongo/src/interfaces/base-service.ts'),
|
|
258
|
-
// Same directory as current service file
|
|
259
|
-
path.join(path.dirname(filePath), baseInterfaceName + '.ts'),
|
|
260
|
-
// Direct filename match in repositories folder
|
|
261
|
-
path.join(projectRoot, 'packages/common/src/repositories', baseInterfaceName + '.ts'),
|
|
262
|
-
];
|
|
263
|
-
|
|
264
|
-
for (const possiblePath of possiblePaths) {
|
|
265
|
-
if (fs.existsSync(possiblePath)) {
|
|
266
|
-
try {
|
|
267
|
-
const baseSourceCode = fs.readFileSync(possiblePath, 'utf8');
|
|
268
|
-
const baseSourceFile = ts.createSourceFile(
|
|
269
|
-
possiblePath,
|
|
270
|
-
baseSourceCode,
|
|
271
|
-
ts.ScriptTarget.Latest,
|
|
272
|
-
true,
|
|
273
|
-
);
|
|
274
|
-
|
|
275
|
-
// Find the base interface and extract its methods
|
|
276
|
-
function visitBase(baseNode) {
|
|
277
|
-
if (
|
|
278
|
-
ts.isInterfaceDeclaration(baseNode) &&
|
|
279
|
-
baseNode.name.text === baseInterfaceName
|
|
280
|
-
) {
|
|
281
|
-
// First, resolve any interfaces this base interface extends
|
|
282
|
-
resolveExtendedInterfaces(baseNode, baseSourceFile);
|
|
283
|
-
// Then extract methods from this base interface
|
|
284
|
-
extractMethodsFromInterface(baseNode, baseSourceFile);
|
|
285
|
-
}
|
|
286
|
-
ts.forEachChild(baseNode, visitBase);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
visitBase(baseSourceFile);
|
|
290
|
-
break; // Found the file, no need to check other paths
|
|
291
|
-
} catch (error) {
|
|
292
|
-
// Continue to next possible path
|
|
293
|
-
console.warn(`⚠️ Could not parse base interface ${baseInterfaceName} from ${possiblePath}:`, error.message);
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
});
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
function visit(node) {
|
|
303
|
-
if (ts.isInterfaceDeclaration(node) && node.name.text === interfaceName) {
|
|
304
|
-
parsedInterfaces.add(interfaceName);
|
|
305
|
-
|
|
306
|
-
// First, resolve extended interfaces (inheritance hierarchy)
|
|
307
|
-
resolveExtendedInterfaces(node, sourceFile);
|
|
308
|
-
|
|
309
|
-
// Then extract methods from this interface (to allow overrides)
|
|
310
|
-
extractMethodsFromInterface(node, sourceFile);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
ts.forEachChild(node, visit);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
visit(sourceFile);
|
|
317
|
-
return { methods, usedEnums: Array.from(usedEnums) };
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* Generate Zod schema string for a parameter type
|
|
322
|
-
*/
|
|
323
|
-
function generateZodType(paramType, isOptional, paramName = '') {
|
|
324
|
-
let zodType;
|
|
325
|
-
|
|
326
|
-
// Handle union types like "string | null | undefined"
|
|
327
|
-
if (paramType.includes('|')) {
|
|
328
|
-
const types = paramType.split('|').map(t => t.trim());
|
|
329
|
-
const hasNull = types.includes('null');
|
|
330
|
-
const hasUndefined = types.includes('undefined');
|
|
331
|
-
const nonNullableTypes = types.filter(t => t !== 'null' && t !== 'undefined');
|
|
332
|
-
|
|
333
|
-
if (nonNullableTypes.length === 1) {
|
|
334
|
-
// Single non-nullable type with null/undefined
|
|
335
|
-
zodType = generateZodType(nonNullableTypes[0], false, paramName);
|
|
336
|
-
if (hasNull || hasUndefined) {
|
|
337
|
-
return `${zodType}.optional().nullable()`;
|
|
338
|
-
}
|
|
339
|
-
return zodType;
|
|
340
|
-
} else if (nonNullableTypes.length > 1) {
|
|
341
|
-
// Multiple types - generate z.union() for known types
|
|
342
|
-
// This handles cases like "IExtensionIdentifier | string"
|
|
343
|
-
const unionTypes = nonNullableTypes.map(type => {
|
|
344
|
-
// Check if it's a GraphQL type
|
|
345
|
-
if (GRAPHQL_ZOD_TYPES.has(type)) {
|
|
346
|
-
const schemaName = type.replace(/^I/, '') + 'Schema';
|
|
347
|
-
return `${schemaName}()`;
|
|
348
|
-
}
|
|
349
|
-
// Handle primitive types
|
|
350
|
-
if (type === 'string') return 'z.string()';
|
|
351
|
-
if (type === 'number') return 'z.number()';
|
|
352
|
-
if (type === 'boolean') return 'z.boolean()';
|
|
353
|
-
// Default to passthrough
|
|
354
|
-
return 'PassthroughObjectSchema';
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
return `z.union([${unionTypes.join(', ')}])`;
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
// Handle array types
|
|
362
|
-
if (paramType.includes('[]')) {
|
|
363
|
-
const innerType = paramType.replace('[]', '').trim();
|
|
364
|
-
const innerZodType = generateZodType(innerType, false, paramName);
|
|
365
|
-
zodType = `z.array(${innerZodType})`;
|
|
366
|
-
}
|
|
367
|
-
// Handle known enums
|
|
368
|
-
else if (KNOWN_ENUMS.some((e) => paramType.includes(e))) {
|
|
369
|
-
const enumName = KNOWN_ENUMS.find((e) => paramType.includes(e));
|
|
370
|
-
zodType = `z.nativeEnum(${enumName})`;
|
|
371
|
-
}
|
|
372
|
-
// Handle const enums (use schema functions from graphql-zod-schemas)
|
|
373
|
-
else if (CONST_ENUM_SCHEMAS.some((e) => paramType.includes(e))) {
|
|
374
|
-
const enumName = CONST_ENUM_SCHEMAS.find((e) => paramType.includes(e));
|
|
375
|
-
zodType = `${enumName}Schema()`;
|
|
376
|
-
}
|
|
377
|
-
// Handle primitive types
|
|
378
|
-
else if (paramType === 'string') {
|
|
379
|
-
zodType = 'z.string()';
|
|
380
|
-
} else if (paramType === 'number') {
|
|
381
|
-
zodType = 'z.number()';
|
|
382
|
-
} else if (paramType === 'boolean') {
|
|
383
|
-
zodType = 'z.boolean()';
|
|
384
|
-
} else if (paramType === 'any') {
|
|
385
|
-
zodType = 'z.any()';
|
|
386
|
-
}
|
|
387
|
-
// Handle Date
|
|
388
|
-
else if (paramType === 'Date') {
|
|
389
|
-
zodType = 'z.date()';
|
|
390
|
-
}
|
|
391
|
-
// Check if it's a GraphQL type with existing Zod schema
|
|
392
|
-
else if (GRAPHQL_ZOD_TYPES.has(paramType)) {
|
|
393
|
-
// Use the GraphQL-generated schema function
|
|
394
|
-
const schemaName = paramType.replace(/^I/, '') + 'Schema';
|
|
395
|
-
zodType = `${schemaName}()`;
|
|
396
|
-
}
|
|
397
|
-
// Handle Promise types (return type, not for validation)
|
|
398
|
-
else if (paramType.startsWith('Promise<')) {
|
|
399
|
-
zodType = 'z.any()';
|
|
400
|
-
}
|
|
401
|
-
// Use reusable schemas for common parameter names
|
|
402
|
-
// Make complex objects optional by default to avoid strict validation errors
|
|
403
|
-
else if (paramName === 'context' || paramName === 'userContext') {
|
|
404
|
-
zodType = 'ContextSchema.optional()';
|
|
405
|
-
} else if (paramName === 'resource') {
|
|
406
|
-
zodType = 'ResourceSchema.optional()';
|
|
407
|
-
} else if (paramName === 'options' || paramName === 'paginationOptions') {
|
|
408
|
-
zodType = 'OptionsSchema.optional()';
|
|
409
|
-
} else if (paramName === 'data' || paramName === 'updateData') {
|
|
410
|
-
zodType = 'DataSchema.optional()';
|
|
411
|
-
} else if (paramName === 'input') {
|
|
412
|
-
zodType = 'InputSchema.optional()';
|
|
413
|
-
} else if (paramName === 'filter' || paramName === 'criteria' || paramName === 'additionalFilters') {
|
|
414
|
-
zodType = 'FilterSchema.optional()';
|
|
415
|
-
} else if (paramName === 'where') {
|
|
416
|
-
zodType = 'WhereSchema.optional()';
|
|
417
|
-
} else if (paramName === 'request') {
|
|
418
|
-
zodType = 'RequestSchema.optional()';
|
|
419
|
-
}
|
|
420
|
-
// Default: treat as object and make it optional
|
|
421
|
-
else {
|
|
422
|
-
zodType = 'PassthroughObjectSchema.optional()';
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
// Only add .optional() if not already added and isOptional is true
|
|
426
|
-
return isOptional && !zodType.includes('.optional()') ? `${zodType}.optional()` : zodType;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
function generateServiceSchemas() {
|
|
430
|
-
const services = findServiceInterfaces();
|
|
431
|
-
|
|
432
|
-
console.log(`\n🔍 Found ${services.length} service interfaces\n`);
|
|
433
|
-
|
|
434
|
-
// Parse all services and collect used enums and GraphQL types
|
|
435
|
-
const allUsedEnums = new Set();
|
|
436
|
-
const allUsedGraphQLTypes = new Set();
|
|
437
|
-
const allUsedConstEnumSchemas = new Set(); // Track const enum schemas
|
|
438
|
-
const servicesWithMethods = [];
|
|
439
|
-
|
|
440
|
-
services.forEach((service) => {
|
|
441
|
-
const { methods, usedEnums } = parseServiceInterface(service.path, service.name);
|
|
442
|
-
usedEnums.forEach((e) => {
|
|
443
|
-
if (CONST_ENUM_SCHEMAS.includes(e)) {
|
|
444
|
-
allUsedConstEnumSchemas.add(e);
|
|
445
|
-
} else {
|
|
446
|
-
allUsedEnums.add(e);
|
|
447
|
-
}
|
|
448
|
-
});
|
|
449
|
-
|
|
450
|
-
// Track which GraphQL types are actually used
|
|
451
|
-
methods.forEach((method) => {
|
|
452
|
-
method.parameters.forEach((param) => {
|
|
453
|
-
if (GRAPHQL_ZOD_TYPES.has(param.type)) {
|
|
454
|
-
allUsedGraphQLTypes.add(param.type);
|
|
455
|
-
}
|
|
456
|
-
// Check in array types
|
|
457
|
-
if (param.type.includes('[]')) {
|
|
458
|
-
const innerType = param.type.replace('[]', '').trim();
|
|
459
|
-
if (GRAPHQL_ZOD_TYPES.has(innerType)) {
|
|
460
|
-
allUsedGraphQLTypes.add(innerType);
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
// Check in union types (e.g., "IExtensionIdentifier | string")
|
|
464
|
-
if (param.type.includes('|')) {
|
|
465
|
-
const types = param.type.split('|').map(t => t.trim());
|
|
466
|
-
types.forEach(type => {
|
|
467
|
-
// Remove null and undefined
|
|
468
|
-
if (type !== 'null' && type !== 'undefined' && GRAPHQL_ZOD_TYPES.has(type)) {
|
|
469
|
-
allUsedGraphQLTypes.add(type);
|
|
470
|
-
}
|
|
471
|
-
});
|
|
472
|
-
}
|
|
473
|
-
});
|
|
474
|
-
});
|
|
475
|
-
|
|
476
|
-
servicesWithMethods.push({
|
|
477
|
-
...service,
|
|
478
|
-
methods,
|
|
479
|
-
});
|
|
480
|
-
});
|
|
481
|
-
|
|
482
|
-
// Start generating output
|
|
483
|
-
let output = `// AUTO-GENERATED FILE - DO NOT EDIT
|
|
484
|
-
// Generated from service interfaces in packages/common/src/services/
|
|
485
|
-
// Run: yarn generateGraphql to regenerate
|
|
486
|
-
|
|
487
|
-
import { z } from 'zod';
|
|
488
|
-
`;
|
|
489
|
-
|
|
490
|
-
// Import GraphQL Zod schemas (including const enum schemas)
|
|
491
|
-
const allGraphQLSchemas = new Set([...allUsedGraphQLTypes, ...allUsedConstEnumSchemas]);
|
|
492
|
-
if (allGraphQLSchemas.size > 0) {
|
|
493
|
-
output += `\n// Import GraphQL-generated Zod schemas for detailed validation\nimport {\n`;
|
|
494
|
-
allGraphQLSchemas.forEach((typeName) => {
|
|
495
|
-
const schemaName = typeName.replace(/^I/, '') + 'Schema';
|
|
496
|
-
output += ` ${schemaName},\n`;
|
|
497
|
-
});
|
|
498
|
-
output += `} from '${GRAPHQL_SCHEMAS_IMPORT_PATH}';\n`;
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
// Import used enums (only regular enums, not const enums)
|
|
502
|
-
if (allUsedEnums.size > 0) {
|
|
503
|
-
output += `\n// Import enums\nimport {\n`;
|
|
504
|
-
allUsedEnums.forEach((enumName) => {
|
|
505
|
-
output += ` ${enumName},\n`;
|
|
506
|
-
});
|
|
507
|
-
output += `} from 'common/server';\n`;
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
output += `
|
|
511
|
-
/**
|
|
512
|
-
* Centralized Zod schemas for all service interfaces
|
|
513
|
-
*
|
|
514
|
-
* Each service has its schemas exported as {ServiceName}Schemas
|
|
515
|
-
* Use with zodSchemasToMoleculer() for Moleculer parameter validation
|
|
516
|
-
*
|
|
517
|
-
* Example:
|
|
518
|
-
* \`\`\`typescript
|
|
519
|
-
* import { AccountServiceSchemas, zodSchemasToMoleculer } from 'common/server';
|
|
520
|
-
* const paramOverrides = zodSchemasToMoleculer(AccountServiceSchemas);
|
|
521
|
-
* \`\`\`
|
|
522
|
-
*/
|
|
523
|
-
|
|
524
|
-
// Reusable schemas for common parameter types
|
|
525
|
-
const PassthroughObjectSchema = z.object({}).passthrough();
|
|
526
|
-
const ContextSchema = PassthroughObjectSchema;
|
|
527
|
-
const ResourceSchema = PassthroughObjectSchema;
|
|
528
|
-
const OptionsSchema = PassthroughObjectSchema;
|
|
529
|
-
const DataSchema = PassthroughObjectSchema;
|
|
530
|
-
const InputSchema = PassthroughObjectSchema;
|
|
531
|
-
const FilterSchema = PassthroughObjectSchema;
|
|
532
|
-
const WhereSchema = PassthroughObjectSchema;
|
|
533
|
-
const RequestSchema = PassthroughObjectSchema;
|
|
534
|
-
|
|
535
|
-
`;
|
|
536
|
-
|
|
537
|
-
// Generate schemas for each service
|
|
538
|
-
servicesWithMethods.forEach((service) => {
|
|
539
|
-
const schemaName = service.name.replace(/^I/, '').replace(/Service$/, 'Service');
|
|
540
|
-
const serviceName = service.name.replace(/^I/, ''); // e.g., "TagService"
|
|
541
|
-
|
|
542
|
-
output += `/**
|
|
543
|
-
* Zod schemas for ${service.name}
|
|
544
|
-
* Source: ${service.file}
|
|
545
|
-
*/
|
|
546
|
-
export const ${schemaName}Schemas = {
|
|
547
|
-
/** Moleculer service topic/name - safe for minification */
|
|
548
|
-
topic: '${serviceName}' as const,
|
|
549
|
-
`;
|
|
550
|
-
|
|
551
|
-
if (service.methods.length === 0) {
|
|
552
|
-
output += ` // No methods found\n`;
|
|
553
|
-
} else {
|
|
554
|
-
// Group methods by name to handle overloads (keep last signature)
|
|
555
|
-
const methodMap = new Map();
|
|
556
|
-
service.methods.forEach((method) => {
|
|
557
|
-
methodMap.set(method.name, method);
|
|
558
|
-
});
|
|
559
|
-
|
|
560
|
-
// Generate schemas for unique methods
|
|
561
|
-
methodMap.forEach((method, methodName) => {
|
|
562
|
-
if (method.parameters.length === 0) {
|
|
563
|
-
output += ` ${methodName}: PassthroughObjectSchema,\n`;
|
|
564
|
-
} else {
|
|
565
|
-
// Check if this method has only destructured params
|
|
566
|
-
const hasOnlyDestructured = method.parameters.length === 1 && method.parameters[0].isDestructured;
|
|
567
|
-
|
|
568
|
-
if (hasOnlyDestructured) {
|
|
569
|
-
// For destructured params, use the type directly
|
|
570
|
-
const param = method.parameters[0];
|
|
571
|
-
const zodType = generateZodType(param.type, param.isOptional, param.name);
|
|
572
|
-
output += ` ${methodName}: ${zodType},\n`;
|
|
573
|
-
} else {
|
|
574
|
-
output += ` ${methodName}: z.object({\n`;
|
|
575
|
-
method.parameters.forEach((param) => {
|
|
576
|
-
if (!param.isDestructured) {
|
|
577
|
-
const zodType = generateZodType(param.type, param.isOptional, param.name);
|
|
578
|
-
output += ` ${param.name}: ${zodType},\n`;
|
|
579
|
-
}
|
|
580
|
-
});
|
|
581
|
-
output += ` }),\n`;
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
});
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
output += `};\n\n`;
|
|
588
|
-
});
|
|
589
|
-
|
|
590
|
-
// Generate aggregated export with explicit type annotation to avoid TS7056 error
|
|
591
|
-
output += `/**
|
|
592
|
-
* All service schemas aggregated
|
|
593
|
-
* Type annotation prevents TypeScript from inferring overly complex types
|
|
594
|
-
*/
|
|
595
|
-
export const AllServiceSchemas: Record<string, Record<string, any>> = {\n`;
|
|
596
|
-
|
|
597
|
-
servicesWithMethods.forEach((service) => {
|
|
598
|
-
const schemaName = service.name.replace(/^I/, '').replace(/Service$/, 'Service');
|
|
599
|
-
output += ` ${service.name}: ${schemaName}Schemas,\n`;
|
|
600
|
-
});
|
|
601
|
-
|
|
602
|
-
output += `};\n`;
|
|
603
|
-
|
|
604
|
-
// Write the file
|
|
605
|
-
fs.writeFileSync(OUTPUT_FILE, output, 'utf8');
|
|
606
|
-
|
|
607
|
-
console.log(`✅ Generated schemas for ${services.length} services`);
|
|
608
|
-
|
|
609
|
-
// Count total methods
|
|
610
|
-
const totalMethods = servicesWithMethods.reduce((sum, s) => sum + s.methods.length, 0);
|
|
611
|
-
console.log(`📊 Total methods: ${totalMethods}`);
|
|
612
|
-
console.log(`📝 Output: ${path.relative(process.cwd(), OUTPUT_FILE)}\n`);
|
|
613
|
-
|
|
614
|
-
return services.length;
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
// Export for CommonJS
|
|
618
|
-
module.exports = { generateServiceSchemas };
|
|
619
|
-
|
|
620
|
-
// CLI execution
|
|
621
|
-
if (require.main === module) {
|
|
622
|
-
try {
|
|
623
|
-
generateServiceSchemas();
|
|
624
|
-
} catch (error) {
|
|
625
|
-
console.error('❌ Error generating service schemas:', error);
|
|
626
|
-
process.exit(1);
|
|
627
|
-
}
|
|
628
|
-
}
|