@specverse/engines 4.1.13 → 4.1.15
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/assets/prompts/core/standard/v9/behavior.prompt.yaml +126 -0
- package/dist/ai/behavior-ai-service.d.ts +65 -0
- package/dist/ai/behavior-ai-service.d.ts.map +1 -0
- package/dist/ai/behavior-ai-service.js +205 -0
- package/dist/ai/behavior-ai-service.js.map +1 -0
- package/dist/ai/index.d.ts +27 -0
- package/dist/ai/index.d.ts.map +1 -1
- package/dist/ai/index.js +30 -0
- package/dist/ai/index.js.map +1 -1
- package/dist/ai/prompt-loader.js +2 -2
- package/dist/inference/quint-transpiler.d.ts.map +1 -1
- package/dist/inference/quint-transpiler.js +204 -4
- package/dist/inference/quint-transpiler.js.map +1 -1
- package/dist/libs/instance-factories/applications/templates/generic/backend-package-json-generator.js +4 -1
- package/dist/libs/instance-factories/applications/templates/generic/backend-tsconfig-generator.js +2 -2
- package/dist/libs/instance-factories/applications/templates/react/runtime-package-json-generator.js +1 -0
- package/dist/libs/instance-factories/cli/templates/commander/command-generator.js +97 -22
- package/dist/libs/instance-factories/communication/templates/eventemitter/bus-generator.js +31 -31
- package/dist/libs/instance-factories/communication/templates/eventemitter/types-generator.js +79 -0
- package/dist/libs/instance-factories/communication/templates/eventemitter/websocket-bridge-generator.js +96 -0
- package/dist/libs/instance-factories/controllers/templates/fastify/routes-generator.js +45 -9
- package/dist/libs/instance-factories/controllers/templates/fastify/server-generator.js +20 -2
- package/dist/libs/instance-factories/scaffolding/templates/generic/tsconfig-generator.js +10 -2
- package/dist/libs/instance-factories/services/templates/prisma/ai-behaviors-generator.js +249 -0
- package/dist/libs/instance-factories/services/templates/prisma/behavior-generator.js +72 -45
- package/dist/libs/instance-factories/services/templates/prisma/controller-generator.js +61 -54
- package/dist/libs/instance-factories/services/templates/prisma/service-generator.js +31 -10
- package/dist/libs/instance-factories/services/templates/prisma/step-conventions.js +101 -84
- package/dist/libs/instance-factories/views/templates/react/components-generator.js +40 -10
- package/dist/realize/index.d.ts.map +1 -1
- package/dist/realize/index.js +192 -23
- package/dist/realize/index.js.map +1 -1
- package/libs/instance-factories/applications/templates/generic/backend-package-json-generator.ts +4 -1
- package/libs/instance-factories/applications/templates/generic/backend-tsconfig-generator.ts +2 -2
- package/libs/instance-factories/applications/templates/react/runtime-package-json-generator.ts +6 -1
- package/libs/instance-factories/cli/templates/commander/command-generator.ts +115 -22
- package/libs/instance-factories/communication/event-emitter.yaml +16 -12
- package/libs/instance-factories/communication/templates/eventemitter/bus-generator.ts +33 -36
- package/libs/instance-factories/communication/templates/eventemitter/types-generator.ts +95 -0
- package/libs/instance-factories/communication/templates/eventemitter/websocket-bridge-generator.ts +105 -0
- package/libs/instance-factories/controllers/templates/fastify/routes-generator.ts +57 -11
- package/libs/instance-factories/controllers/templates/fastify/server-generator.ts +23 -2
- package/libs/instance-factories/scaffolding/templates/generic/tsconfig-generator.ts +23 -2
- package/libs/instance-factories/services/templates/prisma/ai-behaviors-generator.ts +376 -0
- package/libs/instance-factories/services/templates/prisma/behavior-generator.ts +116 -45
- package/libs/instance-factories/services/templates/prisma/controller-generator.ts +83 -59
- package/libs/instance-factories/services/templates/prisma/service-generator.ts +40 -10
- package/libs/instance-factories/services/templates/prisma/step-conventions.ts +169 -85
- package/libs/instance-factories/views/templates/react/components-generator.ts +50 -10
- package/package.json +1 -1
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/controllers/MCPServerController.js +0 -232
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/events/EventEmitter.js +0 -49
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/index.js +0 -18
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/interfaces/ResourceProvider.js +0 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/models/LibrarySuggestion.js +0 -97
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/models/SpecVerseResource.js +0 -64
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/server/mcp-server.js +0 -182
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/CLIProxyService.js +0 -1210
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/EmbeddedResourcesAdapter.js +0 -172
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/EntityModuleService.js +0 -240
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/HybridResourcesProvider.js +0 -147
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/LibraryToolsService.js +0 -281
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/OrchestratorBridge.js +0 -409
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/OrchestratorToolsService.js +0 -414
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/PromptToolsService.js +0 -467
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/ResourcesProviderService.js +0 -135
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/types/index.js +0 -0
- package/dist/libs/instance-factories/tools/templates/vscode/static/extension.js +0 -965
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { buildTransitionMap, isAutoField } from "@specverse/types/spec-rules";
|
|
2
|
+
import { generateBehaviorWithHelpers } from "./behavior-generator.js";
|
|
2
3
|
function generatePrismaController(context) {
|
|
3
4
|
const { controller, model, spec, models: allModels } = context;
|
|
4
5
|
if (!controller) {
|
|
@@ -12,10 +13,12 @@ function generatePrismaController(context) {
|
|
|
12
13
|
const rawModelVar = modelName.charAt(0).toLowerCase() + modelName.slice(1);
|
|
13
14
|
const RESERVED_WORDS = /* @__PURE__ */ new Set(["import", "export", "default", "class", "function", "return", "delete", "new", "this", "switch", "case", "break", "continue", "for", "while", "do", "if", "else", "try", "catch", "finally", "throw", "typeof", "instanceof", "in", "of", "let", "const", "var", "void", "with", "yield", "async", "await", "enum", "implements", "interface", "package", "private", "protected", "public", "static", "super", "extends"]);
|
|
14
15
|
const modelVar = RESERVED_WORDS.has(rawModelVar) ? `${rawModelVar}Item` : rawModelVar;
|
|
16
|
+
const prismaDelegate = RESERVED_WORDS.has(rawModelVar) ? `prisma['${rawModelVar}']` : `prisma.${rawModelVar}`;
|
|
15
17
|
const curedOps = controller.cured || {};
|
|
16
18
|
const idAttr = (Array.isArray(model.attributes) ? model.attributes : Object.values(model.attributes || {})).find((a) => a.name === "id");
|
|
17
19
|
const idType = idAttr?.type || "UUID";
|
|
18
20
|
const needsIntParse = idType === "Integer" || idType === "Int" || idType === "Number";
|
|
21
|
+
const customActions = generateCustomActions(controller, modelName, modelVar);
|
|
19
22
|
return `/**
|
|
20
23
|
* ${controllerName}
|
|
21
24
|
* Model-specific business logic for ${modelName}
|
|
@@ -23,7 +26,8 @@ function generatePrismaController(context) {
|
|
|
23
26
|
*/
|
|
24
27
|
|
|
25
28
|
import { PrismaClient } from '@prisma/client';
|
|
26
|
-
${hasEventPublishing(curedOps, controller) ? `import { eventBus
|
|
29
|
+
${hasEventPublishing(curedOps, controller) ? `import { eventBus } from '../events/eventBus.js';` : ""}
|
|
30
|
+
${customActions.needsAiBehaviors ? `import * as aiBehaviors from '../behaviors/${modelName}Controller.ai.js';` : ""}
|
|
27
31
|
|
|
28
32
|
const prisma = new PrismaClient();
|
|
29
33
|
|
|
@@ -37,12 +41,12 @@ function parseId(id: string): ${needsIntParse ? "number" : "string"} {
|
|
|
37
41
|
*/
|
|
38
42
|
export class ${controllerName} {
|
|
39
43
|
${generateValidateMethod(model, modelName)}
|
|
40
|
-
${curedOps.create ? generateCreateMethod(model, modelName, modelVar, controller, allModels) : ""}
|
|
41
|
-
${curedOps.retrieve ? generateRetrieveMethod(model, modelName, modelVar) : ""}
|
|
42
|
-
${curedOps.update ? generateUpdateMethod(model, modelName, modelVar, controller, allModels) : ""}
|
|
43
|
-
${curedOps.evolve ? generateEvolveMethod(model, modelName, modelVar, controller) : ""}
|
|
44
|
-
${curedOps.delete ? generateDeleteMethod(model, modelName, modelVar, controller) : ""}
|
|
45
|
-
${
|
|
44
|
+
${curedOps.create ? generateCreateMethod(model, modelName, modelVar, prismaDelegate, controller, allModels) : ""}
|
|
45
|
+
${curedOps.retrieve ? generateRetrieveMethod(model, modelName, modelVar, prismaDelegate) : ""}
|
|
46
|
+
${curedOps.update ? generateUpdateMethod(model, modelName, modelVar, prismaDelegate, controller, allModels) : ""}
|
|
47
|
+
${curedOps.evolve ? generateEvolveMethod(model, modelName, modelVar, prismaDelegate, controller) : ""}
|
|
48
|
+
${curedOps.delete ? generateDeleteMethod(model, modelName, modelVar, prismaDelegate, controller) : ""}
|
|
49
|
+
${customActions.code}
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
// Export singleton instance
|
|
@@ -113,9 +117,7 @@ function generateValidationLogic(model, dataParam = "_data", contextParam = "_co
|
|
|
113
117
|
});
|
|
114
118
|
return validations.join("\n") || "// No validation rules defined";
|
|
115
119
|
}
|
|
116
|
-
function generateCreateMethod(model, modelName, modelVar, controller, allModels) {
|
|
117
|
-
const hasEvents = controller.publishes && Array.isArray(controller.publishes);
|
|
118
|
-
const createEvent = hasEvents ? controller.publishes.find((e) => e.includes("Created")) : null;
|
|
120
|
+
function generateCreateMethod(model, modelName, modelVar, prismaDelegate, controller, allModels) {
|
|
119
121
|
return `
|
|
120
122
|
/**
|
|
121
123
|
* Create a new ${modelName}
|
|
@@ -132,29 +134,24 @@ function generateCreateMethod(model, modelName, modelVar, controller, allModels)
|
|
|
132
134
|
${generateFKTransform(model, "prismaData", allModels)}
|
|
133
135
|
|
|
134
136
|
// Create record
|
|
135
|
-
const ${modelVar} = await
|
|
137
|
+
const ${modelVar} = await ${prismaDelegate}.create({
|
|
136
138
|
data: prismaData${generateIncludeRelationships(model)}
|
|
137
139
|
});
|
|
138
140
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
eventBus.publish(EventName.${createEvent}, {
|
|
142
|
-
${modelVar},
|
|
143
|
-
timestamp: new Date().toISOString()
|
|
144
|
-
});
|
|
145
|
-
` : ""}
|
|
141
|
+
// Publish CURED event
|
|
142
|
+
await eventBus.publish('${modelName}Created', { ...${modelVar}, timestamp: new Date().toISOString() } as any);
|
|
146
143
|
|
|
147
144
|
return ${modelVar};
|
|
148
145
|
}
|
|
149
146
|
`;
|
|
150
147
|
}
|
|
151
|
-
function generateRetrieveMethod(model, modelName, modelVar) {
|
|
148
|
+
function generateRetrieveMethod(model, modelName, modelVar, prismaDelegate) {
|
|
152
149
|
return `
|
|
153
150
|
/**
|
|
154
151
|
* Retrieve ${modelName} by ID
|
|
155
152
|
*/
|
|
156
153
|
public async retrieve(id: string): Promise<any> {
|
|
157
|
-
const ${modelVar} = await
|
|
154
|
+
const ${modelVar} = await ${prismaDelegate}.findUnique({
|
|
158
155
|
where: { id: parseId(id) }${generateIncludeRelationships(model)}
|
|
159
156
|
});
|
|
160
157
|
|
|
@@ -169,16 +166,14 @@ function generateRetrieveMethod(model, modelName, modelVar) {
|
|
|
169
166
|
* Retrieve all ${modelName}s
|
|
170
167
|
*/
|
|
171
168
|
public async retrieveAll(options: { skip?: number; take?: number } = {}): Promise<any[]> {
|
|
172
|
-
return await
|
|
169
|
+
return await ${prismaDelegate}.findMany({
|
|
173
170
|
skip: options.skip,
|
|
174
171
|
take: options.take${generateIncludeRelationships(model)}
|
|
175
172
|
});
|
|
176
173
|
}
|
|
177
174
|
`;
|
|
178
175
|
}
|
|
179
|
-
function generateUpdateMethod(model, modelName, modelVar, controller, allModels) {
|
|
180
|
-
const hasEvents = controller.publishes && Array.isArray(controller.publishes);
|
|
181
|
-
const updateEvent = hasEvents ? controller.publishes.find((e) => e.includes("Updated")) : null;
|
|
176
|
+
function generateUpdateMethod(model, modelName, modelVar, prismaDelegate, controller, allModels) {
|
|
182
177
|
return `
|
|
183
178
|
/**
|
|
184
179
|
* Update ${modelName}
|
|
@@ -203,24 +198,19 @@ function generateUpdateMethod(model, modelName, modelVar, controller, allModels)
|
|
|
203
198
|
${generateFKTransform(model, "updateData", allModels)}
|
|
204
199
|
|
|
205
200
|
// Update record
|
|
206
|
-
const ${modelVar} = await
|
|
201
|
+
const ${modelVar} = await ${prismaDelegate}.update({
|
|
207
202
|
where: { id: parseId(id) },
|
|
208
203
|
data: updateData${generateIncludeRelationships(model)}
|
|
209
204
|
});
|
|
210
205
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
eventBus.publish(EventName.${updateEvent}, {
|
|
214
|
-
${modelVar},
|
|
215
|
-
timestamp: new Date().toISOString()
|
|
216
|
-
});
|
|
217
|
-
` : ""}
|
|
206
|
+
// Publish CURED event
|
|
207
|
+
await eventBus.publish('${modelName}Updated', { ...${modelVar}, timestamp: new Date().toISOString() } as any);
|
|
218
208
|
|
|
219
209
|
return ${modelVar};
|
|
220
210
|
}
|
|
221
211
|
`;
|
|
222
212
|
}
|
|
223
|
-
function generateEvolveMethod(model, modelName, modelVar, controller) {
|
|
213
|
+
function generateEvolveMethod(model, modelName, modelVar, prismaDelegate, controller) {
|
|
224
214
|
const lifecycles = Array.isArray(model.lifecycles) ? model.lifecycles : model.lifecycles ? Object.entries(model.lifecycles).map(([name, lc]) => ({ name, ...lc })) : [];
|
|
225
215
|
const lifecycle = lifecycles[0];
|
|
226
216
|
const lifecycleName = lifecycle?.name || "status";
|
|
@@ -239,7 +229,7 @@ function generateEvolveMethod(model, modelName, modelVar, controller) {
|
|
|
239
229
|
}
|
|
240
230
|
|
|
241
231
|
// Get current record to check lifecycle state
|
|
242
|
-
const current = await
|
|
232
|
+
const current = await ${prismaDelegate}.findUnique({ where: { id: parseId(id) } });
|
|
243
233
|
if (!current) {
|
|
244
234
|
throw new Error('${modelName} not found');
|
|
245
235
|
}
|
|
@@ -258,61 +248,78 @@ function generateEvolveMethod(model, modelName, modelVar, controller) {
|
|
|
258
248
|
` : ""}
|
|
259
249
|
|
|
260
250
|
// Update record
|
|
261
|
-
const ${modelVar} = await
|
|
251
|
+
const ${modelVar} = await ${prismaDelegate}.update({
|
|
262
252
|
where: { id: parseId(id) },
|
|
263
253
|
data${generateIncludeRelationships(model)}
|
|
264
254
|
});
|
|
265
255
|
|
|
256
|
+
// Publish CURED event
|
|
257
|
+
await eventBus.publish('${modelName}Evolved', { ...${modelVar}, timestamp: new Date().toISOString() } as any);
|
|
258
|
+
|
|
266
259
|
return ${modelVar};
|
|
267
260
|
}
|
|
268
261
|
`;
|
|
269
262
|
}
|
|
270
|
-
function generateDeleteMethod(model, modelName, modelVar, controller) {
|
|
271
|
-
const hasEvents = controller.publishes && Array.isArray(controller.publishes);
|
|
272
|
-
const deleteEvent = hasEvents ? controller.publishes.find((e) => e.includes("Deleted")) : null;
|
|
263
|
+
function generateDeleteMethod(model, modelName, modelVar, prismaDelegate, controller) {
|
|
273
264
|
return `
|
|
274
265
|
/**
|
|
275
266
|
* Delete ${modelName}
|
|
276
267
|
*/
|
|
277
268
|
public async delete(id: string): Promise<void> {
|
|
278
|
-
${deleteEvent ? `
|
|
279
269
|
// Get record before deletion for event
|
|
280
|
-
const ${modelVar} = await
|
|
281
|
-
` : ""}
|
|
270
|
+
const ${modelVar} = await ${prismaDelegate}.findUnique({ where: { id: parseId(id) } });
|
|
282
271
|
|
|
283
|
-
await
|
|
272
|
+
await ${prismaDelegate}.delete({
|
|
284
273
|
where: { id: parseId(id) }
|
|
285
274
|
});
|
|
286
275
|
|
|
287
|
-
|
|
288
|
-
// Publish event
|
|
276
|
+
// Publish CURED event
|
|
289
277
|
if (${modelVar}) {
|
|
290
|
-
eventBus.publish(
|
|
291
|
-
${modelVar},
|
|
292
|
-
timestamp: new Date().toISOString()
|
|
293
|
-
});
|
|
278
|
+
await eventBus.publish('${modelName}Deleted', { ...${modelVar}, timestamp: new Date().toISOString() } as any);
|
|
294
279
|
}
|
|
295
|
-
` : ""}
|
|
296
280
|
}
|
|
297
281
|
`;
|
|
298
282
|
}
|
|
299
283
|
function generateCustomActions(controller, modelName, modelVar) {
|
|
300
284
|
if (!controller.actions || Object.keys(controller.actions).length === 0) {
|
|
301
|
-
return "";
|
|
285
|
+
return { code: "", unmatchedSteps: [], needsAiBehaviors: false };
|
|
302
286
|
}
|
|
303
287
|
const actions = [];
|
|
288
|
+
const allUnmatchedSteps = [];
|
|
304
289
|
Object.entries(controller.actions).forEach(([actionName, action]) => {
|
|
290
|
+
const behavior = {
|
|
291
|
+
preconditions: action.requires || action.preconditions || [],
|
|
292
|
+
steps: action.steps || [],
|
|
293
|
+
postconditions: action.ensures || action.postconditions || [],
|
|
294
|
+
sideEffects: action.publishes || action.events || [],
|
|
295
|
+
transactional: action.transactional
|
|
296
|
+
};
|
|
297
|
+
const ctx = {
|
|
298
|
+
modelName,
|
|
299
|
+
serviceName: `${modelName}Controller`,
|
|
300
|
+
operationName: actionName,
|
|
301
|
+
prismaModel: modelVar,
|
|
302
|
+
parameterNames: Object.keys(action.parameters || {})
|
|
303
|
+
};
|
|
304
|
+
const result = generateBehaviorWithHelpers(behavior, {}, ctx);
|
|
305
|
+
allUnmatchedSteps.push(...result.unmatchedSteps);
|
|
305
306
|
actions.push(`
|
|
306
307
|
/**
|
|
307
308
|
* ${actionName}
|
|
308
309
|
* ${action.description || ""}
|
|
309
310
|
*/
|
|
310
311
|
public async ${actionName}(${generateActionParams(action)}): Promise<any> {
|
|
311
|
-
|
|
312
|
-
throw new Error('${actionName} not implemented');
|
|
312
|
+
${result.body}
|
|
313
313
|
}`);
|
|
314
|
+
if (result.helperMethods.length > 0) {
|
|
315
|
+
actions.push(...result.helperMethods);
|
|
316
|
+
}
|
|
314
317
|
});
|
|
315
|
-
return
|
|
318
|
+
return {
|
|
319
|
+
code: actions.join("\n"),
|
|
320
|
+
unmatchedSteps: allUnmatchedSteps,
|
|
321
|
+
needsAiBehaviors: allUnmatchedSteps.length > 0
|
|
322
|
+
};
|
|
316
323
|
}
|
|
317
324
|
function generateActionParams(action) {
|
|
318
325
|
if (!action.parameters || Object.keys(action.parameters).length === 0) {
|
|
@@ -391,7 +398,7 @@ ${includes}
|
|
|
391
398
|
}`;
|
|
392
399
|
}
|
|
393
400
|
function hasEventPublishing(curedOps, controller) {
|
|
394
|
-
return
|
|
401
|
+
return true;
|
|
395
402
|
}
|
|
396
403
|
export {
|
|
397
404
|
generatePrismaController as default
|
|
@@ -6,23 +6,27 @@ function generatePrismaService(context) {
|
|
|
6
6
|
}
|
|
7
7
|
const serviceName = service.name;
|
|
8
8
|
const hasEvents = service.publishes && service.publishes.length > 0 || service.subscribes && service.subscribes.length > 0;
|
|
9
|
+
const operationsCode = generateOperationsWithHelpers(service);
|
|
10
|
+
const hasAiBehaviors = /\baiBehaviors\./.test(operationsCode);
|
|
11
|
+
const usesPrisma = /\bprisma\b/.test(operationsCode);
|
|
9
12
|
return `/**
|
|
10
13
|
* ${serviceName}
|
|
11
14
|
* Abstract business logic service
|
|
12
15
|
* ${service.description || ""}
|
|
13
16
|
*/
|
|
14
|
-
|
|
15
|
-
import { PrismaClient } from '@prisma/client'
|
|
16
|
-
${hasEvents ? `import { eventBus
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
${usesPrisma ? `
|
|
18
|
+
import { PrismaClient } from '@prisma/client';` : ""}
|
|
19
|
+
${hasEvents ? `import { eventBus } from '../events/eventBus.js';` : ""}
|
|
20
|
+
${hasAiBehaviors ? `import * as aiBehaviors from '../behaviors/${serviceName}.ai.js';` : ""}
|
|
21
|
+
${usesPrisma ? `
|
|
22
|
+
const prisma = new PrismaClient();` : ""}
|
|
19
23
|
|
|
20
24
|
/**
|
|
21
25
|
* ${serviceName} class
|
|
22
26
|
*/
|
|
23
27
|
export class ${serviceName} {
|
|
24
28
|
${generateConstructor(service)}
|
|
25
|
-
${
|
|
29
|
+
${operationsCode}
|
|
26
30
|
${generateEventSubscriptions(service)}
|
|
27
31
|
}
|
|
28
32
|
|
|
@@ -97,8 +101,10 @@ function generateOperations(service) {
|
|
|
97
101
|
return operations.join("\n");
|
|
98
102
|
}
|
|
99
103
|
function generateOperation(operationName, operation, service) {
|
|
100
|
-
const
|
|
104
|
+
const rawParams = generateOperationParams(operation);
|
|
101
105
|
const hasPublish = service.publishes && service.publishes.length > 0;
|
|
106
|
+
const body = generateOperationLogic(operation, service);
|
|
107
|
+
const params = renameUnusedParams(rawParams, body);
|
|
102
108
|
return `
|
|
103
109
|
/**
|
|
104
110
|
* ${operationName}
|
|
@@ -106,7 +112,7 @@ function generateOperation(operationName, operation, service) {
|
|
|
106
112
|
*/
|
|
107
113
|
public async ${operationName}(${params}): Promise<any> {
|
|
108
114
|
try {
|
|
109
|
-
${
|
|
115
|
+
${body}
|
|
110
116
|
|
|
111
117
|
${hasPublish ? `
|
|
112
118
|
// Publish event (example)
|
|
@@ -125,6 +131,19 @@ function generateOperation(operationName, operation, service) {
|
|
|
125
131
|
}
|
|
126
132
|
`;
|
|
127
133
|
}
|
|
134
|
+
function renameUnusedParams(paramsString, body) {
|
|
135
|
+
if (!paramsString.trim()) return paramsString;
|
|
136
|
+
return paramsString.split(",").map((segment) => {
|
|
137
|
+
const trimmed = segment.trim();
|
|
138
|
+
const nameMatch = trimmed.match(/^(\w+)/);
|
|
139
|
+
if (!nameMatch) return segment;
|
|
140
|
+
const name = nameMatch[1];
|
|
141
|
+
if (name.startsWith("_")) return segment;
|
|
142
|
+
const re = new RegExp(`\\b${name}\\b`);
|
|
143
|
+
if (re.test(body)) return segment;
|
|
144
|
+
return segment.replace(new RegExp(`\\b${name}\\b`), `_${name}`);
|
|
145
|
+
}).join(", ");
|
|
146
|
+
}
|
|
128
147
|
function generateOperationParams(operation) {
|
|
129
148
|
if (!operation.parameters || Object.keys(operation.parameters).length === 0) {
|
|
130
149
|
return "params: any = {}";
|
|
@@ -149,17 +168,19 @@ function generateOperationLogic(operation, service) {
|
|
|
149
168
|
}
|
|
150
169
|
if (impl.preconditions?.length || impl.postconditions?.length || impl.steps?.length || impl.transactional) {
|
|
151
170
|
const modelName = inferModelFromServiceName(service.name);
|
|
171
|
+
const parameterNames = Object.keys(operation.parameters || {});
|
|
152
172
|
const context = {
|
|
153
173
|
modelName,
|
|
154
174
|
serviceName: service.name,
|
|
155
175
|
operationName: operation.name || "execute",
|
|
156
|
-
prismaModel: modelName
|
|
176
|
+
prismaModel: modelName,
|
|
177
|
+
parameterNames
|
|
157
178
|
};
|
|
158
179
|
const behavior = {
|
|
159
180
|
preconditions: impl.preconditions || [],
|
|
160
181
|
postconditions: impl.postconditions || [],
|
|
161
182
|
sideEffects: impl.sideEffects || [],
|
|
162
|
-
steps: impl.steps || [],
|
|
183
|
+
steps: impl.steps || operation.steps || [],
|
|
163
184
|
transactional: impl.transactional || false
|
|
164
185
|
};
|
|
165
186
|
const opMeta = {
|
|
@@ -4,15 +4,47 @@ function toVar(name) {
|
|
|
4
4
|
function toMethod(words) {
|
|
5
5
|
return words.trim().replace(/\s+(.)/g, (_, c) => c.toUpperCase()).replace(/^\w/, (c) => c.toLowerCase());
|
|
6
6
|
}
|
|
7
|
+
function resolveValue(rawValue, ctx) {
|
|
8
|
+
const value = rawValue.trim().replace(/^['"]|['"]$/g, "");
|
|
9
|
+
if (/^(current\s*time|now|timestamp)$/i.test(value)) {
|
|
10
|
+
return "new Date().toISOString()";
|
|
11
|
+
}
|
|
12
|
+
if (value === "true" || value === "false") return value;
|
|
13
|
+
if (/^-?\d+(\.\d+)?$/.test(value)) return value;
|
|
14
|
+
const declared = ctx.declaredVars || /* @__PURE__ */ new Set();
|
|
15
|
+
const params = ctx.parameterNames || [];
|
|
16
|
+
const rootMatch = value.match(/^([a-zA-Z_][a-zA-Z0-9_]*)(\.[a-zA-Z0-9_.]+)?$/);
|
|
17
|
+
if (rootMatch) {
|
|
18
|
+
const root = rootMatch[1];
|
|
19
|
+
if (declared.has(root) || params.includes(root)) {
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const tokens = value.toLowerCase().split(/\s+/);
|
|
24
|
+
for (const declaredName of [...declared, ...params]) {
|
|
25
|
+
const normalized = declaredName.toLowerCase();
|
|
26
|
+
if (tokens.includes(normalized)) return declaredName;
|
|
27
|
+
}
|
|
28
|
+
const calcMatch = value.match(/^(?:the\s+|calculated\s+|computed\s+|resulting\s+)(\w+)/i);
|
|
29
|
+
if (calcMatch) {
|
|
30
|
+
const field = calcMatch[1];
|
|
31
|
+
const stepResults = [...declared].filter((v) => /^step\d+Result$/.test(v)).sort().reverse();
|
|
32
|
+
if (stepResults.length > 0) {
|
|
33
|
+
return `(${stepResults[0]} as any).${field} /* TODO: verify field name */`;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (/\s/.test(value)) {
|
|
37
|
+
return `/* TODO: resolve "${value}" \u2014 no matching declared variable */ null`;
|
|
38
|
+
}
|
|
39
|
+
return `'${value.replace(/'/g, "\\'")}'`;
|
|
40
|
+
}
|
|
7
41
|
const STEP_CONVENTIONS = [
|
|
8
42
|
// --- Validation ---
|
|
9
43
|
{
|
|
10
44
|
name: "validate",
|
|
11
45
|
pattern: /^validate\s+(.+)/i,
|
|
12
46
|
generateCall: (m, ctx) => ` // Step ${ctx.stepNum}: Validate ${m[1]}
|
|
13
|
-
|
|
14
|
-
if (!validationResult.valid) {
|
|
15
|
-
throw new Error(\`Validation failed: \${validationResult.errors.join(', ')}\`);
|
|
47
|
+
// TODO: Add validation logic for ${m[1]}
|
|
16
48
|
}`
|
|
17
49
|
},
|
|
18
50
|
// --- Check / Guard ---
|
|
@@ -23,21 +55,9 @@ const STEP_CONVENTIONS = [
|
|
|
23
55
|
const condition = m[1];
|
|
24
56
|
const methodName = toMethod("check " + condition);
|
|
25
57
|
return ` // Step ${ctx.stepNum}: Check ${condition}
|
|
26
|
-
const checkResult = await this.${methodName}(params);
|
|
27
|
-
if (!checkResult) {
|
|
28
|
-
throw new Error('Check failed: ${condition}');
|
|
29
|
-
}`;
|
|
30
|
-
},
|
|
31
|
-
generateMethod: (m, ctx) => {
|
|
32
|
-
const condition = m[1];
|
|
33
|
-
const methodName = toMethod("check " + condition);
|
|
34
|
-
return `
|
|
35
|
-
private async ${methodName}(params: any): Promise<boolean> {
|
|
36
58
|
// TODO: Implement check \u2014 ${condition}
|
|
37
|
-
const
|
|
38
|
-
if (
|
|
39
|
-
return true;
|
|
40
|
-
}`;
|
|
59
|
+
// const checkResult = ...
|
|
60
|
+
// if (!checkResult) throw new Error('Check failed: ${condition}');`;
|
|
41
61
|
}
|
|
42
62
|
},
|
|
43
63
|
// --- Find / Lookup ---
|
|
@@ -47,21 +67,29 @@ const STEP_CONVENTIONS = [
|
|
|
47
67
|
generateCall: (m, ctx) => {
|
|
48
68
|
const model = m[1];
|
|
49
69
|
const field = m[2];
|
|
70
|
+
const modelVar = toVar(model);
|
|
71
|
+
const params = ctx.parameterNames || [];
|
|
72
|
+
const declared = ctx.declaredVars || /* @__PURE__ */ new Set();
|
|
73
|
+
const idParam = field === "id" ? params.find((p) => p === `${modelVar}Id`) || params.find((p) => p === "id") || `${modelVar}Id` : params.find((p) => p === field) || `params.${field}`;
|
|
74
|
+
if (declared.has(modelVar)) {
|
|
75
|
+
return ` // Step ${ctx.stepNum}: Find ${model} by ${field} (already loaded)`;
|
|
76
|
+
}
|
|
77
|
+
declared.add(modelVar);
|
|
50
78
|
return ` // Step ${ctx.stepNum}: Find ${model} by ${field}
|
|
51
|
-
const ${
|
|
52
|
-
if (!${toVar(model)}) {
|
|
53
|
-
throw new Error('${model} not found by ${field}');
|
|
54
|
-
}`;
|
|
79
|
+
const ${modelVar} = await prisma.${modelVar}.findUniqueOrThrow({ where: { ${field}: ${idParam} } });`;
|
|
55
80
|
}
|
|
56
81
|
},
|
|
57
82
|
// --- Create ---
|
|
58
83
|
{
|
|
59
84
|
name: "create",
|
|
60
|
-
pattern: /^create\s+(\w+)(?:\s+
|
|
85
|
+
pattern: /^create\s+(\w+)(?:\s+(?:with\s+)?(.+))?/i,
|
|
61
86
|
generateCall: (m, ctx) => {
|
|
62
87
|
const model = m[1];
|
|
88
|
+
const modelVar = toVar(model);
|
|
89
|
+
const paramNames = ctx.parameterNames || [];
|
|
90
|
+
const dataFields = paramNames.length > 0 ? `{ ${paramNames.join(", ")} }` : "data";
|
|
63
91
|
return ` // Step ${ctx.stepNum}: Create ${model}
|
|
64
|
-
const ${
|
|
92
|
+
const ${modelVar} = await prisma.${modelVar}.create({ data: ${dataFields} });`;
|
|
65
93
|
}
|
|
66
94
|
},
|
|
67
95
|
// --- Update field ---
|
|
@@ -71,12 +99,12 @@ const STEP_CONVENTIONS = [
|
|
|
71
99
|
generateCall: (m, ctx) => {
|
|
72
100
|
const model = m[1];
|
|
73
101
|
const field = m[2];
|
|
74
|
-
const
|
|
75
|
-
const
|
|
76
|
-
const val =
|
|
77
|
-
return ` // Step ${ctx.stepNum}: Update ${model} ${field} to ${
|
|
78
|
-
await
|
|
79
|
-
where: { id:
|
|
102
|
+
const rawValue = m[3];
|
|
103
|
+
const modelVar = toVar(model);
|
|
104
|
+
const val = resolveValue(rawValue, ctx);
|
|
105
|
+
return ` // Step ${ctx.stepNum}: Update ${model} ${field} to ${rawValue.trim()}
|
|
106
|
+
await prisma.${modelVar}.update({
|
|
107
|
+
where: { id: ${modelVar}.id },
|
|
80
108
|
data: { ${field}: ${val} },
|
|
81
109
|
});`;
|
|
82
110
|
}
|
|
@@ -87,9 +115,10 @@ const STEP_CONVENTIONS = [
|
|
|
87
115
|
pattern: /^update\s+(\w+)(?:\s+(.+))?/i,
|
|
88
116
|
generateCall: (m, ctx) => {
|
|
89
117
|
const model = m[1];
|
|
118
|
+
const modelVar = toVar(model);
|
|
90
119
|
return ` // Step ${ctx.stepNum}: Update ${model}
|
|
91
|
-
await
|
|
92
|
-
where: { id:
|
|
120
|
+
await prisma.${modelVar}.update({
|
|
121
|
+
where: { id: ${modelVar}.id },
|
|
93
122
|
data: params,
|
|
94
123
|
});`;
|
|
95
124
|
}
|
|
@@ -100,8 +129,9 @@ const STEP_CONVENTIONS = [
|
|
|
100
129
|
pattern: /^delete\s+(\w+)/i,
|
|
101
130
|
generateCall: (m, ctx) => {
|
|
102
131
|
const model = m[1];
|
|
132
|
+
const modelVar = toVar(model);
|
|
103
133
|
return ` // Step ${ctx.stepNum}: Delete ${model}
|
|
104
|
-
await
|
|
134
|
+
await prisma.${modelVar}.delete({ where: { id: ${modelVar}.id } });`;
|
|
105
135
|
}
|
|
106
136
|
},
|
|
107
137
|
// --- Transition / Evolve ---
|
|
@@ -111,11 +141,11 @@ const STEP_CONVENTIONS = [
|
|
|
111
141
|
generateCall: (m, ctx) => {
|
|
112
142
|
const model = m[1];
|
|
113
143
|
const state = m[2];
|
|
144
|
+
const modelVar = toVar(model);
|
|
114
145
|
return ` // Step ${ctx.stepNum}: Transition ${model} to ${state}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
where: { id: params.id },
|
|
146
|
+
if (${modelVar}.status === '${state}') throw new Error('${model} is already ${state}');
|
|
147
|
+
await prisma.${modelVar}.update({
|
|
148
|
+
where: { id: ${modelVar}.id },
|
|
119
149
|
data: { status: '${state}' },
|
|
120
150
|
});`;
|
|
121
151
|
}
|
|
@@ -126,12 +156,12 @@ const STEP_CONVENTIONS = [
|
|
|
126
156
|
pattern: /^set\s+(\w+)\s+to\s+(.+)/i,
|
|
127
157
|
generateCall: (m, ctx) => {
|
|
128
158
|
const field = m[1];
|
|
129
|
-
const
|
|
130
|
-
const
|
|
131
|
-
const val =
|
|
132
|
-
return ` // Step ${ctx.stepNum}: Set ${field} to ${
|
|
133
|
-
await
|
|
134
|
-
where: { id:
|
|
159
|
+
const rawValue = m[2];
|
|
160
|
+
const modelVar = toVar(ctx.prismaModel);
|
|
161
|
+
const val = resolveValue(rawValue, ctx);
|
|
162
|
+
return ` // Step ${ctx.stepNum}: Set ${field} to ${rawValue.trim()}
|
|
163
|
+
await prisma.${modelVar}.update({
|
|
164
|
+
where: { id: ${modelVar}.id },
|
|
135
165
|
data: { ${field}: ${val} },
|
|
136
166
|
});`;
|
|
137
167
|
}
|
|
@@ -143,10 +173,11 @@ const STEP_CONVENTIONS = [
|
|
|
143
173
|
generateCall: (m, ctx) => {
|
|
144
174
|
const field = m[1];
|
|
145
175
|
const amount = m[2];
|
|
146
|
-
const
|
|
176
|
+
const modelVar = toVar(ctx.prismaModel);
|
|
177
|
+
const amountVal = /^\d+$/.test(amount) ? amount : amount;
|
|
147
178
|
return ` // Step ${ctx.stepNum}: Increment ${field} by ${amount}
|
|
148
|
-
await
|
|
149
|
-
where: { id:
|
|
179
|
+
await prisma.${modelVar}.update({
|
|
180
|
+
where: { id: ${modelVar}.id },
|
|
150
181
|
data: { ${field}: { increment: ${amountVal} } },
|
|
151
182
|
});`;
|
|
152
183
|
}
|
|
@@ -158,38 +189,18 @@ const STEP_CONVENTIONS = [
|
|
|
158
189
|
generateCall: (m, ctx) => {
|
|
159
190
|
const field = m[1];
|
|
160
191
|
const amount = m[2];
|
|
161
|
-
const
|
|
192
|
+
const modelVar = toVar(ctx.prismaModel);
|
|
193
|
+
const amountVal = /^\d+$/.test(amount) ? amount : amount;
|
|
162
194
|
return ` // Step ${ctx.stepNum}: Decrement ${field} by ${amount}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
throw new Error('Cannot decrement ${field} below zero');
|
|
166
|
-
}
|
|
167
|
-
await this.prisma.${toVar(ctx.prismaModel)}.update({
|
|
168
|
-
where: { id: params.id },
|
|
195
|
+
await prisma.${modelVar}.update({
|
|
196
|
+
where: { id: ${modelVar}.id },
|
|
169
197
|
data: { ${field}: { decrement: ${amountVal} } },
|
|
170
198
|
});`;
|
|
171
199
|
}
|
|
172
200
|
},
|
|
173
|
-
//
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
pattern: /^calculate\s+(.+)/i,
|
|
177
|
-
generateCall: (m, ctx) => {
|
|
178
|
-
const metric = m[1];
|
|
179
|
-
const methodName = toMethod("calculate " + metric);
|
|
180
|
-
return ` // Step ${ctx.stepNum}: Calculate ${metric}
|
|
181
|
-
const ${toVar(metric.replace(/\s+/g, ""))} = await this.${methodName}(params);`;
|
|
182
|
-
},
|
|
183
|
-
generateMethod: (m, ctx) => {
|
|
184
|
-
const metric = m[1];
|
|
185
|
-
const methodName = toMethod("calculate " + metric);
|
|
186
|
-
return `
|
|
187
|
-
private async ${methodName}(params: any): Promise<number> {
|
|
188
|
-
// TODO: Implement calculation \u2014 ${metric}
|
|
189
|
-
return 0;
|
|
190
|
-
}`;
|
|
191
|
-
}
|
|
192
|
-
},
|
|
201
|
+
// NOTE: "calculate X" is intentionally NOT a convention — it falls through to AI
|
|
202
|
+
// behaviors because calculations are domain-specific pure functions that benefit
|
|
203
|
+
// from AI generation with the full input context.
|
|
193
204
|
// --- Send event ---
|
|
194
205
|
{
|
|
195
206
|
name: "send-event",
|
|
@@ -197,7 +208,7 @@ const STEP_CONVENTIONS = [
|
|
|
197
208
|
generateCall: (m, ctx) => {
|
|
198
209
|
const event = m[1];
|
|
199
210
|
return ` // Step ${ctx.stepNum}: Emit ${event} event
|
|
200
|
-
|
|
211
|
+
await eventBus.publish('${event}', { ${toVar(ctx.prismaModel)}Id: ${toVar(ctx.prismaModel)}.id, operation: '${ctx.operationName}', timestamp: new Date().toISOString() });`;
|
|
201
212
|
}
|
|
202
213
|
},
|
|
203
214
|
// --- Send notification ---
|
|
@@ -207,7 +218,7 @@ const STEP_CONVENTIONS = [
|
|
|
207
218
|
generateCall: (m, ctx) => {
|
|
208
219
|
const type = m[1];
|
|
209
220
|
return ` // Step ${ctx.stepNum}: Send ${type} notification
|
|
210
|
-
|
|
221
|
+
await eventBus.publish('${type}Notification', { ${toVar(ctx.prismaModel)}Id: ${toVar(ctx.prismaModel)}.id, operation: '${ctx.operationName}' });`;
|
|
211
222
|
}
|
|
212
223
|
},
|
|
213
224
|
// --- Call service ---
|
|
@@ -218,7 +229,7 @@ const STEP_CONVENTIONS = [
|
|
|
218
229
|
const service = m[1];
|
|
219
230
|
const method = m[2];
|
|
220
231
|
return ` // Step ${ctx.stepNum}: Call ${service}.${method}
|
|
221
|
-
await
|
|
232
|
+
await ${toVar(service)}.${method}({ ${(ctx.parameterNames || []).join(", ")} });`;
|
|
222
233
|
}
|
|
223
234
|
},
|
|
224
235
|
// --- Return ---
|
|
@@ -243,19 +254,25 @@ function matchStep(step, ctx) {
|
|
|
243
254
|
if (match) {
|
|
244
255
|
return {
|
|
245
256
|
call: convention.generateCall(match, ctx),
|
|
246
|
-
helperMethod: convention.generateMethod?.(match, ctx)
|
|
257
|
+
helperMethod: convention.generateMethod?.(match, ctx),
|
|
258
|
+
matched: true
|
|
247
259
|
};
|
|
248
260
|
}
|
|
249
261
|
}
|
|
250
|
-
const
|
|
262
|
+
const functionName = toMethod(step);
|
|
263
|
+
const declared = Array.from(ctx.declaredVars || []);
|
|
264
|
+
const paramNames = ctx.parameterNames || [];
|
|
265
|
+
const inputs = [...paramNames, ...declared];
|
|
266
|
+
const resultVar = ctx.resultName || `step${ctx.stepNum}Result`;
|
|
267
|
+
const inputObj = inputs.length > 0 ? `{ ${inputs.join(", ")} }` : "{}";
|
|
268
|
+
if (ctx.declaredVars) ctx.declaredVars.add(resultVar);
|
|
251
269
|
return {
|
|
252
|
-
call: ` // Step ${ctx.stepNum}: ${step}
|
|
253
|
-
await
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
}`
|
|
270
|
+
call: ` // Step ${ctx.stepNum}: ${step} [AI-generated \u2014 pure function]
|
|
271
|
+
const ${resultVar} = await aiBehaviors.${functionName}(${inputObj});`,
|
|
272
|
+
matched: false,
|
|
273
|
+
functionName,
|
|
274
|
+
inputs,
|
|
275
|
+
resultVar
|
|
259
276
|
};
|
|
260
277
|
}
|
|
261
278
|
export {
|