@specverse/engines 4.1.5 → 4.1.6

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.
Files changed (120) hide show
  1. package/dist/libs/instance-factories/applications/templates/generic/backend-env-generator.js +22 -0
  2. package/dist/libs/instance-factories/applications/templates/generic/backend-package-json-generator.js +66 -0
  3. package/dist/libs/instance-factories/applications/templates/generic/backend-tsconfig-generator.js +54 -0
  4. package/dist/libs/instance-factories/applications/templates/generic/main-generator.js +290 -0
  5. package/dist/libs/instance-factories/applications/templates/react/_view-components-source.js +530 -0
  6. package/dist/libs/instance-factories/applications/templates/react/api-client-generator.js +437 -0
  7. package/dist/libs/instance-factories/applications/templates/react/api-types-generator.js +146 -0
  8. package/dist/libs/instance-factories/applications/templates/react/app-tsx-generator.js +73 -0
  9. package/dist/libs/instance-factories/applications/templates/react/env-example-generator.js +18 -0
  10. package/dist/libs/instance-factories/applications/templates/react/field-helpers-generator.js +99 -0
  11. package/dist/libs/instance-factories/applications/templates/react/gitignore-generator.js +35 -0
  12. package/dist/libs/instance-factories/applications/templates/react/index-css-generator.js +9 -0
  13. package/dist/libs/instance-factories/applications/templates/react/index-html-generator.js +23 -0
  14. package/dist/libs/instance-factories/applications/templates/react/main-tsx-generator.js +29 -0
  15. package/dist/libs/instance-factories/applications/templates/react/package-json-generator.js +49 -0
  16. package/dist/libs/instance-factories/applications/templates/react/pattern-adapter-generator.js +156 -0
  17. package/dist/libs/instance-factories/applications/templates/react/react-pattern-adapter.js +935 -0
  18. package/dist/libs/instance-factories/applications/templates/react/relationship-field-generator.js +143 -0
  19. package/dist/libs/instance-factories/applications/templates/react/runtime-app-tsx-generator.js +101 -0
  20. package/dist/libs/instance-factories/applications/templates/react/runtime-package-json-generator.js +50 -0
  21. package/dist/libs/instance-factories/applications/templates/react/tailwind-adapter-generator.js +646 -0
  22. package/dist/libs/instance-factories/applications/templates/react/tailwind-adapter-wrapper-generator.js +65 -0
  23. package/dist/libs/instance-factories/applications/templates/react/tsconfig-generator.js +28 -0
  24. package/dist/libs/instance-factories/applications/templates/react/use-api-hooks-generator.js +132 -0
  25. package/dist/libs/instance-factories/applications/templates/react/view-dashboard-generator.js +143 -0
  26. package/dist/libs/instance-factories/applications/templates/react/view-detail-generator.js +143 -0
  27. package/dist/libs/instance-factories/applications/templates/react/view-form-generator.js +355 -0
  28. package/dist/libs/instance-factories/applications/templates/react/view-list-generator.js +91 -0
  29. package/dist/libs/instance-factories/applications/templates/react/view-router-generator.js +79 -0
  30. package/dist/libs/instance-factories/applications/templates/react/vite-config-generator.js +42 -0
  31. package/dist/libs/instance-factories/cli/templates/commander/cli-bin-wrapper-generator.js +11 -0
  32. package/dist/libs/instance-factories/cli/templates/commander/cli-entry-generator.js +111 -0
  33. package/dist/libs/instance-factories/cli/templates/commander/command-generator.js +928 -0
  34. package/dist/libs/instance-factories/communication/templates/eventemitter/bus-generator.js +83 -0
  35. package/dist/libs/instance-factories/communication/templates/eventemitter/publisher-generator.js +91 -0
  36. package/dist/libs/instance-factories/communication/templates/eventemitter/subscriber-generator.js +86 -0
  37. package/dist/libs/instance-factories/controllers/templates/fastify/meta-routes-generator.js +93 -0
  38. package/dist/libs/instance-factories/controllers/templates/fastify/routes-generator.js +280 -0
  39. package/dist/libs/instance-factories/controllers/templates/fastify/server-generator.js +125 -0
  40. package/dist/libs/instance-factories/infrastructure/templates/docker-k8s/infrastructure-generator.js +25 -0
  41. package/dist/libs/instance-factories/orms/templates/prisma/schema-generator.js +371 -0
  42. package/dist/libs/instance-factories/orms/templates/prisma/services-generator.js +266 -0
  43. package/dist/libs/instance-factories/scaffolding/templates/generic/env-example-generator.js +51 -0
  44. package/dist/libs/instance-factories/scaffolding/templates/generic/env-generator.js +61 -0
  45. package/dist/libs/instance-factories/scaffolding/templates/generic/gitignore-generator.js +59 -0
  46. package/dist/libs/instance-factories/scaffolding/templates/generic/package-json-generator.js +126 -0
  47. package/dist/libs/instance-factories/scaffolding/templates/generic/readme-generator.js +159 -0
  48. package/dist/libs/instance-factories/scaffolding/templates/generic/tsconfig-generator.js +56 -0
  49. package/dist/libs/instance-factories/scaffolding/templates/generic/tsconfig-react-generator.js +37 -0
  50. package/dist/libs/instance-factories/sdks/templates/python/sdk-generator.js +29 -0
  51. package/dist/libs/instance-factories/sdks/templates/typescript/sdk-generator.js +28 -0
  52. package/dist/libs/instance-factories/services/templates/memory/generate-interpreter.js +14 -0
  53. package/dist/libs/instance-factories/services/templates/memory/step-conventions-memory.js +415 -0
  54. package/dist/libs/instance-factories/services/templates/prisma/behavior-generator.js +177 -0
  55. package/dist/libs/instance-factories/services/templates/prisma/controller-generator.js +413 -0
  56. package/dist/libs/instance-factories/services/templates/prisma/service-generator.js +243 -0
  57. package/dist/libs/instance-factories/services/templates/prisma/step-conventions.js +264 -0
  58. package/dist/libs/instance-factories/services/templates/shared-patterns.js +24 -0
  59. package/dist/libs/instance-factories/shared/path-resolver.js +59 -0
  60. package/dist/libs/instance-factories/storage/templates/mongodb/config-generator.js +13 -0
  61. package/dist/libs/instance-factories/storage/templates/mongodb/docker-generator.js +16 -0
  62. package/dist/libs/instance-factories/storage/templates/postgresql/config-generator.js +45 -0
  63. package/dist/libs/instance-factories/storage/templates/postgresql/docker-generator.js +46 -0
  64. package/dist/libs/instance-factories/storage/templates/redis/config-generator.js +14 -0
  65. package/dist/libs/instance-factories/storage/templates/redis/docker-generator.js +16 -0
  66. package/dist/libs/instance-factories/test-generation.js +145 -0
  67. package/dist/libs/instance-factories/testing/templates/vitest/tests-generator.js +30 -0
  68. package/dist/libs/instance-factories/tools/templates/mcp/mcp-server-generator.js +149 -0
  69. package/dist/libs/instance-factories/tools/templates/mcp/static/src/controllers/MCPServerController.js +232 -0
  70. package/dist/libs/instance-factories/tools/templates/mcp/static/src/events/EventEmitter.js +49 -0
  71. package/dist/libs/instance-factories/tools/templates/mcp/static/src/index.js +18 -0
  72. package/dist/libs/instance-factories/tools/templates/mcp/static/src/interfaces/ResourceProvider.js +0 -0
  73. package/dist/libs/instance-factories/tools/templates/mcp/static/src/models/LibrarySuggestion.js +97 -0
  74. package/dist/libs/instance-factories/tools/templates/mcp/static/src/models/SpecVerseResource.js +64 -0
  75. package/dist/libs/instance-factories/tools/templates/mcp/static/src/server/mcp-server.js +182 -0
  76. package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/CLIProxyService.js +1210 -0
  77. package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/EmbeddedResourcesAdapter.js +172 -0
  78. package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/EntityModuleService.js +240 -0
  79. package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/HybridResourcesProvider.js +147 -0
  80. package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/LibraryToolsService.js +281 -0
  81. package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/OrchestratorBridge.js +409 -0
  82. package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/OrchestratorToolsService.js +414 -0
  83. package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/PromptToolsService.js +467 -0
  84. package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/ResourcesProviderService.js +135 -0
  85. package/dist/libs/instance-factories/tools/templates/mcp/static/src/types/index.js +0 -0
  86. package/dist/libs/instance-factories/tools/templates/vscode/static/extension.js +965 -0
  87. package/dist/libs/instance-factories/tools/templates/vscode/vscode-extension-generator.js +238 -0
  88. package/dist/libs/instance-factories/validation/templates/zod/validation-generator.js +25 -0
  89. package/dist/libs/instance-factories/views/index.js +48 -0
  90. package/dist/libs/instance-factories/views/templates/react/adapters/antd-adapter.js +742 -0
  91. package/dist/libs/instance-factories/views/templates/react/adapters/mui-adapter.js +824 -0
  92. package/dist/libs/instance-factories/views/templates/react/adapters/shadcn-adapter.js +719 -0
  93. package/dist/libs/instance-factories/views/templates/react/app-generator.js +45 -0
  94. package/dist/libs/instance-factories/views/templates/react/components-generator.js +779 -0
  95. package/dist/libs/instance-factories/views/templates/react/forms-generator.js +285 -0
  96. package/dist/libs/instance-factories/views/templates/react/frontend-package-json-generator.js +46 -0
  97. package/dist/libs/instance-factories/views/templates/react/hooks-generator.js +111 -0
  98. package/dist/libs/instance-factories/views/templates/react/index-css-generator.js +9 -0
  99. package/dist/libs/instance-factories/views/templates/react/index-html-generator.js +23 -0
  100. package/dist/libs/instance-factories/views/templates/react/main-tsx-generator.js +21 -0
  101. package/dist/libs/instance-factories/views/templates/react/react-component-generator.js +299 -0
  102. package/dist/libs/instance-factories/views/templates/react/router-generator.js +136 -0
  103. package/dist/libs/instance-factories/views/templates/react/router-generic-generator.js +107 -0
  104. package/dist/libs/instance-factories/views/templates/react/shared-utils-generator.js +179 -0
  105. package/dist/libs/instance-factories/views/templates/react/spec-json-generator.js +7 -0
  106. package/dist/libs/instance-factories/views/templates/react/types-generator.js +56 -0
  107. package/dist/libs/instance-factories/views/templates/react/views-metadata-generator.js +27 -0
  108. package/dist/libs/instance-factories/views/templates/react/vite-config-generator.js +29 -0
  109. package/dist/libs/instance-factories/views/templates/runtime/runtime-view-renderer.js +261 -0
  110. package/dist/libs/instance-factories/views/templates/shared/adapter-types.js +34 -0
  111. package/dist/libs/instance-factories/views/templates/shared/atomic-components-registry.js +800 -0
  112. package/dist/libs/instance-factories/views/templates/shared/base-generator.js +305 -0
  113. package/dist/libs/instance-factories/views/templates/shared/component-metadata.js +517 -0
  114. package/dist/libs/instance-factories/views/templates/shared/composite-pattern-types.js +0 -0
  115. package/dist/libs/instance-factories/views/templates/shared/composite-patterns.js +445 -0
  116. package/dist/libs/instance-factories/views/templates/shared/index.js +80 -0
  117. package/dist/libs/instance-factories/views/templates/shared/pattern-validator.js +210 -0
  118. package/dist/libs/instance-factories/views/templates/shared/property-mapper.js +492 -0
  119. package/dist/libs/instance-factories/views/templates/shared/syntax-mapper.js +321 -0
  120. package/package.json +3 -2
