@specverse/engines 4.1.12 → 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/inference/index.d.ts.map +1 -1
- package/dist/inference/index.js +1 -0
- package/dist/inference/index.js.map +1 -1
- package/dist/inference/logical/logical-engine.d.ts.map +1 -1
- package/dist/inference/logical/logical-engine.js +3 -0
- package/dist/inference/logical/logical-engine.js.map +1 -1
- package/dist/libs/instance-factories/cli/templates/commander/cli-entry-generator.js +12 -11
- package/dist/libs/instance-factories/cli/templates/commander/command-generator.js +21 -5
- 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/parser/processors/AttributeProcessor.d.ts.map +1 -1
- package/dist/parser/processors/AttributeProcessor.js +13 -6
- package/dist/parser/processors/AttributeProcessor.js.map +1 -1
- package/dist/parser/processors/ModelProcessor.d.ts.map +1 -1
- package/dist/parser/processors/ModelProcessor.js +7 -0
- package/dist/parser/processors/ModelProcessor.js.map +1 -1
- 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/cli-entry-generator.ts +12 -11
- package/libs/instance-factories/cli/templates/commander/command-generator.ts +21 -5
- 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
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { matchStep } from "./step-conventions.js";
|
|
2
|
+
async function generateAiBehaviors(context) {
|
|
3
|
+
const { controller, model } = context;
|
|
4
|
+
if (!controller?.actions) return "";
|
|
5
|
+
const modelName = model?.name || controller.model || "Model";
|
|
6
|
+
const modelVar = modelName.charAt(0).toLowerCase() + modelName.slice(1);
|
|
7
|
+
const unmatchedFunctions = [];
|
|
8
|
+
for (const [actionName, action] of Object.entries(controller.actions)) {
|
|
9
|
+
const steps = action.steps || [];
|
|
10
|
+
const parameterNames = Object.keys(action.parameters || {});
|
|
11
|
+
const preconditions = action.requires || action.preconditions || [];
|
|
12
|
+
const declaredVars = /* @__PURE__ */ new Set();
|
|
13
|
+
for (const pc of preconditions) {
|
|
14
|
+
const match = pc.match(/^(\w+)\s+(?:exists|is\s+\w+)$/i);
|
|
15
|
+
if (match) {
|
|
16
|
+
const entity = match[1];
|
|
17
|
+
declaredVars.add(entity.charAt(0).toLowerCase() + entity.slice(1));
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
for (let i = 0; i < steps.length; i++) {
|
|
21
|
+
const step = steps[i];
|
|
22
|
+
if (typeof step !== "string") continue;
|
|
23
|
+
const ctx = {
|
|
24
|
+
modelName,
|
|
25
|
+
prismaModel: modelVar,
|
|
26
|
+
serviceName: `${modelName}Controller`,
|
|
27
|
+
operationName: actionName,
|
|
28
|
+
stepNum: i + 1,
|
|
29
|
+
parameterNames,
|
|
30
|
+
declaredVars
|
|
31
|
+
};
|
|
32
|
+
const result = matchStep(step, ctx);
|
|
33
|
+
if (!result.matched && result.functionName) {
|
|
34
|
+
const existing = unmatchedFunctions.find((f) => f.functionName === result.functionName);
|
|
35
|
+
if (!existing) {
|
|
36
|
+
unmatchedFunctions.push({
|
|
37
|
+
functionName: result.functionName,
|
|
38
|
+
step,
|
|
39
|
+
operationName: actionName,
|
|
40
|
+
parameterNames,
|
|
41
|
+
inputs: result.inputs || []
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (unmatchedFunctions.length === 0) return "";
|
|
48
|
+
let aiService = null;
|
|
49
|
+
try {
|
|
50
|
+
const { BehaviorAIService } = await import("@specverse/engines/ai");
|
|
51
|
+
aiService = new BehaviorAIService();
|
|
52
|
+
if (!aiService.isAvailable) {
|
|
53
|
+
aiService = null;
|
|
54
|
+
} else {
|
|
55
|
+
aiService.startSession(`${modelName}Controller`);
|
|
56
|
+
}
|
|
57
|
+
} catch {
|
|
58
|
+
aiService = null;
|
|
59
|
+
}
|
|
60
|
+
const availableModels = context.spec?.models ? Object.keys(context.spec.models) : [];
|
|
61
|
+
const functions = [];
|
|
62
|
+
for (const { functionName, step, operationName, parameterNames, inputs } of unmatchedFunctions) {
|
|
63
|
+
const signature = inputs.length > 0 ? `input: { ${inputs.map((n) => `${n}: any`).join("; ")} }` : "input: Record<string, never>";
|
|
64
|
+
const destructure = inputs.length > 0 ? ` const { ${inputs.join(", ")} } = input;` : "";
|
|
65
|
+
let body = null;
|
|
66
|
+
let source = "STUB";
|
|
67
|
+
if (aiService) {
|
|
68
|
+
try {
|
|
69
|
+
body = await aiService.generateBehavior({
|
|
70
|
+
step,
|
|
71
|
+
modelName,
|
|
72
|
+
operationName,
|
|
73
|
+
functionName,
|
|
74
|
+
parameterNames: inputs,
|
|
75
|
+
// the actual inputs to the pure function
|
|
76
|
+
availableModels,
|
|
77
|
+
spec: context.spec
|
|
78
|
+
});
|
|
79
|
+
if (body) source = "AI-GENERATED";
|
|
80
|
+
} catch {
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (!body) {
|
|
84
|
+
body = ` throw new Error('Not implemented: ${functionName} \u2014 see behaviors/${modelName}Controller.ai.ts');`;
|
|
85
|
+
} else {
|
|
86
|
+
body = body.split("\n").map((line) => line ? " " + line : line).join("\n");
|
|
87
|
+
}
|
|
88
|
+
const inputsDoc = inputs.length > 0 ? ` * Inputs: ${inputs.join(", ")}
|
|
89
|
+
` : "";
|
|
90
|
+
functions.push(`/**
|
|
91
|
+
* ${functionName}
|
|
92
|
+
*
|
|
93
|
+
* Spec step: "${step}"
|
|
94
|
+
* Called by: ${modelName}Controller.${operationName}()
|
|
95
|
+
${inputsDoc} * Source: ${source}
|
|
96
|
+
* Generated: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}
|
|
97
|
+
*
|
|
98
|
+
* PURE FUNCTION \u2014 no database access, no event publishing, no external services.
|
|
99
|
+
* All data comes in via \`input\`; all effects happen in the calling controller.
|
|
100
|
+
* ${source === "AI-GENERATED" ? "AI-generated implementation. Review and test before deploying." : "STUB \u2014 Claude CLI unavailable. Install Claude Code or implement manually."}
|
|
101
|
+
*/
|
|
102
|
+
export async function ${functionName}(${signature}): Promise<any> {
|
|
103
|
+
${destructure ? destructure + "\n" : ""}${body}
|
|
104
|
+
}`);
|
|
105
|
+
}
|
|
106
|
+
if (aiService?.endSession) aiService.endSession();
|
|
107
|
+
return `/**
|
|
108
|
+
* ${modelName}Controller \u2014 AI-Generated Behaviors
|
|
109
|
+
*
|
|
110
|
+
* \u26A0\uFE0F THIS FILE CONTAINS STUBS FOR STEPS THAT NEED IMPLEMENTATION
|
|
111
|
+
*
|
|
112
|
+
* These functions could not be generated from convention patterns.
|
|
113
|
+
* They are called by ${modelName}Controller when executing custom actions.
|
|
114
|
+
*
|
|
115
|
+
* Options for each function:
|
|
116
|
+
* - Implement manually (recommended for business-critical logic)
|
|
117
|
+
* - Use AI generation: specverse ai generate <function>
|
|
118
|
+
* - Refactor the spec step to use a convention pattern
|
|
119
|
+
*
|
|
120
|
+
* Convention patterns that ARE auto-generated (no AI needed):
|
|
121
|
+
* "Find {Model} by {field}" \u2192 prisma.model.findUniqueOrThrow(...)
|
|
122
|
+
* "Create {Model}" \u2192 prisma.model.create(...)
|
|
123
|
+
* "Update {Model} {field} to {value}" \u2192 prisma.model.update(...)
|
|
124
|
+
* "Delete {Model}" \u2192 prisma.model.delete(...)
|
|
125
|
+
* "Transition {Model} to {state}" \u2192 prisma.model.update({ status: ... })
|
|
126
|
+
* "Count {Model}s per {Group}" \u2192 prisma.model.groupBy(...)
|
|
127
|
+
* See step-conventions.ts for the full list.
|
|
128
|
+
*
|
|
129
|
+
* Generated: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}
|
|
130
|
+
*/
|
|
131
|
+
|
|
132
|
+
import { PrismaClient } from '@prisma/client';
|
|
133
|
+
|
|
134
|
+
const prisma = new PrismaClient();
|
|
135
|
+
|
|
136
|
+
${functions.join("\n\n")}
|
|
137
|
+
`;
|
|
138
|
+
}
|
|
139
|
+
export {
|
|
140
|
+
generateAiBehaviors as default
|
|
141
|
+
};
|
|
@@ -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
|