@omen.foundation/node-microservice-runtime 0.1.71 → 0.1.73
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/add-federation.d.ts +8 -0
- package/dist/cli/commands/add-federation.d.ts.map +1 -0
- package/dist/cli/commands/add-federation.js +291 -0
- package/dist/cli/commands/add-federation.js.map +1 -0
- package/dist/cli/commands/add-storage.d.ts +8 -0
- package/dist/cli/commands/add-storage.d.ts.map +1 -0
- package/dist/cli/commands/add-storage.js +180 -0
- package/dist/cli/commands/add-storage.js.map +1 -0
- package/dist/cli/commands/scaffold.d.ts +8 -0
- package/dist/cli/commands/scaffold.d.ts.map +1 -0
- package/dist/cli/commands/scaffold.js +247 -0
- package/dist/cli/commands/scaffold.js.map +1 -0
- package/dist/cli/index.js +22 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/utils/name-utils.d.ts +27 -0
- package/dist/cli/utils/name-utils.d.ts.map +1 -0
- package/dist/cli/utils/name-utils.js +42 -0
- package/dist/cli/utils/name-utils.js.map +1 -0
- package/dist/cli/utils/prompt-utils.d.ts +13 -0
- package/dist/cli/utils/prompt-utils.d.ts.map +1 -0
- package/dist/cli/utils/prompt-utils.js +39 -0
- package/dist/cli/utils/prompt-utils.js.map +1 -0
- package/dist/cli/utils/version-utils.d.ts +11 -0
- package/dist/cli/utils/version-utils.d.ts.map +1 -0
- package/dist/cli/utils/version-utils.js +92 -0
- package/dist/cli/utils/version-utils.js.map +1 -0
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +104 -4
- package/dist/logger.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command handler for adding federated inventory support to an existing microservice.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Handles the add-federation command.
|
|
6
|
+
*/
|
|
7
|
+
export declare function handleAddFederation(args: string[]): Promise<void>;
|
|
8
|
+
//# sourceMappingURL=add-federation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"add-federation.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/add-federation.ts"],"names":[],"mappings":"AAAA;;GAEG;AAOH;;GAEG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA8DvE"}
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command handler for adding federated inventory support to an existing microservice.
|
|
3
|
+
*/
|
|
4
|
+
import fs from 'node:fs/promises';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { toPascalCase, validateServiceName } from '../utils/name-utils.js';
|
|
7
|
+
import { promptInput, promptYesNo } from '../utils/prompt-utils.js';
|
|
8
|
+
/**
|
|
9
|
+
* Handles the add-federation command.
|
|
10
|
+
*/
|
|
11
|
+
export async function handleAddFederation(args) {
|
|
12
|
+
let identityName = args[0];
|
|
13
|
+
// Interactive prompt for identity name if not provided
|
|
14
|
+
if (!identityName) {
|
|
15
|
+
identityName = await promptInput('Enter federation identity name (alphanumeric and underscores only)');
|
|
16
|
+
}
|
|
17
|
+
// Validate identity name
|
|
18
|
+
if (!identityName || !validateServiceName(identityName)) {
|
|
19
|
+
throw new Error(`Invalid identity name "${identityName}". Only alphanumeric and underscore characters are allowed.`);
|
|
20
|
+
}
|
|
21
|
+
// Find service file
|
|
22
|
+
const serviceFile = await findServiceFile();
|
|
23
|
+
if (!serviceFile) {
|
|
24
|
+
throw new Error('Could not find service file. Make sure you are in a microservice directory with a service class decorated with @Microservice.');
|
|
25
|
+
}
|
|
26
|
+
const createServiceClass = await promptYesNo('Create a separate federation service class?', true);
|
|
27
|
+
// Read existing service file
|
|
28
|
+
const serviceContent = await fs.readFile(serviceFile, 'utf8');
|
|
29
|
+
// Generate federation identity class
|
|
30
|
+
const identityClassContent = generateFederationIdentity(identityName);
|
|
31
|
+
const identityFileName = `${identityName.toLowerCase()}-federation-identity.ts`;
|
|
32
|
+
const identityFilePath = path.join(path.dirname(serviceFile), 'services', identityFileName);
|
|
33
|
+
await fs.mkdir(path.dirname(identityFilePath), { recursive: true });
|
|
34
|
+
await fs.writeFile(identityFilePath, identityClassContent);
|
|
35
|
+
// Generate federation service class if requested
|
|
36
|
+
let federationServiceClassName = null;
|
|
37
|
+
if (createServiceClass) {
|
|
38
|
+
const serviceClassContent = generateFederationService(identityName);
|
|
39
|
+
const serviceFileName = `${identityName.toLowerCase()}-federation-service.ts`;
|
|
40
|
+
const serviceFilePath = path.join(path.dirname(serviceFile), 'services', serviceFileName);
|
|
41
|
+
await fs.writeFile(serviceFilePath, serviceClassContent);
|
|
42
|
+
federationServiceClassName = `${toPascalCase(identityName)}FederationService`;
|
|
43
|
+
}
|
|
44
|
+
// Modify service file
|
|
45
|
+
const modifiedContent = modifyServiceFile(serviceContent, identityName, federationServiceClassName);
|
|
46
|
+
await fs.writeFile(serviceFile, modifiedContent);
|
|
47
|
+
console.log(`\n✅ Successfully added federation support with identity "${identityName}"!`);
|
|
48
|
+
console.log(`\nCreated files:`);
|
|
49
|
+
console.log(` - ${identityFilePath}`);
|
|
50
|
+
if (createServiceClass) {
|
|
51
|
+
console.log(` - ${path.join(path.dirname(serviceFile), 'services', `${identityName.toLowerCase()}-federation-service.ts`)}`);
|
|
52
|
+
}
|
|
53
|
+
console.log(`\nModified:`);
|
|
54
|
+
console.log(` - ${serviceFile}`);
|
|
55
|
+
console.log(`\nNext steps:`);
|
|
56
|
+
console.log(` 1. Implement the federation methods in your service class`);
|
|
57
|
+
if (createServiceClass) {
|
|
58
|
+
console.log(` 2. Implement the methods in the federation service class`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Finds the service file in the current directory.
|
|
63
|
+
*/
|
|
64
|
+
async function findServiceFile() {
|
|
65
|
+
const cwd = process.cwd();
|
|
66
|
+
const srcDir = path.join(cwd, 'src');
|
|
67
|
+
try {
|
|
68
|
+
const entries = await fs.readdir(srcDir, { withFileTypes: true });
|
|
69
|
+
const serviceFiles = [];
|
|
70
|
+
for (const entry of entries) {
|
|
71
|
+
if (entry.isFile() && entry.name.endsWith('Service.ts')) {
|
|
72
|
+
serviceFiles.push(path.join(srcDir, entry.name));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (serviceFiles.length === 0) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
// Check if any file contains @Microservice decorator
|
|
79
|
+
for (const file of serviceFiles) {
|
|
80
|
+
const content = await fs.readFile(file, 'utf8');
|
|
81
|
+
if (content.includes('@Microservice')) {
|
|
82
|
+
return file;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return serviceFiles[0] || null;
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Generates the federation identity class.
|
|
93
|
+
*/
|
|
94
|
+
function generateFederationIdentity(identityName) {
|
|
95
|
+
const className = `${toPascalCase(identityName)}FederationIdentity`;
|
|
96
|
+
return `import type { FederationIdentity } from '@omen.foundation/node-microservice-runtime';
|
|
97
|
+
|
|
98
|
+
export class ${className} implements FederationIdentity {
|
|
99
|
+
getUniqueName(): string {
|
|
100
|
+
return '${identityName.toLowerCase()}';
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
`;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Generates the federation service class.
|
|
107
|
+
*/
|
|
108
|
+
function generateFederationService(identityName) {
|
|
109
|
+
const className = `${toPascalCase(identityName)}FederationService`;
|
|
110
|
+
return `import type {
|
|
111
|
+
RequestContext,
|
|
112
|
+
FederatedAuthenticationRequest,
|
|
113
|
+
FederatedAuthenticationResponse,
|
|
114
|
+
FederatedInventoryProxyState,
|
|
115
|
+
FederatedInventoryStateRequest,
|
|
116
|
+
FederatedInventoryTransactionRequest,
|
|
117
|
+
} from '@omen.foundation/node-microservice-runtime';
|
|
118
|
+
|
|
119
|
+
export class ${className} {
|
|
120
|
+
async authenticate(request: FederatedAuthenticationRequest): Promise<FederatedAuthenticationResponse> {
|
|
121
|
+
// TODO: Implement authentication logic
|
|
122
|
+
const token = request.token?.trim();
|
|
123
|
+
if (!token) {
|
|
124
|
+
return {
|
|
125
|
+
challenge: 'Provide a token representing the external account id.',
|
|
126
|
+
challenge_ttl: 300,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
return { user_id: token };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async getInventoryState(
|
|
133
|
+
_ctx: RequestContext,
|
|
134
|
+
request: FederatedInventoryStateRequest,
|
|
135
|
+
): Promise<FederatedInventoryProxyState> {
|
|
136
|
+
// TODO: Implement inventory state retrieval
|
|
137
|
+
return {
|
|
138
|
+
currencies: {},
|
|
139
|
+
items: {},
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async startInventoryTransaction(
|
|
144
|
+
_ctx: RequestContext,
|
|
145
|
+
request: FederatedInventoryTransactionRequest,
|
|
146
|
+
): Promise<FederatedInventoryProxyState> {
|
|
147
|
+
// TODO: Implement inventory transaction logic
|
|
148
|
+
return {
|
|
149
|
+
currencies: request.currencies ?? {},
|
|
150
|
+
items: {},
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
`;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Modifies the service file to add federation support.
|
|
158
|
+
*/
|
|
159
|
+
function modifyServiceFile(content, identityName, federationServiceClassName) {
|
|
160
|
+
const identityClassName = `${toPascalCase(identityName)}FederationIdentity`;
|
|
161
|
+
const identityFileName = `${identityName.toLowerCase()}-federation-identity`;
|
|
162
|
+
let modified = content;
|
|
163
|
+
// Add imports if not present
|
|
164
|
+
const federationImports = [
|
|
165
|
+
'FederatedInventory',
|
|
166
|
+
'type FederationIdentity',
|
|
167
|
+
'type FederatedAuthenticationRequest',
|
|
168
|
+
'type FederatedAuthenticationResponse',
|
|
169
|
+
'type FederatedInventoryProxyState',
|
|
170
|
+
'type FederatedInventoryStateRequest',
|
|
171
|
+
'type FederatedInventoryTransactionRequest',
|
|
172
|
+
];
|
|
173
|
+
// Check if imports from runtime already exist
|
|
174
|
+
const runtimeImportMatch = modified.match(/from ['"]@omen\.foundation\/node-microservice-runtime['"]/);
|
|
175
|
+
if (runtimeImportMatch) {
|
|
176
|
+
// Add to existing import
|
|
177
|
+
const importLine = modified.split('\n').find((line) => line.includes('from') && line.includes('node-microservice-runtime'));
|
|
178
|
+
if (importLine) {
|
|
179
|
+
// Check which imports are missing
|
|
180
|
+
const missingImports = federationImports.filter((imp) => !importLine.includes(imp.replace('type ', '')));
|
|
181
|
+
if (missingImports.length > 0) {
|
|
182
|
+
// Add missing imports
|
|
183
|
+
const newImports = missingImports.join(',\n ');
|
|
184
|
+
modified = modified.replace(importLine, importLine.replace('}', `,\n ${newImports}\n}`));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
// Add new import block
|
|
190
|
+
const newImport = `import {\n ${federationImports.join(',\n ')},\n} from '@omen.foundation/node-microservice-runtime';`;
|
|
191
|
+
// Find the last import and add after it
|
|
192
|
+
const importLines = modified.split('\n');
|
|
193
|
+
let lastImportIndex = -1;
|
|
194
|
+
for (let i = 0; i < importLines.length; i++) {
|
|
195
|
+
if (importLines[i].trim().startsWith('import ')) {
|
|
196
|
+
lastImportIndex = i;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (lastImportIndex >= 0) {
|
|
200
|
+
importLines.splice(lastImportIndex + 1, 0, newImport);
|
|
201
|
+
modified = importLines.join('\n');
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// Add identity import
|
|
205
|
+
const identityImport = `import { ${identityClassName} } from './services/${identityFileName}.js';`;
|
|
206
|
+
if (!modified.includes(identityImport)) {
|
|
207
|
+
const importLines = modified.split('\n');
|
|
208
|
+
let lastImportIndex = -1;
|
|
209
|
+
for (let i = 0; i < importLines.length; i++) {
|
|
210
|
+
if (importLines[i].trim().startsWith('import ')) {
|
|
211
|
+
lastImportIndex = i;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (lastImportIndex >= 0) {
|
|
215
|
+
importLines.splice(lastImportIndex + 1, 0, identityImport);
|
|
216
|
+
modified = importLines.join('\n');
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// Add federation service import if created
|
|
220
|
+
if (federationServiceClassName) {
|
|
221
|
+
const serviceImport = `import { ${federationServiceClassName} } from './services/${identityName.toLowerCase()}-federation-service.js';`;
|
|
222
|
+
if (!modified.includes(serviceImport)) {
|
|
223
|
+
const importLines = modified.split('\n');
|
|
224
|
+
let lastImportIndex = -1;
|
|
225
|
+
for (let i = 0; i < importLines.length; i++) {
|
|
226
|
+
if (importLines[i].trim().startsWith('import ')) {
|
|
227
|
+
lastImportIndex = i;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (lastImportIndex >= 0) {
|
|
231
|
+
importLines.splice(lastImportIndex + 1, 0, serviceImport);
|
|
232
|
+
modified = importLines.join('\n');
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
// Add @FederatedInventory decorator before @Microservice
|
|
237
|
+
if (!modified.includes('@FederatedInventory')) {
|
|
238
|
+
modified = modified.replace(/@Microservice\(/g, `@FederatedInventory({ identity: ${identityClassName} })\n@Microservice(`);
|
|
239
|
+
}
|
|
240
|
+
// Add federation service to @ConfigureServices if created
|
|
241
|
+
if (federationServiceClassName) {
|
|
242
|
+
const configureServicesMatch = modified.match(/@ConfigureServices\s+static\s+register\([^)]+\):\s*void\s*\{([^}]+)\}/s);
|
|
243
|
+
if (configureServicesMatch) {
|
|
244
|
+
const methodBody = configureServicesMatch[1];
|
|
245
|
+
if (!methodBody.includes(federationServiceClassName)) {
|
|
246
|
+
modified = modified.replace(/(builder\.addSingletonClass\([^)]+\);)/, `$1\n builder.addSingletonClass(${federationServiceClassName});`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// Add federation methods if they don't exist
|
|
251
|
+
const methods = [
|
|
252
|
+
{
|
|
253
|
+
name: 'Authenticate',
|
|
254
|
+
signature: `async Authenticate(\n _ctx: RequestContext,\n request: FederatedAuthenticationRequest,\n ): Promise<FederatedAuthenticationResponse>`,
|
|
255
|
+
body: federationServiceClassName
|
|
256
|
+
? `{\n const federationService = _ctx.provider.resolve(${federationServiceClassName});\n return federationService.authenticate(request);\n }`
|
|
257
|
+
: `{\n // TODO: Implement authentication logic\n const token = request.token?.trim();\n if (!token) {\n return {\n challenge: 'Provide a token representing the external account id.',\n challenge_ttl: 300,\n };\n }\n return { user_id: token };\n }`,
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
name: 'GetInventoryState',
|
|
261
|
+
signature: `async GetInventoryState(\n ctx: RequestContext,\n request: FederatedInventoryStateRequest,\n ): Promise<FederatedInventoryProxyState>`,
|
|
262
|
+
body: federationServiceClassName
|
|
263
|
+
? `{\n const federationService = ctx.provider.resolve(${federationServiceClassName});\n return federationService.getInventoryState(ctx, request);\n }`
|
|
264
|
+
: `{\n // TODO: Implement inventory state retrieval\n return {\n currencies: {},\n items: {},\n };\n }`,
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
name: 'StartInventoryTransaction',
|
|
268
|
+
signature: `async StartInventoryTransaction(\n ctx: RequestContext,\n request: FederatedInventoryTransactionRequest,\n ): Promise<FederatedInventoryProxyState>`,
|
|
269
|
+
body: federationServiceClassName
|
|
270
|
+
? `{\n const federationService = ctx.provider.resolve(${federationServiceClassName});\n return federationService.startInventoryTransaction(ctx, request);\n }`
|
|
271
|
+
: `{\n // TODO: Implement inventory transaction logic\n return {\n currencies: request.currencies ?? {},\n items: {},\n };\n }`,
|
|
272
|
+
},
|
|
273
|
+
];
|
|
274
|
+
// Find the end of the class (before the closing brace)
|
|
275
|
+
const classEndMatch = modified.match(/(\n\})/);
|
|
276
|
+
if (classEndMatch) {
|
|
277
|
+
const classEndIndex = modified.lastIndexOf('\n}');
|
|
278
|
+
const beforeEnd = modified.substring(0, classEndIndex);
|
|
279
|
+
const afterEnd = modified.substring(classEndIndex);
|
|
280
|
+
// Check which methods are missing
|
|
281
|
+
const missingMethods = methods.filter((method) => !beforeEnd.includes(`async ${method.name}(`));
|
|
282
|
+
if (missingMethods.length > 0) {
|
|
283
|
+
const methodsCode = missingMethods
|
|
284
|
+
.map((method) => `\n ${method.signature} ${method.body}\n`)
|
|
285
|
+
.join('\n');
|
|
286
|
+
modified = beforeEnd + methodsCode + afterEnd;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return modified;
|
|
290
|
+
}
|
|
291
|
+
//# sourceMappingURL=add-federation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"add-federation.js","sourceRoot":"","sources":["../../../src/cli/commands/add-federation.ts"],"names":[],"mappings":"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","sourcesContent":["/**\r\n * Command handler for adding federated inventory support to an existing microservice.\r\n */\r\n\r\nimport fs from 'node:fs/promises';\r\nimport path from 'node:path';\r\nimport { toPascalCase, validateServiceName } from '../utils/name-utils.js';\r\nimport { promptInput, promptYesNo } from '../utils/prompt-utils.js';\r\n\r\n/**\r\n * Handles the add-federation command.\r\n */\r\nexport async function handleAddFederation(args: string[]): Promise<void> {\r\n let identityName = args[0];\r\n\r\n // Interactive prompt for identity name if not provided\r\n if (!identityName) {\r\n identityName = await promptInput('Enter federation identity name (alphanumeric and underscores only)');\r\n }\r\n\r\n // Validate identity name\r\n if (!identityName || !validateServiceName(identityName)) {\r\n throw new Error(\r\n `Invalid identity name \"${identityName}\". Only alphanumeric and underscore characters are allowed.`,\r\n );\r\n }\r\n\r\n // Find service file\r\n const serviceFile = await findServiceFile();\r\n if (!serviceFile) {\r\n throw new Error(\r\n 'Could not find service file. Make sure you are in a microservice directory with a service class decorated with @Microservice.',\r\n );\r\n }\r\n\r\n const createServiceClass = await promptYesNo('Create a separate federation service class?', true);\r\n\r\n // Read existing service file\r\n const serviceContent = await fs.readFile(serviceFile, 'utf8');\r\n\r\n // Generate federation identity class\r\n const identityClassContent = generateFederationIdentity(identityName);\r\n const identityFileName = `${identityName.toLowerCase()}-federation-identity.ts`;\r\n const identityFilePath = path.join(path.dirname(serviceFile), 'services', identityFileName);\r\n await fs.mkdir(path.dirname(identityFilePath), { recursive: true });\r\n await fs.writeFile(identityFilePath, identityClassContent);\r\n\r\n // Generate federation service class if requested\r\n let federationServiceClassName: string | null = null;\r\n if (createServiceClass) {\r\n const serviceClassContent = generateFederationService(identityName);\r\n const serviceFileName = `${identityName.toLowerCase()}-federation-service.ts`;\r\n const serviceFilePath = path.join(path.dirname(serviceFile), 'services', serviceFileName);\r\n await fs.writeFile(serviceFilePath, serviceClassContent);\r\n federationServiceClassName = `${toPascalCase(identityName)}FederationService`;\r\n }\r\n\r\n // Modify service file\r\n const modifiedContent = modifyServiceFile(serviceContent, identityName, federationServiceClassName);\r\n await fs.writeFile(serviceFile, modifiedContent);\r\n\r\n console.log(`\\n✅ Successfully added federation support with identity \"${identityName}\"!`);\r\n console.log(`\\nCreated files:`);\r\n console.log(` - ${identityFilePath}`);\r\n if (createServiceClass) {\r\n console.log(` - ${path.join(path.dirname(serviceFile), 'services', `${identityName.toLowerCase()}-federation-service.ts`)}`);\r\n }\r\n console.log(`\\nModified:`);\r\n console.log(` - ${serviceFile}`);\r\n console.log(`\\nNext steps:`);\r\n console.log(` 1. Implement the federation methods in your service class`);\r\n if (createServiceClass) {\r\n console.log(` 2. Implement the methods in the federation service class`);\r\n }\r\n}\r\n\r\n/**\r\n * Finds the service file in the current directory.\r\n */\r\nasync function findServiceFile(): Promise<string | null> {\r\n const cwd = process.cwd();\r\n const srcDir = path.join(cwd, 'src');\r\n\r\n try {\r\n const entries = await fs.readdir(srcDir, { withFileTypes: true });\r\n const serviceFiles: string[] = [];\r\n\r\n for (const entry of entries) {\r\n if (entry.isFile() && entry.name.endsWith('Service.ts')) {\r\n serviceFiles.push(path.join(srcDir, entry.name));\r\n }\r\n }\r\n\r\n if (serviceFiles.length === 0) {\r\n return null;\r\n }\r\n\r\n // Check if any file contains @Microservice decorator\r\n for (const file of serviceFiles) {\r\n const content = await fs.readFile(file, 'utf8');\r\n if (content.includes('@Microservice')) {\r\n return file;\r\n }\r\n }\r\n\r\n return serviceFiles[0] || null;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\n/**\r\n * Generates the federation identity class.\r\n */\r\nfunction generateFederationIdentity(identityName: string): string {\r\n const className = `${toPascalCase(identityName)}FederationIdentity`;\r\n return `import type { FederationIdentity } from '@omen.foundation/node-microservice-runtime';\r\n\r\nexport class ${className} implements FederationIdentity {\r\n getUniqueName(): string {\r\n return '${identityName.toLowerCase()}';\r\n }\r\n}\r\n`;\r\n}\r\n\r\n/**\r\n * Generates the federation service class.\r\n */\r\nfunction generateFederationService(identityName: string): string {\r\n const className = `${toPascalCase(identityName)}FederationService`;\r\n return `import type {\r\n RequestContext,\r\n FederatedAuthenticationRequest,\r\n FederatedAuthenticationResponse,\r\n FederatedInventoryProxyState,\r\n FederatedInventoryStateRequest,\r\n FederatedInventoryTransactionRequest,\r\n} from '@omen.foundation/node-microservice-runtime';\r\n\r\nexport class ${className} {\r\n async authenticate(request: FederatedAuthenticationRequest): Promise<FederatedAuthenticationResponse> {\r\n // TODO: Implement authentication logic\r\n const token = request.token?.trim();\r\n if (!token) {\r\n return {\r\n challenge: 'Provide a token representing the external account id.',\r\n challenge_ttl: 300,\r\n };\r\n }\r\n return { user_id: token };\r\n }\r\n\r\n async getInventoryState(\r\n _ctx: RequestContext,\r\n request: FederatedInventoryStateRequest,\r\n ): Promise<FederatedInventoryProxyState> {\r\n // TODO: Implement inventory state retrieval\r\n return {\r\n currencies: {},\r\n items: {},\r\n };\r\n }\r\n\r\n async startInventoryTransaction(\r\n _ctx: RequestContext,\r\n request: FederatedInventoryTransactionRequest,\r\n ): Promise<FederatedInventoryProxyState> {\r\n // TODO: Implement inventory transaction logic\r\n return {\r\n currencies: request.currencies ?? {},\r\n items: {},\r\n };\r\n }\r\n}\r\n`;\r\n}\r\n\r\n/**\r\n * Modifies the service file to add federation support.\r\n */\r\nfunction modifyServiceFile(\r\n content: string,\r\n identityName: string,\r\n federationServiceClassName: string | null,\r\n): string {\r\n const identityClassName = `${toPascalCase(identityName)}FederationIdentity`;\r\n const identityFileName = `${identityName.toLowerCase()}-federation-identity`;\r\n let modified = content;\r\n\r\n // Add imports if not present\r\n const federationImports = [\r\n 'FederatedInventory',\r\n 'type FederationIdentity',\r\n 'type FederatedAuthenticationRequest',\r\n 'type FederatedAuthenticationResponse',\r\n 'type FederatedInventoryProxyState',\r\n 'type FederatedInventoryStateRequest',\r\n 'type FederatedInventoryTransactionRequest',\r\n ];\r\n\r\n // Check if imports from runtime already exist\r\n const runtimeImportMatch = modified.match(/from ['\"]@omen\\.foundation\\/node-microservice-runtime['\"]/);\r\n if (runtimeImportMatch) {\r\n // Add to existing import\r\n const importLine = modified.split('\\n').find((line) => line.includes('from') && line.includes('node-microservice-runtime'));\r\n if (importLine) {\r\n // Check which imports are missing\r\n const missingImports = federationImports.filter((imp) => !importLine.includes(imp.replace('type ', '')));\r\n if (missingImports.length > 0) {\r\n // Add missing imports\r\n const newImports = missingImports.join(',\\n ');\r\n modified = modified.replace(\r\n importLine,\r\n importLine.replace('}', `,\\n ${newImports}\\n}`),\r\n );\r\n }\r\n }\r\n } else {\r\n // Add new import block\r\n const newImport = `import {\\n ${federationImports.join(',\\n ')},\\n} from '@omen.foundation/node-microservice-runtime';`;\r\n // Find the last import and add after it\r\n const importLines = modified.split('\\n');\r\n let lastImportIndex = -1;\r\n for (let i = 0; i < importLines.length; i++) {\r\n if (importLines[i].trim().startsWith('import ')) {\r\n lastImportIndex = i;\r\n }\r\n }\r\n if (lastImportIndex >= 0) {\r\n importLines.splice(lastImportIndex + 1, 0, newImport);\r\n modified = importLines.join('\\n');\r\n }\r\n }\r\n\r\n // Add identity import\r\n const identityImport = `import { ${identityClassName} } from './services/${identityFileName}.js';`;\r\n if (!modified.includes(identityImport)) {\r\n const importLines = modified.split('\\n');\r\n let lastImportIndex = -1;\r\n for (let i = 0; i < importLines.length; i++) {\r\n if (importLines[i].trim().startsWith('import ')) {\r\n lastImportIndex = i;\r\n }\r\n }\r\n if (lastImportIndex >= 0) {\r\n importLines.splice(lastImportIndex + 1, 0, identityImport);\r\n modified = importLines.join('\\n');\r\n }\r\n }\r\n\r\n // Add federation service import if created\r\n if (federationServiceClassName) {\r\n const serviceImport = `import { ${federationServiceClassName} } from './services/${identityName.toLowerCase()}-federation-service.js';`;\r\n if (!modified.includes(serviceImport)) {\r\n const importLines = modified.split('\\n');\r\n let lastImportIndex = -1;\r\n for (let i = 0; i < importLines.length; i++) {\r\n if (importLines[i].trim().startsWith('import ')) {\r\n lastImportIndex = i;\r\n }\r\n }\r\n if (lastImportIndex >= 0) {\r\n importLines.splice(lastImportIndex + 1, 0, serviceImport);\r\n modified = importLines.join('\\n');\r\n }\r\n }\r\n }\r\n\r\n // Add @FederatedInventory decorator before @Microservice\r\n if (!modified.includes('@FederatedInventory')) {\r\n modified = modified.replace(\r\n /@Microservice\\(/g,\r\n `@FederatedInventory({ identity: ${identityClassName} })\\n@Microservice(`,\r\n );\r\n }\r\n\r\n // Add federation service to @ConfigureServices if created\r\n if (federationServiceClassName) {\r\n const configureServicesMatch = modified.match(/@ConfigureServices\\s+static\\s+register\\([^)]+\\):\\s*void\\s*\\{([^}]+)\\}/s);\r\n if (configureServicesMatch) {\r\n const methodBody = configureServicesMatch[1];\r\n if (!methodBody.includes(federationServiceClassName)) {\r\n modified = modified.replace(\r\n /(builder\\.addSingletonClass\\([^)]+\\);)/,\r\n `$1\\n builder.addSingletonClass(${federationServiceClassName});`,\r\n );\r\n }\r\n }\r\n }\r\n\r\n // Add federation methods if they don't exist\r\n const methods = [\r\n {\r\n name: 'Authenticate',\r\n signature: `async Authenticate(\\n _ctx: RequestContext,\\n request: FederatedAuthenticationRequest,\\n ): Promise<FederatedAuthenticationResponse>`,\r\n body: federationServiceClassName\r\n ? `{\\n const federationService = _ctx.provider.resolve(${federationServiceClassName});\\n return federationService.authenticate(request);\\n }`\r\n : `{\\n // TODO: Implement authentication logic\\n const token = request.token?.trim();\\n if (!token) {\\n return {\\n challenge: 'Provide a token representing the external account id.',\\n challenge_ttl: 300,\\n };\\n }\\n return { user_id: token };\\n }`,\r\n },\r\n {\r\n name: 'GetInventoryState',\r\n signature: `async GetInventoryState(\\n ctx: RequestContext,\\n request: FederatedInventoryStateRequest,\\n ): Promise<FederatedInventoryProxyState>`,\r\n body: federationServiceClassName\r\n ? `{\\n const federationService = ctx.provider.resolve(${federationServiceClassName});\\n return federationService.getInventoryState(ctx, request);\\n }`\r\n : `{\\n // TODO: Implement inventory state retrieval\\n return {\\n currencies: {},\\n items: {},\\n };\\n }`,\r\n },\r\n {\r\n name: 'StartInventoryTransaction',\r\n signature: `async StartInventoryTransaction(\\n ctx: RequestContext,\\n request: FederatedInventoryTransactionRequest,\\n ): Promise<FederatedInventoryProxyState>`,\r\n body: federationServiceClassName\r\n ? `{\\n const federationService = ctx.provider.resolve(${federationServiceClassName});\\n return federationService.startInventoryTransaction(ctx, request);\\n }`\r\n : `{\\n // TODO: Implement inventory transaction logic\\n return {\\n currencies: request.currencies ?? {},\\n items: {},\\n };\\n }`,\r\n },\r\n ];\r\n\r\n // Find the end of the class (before the closing brace)\r\n const classEndMatch = modified.match(/(\\n\\})/);\r\n if (classEndMatch) {\r\n const classEndIndex = modified.lastIndexOf('\\n}');\r\n const beforeEnd = modified.substring(0, classEndIndex);\r\n const afterEnd = modified.substring(classEndIndex);\r\n\r\n // Check which methods are missing\r\n const missingMethods = methods.filter((method) => !beforeEnd.includes(`async ${method.name}(`));\r\n\r\n if (missingMethods.length > 0) {\r\n const methodsCode = missingMethods\r\n .map(\r\n (method) => `\\n ${method.signature} ${method.body}\\n`,\r\n )\r\n .join('\\n');\r\n modified = beforeEnd + methodsCode + afterEnd;\r\n }\r\n }\r\n\r\n return modified;\r\n}\r\n\r\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"add-storage.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/add-storage.ts"],"names":[],"mappings":"AAAA;;GAEG;AAOH;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAqEpE"}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command handler for adding storage support to an existing microservice.
|
|
3
|
+
*/
|
|
4
|
+
import fs from 'node:fs/promises';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { toPascalCase, validateServiceName } from '../utils/name-utils.js';
|
|
7
|
+
import { promptInput, promptYesNo } from '../utils/prompt-utils.js';
|
|
8
|
+
/**
|
|
9
|
+
* Handles the add-storage command.
|
|
10
|
+
*/
|
|
11
|
+
export async function handleAddStorage(args) {
|
|
12
|
+
let storageName = args[0];
|
|
13
|
+
// Interactive prompt for storage name if not provided
|
|
14
|
+
if (!storageName) {
|
|
15
|
+
storageName = await promptInput('Enter storage name (alphanumeric and underscores only)');
|
|
16
|
+
}
|
|
17
|
+
// Validate storage name
|
|
18
|
+
if (!storageName || !validateServiceName(storageName)) {
|
|
19
|
+
throw new Error(`Invalid storage name "${storageName}". Only alphanumeric and underscore characters are allowed.`);
|
|
20
|
+
}
|
|
21
|
+
// Find service file
|
|
22
|
+
const serviceFile = await findServiceFile();
|
|
23
|
+
if (!serviceFile) {
|
|
24
|
+
throw new Error('Could not find service file. Make sure you are in a microservice directory with a service class decorated with @Microservice.');
|
|
25
|
+
}
|
|
26
|
+
const createServiceClass = await promptYesNo('Create a service class that demonstrates storage usage?', true);
|
|
27
|
+
const registerInDI = createServiceClass
|
|
28
|
+
? await promptYesNo('Register service in dependency injection?', true)
|
|
29
|
+
: false;
|
|
30
|
+
// Read existing service file
|
|
31
|
+
const serviceContent = await fs.readFile(serviceFile, 'utf8');
|
|
32
|
+
// Generate storage object class
|
|
33
|
+
const storageClassContent = generateStorageClass(storageName);
|
|
34
|
+
const storageFileName = `${storageName.toLowerCase()}-storage.ts`;
|
|
35
|
+
const storageFilePath = path.join(path.dirname(serviceFile), 'services', storageFileName);
|
|
36
|
+
await fs.mkdir(path.dirname(storageFilePath), { recursive: true });
|
|
37
|
+
await fs.writeFile(storageFilePath, storageClassContent);
|
|
38
|
+
// Generate storage service class if requested
|
|
39
|
+
let storageServiceClassName = null;
|
|
40
|
+
if (createServiceClass) {
|
|
41
|
+
const serviceClassContent = generateStorageService(storageName);
|
|
42
|
+
const serviceFileName = `${storageName.toLowerCase()}-service.ts`;
|
|
43
|
+
const serviceFilePath = path.join(path.dirname(serviceFile), 'services', serviceFileName);
|
|
44
|
+
await fs.writeFile(serviceFilePath, serviceClassContent);
|
|
45
|
+
storageServiceClassName = `${toPascalCase(storageName)}Service`;
|
|
46
|
+
}
|
|
47
|
+
// Modify service file if registration requested
|
|
48
|
+
if (registerInDI && storageServiceClassName) {
|
|
49
|
+
const modifiedContent = modifyServiceFile(serviceContent, storageServiceClassName);
|
|
50
|
+
await fs.writeFile(serviceFile, modifiedContent);
|
|
51
|
+
}
|
|
52
|
+
console.log(`\n✅ Successfully added storage support "${storageName}"!`);
|
|
53
|
+
console.log(`\nCreated files:`);
|
|
54
|
+
console.log(` - ${storageFilePath}`);
|
|
55
|
+
if (createServiceClass) {
|
|
56
|
+
console.log(` - ${path.join(path.dirname(serviceFile), 'services', `${storageName.toLowerCase()}-service.ts`)}`);
|
|
57
|
+
}
|
|
58
|
+
if (registerInDI) {
|
|
59
|
+
console.log(`\nModified:`);
|
|
60
|
+
console.log(` - ${serviceFile}`);
|
|
61
|
+
}
|
|
62
|
+
console.log(`\nNext steps:`);
|
|
63
|
+
console.log(` 1. Use the storage class with ctx.services.storage.getDatabaseFor()`);
|
|
64
|
+
if (createServiceClass) {
|
|
65
|
+
console.log(` 2. Implement the methods in the storage service class`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Finds the service file in the current directory.
|
|
70
|
+
*/
|
|
71
|
+
async function findServiceFile() {
|
|
72
|
+
const cwd = process.cwd();
|
|
73
|
+
const srcDir = path.join(cwd, 'src');
|
|
74
|
+
try {
|
|
75
|
+
const entries = await fs.readdir(srcDir, { withFileTypes: true });
|
|
76
|
+
const serviceFiles = [];
|
|
77
|
+
for (const entry of entries) {
|
|
78
|
+
if (entry.isFile() && entry.name.endsWith('Service.ts')) {
|
|
79
|
+
serviceFiles.push(path.join(srcDir, entry.name));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (serviceFiles.length === 0) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
// Check if any file contains @Microservice decorator
|
|
86
|
+
for (const file of serviceFiles) {
|
|
87
|
+
const content = await fs.readFile(file, 'utf8');
|
|
88
|
+
if (content.includes('@Microservice')) {
|
|
89
|
+
return file;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return serviceFiles[0] || null;
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Generates the storage object class.
|
|
100
|
+
*/
|
|
101
|
+
function generateStorageClass(storageName) {
|
|
102
|
+
const className = `${toPascalCase(storageName)}Storage`;
|
|
103
|
+
return `import { StorageObject } from '@omen.foundation/node-microservice-runtime';
|
|
104
|
+
|
|
105
|
+
@StorageObject('${toPascalCase(storageName)}')
|
|
106
|
+
export class ${className} {}
|
|
107
|
+
`;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Generates the storage service class.
|
|
111
|
+
*/
|
|
112
|
+
function generateStorageService(storageName) {
|
|
113
|
+
const storageClassName = `${toPascalCase(storageName)}Storage`;
|
|
114
|
+
const serviceClassName = `${toPascalCase(storageName)}Service`;
|
|
115
|
+
const dataInterfaceName = `${toPascalCase(storageName)}Data`;
|
|
116
|
+
return `import type { RequestContext } from '@omen.foundation/node-microservice-runtime';
|
|
117
|
+
import { ${storageClassName} } from './${storageName.toLowerCase()}-storage.js';
|
|
118
|
+
|
|
119
|
+
export interface ${dataInterfaceName} {
|
|
120
|
+
userId: number;
|
|
121
|
+
data: Record<string, unknown>;
|
|
122
|
+
updatedAt: string;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export class ${serviceClassName} {
|
|
126
|
+
async getData(ctx: RequestContext, userId: number): Promise<${dataInterfaceName} | null> {
|
|
127
|
+
ctx.services.requireScopes('server');
|
|
128
|
+
const db = await ctx.services.storage.getDatabaseFor(${storageClassName}, { useCache: true });
|
|
129
|
+
const collection = db.collection<${dataInterfaceName}>('${storageName.toLowerCase()}Data');
|
|
130
|
+
return collection.findOne({ userId });
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async setData(
|
|
134
|
+
ctx: RequestContext,
|
|
135
|
+
userId: number,
|
|
136
|
+
data: Record<string, unknown>,
|
|
137
|
+
): Promise<${dataInterfaceName}> {
|
|
138
|
+
ctx.services.requireScopes('server');
|
|
139
|
+
const db = await ctx.services.storage.getDatabaseFor(${storageClassName}, { useCache: true });
|
|
140
|
+
const collection = db.collection<${dataInterfaceName}>('${storageName.toLowerCase()}Data');
|
|
141
|
+
const updatedAt = new Date().toISOString();
|
|
142
|
+
await collection.updateOne(
|
|
143
|
+
{ userId },
|
|
144
|
+
{ $set: { userId, data, updatedAt } },
|
|
145
|
+
{ upsert: true },
|
|
146
|
+
);
|
|
147
|
+
return { userId, data, updatedAt };
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
`;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Modifies the service file to register the storage service in DI.
|
|
154
|
+
*/
|
|
155
|
+
function modifyServiceFile(content, serviceClassName) {
|
|
156
|
+
let modified = content;
|
|
157
|
+
// Add import if not present
|
|
158
|
+
const serviceFileName = serviceClassName.replace('Service', '').toLowerCase();
|
|
159
|
+
const importPath = `./services/${serviceFileName}-service.js`;
|
|
160
|
+
const importStatement = `import { ${serviceClassName} } from '${importPath}';`;
|
|
161
|
+
if (!modified.includes(importStatement)) {
|
|
162
|
+
const importLines = modified.split('\n');
|
|
163
|
+
let lastImportIndex = -1;
|
|
164
|
+
for (let i = 0; i < importLines.length; i++) {
|
|
165
|
+
if (importLines[i].trim().startsWith('import ')) {
|
|
166
|
+
lastImportIndex = i;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (lastImportIndex >= 0) {
|
|
170
|
+
importLines.splice(lastImportIndex + 1, 0, importStatement);
|
|
171
|
+
modified = importLines.join('\n');
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// Add to @ConfigureServices if not already present
|
|
175
|
+
if (!modified.includes(`builder.addSingletonClass(${serviceClassName})`)) {
|
|
176
|
+
modified = modified.replace(/(builder\.addSingletonClass\([^)]+\);)/, `$1\n builder.addSingletonClass(${serviceClassName});`);
|
|
177
|
+
}
|
|
178
|
+
return modified;
|
|
179
|
+
}
|
|
180
|
+
//# sourceMappingURL=add-storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"add-storage.js","sourceRoot":"","sources":["../../../src/cli/commands/add-storage.ts"],"names":[],"mappings":"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","sourcesContent":["/**\r\n * Command handler for adding storage support to an existing microservice.\r\n */\r\n\r\nimport fs from 'node:fs/promises';\r\nimport path from 'node:path';\r\nimport { toPascalCase, validateServiceName } from '../utils/name-utils.js';\r\nimport { promptInput, promptYesNo } from '../utils/prompt-utils.js';\r\n\r\n/**\r\n * Handles the add-storage command.\r\n */\r\nexport async function handleAddStorage(args: string[]): Promise<void> {\r\n let storageName = args[0];\r\n\r\n // Interactive prompt for storage name if not provided\r\n if (!storageName) {\r\n storageName = await promptInput('Enter storage name (alphanumeric and underscores only)');\r\n }\r\n\r\n // Validate storage name\r\n if (!storageName || !validateServiceName(storageName)) {\r\n throw new Error(\r\n `Invalid storage name \"${storageName}\". Only alphanumeric and underscore characters are allowed.`,\r\n );\r\n }\r\n\r\n // Find service file\r\n const serviceFile = await findServiceFile();\r\n if (!serviceFile) {\r\n throw new Error(\r\n 'Could not find service file. Make sure you are in a microservice directory with a service class decorated with @Microservice.',\r\n );\r\n }\r\n\r\n const createServiceClass = await promptYesNo('Create a service class that demonstrates storage usage?', true);\r\n const registerInDI = createServiceClass\r\n ? await promptYesNo('Register service in dependency injection?', true)\r\n : false;\r\n\r\n // Read existing service file\r\n const serviceContent = await fs.readFile(serviceFile, 'utf8');\r\n\r\n // Generate storage object class\r\n const storageClassContent = generateStorageClass(storageName);\r\n const storageFileName = `${storageName.toLowerCase()}-storage.ts`;\r\n const storageFilePath = path.join(path.dirname(serviceFile), 'services', storageFileName);\r\n await fs.mkdir(path.dirname(storageFilePath), { recursive: true });\r\n await fs.writeFile(storageFilePath, storageClassContent);\r\n\r\n // Generate storage service class if requested\r\n let storageServiceClassName: string | null = null;\r\n if (createServiceClass) {\r\n const serviceClassContent = generateStorageService(storageName);\r\n const serviceFileName = `${storageName.toLowerCase()}-service.ts`;\r\n const serviceFilePath = path.join(path.dirname(serviceFile), 'services', serviceFileName);\r\n await fs.writeFile(serviceFilePath, serviceClassContent);\r\n storageServiceClassName = `${toPascalCase(storageName)}Service`;\r\n }\r\n\r\n // Modify service file if registration requested\r\n if (registerInDI && storageServiceClassName) {\r\n const modifiedContent = modifyServiceFile(serviceContent, storageServiceClassName);\r\n await fs.writeFile(serviceFile, modifiedContent);\r\n }\r\n\r\n console.log(`\\n✅ Successfully added storage support \"${storageName}\"!`);\r\n console.log(`\\nCreated files:`);\r\n console.log(` - ${storageFilePath}`);\r\n if (createServiceClass) {\r\n console.log(` - ${path.join(path.dirname(serviceFile), 'services', `${storageName.toLowerCase()}-service.ts`)}`);\r\n }\r\n if (registerInDI) {\r\n console.log(`\\nModified:`);\r\n console.log(` - ${serviceFile}`);\r\n }\r\n console.log(`\\nNext steps:`);\r\n console.log(` 1. Use the storage class with ctx.services.storage.getDatabaseFor()`);\r\n if (createServiceClass) {\r\n console.log(` 2. Implement the methods in the storage service class`);\r\n }\r\n}\r\n\r\n/**\r\n * Finds the service file in the current directory.\r\n */\r\nasync function findServiceFile(): Promise<string | null> {\r\n const cwd = process.cwd();\r\n const srcDir = path.join(cwd, 'src');\r\n\r\n try {\r\n const entries = await fs.readdir(srcDir, { withFileTypes: true });\r\n const serviceFiles: string[] = [];\r\n\r\n for (const entry of entries) {\r\n if (entry.isFile() && entry.name.endsWith('Service.ts')) {\r\n serviceFiles.push(path.join(srcDir, entry.name));\r\n }\r\n }\r\n\r\n if (serviceFiles.length === 0) {\r\n return null;\r\n }\r\n\r\n // Check if any file contains @Microservice decorator\r\n for (const file of serviceFiles) {\r\n const content = await fs.readFile(file, 'utf8');\r\n if (content.includes('@Microservice')) {\r\n return file;\r\n }\r\n }\r\n\r\n return serviceFiles[0] || null;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\n/**\r\n * Generates the storage object class.\r\n */\r\nfunction generateStorageClass(storageName: string): string {\r\n const className = `${toPascalCase(storageName)}Storage`;\r\n return `import { StorageObject } from '@omen.foundation/node-microservice-runtime';\r\n\r\n@StorageObject('${toPascalCase(storageName)}')\r\nexport class ${className} {}\r\n`;\r\n}\r\n\r\n/**\r\n * Generates the storage service class.\r\n */\r\nfunction generateStorageService(storageName: string): string {\r\n const storageClassName = `${toPascalCase(storageName)}Storage`;\r\n const serviceClassName = `${toPascalCase(storageName)}Service`;\r\n const dataInterfaceName = `${toPascalCase(storageName)}Data`;\r\n\r\n return `import type { RequestContext } from '@omen.foundation/node-microservice-runtime';\r\nimport { ${storageClassName} } from './${storageName.toLowerCase()}-storage.js';\r\n\r\nexport interface ${dataInterfaceName} {\r\n userId: number;\r\n data: Record<string, unknown>;\r\n updatedAt: string;\r\n}\r\n\r\nexport class ${serviceClassName} {\r\n async getData(ctx: RequestContext, userId: number): Promise<${dataInterfaceName} | null> {\r\n ctx.services.requireScopes('server');\r\n const db = await ctx.services.storage.getDatabaseFor(${storageClassName}, { useCache: true });\r\n const collection = db.collection<${dataInterfaceName}>('${storageName.toLowerCase()}Data');\r\n return collection.findOne({ userId });\r\n }\r\n\r\n async setData(\r\n ctx: RequestContext,\r\n userId: number,\r\n data: Record<string, unknown>,\r\n ): Promise<${dataInterfaceName}> {\r\n ctx.services.requireScopes('server');\r\n const db = await ctx.services.storage.getDatabaseFor(${storageClassName}, { useCache: true });\r\n const collection = db.collection<${dataInterfaceName}>('${storageName.toLowerCase()}Data');\r\n const updatedAt = new Date().toISOString();\r\n await collection.updateOne(\r\n { userId },\r\n { $set: { userId, data, updatedAt } },\r\n { upsert: true },\r\n );\r\n return { userId, data, updatedAt };\r\n }\r\n}\r\n`;\r\n}\r\n\r\n/**\r\n * Modifies the service file to register the storage service in DI.\r\n */\r\nfunction modifyServiceFile(content: string, serviceClassName: string): string {\r\n let modified = content;\r\n\r\n // Add import if not present\r\n const serviceFileName = serviceClassName.replace('Service', '').toLowerCase();\r\n const importPath = `./services/${serviceFileName}-service.js`;\r\n const importStatement = `import { ${serviceClassName} } from '${importPath}';`;\r\n\r\n if (!modified.includes(importStatement)) {\r\n const importLines = modified.split('\\n');\r\n let lastImportIndex = -1;\r\n for (let i = 0; i < importLines.length; i++) {\r\n if (importLines[i].trim().startsWith('import ')) {\r\n lastImportIndex = i;\r\n }\r\n }\r\n if (lastImportIndex >= 0) {\r\n importLines.splice(lastImportIndex + 1, 0, importStatement);\r\n modified = importLines.join('\\n');\r\n }\r\n }\r\n\r\n // Add to @ConfigureServices if not already present\r\n if (!modified.includes(`builder.addSingletonClass(${serviceClassName})`)) {\r\n modified = modified.replace(\r\n /(builder\\.addSingletonClass\\([^)]+\\);)/,\r\n `$1\\n builder.addSingletonClass(${serviceClassName});`,\r\n );\r\n }\r\n\r\n return modified;\r\n}\r\n\r\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/scaffold.ts"],"names":[],"mappings":"AAAA;;GAEG;AAeH;;GAEG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAiElE"}
|