@@ -0,0 +1,413 @@
1
+ function generatePrismaController(context) {
2
+ const { controller, model, spec, models: allModels } = context;
3
+ if (!controller) {
4
+ throw new Error("Controller is required in template context");
5
+ }
6
+ if (!model) {
7
+ throw new Error("Model is required for controller generation");
8
+ }
9
+ const controllerName = controller.name;
10
+ const modelName = model.name;
11
+ const rawModelVar = modelName.charAt(0).toLowerCase() + modelName.slice(1);
12
+ 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"]);
13
+ const modelVar = RESERVED_WORDS.has(rawModelVar) ? `${rawModelVar}Item` : rawModelVar;
14
+ const curedOps = controller.cured || {};
15
+ const idAttr = (Array.isArray(model.attributes) ? model.attributes : Object.values(model.attributes || {})).find((a) => a.name === "id");
16
+ const idType = idAttr?.type || "UUID";
17
+ const needsIntParse = idType === "Integer" || idType === "Int" || idType === "Number";
18
+ return `/**
19
+ * ${controllerName}
20
+ * Model-specific business logic for ${modelName}
21
+ * ${controller.description || ""}
22
+ */
23
+
24
+ import { PrismaClient } from '@prisma/client';
25
+ ${hasEventPublishing(curedOps, controller) ? `import { eventBus, EventName } from '../events/eventBus.js';` : ""}
26
+
27
+ const prisma = new PrismaClient();
28
+
29
+ /** Parse ID from string to the correct type for this model */
30
+ function parseId(id: string): ${needsIntParse ? "number" : "string"} {
31
+ ${needsIntParse ? "return parseInt(id, 10);" : "return id;"}
32
+ }
33
+
34
+ /**
35
+ * ${controllerName} class
36
+ */
37
+ export class ${controllerName} {
38
+ ${generateValidateMethod(model, modelName)}
39
+ ${curedOps.create ? generateCreateMethod(model, modelName, modelVar, controller, allModels) : ""}
40
+ ${curedOps.retrieve ? generateRetrieveMethod(model, modelName, modelVar) : ""}
41
+ ${curedOps.update ? generateUpdateMethod(model, modelName, modelVar, controller, allModels) : ""}
42
+ ${curedOps.evolve ? generateEvolveMethod(model, modelName, modelVar, controller) : ""}
43
+ ${curedOps.delete ? generateDeleteMethod(model, modelName, modelVar, controller) : ""}
44
+ ${generateCustomActions(controller, modelName, modelVar)}
45
+ }
46
+
47
+ // Export singleton instance
48
+ export const ${modelVar}Controller = new ${controllerName}();
49
+ export default ${modelVar}Controller;
50
+ `;
51
+ }
52
+ function generateValidateMethod(model, modelName) {
53
+ return `
54
+ /**
55
+ * Validate ${modelName} data
56
+ * Unified validation method for all operations
57
+ */
58
+ public validate(
59
+ _data: any,
60
+ _context: { operation: 'create' | 'update' | 'evolve' }
61
+ ): { valid: boolean; errors: string[] } {
62
+ const errors: string[] = [];
63
+
64
+ ${generateValidationLogic(model, "_data", "_context")}
65
+
66
+ return {
67
+ valid: errors.length === 0,
68
+ errors
69
+ };
70
+ }
71
+ `;
72
+ }
73
+ function generateValidationLogic(model, dataParam = "_data", contextParam = "_context") {
74
+ if (!model.attributes) return "// No validation rules defined";
75
+ const validations = [];
76
+ const attrList = Array.isArray(model.attributes) ? model.attributes.map((a) => [a.name, a]) : Object.entries(model.attributes);
77
+ const AUTO_FIELDS = /* @__PURE__ */ new Set(["id", "createdAt", "updatedAt", "created_at", "updated_at", "createdBy", "updatedBy"]);
78
+ attrList.forEach(([name, attr]) => {
79
+ if (attr.required && !attr.auto && !AUTO_FIELDS.has(name)) {
80
+ validations.push(`
81
+ // ${name} is required
82
+ if (${contextParam}.operation === 'create' && !${dataParam}.${name}) {
83
+ errors.push('${name} is required');
84
+ }`);
85
+ }
86
+ if (attr.type === "String" || attr.type === "string") {
87
+ if (attr.min) {
88
+ validations.push(`
89
+ if (${dataParam}.${name} && ${dataParam}.${name}.length < ${attr.min}) {
90
+ errors.push('${name} must be at least ${attr.min} characters');
91
+ }`);
92
+ }
93
+ if (attr.max) {
94
+ validations.push(`
95
+ if (${dataParam}.${name} && ${dataParam}.${name}.length > ${attr.max}) {
96
+ errors.push('${name} must be at most ${attr.max} characters');
97
+ }`);
98
+ }
99
+ }
100
+ if (attr.values && Array.isArray(attr.values)) {
101
+ const values = attr.values.map((v) => `'${v}'`).join(", ");
102
+ validations.push(`
103
+ if (${dataParam}.${name} && ![${values}].includes(${dataParam}.${name})) {
104
+ errors.push('${name} must be one of: ${attr.values.join(", ")}');
105
+ }`);
106
+ }
107
+ if (attr.format === "email") {
108
+ validations.push(`
109
+ if (${dataParam}.${name} && !/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(${dataParam}.${name})) {
110
+ errors.push('${name} must be a valid email address');
111
+ }`);
112
+ }
113
+ });
114
+ return validations.join("\n") || "// No validation rules defined";
115
+ }
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;
119
+ return `
120
+ /**
121
+ * Create a new ${modelName}
122
+ */
123
+ public async create(data: any): Promise<any> {
124
+ // Validate input
125
+ const validationResult = this.validate(data, { operation: 'create' });
126
+ if (!validationResult.valid) {
127
+ throw new Error(\`Validation failed: \${validationResult.errors.join(', ')}\`);
128
+ }
129
+
130
+ // Transform FK fields to Prisma connect format
131
+ const prismaData = { ...data };
132
+ ${generateFKTransform(model, "prismaData", allModels)}
133
+
134
+ // Create record
135
+ const ${modelVar} = await prisma.${modelVar}.create({
136
+ data: prismaData${generateIncludeRelationships(model)}
137
+ });
138
+
139
+ ${createEvent ? `
140
+ // Publish event
141
+ eventBus.publish(EventName.${createEvent}, {
142
+ ${modelVar},
143
+ timestamp: new Date().toISOString()
144
+ });
145
+ ` : ""}
146
+
147
+ return ${modelVar};
148
+ }
149
+ `;
150
+ }
151
+ function generateRetrieveMethod(model, modelName, modelVar) {
152
+ return `
153
+ /**
154
+ * Retrieve ${modelName} by ID
155
+ */
156
+ public async retrieve(id: string): Promise<any> {
157
+ const ${modelVar} = await prisma.${modelVar}.findUnique({
158
+ where: { id: parseId(id) }${generateIncludeRelationships(model)}
159
+ });
160
+
161
+ if (!${modelVar}) {
162
+ throw new Error('${modelName} not found');
163
+ }
164
+
165
+ return ${modelVar};
166
+ }
167
+
168
+ /**
169
+ * Retrieve all ${modelName}s
170
+ */
171
+ public async retrieveAll(options: { skip?: number; take?: number } = {}): Promise<any[]> {
172
+ return await prisma.${modelVar}.findMany({
173
+ skip: options.skip,
174
+ take: options.take${generateIncludeRelationships(model)}
175
+ });
176
+ }
177
+ `;
178
+ }
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;
182
+ return `
183
+ /**
184
+ * Update ${modelName}
185
+ */
186
+ public async update(id: string, data: any): Promise<any> {
187
+ // Validate input
188
+ const validationResult = this.validate(data, { operation: 'update' });
189
+ if (!validationResult.valid) {
190
+ throw new Error(\`Validation failed: \${validationResult.errors.join(', ')}\`);
191
+ }
192
+
193
+ // Strip nested relations and id \u2014 only send scalar fields to Prisma
194
+ const updateData: any = {};
195
+ for (const [key, value] of Object.entries(data)) {
196
+ if (key === 'id') continue;
197
+ if (Array.isArray(value)) continue;
198
+ if (value !== null && typeof value === 'object' && !(value instanceof Date)) continue;
199
+ updateData[key] = value;
200
+ }
201
+
202
+ // Transform FK fields to Prisma connect format
203
+ ${generateFKTransform(model, "updateData", allModels)}
204
+
205
+ // Update record
206
+ const ${modelVar} = await prisma.${modelVar}.update({
207
+ where: { id: parseId(id) },
208
+ data: updateData${generateIncludeRelationships(model)}
209
+ });
210
+
211
+ ${updateEvent ? `
212
+ // Publish event
213
+ eventBus.publish(EventName.${updateEvent}, {
214
+ ${modelVar},
215
+ timestamp: new Date().toISOString()
216
+ });
217
+ ` : ""}
218
+
219
+ return ${modelVar};
220
+ }
221
+ `;
222
+ }
223
+ function generateEvolveMethod(model, modelName, modelVar, controller) {
224
+ const lifecycles = Array.isArray(model.lifecycles) ? model.lifecycles : model.lifecycles ? Object.entries(model.lifecycles).map(([name, lc]) => ({ name, ...lc })) : [];
225
+ const lifecycle = lifecycles[0];
226
+ const lifecycleName = lifecycle?.name || "status";
227
+ const states = lifecycle?.states || [];
228
+ const validTransitions = {};
229
+ if (states.length > 1) {
230
+ for (let i = 0; i < states.length - 1; i++) {
231
+ validTransitions[states[i]] = [states[i + 1]];
232
+ }
233
+ }
234
+ if (lifecycle?.transitions) {
235
+ const transitions = Array.isArray(lifecycle.transitions) ? lifecycle.transitions : Object.entries(lifecycle.transitions).map(([name, t]) => ({ name, ...t }));
236
+ for (const t of transitions) {
237
+ const fromStates = Array.isArray(t.from) ? t.from : [t.from];
238
+ for (const from of fromStates) {
239
+ if (!validTransitions[from]) validTransitions[from] = [];
240
+ if (!validTransitions[from].includes(t.to)) validTransitions[from].push(t.to);
241
+ }
242
+ }
243
+ }
244
+ return `
245
+ /**
246
+ * Evolve ${modelName} through lifecycle
247
+ * States: ${states.join(" \u2192 ")}
248
+ */
249
+ public async evolve(id: string, data: any): Promise<any> {
250
+ // Validate input
251
+ const validationResult = this.validate(data, { operation: 'evolve' });
252
+ if (!validationResult.valid) {
253
+ throw new Error(\`Validation failed: \${validationResult.errors.join(', ')}\`);
254
+ }
255
+
256
+ // Get current record to check lifecycle state
257
+ const current = await prisma.${modelVar}.findUnique({ where: { id: parseId(id) } });
258
+ if (!current) {
259
+ throw new Error('${modelName} not found');
260
+ }
261
+
262
+ ${states.length > 0 ? `
263
+ // Validate lifecycle transition
264
+ const currentState = (current as any).${lifecycleName};
265
+ const newState = data.${lifecycleName};
266
+ if (newState) {
267
+ const validTransitions: Record<string, string[]> = ${JSON.stringify(validTransitions)};
268
+ const allowed = validTransitions[currentState] || [];
269
+ if (!allowed.includes(newState)) {
270
+ throw new Error(\`Invalid transition: \${currentState} \u2192 \${newState}. Allowed: \${allowed.join(', ') || 'none'}\`);
271
+ }
272
+ }
273
+ ` : ""}
274
+
275
+ // Update record
276
+ const ${modelVar} = await prisma.${modelVar}.update({
277
+ where: { id: parseId(id) },
278
+ data${generateIncludeRelationships(model)}
279
+ });
280
+
281
+ return ${modelVar};
282
+ }
283
+ `;
284
+ }
285
+ function generateDeleteMethod(model, modelName, modelVar, controller) {
286
+ const hasEvents = controller.publishes && Array.isArray(controller.publishes);
287
+ const deleteEvent = hasEvents ? controller.publishes.find((e) => e.includes("Deleted")) : null;
288
+ return `
289
+ /**
290
+ * Delete ${modelName}
291
+ */
292
+ public async delete(id: string): Promise<void> {
293
+ ${deleteEvent ? `
294
+ // Get record before deletion for event
295
+ const ${modelVar} = await prisma.${modelVar}.findUnique({ where: { id: parseId(id) } });
296
+ ` : ""}
297
+
298
+ await prisma.${modelVar}.delete({
299
+ where: { id: parseId(id) }
300
+ });
301
+
302
+ ${deleteEvent ? `
303
+ // Publish event
304
+ if (${modelVar}) {
305
+ eventBus.publish(EventName.${deleteEvent}, {
306
+ ${modelVar},
307
+ timestamp: new Date().toISOString()
308
+ });
309
+ }
310
+ ` : ""}
311
+ }
312
+ `;
313
+ }
314
+ function generateCustomActions(controller, modelName, modelVar) {
315
+ if (!controller.actions || Object.keys(controller.actions).length === 0) {
316
+ return "";
317
+ }
318
+ const actions = [];
319
+ Object.entries(controller.actions).forEach(([actionName, action]) => {
320
+ actions.push(`
321
+ /**
322
+ * ${actionName}
323
+ * ${action.description || ""}
324
+ */
325
+ public async ${actionName}(${generateActionParams(action)}): Promise<any> {
326
+ // TODO: Implement ${actionName} logic
327
+ throw new Error('${actionName} not implemented');
328
+ }`);
329
+ });
330
+ return actions.join("\n");
331
+ }
332
+ function generateActionParams(action) {
333
+ if (!action.parameters || Object.keys(action.parameters).length === 0) {
334
+ return "";
335
+ }
336
+ const params = Object.entries(action.parameters).map(([name, param]) => {
337
+ const optional = !param.required;
338
+ return `${name}${optional ? "?" : ""}: ${mapTypeToTypeScript(param.type)}`;
339
+ });
340
+ return params.join(", ");
341
+ }
342
+ function mapTypeToTypeScript(type) {
343
+ const typeMap = {
344
+ String: "string",
345
+ Integer: "number",
346
+ Float: "number",
347
+ Boolean: "boolean",
348
+ Date: "Date",
349
+ DateTime: "Date",
350
+ UUID: "string",
351
+ JSON: "any"
352
+ };
353
+ return typeMap[type] || "any";
354
+ }
355
+ function generateFKTransform(model, varName = "prismaData", allModels) {
356
+ const rels = Array.isArray(model.relationships) ? model.relationships : Object.values(model.relationships || {});
357
+ const belongsToRels = rels.filter((r) => r.type === "belongsTo");
358
+ if (belongsToRels.length === 0) return "";
359
+ return belongsToRels.map((rel) => {
360
+ const relName = rel.name;
361
+ const fkField = `${relName}Id`;
362
+ let parseExpr = `${varName}.${fkField}`;
363
+ if (allModels) {
364
+ const targetModel = allModels.find((m) => m.name === rel.target);
365
+ if (targetModel) {
366
+ const idAttr = (Array.isArray(targetModel.attributes) ? targetModel.attributes : Object.values(targetModel.attributes || {})).find((a) => a.name === "id");
367
+ const idType = idAttr?.type || "String";
368
+ if (idType === "Integer" || idType === "Int" || idType === "Number") {
369
+ parseExpr = `parseInt(${varName}.${fkField}, 10)`;
370
+ }
371
+ }
372
+ }
373
+ return `if (${varName}.${fkField}) {
374
+ ${varName}.${relName} = { connect: { id: ${parseExpr} } };
375
+ delete ${varName}.${fkField};
376
+ }`;
377
+ }).join("\n ");
378
+ }
379
+ function generateIncludeRelationships(model) {
380
+ if (!model.relationships) {
381
+ return "";
382
+ }
383
+ let relationshipKeys;
384
+ if (Array.isArray(model.relationships)) {
385
+ relationshipKeys = model.relationships.map((rel) => {
386
+ if (typeof rel === "string") {
387
+ return rel;
388
+ } else if (typeof rel === "object" && rel.name) {
389
+ return rel.name;
390
+ } else {
391
+ return null;
392
+ }
393
+ }).filter((rel) => rel !== null);
394
+ } else if (typeof model.relationships === "object") {
395
+ relationshipKeys = Object.keys(model.relationships);
396
+ } else {
397
+ return "";
398
+ }
399
+ if (relationshipKeys.length === 0) {
400
+ return "";
401
+ }
402
+ const includes = relationshipKeys.map((rel) => ` ${rel}: true`).join(",\n");
403
+ return `,
404
+ include: {
405
+ ${includes}
406
+ }`;
407
+ }
408
+ function hasEventPublishing(curedOps, controller) {
409
+ return controller.publishes && Array.isArray(controller.publishes) && controller.publishes.length > 0;
410
+ }
411
+ export {
412
+ generatePrismaController as default
413
+ };
@@ -0,0 +1,243 @@
1
+ import { generateBehaviorWithHelpers } from "./behavior-generator.js";
2
+ function generatePrismaService(context) {
3
+ const { service, spec } = context;
4
+ if (!service) {
5
+ throw new Error("Service is required in template context");
6
+ }
7
+ const serviceName = service.name;
8
+ const hasEvents = service.publishes && service.publishes.length > 0 || service.subscribes && service.subscribes.length > 0;
9
+ return `/**
10
+ * ${serviceName}
11
+ * Abstract business logic service
12
+ * ${service.description || ""}
13
+ */
14
+
15
+ import { PrismaClient } from '@prisma/client';
16
+ ${hasEvents ? `import { eventBus, EventName } from '../events/eventBus.js';` : ""}
17
+
18
+ const prisma = new PrismaClient();
19
+
20
+ /**
21
+ * ${serviceName} class
22
+ */
23
+ export class ${serviceName} {
24
+ ${generateConstructor(service)}
25
+ ${generateOperationsWithHelpers(service)}
26
+ ${generateEventSubscriptions(service)}
27
+ }
28
+
29
+ // Export singleton instance
30
+ export const ${serviceName.charAt(0).toLowerCase() + serviceName.slice(1)} = new ${serviceName}();
31
+ export default ${serviceName.charAt(0).toLowerCase() + serviceName.slice(1)};
32
+ `;
33
+ }
34
+ function generateConstructor(service) {
35
+ const hasSubscriptions = service.subscribes && service.subscribes.length > 0;
36
+ if (!hasSubscriptions) {
37
+ return "";
38
+ }
39
+ return `
40
+ private unsubscribers: Array<() => void> = [];
41
+
42
+ constructor() {
43
+ // Subscribe to events
44
+ this.setupEventSubscriptions();
45
+ }
46
+
47
+ /**
48
+ * Setup event subscriptions
49
+ */
50
+ private setupEventSubscriptions(): void {
51
+ ${service.subscribes.map((eventName) => `
52
+ // Subscribe to ${eventName}
53
+ const unsubscribe${eventName} = eventBus.subscribe(
54
+ EventName.${eventName},
55
+ this.on${eventName}.bind(this)
56
+ );
57
+ this.unsubscribers.push(unsubscribe${eventName});
58
+ `).join("\n")}
59
+ }
60
+
61
+ /**
62
+ * Cleanup subscriptions
63
+ */
64
+ public destroy(): void {
65
+ this.unsubscribers.forEach(unsubscribe => unsubscribe());
66
+ this.unsubscribers = [];
67
+ }
68
+ `;
69
+ }
70
+ function generateOperationsWithHelpers(service) {
71
+ const ops = generateOperations(service);
72
+ if (_collectedHelpers.length > 0) {
73
+ const helpers = [...new Set(_collectedHelpers)].join("\n");
74
+ _collectedHelpers.length = 0;
75
+ return ops + "\n" + helpers;
76
+ }
77
+ return ops;
78
+ }
79
+ const _collectedHelpers = [];
80
+ function generateOperations(service) {
81
+ if (!service.operations || Array.isArray(service.operations) && service.operations.length === 0 || !Array.isArray(service.operations) && Object.keys(service.operations).length === 0) {
82
+ return `
83
+ /**
84
+ * Execute service logic
85
+ */
86
+ public async execute(params: any): Promise<any> {
87
+ // TODO: Implement service logic
88
+ throw new Error('Service logic not implemented');
89
+ }
90
+ `;
91
+ }
92
+ const entries = Array.isArray(service.operations) ? service.operations.map((op) => [op.name, op]) : Object.entries(service.operations);
93
+ const operations = [];
94
+ entries.forEach(([operationName, operation]) => {
95
+ operations.push(generateOperation(operationName, operation, service));
96
+ });
97
+ return operations.join("\n");
98
+ }
99
+ function generateOperation(operationName, operation, service) {
100
+ const params = generateOperationParams(operation);
101
+ const hasPublish = service.publishes && service.publishes.length > 0;
102
+ return `
103
+ /**
104
+ * ${operationName}
105
+ * ${operation.description || ""}
106
+ */
107
+ public async ${operationName}(${params}): Promise<any> {
108
+ try {
109
+ ${generateOperationLogic(operation, service)}
110
+
111
+ ${hasPublish ? `
112
+ // Publish event (example)
113
+ // eventBus.publish(EventName.SomeEvent, {
114
+ // operation: '${operationName}',
115
+ // timestamp: new Date().toISOString()
116
+ // });
117
+ ` : ""}
118
+
119
+ // Return result
120
+ return { success: true };
121
+ } catch (error) {
122
+ console.error(\`[${service.name}] ${operationName} failed:\`, error);
123
+ throw error;
124
+ }
125
+ }
126
+ `;
127
+ }
128
+ function generateOperationParams(operation) {
129
+ if (!operation.parameters || Object.keys(operation.parameters).length === 0) {
130
+ return "params: any = {}";
131
+ }
132
+ const params = Object.entries(operation.parameters).map(([name, param]) => {
133
+ const optional = !param.required;
134
+ return `${name}${optional ? "?" : ""}: ${mapTypeToTypeScript(param.type)}`;
135
+ });
136
+ return params.join(", ");
137
+ }
138
+ function generateOperationLogic(operation, service) {
139
+ const impl = operation.implementation || {};
140
+ const meta = operation.metadata;
141
+ if (!impl.preconditions && operation.requires) {
142
+ impl.preconditions = Array.isArray(operation.requires) ? operation.requires : [operation.requires];
143
+ }
144
+ if (!impl.postconditions && operation.ensures) {
145
+ impl.postconditions = Array.isArray(operation.ensures) ? operation.ensures : [operation.ensures];
146
+ }
147
+ if (!impl.sideEffects && operation.publishes) {
148
+ impl.sideEffects = (Array.isArray(operation.publishes) ? operation.publishes : [operation.publishes]).map((e) => `Publish event: ${e}`);
149
+ }
150
+ if (impl.preconditions?.length || impl.postconditions?.length || impl.steps?.length || impl.transactional) {
151
+ const modelName = inferModelFromServiceName(service.name);
152
+ const context = {
153
+ modelName,
154
+ serviceName: service.name,
155
+ operationName: operation.name || "execute",
156
+ prismaModel: modelName
157
+ };
158
+ const behavior = {
159
+ preconditions: impl.preconditions || [],
160
+ postconditions: impl.postconditions || [],
161
+ sideEffects: impl.sideEffects || [],
162
+ steps: impl.steps || [],
163
+ transactional: impl.transactional || false
164
+ };
165
+ const opMeta = {
166
+ async: meta?.async ?? true,
167
+ cacheable: meta?.cacheable ?? false,
168
+ idempotent: meta?.idempotent ?? false
169
+ };
170
+ const result = generateBehaviorWithHelpers(behavior, opMeta, context);
171
+ _collectedHelpers.push(...result.helperMethods);
172
+ return result.body;
173
+ }
174
+ if (operation.description) {
175
+ return `// ${operation.description}`;
176
+ }
177
+ return `// TODO: Implement operation logic`;
178
+ }
179
+ function inferModelFromServiceName(serviceName) {
180
+ return serviceName.replace(/RelationshipService$/, "").replace(/Service$/, "") || "Entity";
181
+ }
182
+ function generateEventSubscriptions(service) {
183
+ if (!service.subscribes || service.subscribes.length === 0) {
184
+ return "";
185
+ }
186
+ const handlers = [];
187
+ service.subscribes.forEach((eventName) => {
188
+ handlers.push(`
189
+ /**
190
+ * Handle ${eventName} event
191
+ */
192
+ private async on${eventName}(payload: any): Promise<void> {
193
+ try {
194
+ console.log(\`[${service.name}] Received ${eventName} event\`, payload);
195
+
196
+ // TODO: Implement event handling logic
197
+ ${generateEventHandlerLogic(eventName, service)}
198
+
199
+ console.log(\`[${service.name}] ${eventName} event processed successfully\`);
200
+ } catch (error) {
201
+ console.error(\`[${service.name}] Failed to process ${eventName} event\`, error);
202
+ // TODO: Add error handling (retry, dead letter queue, etc.)
203
+ }
204
+ }`);
205
+ });
206
+ return handlers.join("\n");
207
+ }
208
+ function generateEventHandlerLogic(eventName, service) {
209
+ return `
210
+ // Example: Update related records
211
+ // await prisma.someModel.update({
212
+ // where: { id: payload.id },
213
+ // data: { ... }
214
+ // });
215
+
216
+ // Example: Trigger workflows
217
+ // await this.triggerWorkflow(payload);
218
+
219
+ // Example: Publish follow-up events
220
+ // eventBus.publish(EventName.AnotherEvent, {
221
+ // original: payload,
222
+ // processed: true
223
+ // });
224
+ `;
225
+ }
226
+ function mapTypeToTypeScript(type) {
227
+ const typeMap = {
228
+ String: "string",
229
+ Integer: "number",
230
+ Float: "number",
231
+ Boolean: "boolean",
232
+ Date: "Date",
233
+ DateTime: "Date",
234
+ UUID: "string",
235
+ JSON: "any",
236
+ Array: "any[]",
237
+ Object: "Record<string, any>"
238
+ };
239
+ return typeMap[type] || "any";
240
+ }
241
+ export {
242
+ generatePrismaService as default
243
+ };