@specverse/engines 4.1.13 → 4.1.14
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 +120 -0
- package/dist/ai/behavior-ai-service.d.ts +63 -0
- package/dist/ai/behavior-ai-service.d.ts.map +1 -0
- package/dist/ai/behavior-ai-service.js +203 -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/libs/instance-factories/cli/templates/commander/command-generator.js +16 -0
- package/dist/libs/instance-factories/communication/templates/eventemitter/bus-generator.js +31 -30
- 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 +25 -9
- package/dist/libs/instance-factories/controllers/templates/fastify/server-generator.js +20 -2
- package/dist/libs/instance-factories/services/templates/prisma/ai-behaviors-generator.js +141 -0
- package/dist/libs/instance-factories/services/templates/prisma/behavior-generator.js +62 -42
- package/dist/libs/instance-factories/services/templates/prisma/controller-generator.js +39 -7
- package/dist/libs/instance-factories/services/templates/prisma/step-conventions.js +101 -84
- package/dist/realize/index.d.ts.map +1 -1
- package/dist/realize/index.js +54 -0
- package/dist/realize/index.js.map +1 -1
- package/libs/instance-factories/cli/templates/commander/command-generator.ts +16 -0
- package/libs/instance-factories/communication/event-emitter.yaml +16 -12
- package/libs/instance-factories/communication/templates/eventemitter/bus-generator.ts +33 -35
- 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 +32 -11
- package/libs/instance-factories/controllers/templates/fastify/server-generator.ts +23 -2
- package/libs/instance-factories/services/templates/prisma/ai-behaviors-generator.ts +211 -0
- package/libs/instance-factories/services/templates/prisma/behavior-generator.ts +86 -40
- package/libs/instance-factories/services/templates/prisma/controller-generator.ts +54 -8
- package/libs/instance-factories/services/templates/prisma/step-conventions.ts +166 -85
- package/package.json +1 -1
|
@@ -6,14 +6,17 @@ function generateBehaviorBody(behavior, opMeta, context) {
|
|
|
6
6
|
function generateBehaviorWithHelpers(behavior, opMeta, context) {
|
|
7
7
|
const parts = [];
|
|
8
8
|
const helperMethods = [];
|
|
9
|
+
const preconditionDeclared = /* @__PURE__ */ new Set();
|
|
9
10
|
const preconditions = generatePreconditionChecks(
|
|
10
11
|
behavior.preconditions || [],
|
|
11
|
-
context
|
|
12
|
+
context,
|
|
13
|
+
preconditionDeclared
|
|
12
14
|
);
|
|
13
15
|
if (preconditions) parts.push(preconditions);
|
|
14
|
-
const { code, helpers } = generateStepLogic(
|
|
16
|
+
const { code, helpers, unmatchedSteps } = generateStepLogic(
|
|
15
17
|
behavior.steps || [],
|
|
16
|
-
context
|
|
18
|
+
context,
|
|
19
|
+
preconditionDeclared
|
|
17
20
|
);
|
|
18
21
|
parts.push(code);
|
|
19
22
|
helperMethods.push(...helpers);
|
|
@@ -23,76 +26,80 @@ function generateBehaviorWithHelpers(behavior, opMeta, context) {
|
|
|
23
26
|
if (postconditions) parts.push(postconditions);
|
|
24
27
|
const events = generateEventPublishing(
|
|
25
28
|
behavior.sideEffects || [],
|
|
26
|
-
context.operationName
|
|
29
|
+
context.operationName,
|
|
30
|
+
context.parameterNames
|
|
27
31
|
);
|
|
28
32
|
if (events) parts.push(events);
|
|
29
33
|
let body = parts.join("\n\n");
|
|
30
34
|
if (behavior.transactional) {
|
|
31
35
|
body = generateTransactionWrapper(body, context);
|
|
32
36
|
}
|
|
33
|
-
return { body, helperMethods };
|
|
37
|
+
return { body, helperMethods, unmatchedSteps };
|
|
34
38
|
}
|
|
35
|
-
function generatePreconditionChecks(preconditions, context) {
|
|
39
|
+
function generatePreconditionChecks(preconditions, context, declared) {
|
|
36
40
|
if (preconditions.length === 0) return "";
|
|
37
|
-
|
|
41
|
+
if (!declared) declared = /* @__PURE__ */ new Set();
|
|
42
|
+
const checks = preconditions.map((pc) => matchPreconditionPattern(pc, context, declared));
|
|
38
43
|
return ` // === PRECONDITIONS ===
|
|
39
44
|
${checks.join("\n")}`;
|
|
40
45
|
}
|
|
41
|
-
function
|
|
46
|
+
function findIdParam(modelName, paramNames) {
|
|
47
|
+
const modelVar = modelName.charAt(0).toLowerCase() + modelName.slice(1);
|
|
48
|
+
const idParam = paramNames.find((p) => p === `${modelVar}Id`);
|
|
49
|
+
if (idParam) return idParam;
|
|
50
|
+
if (paramNames.includes("id")) return "id";
|
|
51
|
+
return `${modelVar}Id`;
|
|
52
|
+
}
|
|
53
|
+
function matchPreconditionPattern(precondition, context, declared) {
|
|
42
54
|
const pc = precondition.toLowerCase();
|
|
43
|
-
const
|
|
55
|
+
const params = context.parameterNames || [];
|
|
44
56
|
const existsMatch = precondition.match(/^(\w+)\s+exists/i);
|
|
45
57
|
if (existsMatch) {
|
|
46
58
|
const entity = existsMatch[1];
|
|
47
59
|
const entityVar = entity.charAt(0).toLowerCase() + entity.slice(1);
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
60
|
+
const idParam = findIdParam(entity, params);
|
|
61
|
+
if (declared.has(entityVar)) {
|
|
62
|
+
return ` // Guard: ${precondition} (already fetched)`;
|
|
63
|
+
}
|
|
64
|
+
declared.add(entityVar);
|
|
65
|
+
return ` const ${entityVar} = await prisma.${entityVar}.findUniqueOrThrow({ where: { id: ${idParam} } });`;
|
|
53
66
|
}
|
|
54
67
|
if (pc.includes("is not empty") || pc.includes("is required")) {
|
|
55
68
|
const fieldMatch = precondition.match(/^(\w+)\s+is/i);
|
|
56
69
|
if (fieldMatch) {
|
|
57
70
|
const field = fieldMatch[1];
|
|
58
|
-
|
|
59
|
-
if (
|
|
60
|
-
throw new Error('Precondition failed: ${precondition}');
|
|
61
|
-
}`;
|
|
71
|
+
const paramRef = params.includes(field) ? field : `params.${field}`;
|
|
72
|
+
return ` if (!${paramRef}) throw new Error('${field} is required');`;
|
|
62
73
|
}
|
|
63
74
|
}
|
|
64
75
|
if (pc.includes("is valid")) {
|
|
65
|
-
return ` //
|
|
66
|
-
const validation = this.validate(params, { operation: '${context.operationName}' });
|
|
67
|
-
if (!validation.valid) {
|
|
68
|
-
throw new Error('Precondition failed: ${precondition} \u2014 ' + validation.errors.join(', '));
|
|
69
|
-
}`;
|
|
76
|
+
return ` // TODO: Implement validation: ${precondition}`;
|
|
70
77
|
}
|
|
71
78
|
const matchesMatch = precondition.match(/^(\w+)\s+(?:matches|equals)\s+(.+)/i);
|
|
72
79
|
if (matchesMatch) {
|
|
73
|
-
const left = matchesMatch[1];
|
|
74
|
-
const right = matchesMatch[2];
|
|
75
|
-
return `
|
|
76
|
-
if (params.${left.charAt(0).toLowerCase() + left.slice(1)} !== params.${right.charAt(0).toLowerCase() + right.slice(1)}) {
|
|
77
|
-
throw new Error('Precondition failed: ${precondition}');
|
|
78
|
-
}`;
|
|
80
|
+
const left = matchesMatch[1].charAt(0).toLowerCase() + matchesMatch[1].slice(1);
|
|
81
|
+
const right = matchesMatch[2].charAt(0).toLowerCase() + matchesMatch[2].slice(1);
|
|
82
|
+
return ` if (${left} !== ${right}) throw new Error('${matchesMatch[1]} must match ${matchesMatch[2]}');`;
|
|
79
83
|
}
|
|
80
84
|
const stateMatch = precondition.match(/^(\w+)\s+is\s+(\w+)$/i);
|
|
81
85
|
if (stateMatch) {
|
|
82
86
|
const model = stateMatch[1];
|
|
83
87
|
const state = stateMatch[2];
|
|
84
88
|
const modelVar = model.charAt(0).toLowerCase() + model.slice(1);
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
89
|
+
const idParam = findIdParam(model, params);
|
|
90
|
+
if (!declared.has(modelVar)) {
|
|
91
|
+
declared.add(modelVar);
|
|
92
|
+
return ` const ${modelVar} = await prisma.${modelVar}.findUniqueOrThrow({ where: { id: ${idParam} } });
|
|
93
|
+
if (${modelVar}.status !== '${state}') throw new Error('${model} must be ${state}, got ' + ${modelVar}.status);`;
|
|
94
|
+
}
|
|
95
|
+
return ` if (${modelVar}.status !== '${state}') throw new Error('${model} must be ${state}, got ' + ${modelVar}.status);`;
|
|
90
96
|
}
|
|
91
|
-
return ` //
|
|
92
|
-
// TODO: Implement precondition check`;
|
|
97
|
+
return ` // TODO: Implement precondition: ${precondition}`;
|
|
93
98
|
}
|
|
94
|
-
function generateStepLogic(steps, context) {
|
|
99
|
+
function generateStepLogic(steps, context, preconditionDeclared) {
|
|
95
100
|
const helpers = [];
|
|
101
|
+
const unmatchedSteps = [];
|
|
102
|
+
const declaredVars = preconditionDeclared || /* @__PURE__ */ new Set();
|
|
96
103
|
if (steps && steps.length > 0) {
|
|
97
104
|
const stepCode = steps.map((step, i) => {
|
|
98
105
|
if (typeof step !== "string") {
|
|
@@ -103,24 +110,36 @@ function generateStepLogic(steps, context) {
|
|
|
103
110
|
prismaModel: context.prismaModel || context.modelName,
|
|
104
111
|
serviceName: context.serviceName,
|
|
105
112
|
operationName: context.operationName,
|
|
106
|
-
stepNum: i + 1
|
|
113
|
+
stepNum: i + 1,
|
|
114
|
+
parameterNames: context.parameterNames,
|
|
115
|
+
declaredVars
|
|
107
116
|
};
|
|
108
117
|
const result = matchStep(step, ctx);
|
|
109
118
|
if (result.helperMethod) {
|
|
110
119
|
helpers.push(result.helperMethod);
|
|
111
120
|
}
|
|
121
|
+
if (!result.matched && result.functionName) {
|
|
122
|
+
unmatchedSteps.push({
|
|
123
|
+
step,
|
|
124
|
+
functionName: result.functionName,
|
|
125
|
+
operationName: context.operationName,
|
|
126
|
+
inputs: result.inputs || []
|
|
127
|
+
});
|
|
128
|
+
}
|
|
112
129
|
return result.call;
|
|
113
130
|
});
|
|
114
131
|
return {
|
|
115
132
|
code: ` // === EXECUTE ===
|
|
116
133
|
${stepCode.join("\n\n")}`,
|
|
117
|
-
helpers
|
|
134
|
+
helpers,
|
|
135
|
+
unmatchedSteps
|
|
118
136
|
};
|
|
119
137
|
}
|
|
120
138
|
return {
|
|
121
139
|
code: ` // === EXECUTE ===
|
|
122
140
|
${inferLogicFromOperationName(context)}`,
|
|
123
|
-
helpers
|
|
141
|
+
helpers,
|
|
142
|
+
unmatchedSteps
|
|
124
143
|
};
|
|
125
144
|
}
|
|
126
145
|
function inferLogicFromOperationName(context) {
|
|
@@ -154,10 +173,11 @@ function generatePostconditionVerification(postconditions) {
|
|
|
154
173
|
${checks.join("\n")}
|
|
155
174
|
}`;
|
|
156
175
|
}
|
|
157
|
-
function generateEventPublishing(sideEffects, operationName) {
|
|
176
|
+
function generateEventPublishing(sideEffects, operationName, parameterNames) {
|
|
158
177
|
if (!sideEffects || sideEffects.length === 0) return "";
|
|
178
|
+
const paramFields = parameterNames?.length ? parameterNames.join(", ") + ", " : "";
|
|
159
179
|
const publishes = sideEffects.map(
|
|
160
|
-
(event) => `
|
|
180
|
+
(event) => ` await eventBus.publish('${event}', { ${paramFields}timestamp: new Date().toISOString() });`
|
|
161
181
|
);
|
|
162
182
|
return ` // === EVENTS ===
|
|
163
183
|
${publishes.join("\n")}`;
|
|
@@ -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) {
|
|
@@ -16,6 +17,7 @@ function generatePrismaController(context) {
|
|
|
16
17
|
const idAttr = (Array.isArray(model.attributes) ? model.attributes : Object.values(model.attributes || {})).find((a) => a.name === "id");
|
|
17
18
|
const idType = idAttr?.type || "UUID";
|
|
18
19
|
const needsIntParse = idType === "Integer" || idType === "Int" || idType === "Number";
|
|
20
|
+
const customActions = generateCustomActions(controller, modelName, modelVar);
|
|
19
21
|
return `/**
|
|
20
22
|
* ${controllerName}
|
|
21
23
|
* Model-specific business logic for ${modelName}
|
|
@@ -23,7 +25,8 @@ function generatePrismaController(context) {
|
|
|
23
25
|
*/
|
|
24
26
|
|
|
25
27
|
import { PrismaClient } from '@prisma/client';
|
|
26
|
-
${hasEventPublishing(curedOps, controller) ? `import { eventBus
|
|
28
|
+
${hasEventPublishing(curedOps, controller) ? `import { eventBus } from '../events/eventBus.js';` : ""}
|
|
29
|
+
${customActions.needsAiBehaviors ? `import * as aiBehaviors from '../behaviors/${modelName}Controller.ai.js';` : ""}
|
|
27
30
|
|
|
28
31
|
const prisma = new PrismaClient();
|
|
29
32
|
|
|
@@ -42,7 +45,7 @@ export class ${controllerName} {
|
|
|
42
45
|
${curedOps.update ? generateUpdateMethod(model, modelName, modelVar, controller, allModels) : ""}
|
|
43
46
|
${curedOps.evolve ? generateEvolveMethod(model, modelName, modelVar, controller) : ""}
|
|
44
47
|
${curedOps.delete ? generateDeleteMethod(model, modelName, modelVar, controller) : ""}
|
|
45
|
-
${
|
|
48
|
+
${customActions.code}
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
// Export singleton instance
|
|
@@ -298,21 +301,44 @@ function generateDeleteMethod(model, modelName, modelVar, controller) {
|
|
|
298
301
|
}
|
|
299
302
|
function generateCustomActions(controller, modelName, modelVar) {
|
|
300
303
|
if (!controller.actions || Object.keys(controller.actions).length === 0) {
|
|
301
|
-
return "";
|
|
304
|
+
return { code: "", unmatchedSteps: [], needsAiBehaviors: false };
|
|
302
305
|
}
|
|
303
306
|
const actions = [];
|
|
307
|
+
const allUnmatchedSteps = [];
|
|
304
308
|
Object.entries(controller.actions).forEach(([actionName, action]) => {
|
|
309
|
+
const behavior = {
|
|
310
|
+
preconditions: action.requires || action.preconditions || [],
|
|
311
|
+
steps: action.steps || [],
|
|
312
|
+
postconditions: action.ensures || action.postconditions || [],
|
|
313
|
+
sideEffects: action.publishes || action.events || [],
|
|
314
|
+
transactional: action.transactional
|
|
315
|
+
};
|
|
316
|
+
const ctx = {
|
|
317
|
+
modelName,
|
|
318
|
+
serviceName: `${modelName}Controller`,
|
|
319
|
+
operationName: actionName,
|
|
320
|
+
prismaModel: modelVar,
|
|
321
|
+
parameterNames: Object.keys(action.parameters || {})
|
|
322
|
+
};
|
|
323
|
+
const result = generateBehaviorWithHelpers(behavior, {}, ctx);
|
|
324
|
+
allUnmatchedSteps.push(...result.unmatchedSteps);
|
|
305
325
|
actions.push(`
|
|
306
326
|
/**
|
|
307
327
|
* ${actionName}
|
|
308
328
|
* ${action.description || ""}
|
|
309
329
|
*/
|
|
310
330
|
public async ${actionName}(${generateActionParams(action)}): Promise<any> {
|
|
311
|
-
|
|
312
|
-
throw new Error('${actionName} not implemented');
|
|
331
|
+
${result.body}
|
|
313
332
|
}`);
|
|
333
|
+
if (result.helperMethods.length > 0) {
|
|
334
|
+
actions.push(...result.helperMethods);
|
|
335
|
+
}
|
|
314
336
|
});
|
|
315
|
-
return
|
|
337
|
+
return {
|
|
338
|
+
code: actions.join("\n"),
|
|
339
|
+
unmatchedSteps: allUnmatchedSteps,
|
|
340
|
+
needsAiBehaviors: allUnmatchedSteps.length > 0
|
|
341
|
+
};
|
|
316
342
|
}
|
|
317
343
|
function generateActionParams(action) {
|
|
318
344
|
if (!action.parameters || Object.keys(action.parameters).length === 0) {
|
|
@@ -391,7 +417,13 @@ ${includes}
|
|
|
391
417
|
}`;
|
|
392
418
|
}
|
|
393
419
|
function hasEventPublishing(curedOps, controller) {
|
|
394
|
-
|
|
420
|
+
if (controller.publishes && Array.isArray(controller.publishes) && controller.publishes.length > 0) return true;
|
|
421
|
+
if (controller.actions) {
|
|
422
|
+
for (const action of Object.values(controller.actions)) {
|
|
423
|
+
if (action.publishes?.length > 0 || action.events?.length > 0 || action.sideEffects?.length > 0) return true;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
return false;
|
|
395
427
|
}
|
|
396
428
|
export {
|
|
397
429
|
generatePrismaController as default
|
|
@@ -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 = `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 {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/realize/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,cAAc,kBAAkB,CAAC;AAGjC,cAAc,kBAAkB,CAAC;AAGjC,cAAc,uBAAuB,CAAC;AAGtC,OAAO,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AACpF,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAGvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAMlE,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AASnF,cAAM,sBAAuB,YAAW,aAAa;IACnD,IAAI,SAAa;IACjB,OAAO,SAAW;IAClB,YAAY,WAA+E;IAE3F,OAAO,CAAC,OAAO,CAAa;IAC5B,OAAO,CAAC,QAAQ,CAAa;IAC7B,OAAO,CAAC,aAAa,CAAa;IAClC,OAAO,CAAC,QAAQ,CAAa;IAC7B,OAAO,CAAC,WAAW,CAAS;IAEtB,UAAU,CAAC,MAAM,CAAC,EAAE;QACxB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBjB,OAAO,IAAI,UAAU;IAIrB,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,GAAG;IAK1B,QAAQ,CAAC,QAAQ,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,GAAG,OAAO,CAAC,eAAe,CAAC;IAKvF;;;OAGG;IACG,UAAU,CAAC,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QAAC,MAAM,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/realize/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,cAAc,kBAAkB,CAAC;AAGjC,cAAc,kBAAkB,CAAC;AAGjC,cAAc,uBAAuB,CAAC;AAGtC,OAAO,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AACpF,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAGvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAMlE,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AASnF,cAAM,sBAAuB,YAAW,aAAa;IACnD,IAAI,SAAa;IACjB,OAAO,SAAW;IAClB,YAAY,WAA+E;IAE3F,OAAO,CAAC,OAAO,CAAa;IAC5B,OAAO,CAAC,QAAQ,CAAa;IAC7B,OAAO,CAAC,aAAa,CAAa;IAClC,OAAO,CAAC,QAAQ,CAAa;IAC7B,OAAO,CAAC,WAAW,CAAS;IAEtB,UAAU,CAAC,MAAM,CAAC,EAAE;QACxB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBjB,OAAO,IAAI,UAAU;IAIrB,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,GAAG;IAK1B,QAAQ,CAAC,QAAQ,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,GAAG,OAAO,CAAC,eAAe,CAAC;IAKvF;;;OAGG;IACG,UAAU,CAAC,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QAAC,MAAM,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IA4d9F;;;OAGG;YACW,UAAU;IAwDxB,OAAO,CAAC,oBAAoB;CAuB7B;AAED,eAAO,MAAM,MAAM,wBAA+B,CAAC;AACnD,eAAe,MAAM,CAAC;AACtB,OAAO,EAAE,sBAAsB,EAAE,CAAC"}
|