@specverse/engines 4.1.5 → 4.1.7
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/dist/libs/instance-factories/applications/templates/generic/backend-env-generator.js +22 -0
- package/dist/libs/instance-factories/applications/templates/generic/backend-package-json-generator.js +66 -0
- package/dist/libs/instance-factories/applications/templates/generic/backend-tsconfig-generator.js +54 -0
- package/dist/libs/instance-factories/applications/templates/generic/main-generator.js +290 -0
- package/dist/libs/instance-factories/applications/templates/react/_view-components-source.js +530 -0
- package/dist/libs/instance-factories/applications/templates/react/api-client-generator.js +437 -0
- package/dist/libs/instance-factories/applications/templates/react/api-types-generator.js +146 -0
- package/dist/libs/instance-factories/applications/templates/react/app-tsx-generator.js +73 -0
- package/dist/libs/instance-factories/applications/templates/react/env-example-generator.js +18 -0
- package/dist/libs/instance-factories/applications/templates/react/field-helpers-generator.js +99 -0
- package/dist/libs/instance-factories/applications/templates/react/gitignore-generator.js +35 -0
- package/dist/libs/instance-factories/applications/templates/react/index-css-generator.js +9 -0
- package/dist/libs/instance-factories/applications/templates/react/index-html-generator.js +23 -0
- package/dist/libs/instance-factories/applications/templates/react/main-tsx-generator.js +29 -0
- package/dist/libs/instance-factories/applications/templates/react/package-json-generator.js +49 -0
- package/dist/libs/instance-factories/applications/templates/react/pattern-adapter-generator.js +156 -0
- package/dist/libs/instance-factories/applications/templates/react/react-pattern-adapter.js +935 -0
- package/dist/libs/instance-factories/applications/templates/react/relationship-field-generator.js +143 -0
- package/dist/libs/instance-factories/applications/templates/react/runtime-app-tsx-generator.js +101 -0
- package/dist/libs/instance-factories/applications/templates/react/runtime-package-json-generator.js +50 -0
- package/dist/libs/instance-factories/applications/templates/react/tailwind-adapter-generator.js +646 -0
- package/dist/libs/instance-factories/applications/templates/react/tailwind-adapter-wrapper-generator.js +65 -0
- package/dist/libs/instance-factories/applications/templates/react/tsconfig-generator.js +28 -0
- package/dist/libs/instance-factories/applications/templates/react/use-api-hooks-generator.js +132 -0
- package/dist/libs/instance-factories/applications/templates/react/view-dashboard-generator.js +143 -0
- package/dist/libs/instance-factories/applications/templates/react/view-detail-generator.js +143 -0
- package/dist/libs/instance-factories/applications/templates/react/view-form-generator.js +355 -0
- package/dist/libs/instance-factories/applications/templates/react/view-list-generator.js +91 -0
- package/dist/libs/instance-factories/applications/templates/react/view-router-generator.js +79 -0
- package/dist/libs/instance-factories/applications/templates/react/vite-config-generator.js +42 -0
- package/dist/libs/instance-factories/cli/templates/commander/cli-bin-wrapper-generator.js +11 -0
- package/dist/libs/instance-factories/cli/templates/commander/cli-entry-generator.js +111 -0
- package/dist/libs/instance-factories/cli/templates/commander/command-generator.js +928 -0
- package/dist/libs/instance-factories/communication/templates/eventemitter/bus-generator.js +83 -0
- package/dist/libs/instance-factories/communication/templates/eventemitter/publisher-generator.js +91 -0
- package/dist/libs/instance-factories/communication/templates/eventemitter/subscriber-generator.js +86 -0
- package/dist/libs/instance-factories/controllers/templates/fastify/meta-routes-generator.js +93 -0
- package/dist/libs/instance-factories/controllers/templates/fastify/routes-generator.js +280 -0
- package/dist/libs/instance-factories/controllers/templates/fastify/server-generator.js +125 -0
- package/dist/libs/instance-factories/infrastructure/templates/docker-k8s/infrastructure-generator.js +25 -0
- package/dist/libs/instance-factories/orms/templates/prisma/schema-generator.js +371 -0
- package/dist/libs/instance-factories/orms/templates/prisma/services-generator.js +266 -0
- package/dist/libs/instance-factories/scaffolding/templates/generic/env-example-generator.js +51 -0
- package/dist/libs/instance-factories/scaffolding/templates/generic/env-generator.js +61 -0
- package/dist/libs/instance-factories/scaffolding/templates/generic/gitignore-generator.js +59 -0
- package/dist/libs/instance-factories/scaffolding/templates/generic/package-json-generator.js +126 -0
- package/dist/libs/instance-factories/scaffolding/templates/generic/readme-generator.js +159 -0
- package/dist/libs/instance-factories/scaffolding/templates/generic/tsconfig-generator.js +56 -0
- package/dist/libs/instance-factories/scaffolding/templates/generic/tsconfig-react-generator.js +37 -0
- package/dist/libs/instance-factories/sdks/templates/python/sdk-generator.js +29 -0
- package/dist/libs/instance-factories/sdks/templates/typescript/sdk-generator.js +28 -0
- package/dist/libs/instance-factories/services/templates/memory/generate-interpreter.js +14 -0
- package/dist/libs/instance-factories/services/templates/memory/step-conventions-memory.js +415 -0
- package/dist/libs/instance-factories/services/templates/prisma/behavior-generator.js +177 -0
- package/dist/libs/instance-factories/services/templates/prisma/controller-generator.js +413 -0
- package/dist/libs/instance-factories/services/templates/prisma/service-generator.js +243 -0
- package/dist/libs/instance-factories/services/templates/prisma/step-conventions.js +264 -0
- package/dist/libs/instance-factories/services/templates/shared-patterns.js +24 -0
- package/dist/libs/instance-factories/shared/path-resolver.js +59 -0
- package/dist/libs/instance-factories/storage/templates/mongodb/config-generator.js +13 -0
- package/dist/libs/instance-factories/storage/templates/mongodb/docker-generator.js +16 -0
- package/dist/libs/instance-factories/storage/templates/postgresql/config-generator.js +45 -0
- package/dist/libs/instance-factories/storage/templates/postgresql/docker-generator.js +46 -0
- package/dist/libs/instance-factories/storage/templates/redis/config-generator.js +14 -0
- package/dist/libs/instance-factories/storage/templates/redis/docker-generator.js +16 -0
- package/dist/libs/instance-factories/test-generation.js +145 -0
- package/dist/libs/instance-factories/testing/templates/vitest/tests-generator.js +30 -0
- package/dist/libs/instance-factories/tools/templates/mcp/mcp-server-generator.js +149 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/controllers/MCPServerController.js +232 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/events/EventEmitter.js +49 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/index.js +18 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/interfaces/ResourceProvider.js +0 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/models/LibrarySuggestion.js +97 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/models/SpecVerseResource.js +64 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/server/mcp-server.js +182 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/CLIProxyService.js +1210 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/EmbeddedResourcesAdapter.js +172 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/EntityModuleService.js +240 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/HybridResourcesProvider.js +147 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/LibraryToolsService.js +281 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/OrchestratorBridge.js +409 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/OrchestratorToolsService.js +414 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/PromptToolsService.js +467 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/ResourcesProviderService.js +135 -0
- package/dist/libs/instance-factories/tools/templates/mcp/static/src/types/index.js +0 -0
- package/dist/libs/instance-factories/tools/templates/vscode/static/extension.js +965 -0
- package/dist/libs/instance-factories/tools/templates/vscode/vscode-extension-generator.js +238 -0
- package/dist/libs/instance-factories/validation/templates/zod/validation-generator.js +25 -0
- package/dist/libs/instance-factories/views/index.js +48 -0
- package/dist/libs/instance-factories/views/templates/react/adapters/antd-adapter.js +742 -0
- package/dist/libs/instance-factories/views/templates/react/adapters/mui-adapter.js +824 -0
- package/dist/libs/instance-factories/views/templates/react/adapters/shadcn-adapter.js +719 -0
- package/dist/libs/instance-factories/views/templates/react/app-generator.js +45 -0
- package/dist/libs/instance-factories/views/templates/react/components-generator.js +779 -0
- package/dist/libs/instance-factories/views/templates/react/forms-generator.js +285 -0
- package/dist/libs/instance-factories/views/templates/react/frontend-package-json-generator.js +46 -0
- package/dist/libs/instance-factories/views/templates/react/hooks-generator.js +111 -0
- package/dist/libs/instance-factories/views/templates/react/index-css-generator.js +9 -0
- package/dist/libs/instance-factories/views/templates/react/index-html-generator.js +23 -0
- package/dist/libs/instance-factories/views/templates/react/main-tsx-generator.js +21 -0
- package/dist/libs/instance-factories/views/templates/react/react-component-generator.js +299 -0
- package/dist/libs/instance-factories/views/templates/react/router-generator.js +136 -0
- package/dist/libs/instance-factories/views/templates/react/router-generic-generator.js +107 -0
- package/dist/libs/instance-factories/views/templates/react/shared-utils-generator.js +179 -0
- package/dist/libs/instance-factories/views/templates/react/spec-json-generator.js +7 -0
- package/dist/libs/instance-factories/views/templates/react/types-generator.js +56 -0
- package/dist/libs/instance-factories/views/templates/react/views-metadata-generator.js +27 -0
- package/dist/libs/instance-factories/views/templates/react/vite-config-generator.js +29 -0
- package/dist/libs/instance-factories/views/templates/runtime/runtime-view-renderer.js +261 -0
- package/dist/libs/instance-factories/views/templates/shared/adapter-types.js +34 -0
- package/dist/libs/instance-factories/views/templates/shared/atomic-components-registry.js +800 -0
- package/dist/libs/instance-factories/views/templates/shared/base-generator.js +305 -0
- package/dist/libs/instance-factories/views/templates/shared/component-metadata.js +517 -0
- package/dist/libs/instance-factories/views/templates/shared/composite-pattern-types.js +0 -0
- package/dist/libs/instance-factories/views/templates/shared/composite-patterns.js +445 -0
- package/dist/libs/instance-factories/views/templates/shared/index.js +80 -0
- package/dist/libs/instance-factories/views/templates/shared/pattern-validator.js +210 -0
- package/dist/libs/instance-factories/views/templates/shared/property-mapper.js +492 -0
- package/dist/libs/instance-factories/views/templates/shared/syntax-mapper.js +321 -0
- package/dist/realize/index.js +36 -12
- package/dist/realize/index.js.map +1 -1
- package/package.json +3 -2
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
function generateEventBus(context) {
|
|
2
|
+
const { spec } = context;
|
|
3
|
+
const events = spec.events ? Object.keys(spec.events) : [];
|
|
4
|
+
return `/**
|
|
5
|
+
* Event Bus
|
|
6
|
+
* In-memory event bus using EventEmitter3
|
|
7
|
+
* Generated from SpecVerse specification
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import EventEmitter from 'eventemitter3';
|
|
11
|
+
|
|
12
|
+
// Event type definitions
|
|
13
|
+
${events.map((event) => `export type ${event}Payload = any; // TODO: Define payload type`).join("\n")}
|
|
14
|
+
|
|
15
|
+
// Event names enum
|
|
16
|
+
export enum EventName {
|
|
17
|
+
${events.map((event) => ` ${event} = '${event}',`).join("\n")}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Event Bus Singleton
|
|
22
|
+
*/
|
|
23
|
+
class EventBus extends EventEmitter {
|
|
24
|
+
private static instance: EventBus;
|
|
25
|
+
|
|
26
|
+
private constructor() {
|
|
27
|
+
super();
|
|
28
|
+
this.setMaxListeners(100); // Configure max listeners
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Get singleton instance
|
|
33
|
+
*/
|
|
34
|
+
public static getInstance(): EventBus {
|
|
35
|
+
if (!EventBus.instance) {
|
|
36
|
+
EventBus.instance = new EventBus();
|
|
37
|
+
}
|
|
38
|
+
return EventBus.instance;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Publish an event
|
|
43
|
+
*/
|
|
44
|
+
public publish<T = any>(event: EventName | string, payload: T): void {
|
|
45
|
+
console.log(\`[EventBus] Publishing event: \${event}\`, payload);
|
|
46
|
+
this.emit(event, payload);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Subscribe to an event
|
|
51
|
+
*/
|
|
52
|
+
public subscribe<T = any>(
|
|
53
|
+
event: EventName | string,
|
|
54
|
+
handler: (payload: T) => void | Promise<void>
|
|
55
|
+
): () => void {
|
|
56
|
+
console.log(\`[EventBus] Subscribing to event: \${event}\`);
|
|
57
|
+
this.on(event, handler);
|
|
58
|
+
|
|
59
|
+
// Return unsubscribe function
|
|
60
|
+
return () => {
|
|
61
|
+
this.off(event, handler);
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Subscribe to an event (one-time)
|
|
67
|
+
*/
|
|
68
|
+
public subscribeOnce<T = any>(
|
|
69
|
+
event: EventName | string,
|
|
70
|
+
handler: (payload: T) => void | Promise<void>
|
|
71
|
+
): void {
|
|
72
|
+
this.once(event, handler);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Export singleton instance
|
|
77
|
+
export const eventBus = EventBus.getInstance();
|
|
78
|
+
export default eventBus;
|
|
79
|
+
`;
|
|
80
|
+
}
|
|
81
|
+
export {
|
|
82
|
+
generateEventBus as default
|
|
83
|
+
};
|
package/dist/libs/instance-factories/communication/templates/eventemitter/publisher-generator.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
function generateEventPublisher(context) {
|
|
2
|
+
const { event, spec } = context;
|
|
3
|
+
if (!event) {
|
|
4
|
+
throw new Error("Event is required in template context");
|
|
5
|
+
}
|
|
6
|
+
const eventName = event.name;
|
|
7
|
+
const publisherName = `${eventName}Publisher`;
|
|
8
|
+
return `/**
|
|
9
|
+
* ${publisherName}
|
|
10
|
+
* Publisher for ${eventName} events
|
|
11
|
+
* ${event.description || ""}
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { eventBus, EventName } from '../eventBus.js';
|
|
15
|
+
|
|
16
|
+
export interface ${eventName}Payload {
|
|
17
|
+
${generatePayloadInterface(event)}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* ${publisherName} class
|
|
22
|
+
*/
|
|
23
|
+
export class ${publisherName} {
|
|
24
|
+
/**
|
|
25
|
+
* Publish ${eventName} event
|
|
26
|
+
*/
|
|
27
|
+
public async publish(payload: ${eventName}Payload): Promise<void> {
|
|
28
|
+
try {
|
|
29
|
+
// Validate payload
|
|
30
|
+
this.validate(payload);
|
|
31
|
+
|
|
32
|
+
// Add timestamp if not present
|
|
33
|
+
const enrichedPayload = {
|
|
34
|
+
...payload,
|
|
35
|
+
timestamp: payload.timestamp || new Date().toISOString(),
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// Publish event
|
|
39
|
+
eventBus.publish(EventName.${eventName}, enrichedPayload);
|
|
40
|
+
|
|
41
|
+
console.log(\`[${publisherName}] Published event\`, enrichedPayload);
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.error(\`[${publisherName}] Failed to publish event\`, error);
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Validate event payload
|
|
50
|
+
*/
|
|
51
|
+
private validate(payload: ${eventName}Payload): void {
|
|
52
|
+
// TODO: Add validation logic
|
|
53
|
+
${generateValidation(event)}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Export singleton instance
|
|
58
|
+
export const ${eventName.toLowerCase()}Publisher = new ${publisherName}();
|
|
59
|
+
export default ${eventName.toLowerCase()}Publisher;
|
|
60
|
+
`;
|
|
61
|
+
}
|
|
62
|
+
function generatePayloadInterface(event) {
|
|
63
|
+
const attributes = event.attributes || {};
|
|
64
|
+
const fields = [];
|
|
65
|
+
for (const [name, config] of Object.entries(attributes)) {
|
|
66
|
+
const attr = config;
|
|
67
|
+
const type = attr.type || "String";
|
|
68
|
+
let tsType = "string";
|
|
69
|
+
if (type === "Integer" || type === "Number") tsType = "number";
|
|
70
|
+
if (type === "Boolean") tsType = "boolean";
|
|
71
|
+
if (type === "DateTime") tsType = "string";
|
|
72
|
+
if (type === "UUID") tsType = "string";
|
|
73
|
+
const optional = attr.required ? "" : "?";
|
|
74
|
+
fields.push(` ${name}${optional}: ${tsType};`);
|
|
75
|
+
}
|
|
76
|
+
return fields.join("\n") || " // No attributes defined";
|
|
77
|
+
}
|
|
78
|
+
function generateValidation(event) {
|
|
79
|
+
const attributes = event.attributes || {};
|
|
80
|
+
const validations = [];
|
|
81
|
+
for (const [name, config] of Object.entries(attributes)) {
|
|
82
|
+
const attr = config;
|
|
83
|
+
if (attr.required) {
|
|
84
|
+
validations.push(` if (!payload.${name}) throw new Error('${name} is required');`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return validations.join("\n") || " // No validation rules";
|
|
88
|
+
}
|
|
89
|
+
export {
|
|
90
|
+
generateEventPublisher as default
|
|
91
|
+
};
|
package/dist/libs/instance-factories/communication/templates/eventemitter/subscriber-generator.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
function generateEventSubscriber(context) {
|
|
2
|
+
const { event, service, spec } = context;
|
|
3
|
+
if (!event) {
|
|
4
|
+
throw new Error("Event is required in template context");
|
|
5
|
+
}
|
|
6
|
+
const eventName = event.name;
|
|
7
|
+
const subscriberName = `${eventName}Subscriber`;
|
|
8
|
+
return `/**
|
|
9
|
+
* ${subscriberName}
|
|
10
|
+
* Subscriber for ${eventName} events
|
|
11
|
+
* ${event.description || ""}
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { eventBus, EventName, type ${eventName}Payload } from '../eventBus.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* ${subscriberName} class
|
|
18
|
+
*/
|
|
19
|
+
export class ${subscriberName} {
|
|
20
|
+
private unsubscribe?: () => void;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Start listening for ${eventName} events
|
|
24
|
+
*/
|
|
25
|
+
public start(): void {
|
|
26
|
+
console.log(\`[${subscriberName}] Starting subscriber\`);
|
|
27
|
+
|
|
28
|
+
this.unsubscribe = eventBus.subscribe<${eventName}Payload>(
|
|
29
|
+
EventName.${eventName},
|
|
30
|
+
this.handleEvent.bind(this)
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Stop listening for ${eventName} events
|
|
36
|
+
*/
|
|
37
|
+
public stop(): void {
|
|
38
|
+
if (this.unsubscribe) {
|
|
39
|
+
console.log(\`[${subscriberName}] Stopping subscriber\`);
|
|
40
|
+
this.unsubscribe();
|
|
41
|
+
this.unsubscribe = undefined;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Handle ${eventName} event
|
|
47
|
+
*/
|
|
48
|
+
private async handleEvent(payload: ${eventName}Payload): Promise<void> {
|
|
49
|
+
try {
|
|
50
|
+
console.log(\`[${subscriberName}] Received event\`, payload);
|
|
51
|
+
|
|
52
|
+
// Process event
|
|
53
|
+
await this.processEvent(payload);
|
|
54
|
+
|
|
55
|
+
console.log(\`[${subscriberName}] Event processed successfully\`);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.error(\`[${subscriberName}] Failed to process event\`, error);
|
|
58
|
+
// TODO: Add error handling (dead letter queue, retry logic, etc.)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Process ${eventName} event
|
|
64
|
+
*/
|
|
65
|
+
private async processEvent(payload: ${eventName}Payload): Promise<void> {
|
|
66
|
+
// TODO: Implement event processing logic
|
|
67
|
+
${generateProcessingLogic(event)}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Export singleton instance
|
|
72
|
+
export const ${eventName.toLowerCase()}Subscriber = new ${subscriberName}();
|
|
73
|
+
export default ${eventName.toLowerCase()}Subscriber;
|
|
74
|
+
`;
|
|
75
|
+
}
|
|
76
|
+
function generateProcessingLogic(event) {
|
|
77
|
+
return `
|
|
78
|
+
// Example: Update database, send notifications, trigger workflows, etc.
|
|
79
|
+
console.log('Processing ${event.name} event:', payload);
|
|
80
|
+
|
|
81
|
+
// Add your business logic here
|
|
82
|
+
`;
|
|
83
|
+
}
|
|
84
|
+
export {
|
|
85
|
+
generateEventSubscriber as default
|
|
86
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
function generateMetaRoutes(context) {
|
|
2
|
+
const { spec } = context;
|
|
3
|
+
return `/**
|
|
4
|
+
* Meta API Routes
|
|
5
|
+
*
|
|
6
|
+
* Provides spec and view metadata endpoints for the frontend
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { FastifyInstance, FastifyPluginOptions } from 'fastify';
|
|
10
|
+
import { readFile } from 'fs/promises';
|
|
11
|
+
import { join } from 'path';
|
|
12
|
+
import { parse } from 'yaml';
|
|
13
|
+
|
|
14
|
+
export default async function MetaRoutes(
|
|
15
|
+
fastify: FastifyInstance,
|
|
16
|
+
options: FastifyPluginOptions
|
|
17
|
+
) {
|
|
18
|
+
// Get specification
|
|
19
|
+
fastify.get('/spec', async (request, reply) => {
|
|
20
|
+
try {
|
|
21
|
+
const specPath = join(process.cwd(), '../../../specs/main.specly');
|
|
22
|
+
const specContent = await readFile(specPath, 'utf-8');
|
|
23
|
+
const spec = parse(specContent);
|
|
24
|
+
return spec;
|
|
25
|
+
} catch (error: any) {
|
|
26
|
+
reply.status(500).send({ error: \`Failed to load spec: \${error.message}\` });
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Get all views
|
|
31
|
+
fastify.get('/views', async (request, reply) => {
|
|
32
|
+
try {
|
|
33
|
+
const specPath = join(process.cwd(), '../../../specs/main.specly');
|
|
34
|
+
const specContent = await readFile(specPath, 'utf-8');
|
|
35
|
+
const spec = parse(specContent);
|
|
36
|
+
|
|
37
|
+
// Extract views from the spec
|
|
38
|
+
const views = [];
|
|
39
|
+
if (spec.components && typeof spec.components === 'object') {
|
|
40
|
+
// components is an object with component names as keys
|
|
41
|
+
for (const [componentName, component] of Object.entries(spec.components)) {
|
|
42
|
+
if (component && typeof component === 'object' && 'views' in component) {
|
|
43
|
+
const componentViews = component.views as Record<string, any>;
|
|
44
|
+
for (const [viewName, viewDef] of Object.entries(componentViews)) {
|
|
45
|
+
views.push({
|
|
46
|
+
name: viewName,
|
|
47
|
+
...viewDef as any
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return { views };
|
|
55
|
+
} catch (error: any) {
|
|
56
|
+
reply.status(500).send({ error: \`Failed to load views: \${error.message}\` });
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Get individual view by name
|
|
61
|
+
fastify.get('/views/:viewName', async (request, reply) => {
|
|
62
|
+
try {
|
|
63
|
+
const { viewName } = request.params as { viewName: string };
|
|
64
|
+
const specPath = join(process.cwd(), '../../../specs/main.specly');
|
|
65
|
+
const specContent = await readFile(specPath, 'utf-8');
|
|
66
|
+
const spec = parse(specContent);
|
|
67
|
+
|
|
68
|
+
// Find the view
|
|
69
|
+
if (spec.components && typeof spec.components === 'object') {
|
|
70
|
+
for (const [componentName, component] of Object.entries(spec.components)) {
|
|
71
|
+
if (component && typeof component === 'object' && 'views' in component) {
|
|
72
|
+
const componentViews = component.views as Record<string, any>;
|
|
73
|
+
if (componentViews[viewName]) {
|
|
74
|
+
return {
|
|
75
|
+
name: viewName,
|
|
76
|
+
...componentViews[viewName]
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
reply.status(404).send({ error: \`View \${viewName} not found\` });
|
|
84
|
+
} catch (error: any) {
|
|
85
|
+
reply.status(500).send({ error: \`Failed to load view: \${error.message}\` });
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
`;
|
|
90
|
+
}
|
|
91
|
+
export {
|
|
92
|
+
generateMetaRoutes as default
|
|
93
|
+
};
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
function generateFastifyRoutes(context) {
|
|
2
|
+
const { controller, model, spec, implType } = context;
|
|
3
|
+
if (!controller) {
|
|
4
|
+
throw new Error("Controller is required in template context");
|
|
5
|
+
}
|
|
6
|
+
const modelName = model?.name || controller.modelReference;
|
|
7
|
+
const controllerName = controller.name;
|
|
8
|
+
const routeName = controllerName || `${modelName}Controller`;
|
|
9
|
+
const isModelController = !!modelName;
|
|
10
|
+
const handlerName = isModelController ? `${modelName}Controller` : controllerName;
|
|
11
|
+
const imports = generateImports(controller, modelName, handlerName, isModelController, implType);
|
|
12
|
+
let endpoints = controller.endpoints;
|
|
13
|
+
if (!endpoints || endpoints.length === 0) {
|
|
14
|
+
if (controller.cured) {
|
|
15
|
+
endpoints = curedToEndpoints(controller.cured, modelName);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
if (!endpoints || endpoints.length === 0) {
|
|
19
|
+
console.warn(`Warning: Controller ${controllerName} has no endpoints. Generating empty routes file.`);
|
|
20
|
+
}
|
|
21
|
+
const routeHandlers = endpoints?.map((endpoint) => {
|
|
22
|
+
return generateRouteHandler(endpoint, modelName, handlerName, isModelController, implType, controllerName);
|
|
23
|
+
}).join("\n\n") || "";
|
|
24
|
+
return `${imports}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* ${routeName} Routes
|
|
28
|
+
* Generated from SpecVerse specification
|
|
29
|
+
*
|
|
30
|
+
* ${isModelController ? `Model: ${modelName}` : `Service: ${controllerName}`}
|
|
31
|
+
* Operations: ${controller.endpoints?.map((e) => e.operation).join(", ") || "CURED"}
|
|
32
|
+
*/
|
|
33
|
+
export default async function ${routeName.replace("Controller", "")}Routes(
|
|
34
|
+
fastify: FastifyInstance,
|
|
35
|
+
options: any
|
|
36
|
+
) {
|
|
37
|
+
const handler = ${isModelController ? "options.controllers" : "options.services"}.${handlerName};
|
|
38
|
+
|
|
39
|
+
${routeHandlers.split("\n").map((line) => " " + line).join("\n")}
|
|
40
|
+
}
|
|
41
|
+
`;
|
|
42
|
+
}
|
|
43
|
+
function generateImports(controller, modelName, handlerName, isModelController, implType) {
|
|
44
|
+
const imports = [
|
|
45
|
+
`import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';`
|
|
46
|
+
];
|
|
47
|
+
if (implType?.technology?.validation === "zod" && modelName) {
|
|
48
|
+
imports.push(`import { ${modelName}Schema } from '../validation/${modelName}.schema.js';`);
|
|
49
|
+
}
|
|
50
|
+
return imports.join("\n");
|
|
51
|
+
}
|
|
52
|
+
function generateRouteHandler(endpoint, modelName, handlerName, isModelController, implType, controllerName) {
|
|
53
|
+
let operation = endpoint.operation || endpoint.name;
|
|
54
|
+
if (!operation || operation === "custom") {
|
|
55
|
+
const serviceOp = endpoint.serviceOperation?.type;
|
|
56
|
+
if (serviceOp === "custom" && endpoint.path) {
|
|
57
|
+
const pathParts = endpoint.path.split("/").filter((p) => p);
|
|
58
|
+
const lastPart = pathParts[pathParts.length - 1];
|
|
59
|
+
operation = lastPart.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
60
|
+
} else if (!operation) {
|
|
61
|
+
operation = serviceOp;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (!operation) {
|
|
65
|
+
operation = inferOperationFromMethodAndPath(endpoint.method, endpoint.path);
|
|
66
|
+
}
|
|
67
|
+
if (!operation || operation === "unknown") {
|
|
68
|
+
console.warn(`Warning: Could not determine operation for endpoint in ${controllerName || "controller"}. Endpoint data:`, {
|
|
69
|
+
operation: endpoint.operation,
|
|
70
|
+
name: endpoint.name,
|
|
71
|
+
method: endpoint.method,
|
|
72
|
+
path: endpoint.path,
|
|
73
|
+
serviceOperation: endpoint.serviceOperation
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
const method = endpoint.method?.toLowerCase() || inferHttpMethod(operation);
|
|
77
|
+
const path = inferPath(operation, endpoint);
|
|
78
|
+
const handler = generateHandlerBody(operation, modelName, handlerName, isModelController, implType);
|
|
79
|
+
let route = `// ${operation} ${modelName}
|
|
80
|
+
`;
|
|
81
|
+
route += `fastify.${method}('${path}', {
|
|
82
|
+
`;
|
|
83
|
+
if (implType?.technology?.validation === "zod") {
|
|
84
|
+
route += ` schema: {
|
|
85
|
+
`;
|
|
86
|
+
if (operation === "create" || operation === "update" || operation === "evolve") {
|
|
87
|
+
route += ` body: ${modelName}Schema.${operation},
|
|
88
|
+
`;
|
|
89
|
+
}
|
|
90
|
+
if (operation === "retrieve" || operation === "update" || operation === "delete") {
|
|
91
|
+
route += ` params: ${modelName}Schema.params,
|
|
92
|
+
`;
|
|
93
|
+
}
|
|
94
|
+
route += ` response: {
|
|
95
|
+
`;
|
|
96
|
+
route += ` 200: ${modelName}Schema.response
|
|
97
|
+
`;
|
|
98
|
+
route += ` }
|
|
99
|
+
`;
|
|
100
|
+
route += ` },
|
|
101
|
+
`;
|
|
102
|
+
}
|
|
103
|
+
const usesRequest = ["create", "retrieve", "update", "evolve", "delete", "validate"].includes(operation) || !["list"].includes(operation);
|
|
104
|
+
const requestParam = usesRequest ? "request" : "_request";
|
|
105
|
+
route += ` handler: async (${requestParam}: FastifyRequest, reply: FastifyReply) => {
|
|
106
|
+
`;
|
|
107
|
+
route += handler.split("\n").map((line) => " " + line).join("\n") + "\n";
|
|
108
|
+
route += ` }
|
|
109
|
+
`;
|
|
110
|
+
route += `});`;
|
|
111
|
+
return route;
|
|
112
|
+
}
|
|
113
|
+
function generateHandlerBody(operation, modelName, handlerName, isModelController, implType) {
|
|
114
|
+
const rawLowerModel = modelName?.toLowerCase() || "item";
|
|
115
|
+
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"]);
|
|
116
|
+
const lowerModel = RESERVED_WORDS.has(rawLowerModel) ? `${rawLowerModel}Item` : rawLowerModel;
|
|
117
|
+
const operationMap = {
|
|
118
|
+
"custom": "create",
|
|
119
|
+
// POST without ID -> create
|
|
120
|
+
"findUnique": "retrieve",
|
|
121
|
+
// GET with ID -> retrieve
|
|
122
|
+
"findMany": "list",
|
|
123
|
+
// GET without ID -> list
|
|
124
|
+
"updateUnique": "update",
|
|
125
|
+
// PUT with ID -> update
|
|
126
|
+
"deleteUnique": "delete"
|
|
127
|
+
// DELETE with ID -> delete
|
|
128
|
+
};
|
|
129
|
+
const mappedOperation = operationMap[operation] || operation;
|
|
130
|
+
switch (mappedOperation) {
|
|
131
|
+
case "create":
|
|
132
|
+
return `try {
|
|
133
|
+
const ${lowerModel} = await handler.create(request.body as any);
|
|
134
|
+
return reply.status(201).send(${lowerModel});
|
|
135
|
+
} catch (error) {
|
|
136
|
+
return reply.status(400).send({
|
|
137
|
+
error: 'Failed to create ${lowerModel}',
|
|
138
|
+
message: error instanceof Error ? error.message : String(error)
|
|
139
|
+
});
|
|
140
|
+
}`;
|
|
141
|
+
case "retrieve":
|
|
142
|
+
return `try {
|
|
143
|
+
const { id } = request.params as { id: string };
|
|
144
|
+
const ${lowerModel} = await handler.retrieve(id);
|
|
145
|
+
|
|
146
|
+
if (!${lowerModel}) {
|
|
147
|
+
return reply.status(404).send({ error: '${modelName} not found' });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return reply.send(${lowerModel});
|
|
151
|
+
} catch (error) {
|
|
152
|
+
return reply.status(500).send({
|
|
153
|
+
error: 'Failed to retrieve ${lowerModel}',
|
|
154
|
+
message: error instanceof Error ? error.message : String(error)
|
|
155
|
+
});
|
|
156
|
+
}`;
|
|
157
|
+
case "update":
|
|
158
|
+
case "evolve":
|
|
159
|
+
return `try {
|
|
160
|
+
const { id } = request.params as { id: string };
|
|
161
|
+
const ${lowerModel} = await handler.${operation}(id, request.body as any);
|
|
162
|
+
return reply.send(${lowerModel});
|
|
163
|
+
} catch (error) {
|
|
164
|
+
return reply.status(400).send({
|
|
165
|
+
error: 'Failed to ${operation} ${lowerModel}',
|
|
166
|
+
message: error instanceof Error ? error.message : String(error)
|
|
167
|
+
});
|
|
168
|
+
}`;
|
|
169
|
+
case "delete":
|
|
170
|
+
return `try {
|
|
171
|
+
const { id } = request.params as { id: string };
|
|
172
|
+
await handler.delete(id);
|
|
173
|
+
return reply.status(204).send();
|
|
174
|
+
} catch (error) {
|
|
175
|
+
return reply.status(500).send({
|
|
176
|
+
error: 'Failed to delete ${lowerModel}',
|
|
177
|
+
message: error instanceof Error ? error.message : String(error)
|
|
178
|
+
});
|
|
179
|
+
}`;
|
|
180
|
+
case "list":
|
|
181
|
+
return `try {
|
|
182
|
+
const ${lowerModel}s = await handler.retrieveAll();
|
|
183
|
+
return reply.send(${lowerModel}s);
|
|
184
|
+
} catch (error) {
|
|
185
|
+
return reply.status(500).send({
|
|
186
|
+
error: 'Failed to list ${lowerModel}s',
|
|
187
|
+
message: error instanceof Error ? error.message : String(error)
|
|
188
|
+
});
|
|
189
|
+
}`;
|
|
190
|
+
case "validate":
|
|
191
|
+
return `try {
|
|
192
|
+
const { data, operation: op } = request.body as { data: any; operation: string };
|
|
193
|
+
const result = handler.validate(data, { operation: op });
|
|
194
|
+
return reply.send(result);
|
|
195
|
+
} catch (error) {
|
|
196
|
+
return reply.status(400).send({
|
|
197
|
+
error: 'Validation failed',
|
|
198
|
+
message: error instanceof Error ? error.message : String(error)
|
|
199
|
+
});
|
|
200
|
+
}`;
|
|
201
|
+
default:
|
|
202
|
+
return `try {
|
|
203
|
+
const result = await handler.${operation}(request.body as any);
|
|
204
|
+
return reply.send(result);
|
|
205
|
+
} catch (error) {
|
|
206
|
+
return reply.status(400).send({
|
|
207
|
+
error: 'Failed to execute ${operation}',
|
|
208
|
+
message: error instanceof Error ? error.message : String(error)
|
|
209
|
+
});
|
|
210
|
+
}`;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function inferOperationFromMethodAndPath(method, path) {
|
|
214
|
+
if (!method || !path) return "unknown";
|
|
215
|
+
const methodLower = method.toLowerCase();
|
|
216
|
+
const hasIdParam = path.includes(":id");
|
|
217
|
+
if (methodLower === "post" && !hasIdParam) return "create";
|
|
218
|
+
if (methodLower === "get" && hasIdParam) return "retrieve";
|
|
219
|
+
if (methodLower === "get" && !hasIdParam) return "list";
|
|
220
|
+
if (methodLower === "put" && hasIdParam) return "update";
|
|
221
|
+
if (methodLower === "patch" && hasIdParam) return "evolve";
|
|
222
|
+
if (methodLower === "delete" && hasIdParam) return "delete";
|
|
223
|
+
return "unknown";
|
|
224
|
+
}
|
|
225
|
+
function inferHttpMethod(operation) {
|
|
226
|
+
if (!operation) {
|
|
227
|
+
console.warn("Warning: undefined operation in inferHttpMethod");
|
|
228
|
+
return "post";
|
|
229
|
+
}
|
|
230
|
+
const opLower = operation.toLowerCase();
|
|
231
|
+
if (opLower === "create" || opLower === "validate") return "post";
|
|
232
|
+
if (opLower === "retrieve" || opLower === "list") return "get";
|
|
233
|
+
if (opLower === "update" || opLower === "evolve") return "put";
|
|
234
|
+
if (opLower === "delete") return "delete";
|
|
235
|
+
return "post";
|
|
236
|
+
}
|
|
237
|
+
function inferPath(operation, endpoint) {
|
|
238
|
+
const opLower = operation.toLowerCase();
|
|
239
|
+
if (opLower === "create") return "/";
|
|
240
|
+
if (opLower === "list") return "/";
|
|
241
|
+
if (opLower === "retrieve") return "/:id";
|
|
242
|
+
if (opLower === "update") return "/:id";
|
|
243
|
+
if (opLower === "evolve") return "/:id/evolve";
|
|
244
|
+
if (opLower === "delete") return "/:id";
|
|
245
|
+
if (opLower === "validate") return "/validate";
|
|
246
|
+
if (endpoint.path && endpoint.serviceOperation?.type === "custom") {
|
|
247
|
+
const pathParts = endpoint.path.split("/").filter((p) => p);
|
|
248
|
+
const lastPart = pathParts[pathParts.length - 1];
|
|
249
|
+
return `/${lastPart}`;
|
|
250
|
+
}
|
|
251
|
+
return `/${opLower}`;
|
|
252
|
+
}
|
|
253
|
+
function curedToEndpoints(cured, modelName) {
|
|
254
|
+
const endpoints = [];
|
|
255
|
+
if (cured.create) {
|
|
256
|
+
endpoints.push({ operation: "create", method: "POST" });
|
|
257
|
+
}
|
|
258
|
+
if (cured.retrieve) {
|
|
259
|
+
endpoints.push({ operation: "retrieve", method: "GET" });
|
|
260
|
+
}
|
|
261
|
+
if (cured.retrieve_many) {
|
|
262
|
+
endpoints.push({ operation: "list", method: "GET" });
|
|
263
|
+
}
|
|
264
|
+
if (cured.update) {
|
|
265
|
+
endpoints.push({ operation: "update", method: "PUT" });
|
|
266
|
+
}
|
|
267
|
+
if (cured.evolve) {
|
|
268
|
+
endpoints.push({ operation: "evolve", method: "PATCH" });
|
|
269
|
+
}
|
|
270
|
+
if (cured.delete) {
|
|
271
|
+
endpoints.push({ operation: "delete", method: "DELETE" });
|
|
272
|
+
}
|
|
273
|
+
if (cured.validate) {
|
|
274
|
+
endpoints.push({ operation: "validate", method: "POST" });
|
|
275
|
+
}
|
|
276
|
+
return endpoints;
|
|
277
|
+
}
|
|
278
|
+
export {
|
|
279
|
+
generateFastifyRoutes as default
|
|
280
|
+
};
|