@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.
Files changed (68) hide show
  1. package/assets/prompts/core/standard/v9/behavior.prompt.yaml +126 -0
  2. package/dist/ai/behavior-ai-service.d.ts +65 -0
  3. package/dist/ai/behavior-ai-service.d.ts.map +1 -0
  4. package/dist/ai/behavior-ai-service.js +205 -0
  5. package/dist/ai/behavior-ai-service.js.map +1 -0
  6. package/dist/ai/index.d.ts +27 -0
  7. package/dist/ai/index.d.ts.map +1 -1
  8. package/dist/ai/index.js +30 -0
  9. package/dist/ai/index.js.map +1 -1
  10. package/dist/ai/prompt-loader.js +2 -2
  11. package/dist/inference/quint-transpiler.d.ts.map +1 -1
  12. package/dist/inference/quint-transpiler.js +204 -4
  13. package/dist/inference/quint-transpiler.js.map +1 -1
  14. package/dist/libs/instance-factories/applications/templates/generic/backend-package-json-generator.js +4 -1
  15. package/dist/libs/instance-factories/applications/templates/generic/backend-tsconfig-generator.js +2 -2
  16. package/dist/libs/instance-factories/applications/templates/react/runtime-package-json-generator.js +1 -0
  17. package/dist/libs/instance-factories/cli/templates/commander/command-generator.js +97 -22
  18. package/dist/libs/instance-factories/communication/templates/eventemitter/bus-generator.js +31 -31
  19. package/dist/libs/instance-factories/communication/templates/eventemitter/types-generator.js +79 -0
  20. package/dist/libs/instance-factories/communication/templates/eventemitter/websocket-bridge-generator.js +96 -0
  21. package/dist/libs/instance-factories/controllers/templates/fastify/routes-generator.js +45 -9
  22. package/dist/libs/instance-factories/controllers/templates/fastify/server-generator.js +20 -2
  23. package/dist/libs/instance-factories/scaffolding/templates/generic/tsconfig-generator.js +10 -2
  24. package/dist/libs/instance-factories/services/templates/prisma/ai-behaviors-generator.js +249 -0
  25. package/dist/libs/instance-factories/services/templates/prisma/behavior-generator.js +72 -45
  26. package/dist/libs/instance-factories/services/templates/prisma/controller-generator.js +61 -54
  27. package/dist/libs/instance-factories/services/templates/prisma/service-generator.js +31 -10
  28. package/dist/libs/instance-factories/services/templates/prisma/step-conventions.js +101 -84
  29. package/dist/libs/instance-factories/views/templates/react/components-generator.js +40 -10
  30. package/dist/realize/index.d.ts.map +1 -1
  31. package/dist/realize/index.js +192 -23
  32. package/dist/realize/index.js.map +1 -1
  33. package/libs/instance-factories/applications/templates/generic/backend-package-json-generator.ts +4 -1
  34. package/libs/instance-factories/applications/templates/generic/backend-tsconfig-generator.ts +2 -2
  35. package/libs/instance-factories/applications/templates/react/runtime-package-json-generator.ts +6 -1
  36. package/libs/instance-factories/cli/templates/commander/command-generator.ts +115 -22
  37. package/libs/instance-factories/communication/event-emitter.yaml +16 -12
  38. package/libs/instance-factories/communication/templates/eventemitter/bus-generator.ts +33 -36
  39. package/libs/instance-factories/communication/templates/eventemitter/types-generator.ts +95 -0
  40. package/libs/instance-factories/communication/templates/eventemitter/websocket-bridge-generator.ts +105 -0
  41. package/libs/instance-factories/controllers/templates/fastify/routes-generator.ts +57 -11
  42. package/libs/instance-factories/controllers/templates/fastify/server-generator.ts +23 -2
  43. package/libs/instance-factories/scaffolding/templates/generic/tsconfig-generator.ts +23 -2
  44. package/libs/instance-factories/services/templates/prisma/ai-behaviors-generator.ts +376 -0
  45. package/libs/instance-factories/services/templates/prisma/behavior-generator.ts +116 -45
  46. package/libs/instance-factories/services/templates/prisma/controller-generator.ts +83 -59
  47. package/libs/instance-factories/services/templates/prisma/service-generator.ts +40 -10
  48. package/libs/instance-factories/services/templates/prisma/step-conventions.ts +169 -85
  49. package/libs/instance-factories/views/templates/react/components-generator.ts +50 -10
  50. package/package.json +1 -1
  51. package/dist/libs/instance-factories/tools/templates/mcp/static/src/controllers/MCPServerController.js +0 -232
  52. package/dist/libs/instance-factories/tools/templates/mcp/static/src/events/EventEmitter.js +0 -49
  53. package/dist/libs/instance-factories/tools/templates/mcp/static/src/index.js +0 -18
  54. package/dist/libs/instance-factories/tools/templates/mcp/static/src/interfaces/ResourceProvider.js +0 -0
  55. package/dist/libs/instance-factories/tools/templates/mcp/static/src/models/LibrarySuggestion.js +0 -97
  56. package/dist/libs/instance-factories/tools/templates/mcp/static/src/models/SpecVerseResource.js +0 -64
  57. package/dist/libs/instance-factories/tools/templates/mcp/static/src/server/mcp-server.js +0 -182
  58. package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/CLIProxyService.js +0 -1210
  59. package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/EmbeddedResourcesAdapter.js +0 -172
  60. package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/EntityModuleService.js +0 -240
  61. package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/HybridResourcesProvider.js +0 -147
  62. package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/LibraryToolsService.js +0 -281
  63. package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/OrchestratorBridge.js +0 -409
  64. package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/OrchestratorToolsService.js +0 -414
  65. package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/PromptToolsService.js +0 -467
  66. package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/ResourcesProviderService.js +0 -135
  67. package/dist/libs/instance-factories/tools/templates/mcp/static/src/types/index.js +0 -0
  68. package/dist/libs/instance-factories/tools/templates/vscode/static/extension.js +0 -965
@@ -1,20 +1,26 @@
1
1
  function generateEventBus(context) {
2
2
  const { spec } = context;
3
- const events = spec.events ? Object.keys(spec.events) : [];
3
+ const hasEvents = spec.events && Object.keys(spec.events).length > 0;
4
+ const models = spec.models ? Object.keys(spec.models) : [];
4
5
  return `/**
5
6
  * Event Bus
6
- * In-memory event bus using EventEmitter3
7
+ * Typed in-memory event bus using EventEmitter3
7
8
  * Generated from SpecVerse specification
8
9
  */
9
10
 
10
- import EventEmitter from 'eventemitter3';
11
+ import { EventEmitter } from 'eventemitter3';
12
+ ${hasEvents || models.length > 0 ? `import type { EventPayloads } from './event-types.js';` : ""}
11
13
 
12
- // Event type definitions
13
- ${events.map((event) => `export type ${event}Payload = any; // TODO: Define payload type`).join("\n")}
14
+ // Re-export types for consumers
15
+ ${hasEvents || models.length > 0 ? `export type { EventPayloads, EventName } from './event-types.js';` : ""}
14
16
 
15
- // Event names enum
16
- export enum EventName {
17
- ${events.map((event) => ` ${event} = '${event}',`).join("\n")}
17
+ /**
18
+ * Event history entry
19
+ */
20
+ interface EventHistoryEntry {
21
+ event: string;
22
+ payload: any;
23
+ timestamp: Date;
18
24
  }
19
25
 
20
26
  /**
@@ -22,15 +28,13 @@ ${events.map((event) => ` ${event} = '${event}',`).join("\n")}
22
28
  */
23
29
  class EventBus extends EventEmitter {
24
30
  private static instance: EventBus;
31
+ private history: EventHistoryEntry[] = [];
32
+ private maxHistory = 1000;
25
33
 
26
34
  private constructor() {
27
35
  super();
28
- this.setMaxListeners(100); // Configure max listeners
29
36
  }
30
37
 
31
- /**
32
- * Get singleton instance
33
- */
34
38
  public static getInstance(): EventBus {
35
39
  if (!EventBus.instance) {
36
40
  EventBus.instance = new EventBus();
@@ -39,37 +43,33 @@ class EventBus extends EventEmitter {
39
43
  }
40
44
 
41
45
  /**
42
- * Publish an event
46
+ * Publish a typed event
43
47
  */
44
- public publish<T = any>(event: EventName | string, payload: T): void {
45
- console.log(\`[EventBus] Publishing event: \${event}\`, payload);
48
+ public publish<K extends string>(event: K, payload: K extends keyof EventPayloads ? EventPayloads[K] : any): void {
49
+ this.history.push({ event, payload, timestamp: new Date() });
50
+ if (this.history.length > this.maxHistory) this.history.shift();
46
51
  this.emit(event, payload);
47
52
  }
48
53
 
49
54
  /**
50
- * Subscribe to an event
55
+ * Subscribe to a typed event
51
56
  */
52
- public subscribe<T = any>(
53
- event: EventName | string,
54
- handler: (payload: T) => void | Promise<void>
57
+ public subscribe<K extends string>(
58
+ event: K,
59
+ handler: (payload: K extends keyof EventPayloads ? EventPayloads[K] : any) => void | Promise<void>
55
60
  ): () => void {
56
- console.log(\`[EventBus] Subscribing to event: \${event}\`);
57
61
  this.on(event, handler);
58
-
59
- // Return unsubscribe function
60
- return () => {
61
- this.off(event, handler);
62
- };
62
+ return () => { this.off(event, handler); };
63
63
  }
64
64
 
65
65
  /**
66
- * Subscribe to an event (one-time)
66
+ * Get event history
67
67
  */
68
- public subscribeOnce<T = any>(
69
- event: EventName | string,
70
- handler: (payload: T) => void | Promise<void>
71
- ): void {
72
- this.once(event, handler);
68
+ public getHistory(eventName?: string, limit = 50): EventHistoryEntry[] {
69
+ const filtered = eventName
70
+ ? this.history.filter(e => e.event === eventName)
71
+ : this.history;
72
+ return filtered.slice(-limit);
73
73
  }
74
74
  }
75
75
 
@@ -0,0 +1,79 @@
1
+ function tsType(specType) {
2
+ const map = {
3
+ String: "string",
4
+ Text: "string",
5
+ Email: "string",
6
+ URL: "string",
7
+ Integer: "number",
8
+ Float: "number",
9
+ Number: "number",
10
+ Decimal: "number",
11
+ Money: "number",
12
+ Boolean: "boolean",
13
+ Date: "string",
14
+ DateTime: "string",
15
+ Timestamp: "string",
16
+ UUID: "string",
17
+ Object: "Record<string, any>",
18
+ Array: "any[]"
19
+ };
20
+ return map[specType] || "any";
21
+ }
22
+ function generateEventTypes(context) {
23
+ const { spec } = context;
24
+ const events = spec.events && typeof spec.events === "object" ? Object.entries(spec.events) : [];
25
+ if (events.length === 0) {
26
+ return `/**
27
+ * Event Types \u2014 no events defined in specification
28
+ */
29
+ export type EventPayloads = Record<string, any>;
30
+ `;
31
+ }
32
+ const interfaces = [];
33
+ const payloadMapEntries = [];
34
+ for (const [eventName, eventDef] of events) {
35
+ const def = eventDef;
36
+ const attrs = def.attributes || def.payload || {};
37
+ const attrEntries = Array.isArray(attrs) ? attrs.map((a) => [a.name, a]) : Object.entries(attrs);
38
+ const fields = attrEntries.map(([name, attrDef]) => {
39
+ const type = typeof attrDef === "string" ? tsType(attrDef.split(" ")[0]) : tsType(attrDef?.type || "String");
40
+ const required = typeof attrDef === "string" ? attrDef.includes("required") : attrDef?.required;
41
+ return ` ${name}${required ? "" : "?"}: ${type};`;
42
+ });
43
+ interfaces.push(`export interface ${eventName}Payload {
44
+ ${fields.join("\n")}
45
+ }`);
46
+ payloadMapEntries.push(` ${eventName}: ${eventName}Payload;`);
47
+ }
48
+ const models = spec.models ? Object.keys(spec.models) : [];
49
+ for (const model of models) {
50
+ for (const suffix of ["Created", "Updated", "Deleted", "Evolved"]) {
51
+ const name = `${model}${suffix}`;
52
+ if (!payloadMapEntries.some((e) => e.includes(name))) {
53
+ payloadMapEntries.push(` ${name}: { id: string; timestamp: string; [key: string]: any };`);
54
+ }
55
+ }
56
+ }
57
+ return `/**
58
+ * Event Payload Types
59
+ * Generated from SpecVerse specification
60
+ */
61
+
62
+ ${interfaces.join("\n\n")}
63
+
64
+ /**
65
+ * Map of all event names to their payload types
66
+ */
67
+ export type EventPayloads = {
68
+ ${payloadMapEntries.join("\n")}
69
+ };
70
+
71
+ /**
72
+ * All known event names
73
+ */
74
+ export type EventName = keyof EventPayloads;
75
+ `;
76
+ }
77
+ export {
78
+ generateEventTypes as default
79
+ };
@@ -0,0 +1,96 @@
1
+ function generateWebSocketBridge(context) {
2
+ const { spec } = context;
3
+ const models = spec.models ? Object.keys(spec.models) : [];
4
+ const specEvents = spec.events ? Object.keys(spec.events) : [];
5
+ const curedEvents = models.flatMap(
6
+ (m) => ["Created", "Updated", "Deleted", "Evolved"].map((s) => `${m}${s}`)
7
+ );
8
+ const allEvents = [.../* @__PURE__ */ new Set([...specEvents, ...curedEvents])];
9
+ return `/**
10
+ * WebSocket Bridge
11
+ * Bridges the event bus to frontend clients for real-time updates.
12
+ * Generated from SpecVerse specification
13
+ */
14
+
15
+ import type { FastifyInstance } from 'fastify';
16
+ import type { WebSocket } from '@fastify/websocket';
17
+ import { eventBus } from './eventBus.js';
18
+
19
+ interface ClientSubscription {
20
+ ws: WebSocket;
21
+ events: Set<string>;
22
+ }
23
+
24
+ const clients = new Map<WebSocket, ClientSubscription>();
25
+
26
+ // All events this bridge knows about
27
+ const ALL_EVENTS = ${JSON.stringify(allEvents, null, 2)};
28
+
29
+ /**
30
+ * Register WebSocket bridge with Fastify
31
+ */
32
+ export async function registerWebSocketBridge(fastify: FastifyInstance): Promise<void> {
33
+ // Register @fastify/websocket plugin
34
+ const websocketPlugin = await import('@fastify/websocket');
35
+ await fastify.register(websocketPlugin.default || websocketPlugin);
36
+
37
+ // WebSocket route
38
+ fastify.get('/ws', { websocket: true }, (socket: WebSocket) => {
39
+ const subscription: ClientSubscription = { ws: socket, events: new Set() };
40
+ clients.set(socket, subscription);
41
+
42
+ socket.on('message', (raw: Buffer) => {
43
+ try {
44
+ const msg = JSON.parse(raw.toString());
45
+
46
+ if (msg.type === 'subscribe' && msg.event) {
47
+ subscription.events.add(msg.event);
48
+ socket.send(JSON.stringify({ type: 'subscribed', event: msg.event }));
49
+ }
50
+
51
+ if (msg.type === 'unsubscribe' && msg.event) {
52
+ subscription.events.delete(msg.event);
53
+ socket.send(JSON.stringify({ type: 'unsubscribed', event: msg.event }));
54
+ }
55
+
56
+ if (msg.type === 'subscribe-all') {
57
+ ALL_EVENTS.forEach(e => subscription.events.add(e));
58
+ socket.send(JSON.stringify({ type: 'subscribed-all', events: ALL_EVENTS }));
59
+ }
60
+ } catch { /* ignore malformed messages */ }
61
+ });
62
+
63
+ socket.on('close', () => {
64
+ clients.delete(socket);
65
+ });
66
+
67
+ // Send initial connection confirmation
68
+ socket.send(JSON.stringify({
69
+ type: 'connected',
70
+ availableEvents: ALL_EVENTS,
71
+ }));
72
+ });
73
+
74
+ // Subscribe to all events and broadcast to interested clients
75
+ for (const eventName of ALL_EVENTS) {
76
+ eventBus.subscribe(eventName, (payload: any) => {
77
+ const message = JSON.stringify({
78
+ type: 'event',
79
+ event: eventName,
80
+ payload,
81
+ timestamp: new Date().toISOString(),
82
+ });
83
+
84
+ for (const [ws, sub] of clients) {
85
+ if (sub.events.has(eventName) && ws.readyState === 1) {
86
+ try { ws.send(message); } catch { /* client disconnected */ }
87
+ }
88
+ }
89
+ });
90
+ }
91
+ }
92
+ `;
93
+ }
94
+ export {
95
+ generateWebSocketBridge as default
96
+ };
@@ -16,6 +16,20 @@ function generateFastifyRoutes(context) {
16
16
  endpoints = curedToEndpoints(controller.cured, modelName);
17
17
  }
18
18
  }
19
+ if (controller.actions) {
20
+ if (!endpoints) endpoints = [];
21
+ for (const [actionName, action] of Object.entries(controller.actions)) {
22
+ const params = action.parameters || {};
23
+ const hasIdParam = Object.keys(params).some((p) => p === "id" || p === `${modelName?.charAt(0).toLowerCase()}${modelName?.slice(1)}Id`);
24
+ endpoints.push({
25
+ operation: actionName,
26
+ method: "POST",
27
+ path: hasIdParam ? `/:id/${actionName}` : `/${actionName}`,
28
+ parameters: params,
29
+ description: action.description || `Custom action: ${actionName}`
30
+ });
31
+ }
32
+ }
19
33
  if (!endpoints || endpoints.length === 0) {
20
34
  console.warn(`Warning: Controller ${controllerName} has no endpoints. Generating empty routes file.`);
21
35
  }
@@ -76,7 +90,7 @@ function generateRouteHandler(endpoint, modelName, handlerName, isModelControlle
76
90
  }
77
91
  const method = endpoint.method?.toLowerCase() || inferHttpMethod(operation);
78
92
  const path = inferPath(operation, endpoint);
79
- const handler = generateHandlerBody(operation, modelName, handlerName, isModelController, implType);
93
+ const handler = generateHandlerBody(operation, modelName, handlerName, isModelController, implType, endpoint);
80
94
  let route = `// ${operation} ${modelName}
81
95
  `;
82
96
  route += `fastify.${method}('${path}', {
@@ -111,7 +125,7 @@ function generateRouteHandler(endpoint, modelName, handlerName, isModelControlle
111
125
  route += `});`;
112
126
  return route;
113
127
  }
114
- function generateHandlerBody(operation, modelName, handlerName, isModelController, implType) {
128
+ function generateHandlerBody(operation, modelName, handlerName, isModelController, implType, endpoint) {
115
129
  const rawLowerModel = modelName?.toLowerCase() || "item";
116
130
  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"]);
117
131
  const lowerModel = RESERVED_WORDS.has(rawLowerModel) ? `${rawLowerModel}Item` : rawLowerModel;
@@ -199,16 +213,40 @@ function generateHandlerBody(operation, modelName, handlerName, isModelControlle
199
213
  message: error instanceof Error ? error.message : String(error)
200
214
  });
201
215
  }`;
202
- default:
216
+ default: {
217
+ const params = endpoint?.parameters || {};
218
+ const paramNames = Object.keys(params);
219
+ const callArgs = paramNames.length > 0 ? paramNames.map((p) => `body.${p}`).join(", ") : "body";
220
+ const validations = [];
221
+ for (const [pName, pDef] of Object.entries(params)) {
222
+ const required = typeof pDef === "string" ? pDef.includes("required") : pDef?.required;
223
+ const typeStr = typeof pDef === "string" ? pDef.split(" ")[0] : pDef?.type || "String";
224
+ if (required) {
225
+ validations.push(` if (body.${pName} === undefined || body.${pName} === null) errors.push({ field: '${pName}', message: '${pName} is required' });`);
226
+ }
227
+ if (typeStr === "UUID" || typeStr === "String" || typeStr === "Email") {
228
+ validations.push(` if (body.${pName} !== undefined && typeof body.${pName} !== 'string') errors.push({ field: '${pName}', message: '${pName} must be a string' });`);
229
+ } else if (typeStr === "Integer" || typeStr === "Number" || typeStr === "Float") {
230
+ validations.push(` if (body.${pName} !== undefined && typeof body.${pName} !== 'number') errors.push({ field: '${pName}', message: '${pName} must be a number' });`);
231
+ } else if (typeStr === "Boolean") {
232
+ validations.push(` if (body.${pName} !== undefined && typeof body.${pName} !== 'boolean') errors.push({ field: '${pName}', message: '${pName} must be a boolean' });`);
233
+ }
234
+ }
235
+ const validationBlock = validations.length > 0 ? ` const errors: Array<{ field: string; message: string }> = [];
236
+ ${validations.join("\n")}
237
+ if (errors.length > 0) return reply.status(400).send({ error: 'Validation failed', details: errors });` : "";
203
238
  return `try {
204
- const result = await handler.${operation}(request.body as any);
205
- return reply.send(result);
239
+ const body = (request.body || {}) as Record<string, any>;
240
+ ${validationBlock}
241
+ const result = await handler.${operation}(${callArgs});
242
+ return reply.send(result || { success: true });
206
243
  } catch (error) {
207
244
  return reply.status(400).send({
208
245
  error: 'Failed to execute ${operation}',
209
246
  message: error instanceof Error ? error.message : String(error)
210
247
  });
211
248
  }`;
249
+ }
212
250
  }
213
251
  }
214
252
  function inferOperationFromMethodAndPath(method, path) {
@@ -227,10 +265,8 @@ function inferHttpMethod(operation) {
227
265
  return sharedInferHttpMethod(operation);
228
266
  }
229
267
  function inferPath(operation, endpoint) {
230
- if (endpoint?.path && endpoint.serviceOperation?.type === "custom") {
231
- const pathParts = endpoint.path.split("/").filter((p) => p);
232
- const lastPart = pathParts[pathParts.length - 1];
233
- return `/${lastPart}`;
268
+ if (endpoint?.path) {
269
+ return endpoint.path;
234
270
  }
235
271
  return sharedInferPath(operation);
236
272
  }
@@ -10,6 +10,12 @@ function generateFastifyServer(context) {
10
10
  const path = deriveBasePath(name);
11
11
  return ` await fastify.register(${name}Routes, { prefix: '${path}', controllers: { ${name}Controller: new (await import('./controllers/${name}Controller.js')).${name}Controller() } });`;
12
12
  }).join("\n");
13
+ const specEvents = spec.events ? Object.keys(spec.events) : [];
14
+ const hasEvents = specEvents.length > 0 || modelNames.length > 0;
15
+ const servicesList = spec.services ? Object.keys(spec.services) : [];
16
+ const serviceImports = servicesList.map(
17
+ (name) => `import './${name.charAt(0).toLowerCase() + name.slice(1)}.js'; // Initialize ${name} event subscriptions`
18
+ );
13
19
  return `/**
14
20
  * Fastify Server
15
21
  * Generated from SpecVerse specification
@@ -18,6 +24,8 @@ function generateFastifyServer(context) {
18
24
  import Fastify from 'fastify';
19
25
  import cors from '@fastify/cors';
20
26
  import { PrismaClient } from '@prisma/client';
27
+ ${hasEvents ? `import { eventBus } from './events/eventBus.js';
28
+ import { registerWebSocketBridge } from './events/websocket-bridge.js';` : ""}
21
29
 
22
30
  // Initialize Prisma
23
31
  export const prisma = new PrismaClient();
@@ -42,13 +50,19 @@ fastify.get('/api/spec', async () => embeddedSpec);
42
50
  fastify.get('/api/runtime/info', async () => ({
43
51
  controllers: ${JSON.stringify(modelNames.map((n) => `${n}Controller`))},
44
52
  models: ${JSON.stringify(modelNames)},
45
- events: [],
46
- services: []
53
+ events: ${JSON.stringify(specEvents)},
54
+ services: ${JSON.stringify(servicesList)}
47
55
  }));
48
56
 
57
+ ${hasEvents ? `// Event history endpoint
58
+ fastify.get('/api/events', async () => eventBus.getHistory());` : ""}
59
+
49
60
  // Register routes
50
61
  ${routeImports}
51
62
 
63
+ ${serviceImports.length > 0 ? `// Initialize services (registers event subscriptions)
64
+ ${serviceImports.join("\n")}` : ""}
65
+
52
66
  async function registerRoutes() {
53
67
  ${routeRegistrations}
54
68
  }
@@ -57,10 +71,14 @@ ${routeRegistrations}
57
71
  const start = async () => {
58
72
  try {
59
73
  await registerRoutes();
74
+ ${hasEvents ? ` // Register WebSocket bridge for real-time frontend events
75
+ await registerWebSocketBridge(fastify);` : ""}
60
76
  const port = parseInt(process.env.PORT || '3000');
61
77
  await fastify.listen({ port, host: '0.0.0.0' });
62
78
  console.log(\`Server running at http://localhost:\${port}\`);
63
79
  console.log(\`API endpoints: ${modelNames.map((n) => `/api/${n.toLowerCase()}s`).join(", ")}\`);
80
+ ${hasEvents ? ` console.log(\`WebSocket: ws://localhost:\${port}/ws\`);
81
+ console.log(\`Events: ${specEvents.join(", ")}\`);` : ""}
64
82
  } catch (err) {
65
83
  fastify.log.error(err);
66
84
  process.exit(1);
@@ -1,8 +1,16 @@
1
1
  function generateTsConfig(context) {
2
2
  const { manifest, spec } = context;
3
- const mergedOptions = extractTsConfigOptions(context.implementationTypes || []);
3
+ const implementationTypes = context.implementationTypes || [];
4
+ const isMonorepo = !implementationTypes.some((it) => {
5
+ const cfg = it?.configuration || it?.instanceFactory?.configuration || {};
6
+ return cfg.outputStructure === "standalone";
7
+ });
8
+ if (isMonorepo) {
9
+ return "";
10
+ }
11
+ const mergedOptions = extractTsConfigOptions(implementationTypes);
4
12
  const hasViews = spec && (spec.views || Array.isArray(spec.views) && spec.views.length > 0);
5
- const usesReact = hasViews && (context.implementationTypes || []).some(
13
+ const usesReact = hasViews && implementationTypes.some(
6
14
  (implType) => implType.capabilities?.provides?.includes("ui.components") && implType.technology?.framework === "react"
7
15
  );
8
16
  const tsconfig = {