@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.
- 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/package.json +3 -2
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
function normalizeSpec(spec) {
|
|
2
|
+
if (!spec) return spec;
|
|
3
|
+
const normalized = { ...spec };
|
|
4
|
+
if (normalized.models && typeof normalized.models === "object") {
|
|
5
|
+
for (const [name, model] of Object.entries(normalized.models)) {
|
|
6
|
+
if (Array.isArray(model.attributes)) {
|
|
7
|
+
const obj = {};
|
|
8
|
+
for (const attr of model.attributes) {
|
|
9
|
+
if (attr.name) obj[attr.name] = attr;
|
|
10
|
+
}
|
|
11
|
+
model.attributes = obj;
|
|
12
|
+
}
|
|
13
|
+
if (Array.isArray(model.relationships)) {
|
|
14
|
+
const obj = {};
|
|
15
|
+
for (const rel of model.relationships) {
|
|
16
|
+
if (rel.name) obj[rel.name] = { ...rel, targetModel: rel.target || rel.targetModel };
|
|
17
|
+
}
|
|
18
|
+
model.relationships = obj;
|
|
19
|
+
}
|
|
20
|
+
if (Array.isArray(model.lifecycles)) {
|
|
21
|
+
const obj = {};
|
|
22
|
+
for (const lc of model.lifecycles) {
|
|
23
|
+
if (lc.name) obj[lc.name] = lc;
|
|
24
|
+
}
|
|
25
|
+
model.lifecycles = obj;
|
|
26
|
+
}
|
|
27
|
+
if (model.lifecycles && typeof model.lifecycles === "object") {
|
|
28
|
+
for (const lc of Object.values(model.lifecycles)) {
|
|
29
|
+
if (lc.flow && typeof lc.flow === "string" && !lc.states) {
|
|
30
|
+
const stateNames = lc.flow.split("->").map((s) => s.trim()).filter(Boolean);
|
|
31
|
+
lc.states = stateNames.map((s) => ({ name: s }));
|
|
32
|
+
lc.transitions = [];
|
|
33
|
+
lc.initialState = stateNames[0];
|
|
34
|
+
for (let i = 0; i < stateNames.length - 1; i++) {
|
|
35
|
+
lc.transitions.push({ from: stateNames[i], to: stateNames[i + 1] });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (normalized.views && typeof normalized.views === "object") {
|
|
43
|
+
for (const view of Object.values(normalized.views)) {
|
|
44
|
+
if (Array.isArray(view.model)) {
|
|
45
|
+
view.models = view.model;
|
|
46
|
+
view.model = view.model[0];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return normalized;
|
|
51
|
+
}
|
|
52
|
+
function generateFastifyServer(context) {
|
|
53
|
+
const { spec, models } = context;
|
|
54
|
+
const allModels = models || (spec?.models ? Object.values(spec.models) : []);
|
|
55
|
+
const modelNames = allModels.map((m) => m.name).filter(Boolean);
|
|
56
|
+
const routeImports = modelNames.map(
|
|
57
|
+
(name) => `import ${name}Routes from './routes/${name}Controller.js';`
|
|
58
|
+
).join("\n");
|
|
59
|
+
const routeRegistrations = modelNames.map((name) => {
|
|
60
|
+
const path = `/api/${name.toLowerCase()}s`;
|
|
61
|
+
return ` await fastify.register(${name}Routes, { prefix: '${path}', controllers: { ${name}Controller: new (await import('./controllers/${name}Controller.js')).${name}Controller() } });`;
|
|
62
|
+
}).join("\n");
|
|
63
|
+
return `/**
|
|
64
|
+
* Fastify Server
|
|
65
|
+
* Generated from SpecVerse specification
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
import Fastify from 'fastify';
|
|
69
|
+
import cors from '@fastify/cors';
|
|
70
|
+
import { PrismaClient } from '@prisma/client';
|
|
71
|
+
|
|
72
|
+
// Initialize Prisma
|
|
73
|
+
export const prisma = new PrismaClient();
|
|
74
|
+
|
|
75
|
+
// Initialize Fastify
|
|
76
|
+
const fastify = Fastify({
|
|
77
|
+
logger: { level: 'info' },
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Register plugins
|
|
81
|
+
await fastify.register(cors, { origin: true, credentials: true });
|
|
82
|
+
|
|
83
|
+
// Health check
|
|
84
|
+
fastify.get('/health', async () => ({ status: 'ok', timestamp: new Date().toISOString() }));
|
|
85
|
+
fastify.get('/', async () => ({ name: 'SpecVerse Generated API', models: ${JSON.stringify(modelNames)} }));
|
|
86
|
+
|
|
87
|
+
// Spec endpoint \u2014 serves the specification for the runtime frontend
|
|
88
|
+
const embeddedSpec = ${JSON.stringify(normalizeSpec(spec), null, 2)};
|
|
89
|
+
fastify.get('/api/spec', async () => embeddedSpec);
|
|
90
|
+
|
|
91
|
+
// Runtime info endpoint \u2014 models and controllers for DevShell
|
|
92
|
+
fastify.get('/api/runtime/info', async () => ({
|
|
93
|
+
controllers: ${JSON.stringify(modelNames.map((n) => `${n}Controller`))},
|
|
94
|
+
models: ${JSON.stringify(modelNames)},
|
|
95
|
+
events: [],
|
|
96
|
+
services: []
|
|
97
|
+
}));
|
|
98
|
+
|
|
99
|
+
// Register routes
|
|
100
|
+
${routeImports}
|
|
101
|
+
|
|
102
|
+
async function registerRoutes() {
|
|
103
|
+
${routeRegistrations}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Start server
|
|
107
|
+
const start = async () => {
|
|
108
|
+
try {
|
|
109
|
+
await registerRoutes();
|
|
110
|
+
const port = parseInt(process.env.PORT || '3000');
|
|
111
|
+
await fastify.listen({ port, host: '0.0.0.0' });
|
|
112
|
+
console.log(\`Server running at http://localhost:\${port}\`);
|
|
113
|
+
console.log(\`API endpoints: ${modelNames.map((n) => `/api/${n.toLowerCase()}s`).join(", ")}\`);
|
|
114
|
+
} catch (err) {
|
|
115
|
+
fastify.log.error(err);
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
start();
|
|
121
|
+
`;
|
|
122
|
+
}
|
|
123
|
+
export {
|
|
124
|
+
generateFastifyServer as default
|
|
125
|
+
};
|
package/dist/libs/instance-factories/infrastructure/templates/docker-k8s/infrastructure-generator.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { generateInfrastructure } from "../../../../../scripts/generate-infrastructure.js";
|
|
2
|
+
function generateInfra(context) {
|
|
3
|
+
const { spec, implType, outputDir } = context;
|
|
4
|
+
if (!spec) {
|
|
5
|
+
throw new Error("Specification is required in template context");
|
|
6
|
+
}
|
|
7
|
+
const config = implType.configuration || {};
|
|
8
|
+
const options = {
|
|
9
|
+
docker: config.docker || {},
|
|
10
|
+
kubernetes: config.kubernetes || {},
|
|
11
|
+
cicd: config.cicd || {}
|
|
12
|
+
};
|
|
13
|
+
const result = generateInfrastructure(spec, outputDir || "./infrastructure", options);
|
|
14
|
+
return JSON.stringify({
|
|
15
|
+
message: "Infrastructure code generated successfully",
|
|
16
|
+
dockerFiles: result.dockerFiles,
|
|
17
|
+
kubernetesFiles: result.kubernetesFiles,
|
|
18
|
+
cicdFiles: result.cicdFiles,
|
|
19
|
+
outputDir: result.outputDir,
|
|
20
|
+
files: result.files
|
|
21
|
+
}, null, 2);
|
|
22
|
+
}
|
|
23
|
+
export {
|
|
24
|
+
generateInfra as default
|
|
25
|
+
};
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import { pluralize } from "@specverse/types";
|
|
2
|
+
function generatePrismaSchema(context) {
|
|
3
|
+
const { spec, models, implType } = context;
|
|
4
|
+
const allModels = models || spec?.models || [];
|
|
5
|
+
if (allModels.length === 0) {
|
|
6
|
+
throw new Error("No models found in context for schema generation");
|
|
7
|
+
}
|
|
8
|
+
const relationMap = buildRelationMap(allModels);
|
|
9
|
+
const backRefs = buildMissingBackRefs(allModels, relationMap);
|
|
10
|
+
const hasOneTargets = /* @__PURE__ */ new Set();
|
|
11
|
+
for (const m of allModels) {
|
|
12
|
+
const rels = Array.isArray(m.relationships) ? m.relationships : Object.values(m.relationships || {});
|
|
13
|
+
for (const r of rels) {
|
|
14
|
+
if (r.type === "hasOne") {
|
|
15
|
+
hasOneTargets.add(`${m.name}->${r.target}`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const header = generateHeader(implType);
|
|
20
|
+
const modelSchemas = allModels.map(
|
|
21
|
+
(model) => generateModelSchema(model, relationMap, backRefs, hasOneTargets, allModels)
|
|
22
|
+
).join("\n\n");
|
|
23
|
+
return `${header}
|
|
24
|
+
|
|
25
|
+
${modelSchemas}`;
|
|
26
|
+
}
|
|
27
|
+
function buildRelationMap(allModels) {
|
|
28
|
+
const targetRefs = /* @__PURE__ */ new Map();
|
|
29
|
+
for (const model of allModels) {
|
|
30
|
+
const relationships = Array.isArray(model.relationships) ? model.relationships : Object.values(model.relationships || {});
|
|
31
|
+
for (const rel of relationships) {
|
|
32
|
+
const target = rel.target;
|
|
33
|
+
if (!target) continue;
|
|
34
|
+
const fieldName = rel.name || (rel.type === "hasMany" || rel.type === "manyToMany" ? pluralize(target.toLowerCase()) : target.toLowerCase());
|
|
35
|
+
if (!targetRefs.has(target)) {
|
|
36
|
+
targetRefs.set(target, []);
|
|
37
|
+
}
|
|
38
|
+
targetRefs.get(target).push({
|
|
39
|
+
sourceModel: model.name,
|
|
40
|
+
fieldName,
|
|
41
|
+
relType: rel.type
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const nameMap = /* @__PURE__ */ new Map();
|
|
46
|
+
for (const [target, refs] of targetRefs) {
|
|
47
|
+
for (const ref of refs) {
|
|
48
|
+
const key = `${ref.sourceModel}.${ref.fieldName}`;
|
|
49
|
+
if (ref.relType === "belongsTo") {
|
|
50
|
+
const parentRefs = targetRefs.get(ref.sourceModel) || [];
|
|
51
|
+
const matchingParent = parentRefs.find(
|
|
52
|
+
(p) => p.sourceModel === target && (p.relType === "hasMany" || p.relType === "hasOne")
|
|
53
|
+
);
|
|
54
|
+
if (matchingParent) {
|
|
55
|
+
nameMap.set(key, `${matchingParent.sourceModel}_${matchingParent.fieldName}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return nameMap;
|
|
61
|
+
}
|
|
62
|
+
function buildMissingBackRefs(allModels, relationMap) {
|
|
63
|
+
const existingBelongsTo = /* @__PURE__ */ new Set();
|
|
64
|
+
for (const model of allModels) {
|
|
65
|
+
const relationships = Array.isArray(model.relationships) ? model.relationships : Object.values(model.relationships || {});
|
|
66
|
+
for (const rel of relationships) {
|
|
67
|
+
if (rel.type === "belongsTo") {
|
|
68
|
+
existingBelongsTo.add(`${model.name}->${rel.target}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const backRefs = /* @__PURE__ */ new Map();
|
|
73
|
+
for (const model of allModels) {
|
|
74
|
+
const relationships = Array.isArray(model.relationships) ? model.relationships : Object.values(model.relationships || {});
|
|
75
|
+
for (const rel of relationships) {
|
|
76
|
+
if (rel.type !== "hasMany" && rel.type !== "hasOne") continue;
|
|
77
|
+
const target = rel.target;
|
|
78
|
+
const backKey = `${target}->${model.name}`;
|
|
79
|
+
if (!existingBelongsTo.has(backKey)) {
|
|
80
|
+
const fieldName = rel.name || (rel.type === "hasMany" ? pluralize(target.toLowerCase()) : target.toLowerCase());
|
|
81
|
+
const relName = `${model.name}_${fieldName}`;
|
|
82
|
+
const parentRelsToSameTarget = relationships.filter(
|
|
83
|
+
(r) => r.target === target && (r.type === "hasMany" || r.type === "hasOne")
|
|
84
|
+
);
|
|
85
|
+
const needsFieldInFk = parentRelsToSameTarget.length > 1;
|
|
86
|
+
const fkSuffix = needsFieldInFk ? camelToSnake(fieldName) + "_id" : camelToSnake(model.name.charAt(0).toLowerCase() + model.name.slice(1)) + "_id";
|
|
87
|
+
const fkName = fkSuffix;
|
|
88
|
+
const fkPadding = " ".repeat(Math.max(1, 15 - fkName.length));
|
|
89
|
+
const refFieldName = needsFieldInFk ? fieldName + model.name : model.name.charAt(0).toLowerCase() + model.name.slice(1);
|
|
90
|
+
const refPadding = " ".repeat(Math.max(1, 15 - refFieldName.length));
|
|
91
|
+
let relDef = `${refFieldName}${refPadding}${model.name}`;
|
|
92
|
+
relDef += ` @relation("${relName}", fields: [${fkName}], references: [id])`;
|
|
93
|
+
if (!backRefs.has(target)) {
|
|
94
|
+
backRefs.set(target, []);
|
|
95
|
+
}
|
|
96
|
+
const uniqueModifier = rel.type === "hasOne" ? " @unique" : "";
|
|
97
|
+
backRefs.get(target).push(`${fkName}${fkPadding}String${uniqueModifier}`);
|
|
98
|
+
backRefs.get(target).push(relDef);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const existingHasRelation = /* @__PURE__ */ new Set();
|
|
103
|
+
for (const model of allModels) {
|
|
104
|
+
const rels = Array.isArray(model.relationships) ? model.relationships : Object.values(model.relationships || {});
|
|
105
|
+
for (const rel of rels) {
|
|
106
|
+
if (rel.type === "hasMany" || rel.type === "hasOne") {
|
|
107
|
+
existingHasRelation.add(`${model.name}->${rel.target}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
for (const model of allModels) {
|
|
112
|
+
const relationships = Array.isArray(model.relationships) ? model.relationships : Object.values(model.relationships || {});
|
|
113
|
+
for (const rel of relationships) {
|
|
114
|
+
if (rel.type !== "belongsTo") continue;
|
|
115
|
+
const parent = rel.target;
|
|
116
|
+
const forwardKey = `${parent}->${model.name}`;
|
|
117
|
+
if (!existingHasRelation.has(forwardKey)) {
|
|
118
|
+
const fieldName = rel.name || parent.toLowerCase();
|
|
119
|
+
const relName = getRelationName(model.name, fieldName, parent, relationMap);
|
|
120
|
+
const arrayFieldName = pluralize(model.name.charAt(0).toLowerCase() + model.name.slice(1));
|
|
121
|
+
const padding = " ".repeat(Math.max(1, 15 - arrayFieldName.length));
|
|
122
|
+
if (!backRefs.has(parent)) {
|
|
123
|
+
backRefs.set(parent, []);
|
|
124
|
+
}
|
|
125
|
+
backRefs.get(parent).push(`${arrayFieldName}${padding}${model.name}[] @relation("${relName}")`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return backRefs;
|
|
130
|
+
}
|
|
131
|
+
function getRelationName(sourceModel, fieldName, target, relationMap) {
|
|
132
|
+
const mapped = relationMap.get(`${sourceModel}.${fieldName}`);
|
|
133
|
+
if (mapped) return mapped;
|
|
134
|
+
return `${sourceModel}_${fieldName}`;
|
|
135
|
+
}
|
|
136
|
+
function generateHeader(implType) {
|
|
137
|
+
const provider = implType?.configuration?.orm?.provider || "sqlite";
|
|
138
|
+
return `// Generated Prisma schema from SpecVerse specification
|
|
139
|
+
// This is your Prisma schema file
|
|
140
|
+
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
|
141
|
+
|
|
142
|
+
generator client {
|
|
143
|
+
provider = "prisma-client-js"
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
datasource db {
|
|
147
|
+
provider = "${provider}"
|
|
148
|
+
url = ${provider === "sqlite" ? '"file:./dev.db"' : 'env("DATABASE_URL")'}
|
|
149
|
+
}`;
|
|
150
|
+
}
|
|
151
|
+
function generateModelSchema(model, relationMap, backRefs, hasOneTargets, allModels) {
|
|
152
|
+
const modelName = model.name;
|
|
153
|
+
let schema = `model ${modelName} {
|
|
154
|
+
`;
|
|
155
|
+
const attributes = Array.isArray(model.attributes) ? model.attributes : Object.values(model.attributes || {});
|
|
156
|
+
const relationships = Array.isArray(model.relationships) ? model.relationships : Object.values(model.relationships || {});
|
|
157
|
+
attributes.forEach((attr) => {
|
|
158
|
+
schema += ` ${generateField(attr, model)}
|
|
159
|
+
`;
|
|
160
|
+
});
|
|
161
|
+
const rawLifecycles = model.lifecycles || [];
|
|
162
|
+
const lifecycleList = Array.isArray(rawLifecycles) ? rawLifecycles : Object.entries(rawLifecycles).map(([name, lc]) => ({ name, ...typeof lc === "object" ? lc : {} }));
|
|
163
|
+
for (const lifecycle of lifecycleList) {
|
|
164
|
+
const fieldName = lifecycle.name || "status";
|
|
165
|
+
const exists = attributes.some((a) => a.name === fieldName);
|
|
166
|
+
if (!exists && lifecycle.states?.length > 0) {
|
|
167
|
+
const defaultState = lifecycle.states[0];
|
|
168
|
+
const padding = " ".repeat(Math.max(1, 15 - fieldName.length));
|
|
169
|
+
schema += ` ${fieldName}${padding}String @default("${defaultState}")
|
|
170
|
+
`;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
relationships.forEach((rel) => {
|
|
174
|
+
const fields = generateRelationship(rel, model, relationMap, hasOneTargets, allModels);
|
|
175
|
+
fields.forEach((field) => {
|
|
176
|
+
schema += ` ${field}
|
|
177
|
+
`;
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
const modelBackRefs = backRefs.get(modelName);
|
|
181
|
+
if (modelBackRefs) {
|
|
182
|
+
for (const line of modelBackRefs) {
|
|
183
|
+
schema += ` ${line}
|
|
184
|
+
`;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
schema += `}`;
|
|
188
|
+
return schema;
|
|
189
|
+
}
|
|
190
|
+
function generateField(attr, model) {
|
|
191
|
+
const name = attr.name;
|
|
192
|
+
const prismaType = mapTypeToPrisma(attr.type, attr.dbMapping?.columnType);
|
|
193
|
+
const isOptional = !(attr.required || attr.constraints?.required);
|
|
194
|
+
const isUnique = attr.unique || attr.constraints?.unique;
|
|
195
|
+
const metadata = model?.metadata || {};
|
|
196
|
+
let modifiers = "";
|
|
197
|
+
let hasDefault = false;
|
|
198
|
+
if (name.toLowerCase() === "id") {
|
|
199
|
+
modifiers += " @id";
|
|
200
|
+
if (metadata.id === "uuid" || metadata.id === "auto" || prismaType === "String") {
|
|
201
|
+
modifiers += " @default(uuid())";
|
|
202
|
+
hasDefault = true;
|
|
203
|
+
} else if (metadata.id === "integer" || prismaType === "Int") {
|
|
204
|
+
modifiers += " @default(autoincrement())";
|
|
205
|
+
hasDefault = true;
|
|
206
|
+
}
|
|
207
|
+
} else if (isUnique) {
|
|
208
|
+
modifiers += " @unique";
|
|
209
|
+
}
|
|
210
|
+
if (name === "updatedAt" || name === "updated_at") {
|
|
211
|
+
modifiers += " @updatedAt";
|
|
212
|
+
hasDefault = true;
|
|
213
|
+
}
|
|
214
|
+
if (attr.auto && !hasDefault) {
|
|
215
|
+
const autoValue = typeof attr.auto === "string" ? attr.auto.toLowerCase() : attr.auto;
|
|
216
|
+
if (autoValue === "now" || autoValue === true) {
|
|
217
|
+
if (prismaType === "DateTime") {
|
|
218
|
+
modifiers += " @default(now())";
|
|
219
|
+
hasDefault = true;
|
|
220
|
+
}
|
|
221
|
+
} else if (autoValue === "uuid" || autoValue === "uuid4") {
|
|
222
|
+
if (prismaType === "String") {
|
|
223
|
+
modifiers += " @default(uuid())";
|
|
224
|
+
hasDefault = true;
|
|
225
|
+
}
|
|
226
|
+
} else if (autoValue === "autoincrement") {
|
|
227
|
+
if (prismaType === "Int") {
|
|
228
|
+
modifiers += " @default(autoincrement())";
|
|
229
|
+
hasDefault = true;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (!hasDefault && prismaType === "DateTime") {
|
|
234
|
+
const autoTimestampFields = ["joinedAt", "registeredAt", "enrolledAt", "startedAt", "completedAt", "verifiedAt"];
|
|
235
|
+
if (autoTimestampFields.includes(name)) {
|
|
236
|
+
modifiers += " @default(now())";
|
|
237
|
+
hasDefault = true;
|
|
238
|
+
}
|
|
239
|
+
if ((name === "createdAt" || name === "created_at") && metadata.audit) {
|
|
240
|
+
modifiers += " @default(now())";
|
|
241
|
+
hasDefault = true;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (metadata.softDelete && isSoftDeleteField(name)) {
|
|
245
|
+
if (name === "isDeleted" || name === "is_deleted") {
|
|
246
|
+
modifiers += " @default(false)";
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (metadata.version && isVersionField(name)) {
|
|
250
|
+
modifiers += " @default(0)";
|
|
251
|
+
}
|
|
252
|
+
if (attr.dbMapping?.columnType === "TEXT") {
|
|
253
|
+
modifiers += " @db.Text";
|
|
254
|
+
} else if (attr.dbMapping?.columnType?.startsWith("VARCHAR")) {
|
|
255
|
+
const match = attr.dbMapping.columnType.match(/VARCHAR\((\d+)\)/);
|
|
256
|
+
if (match) {
|
|
257
|
+
modifiers += ` @db.VarChar(${match[1]})`;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
const padding = " ".repeat(Math.max(1, 15 - name.length));
|
|
261
|
+
const typeStr = isOptional ? `${prismaType}?` : prismaType;
|
|
262
|
+
return `${name}${padding}${typeStr}${modifiers}`;
|
|
263
|
+
}
|
|
264
|
+
function generateRelationship(rel, model, relationMap, hasOneTargets, allModels) {
|
|
265
|
+
const fields = [];
|
|
266
|
+
const name = rel.name || rel.target.toLowerCase();
|
|
267
|
+
const padding = " ".repeat(Math.max(1, 15 - name.length));
|
|
268
|
+
const isOptional = rel.optional || false;
|
|
269
|
+
const relName = getRelationName(model.name, name, rel.target, relationMap);
|
|
270
|
+
const relAnnotation = ` @relation("${relName}")`;
|
|
271
|
+
switch (rel.type) {
|
|
272
|
+
case "belongsTo":
|
|
273
|
+
const fkBase = rel.foreignKey || camelToSnake(name) + "_id";
|
|
274
|
+
const fkPadding = " ".repeat(Math.max(1, 15 - fkBase.length));
|
|
275
|
+
const isUniqueFK = hasOneTargets.has(`${rel.target}->${model.name}`);
|
|
276
|
+
let fkType = "String";
|
|
277
|
+
if (allModels) {
|
|
278
|
+
const targetModel = allModels.find((m) => m.name === rel.target);
|
|
279
|
+
if (targetModel) {
|
|
280
|
+
const idAttr = (Array.isArray(targetModel.attributes) ? targetModel.attributes : Object.values(targetModel.attributes || {})).find((a) => a.name === "id");
|
|
281
|
+
if (idAttr) {
|
|
282
|
+
const idType = idAttr.type || "String";
|
|
283
|
+
fkType = mapTypeToPrisma(idType);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
fields.push(`${fkBase}${fkPadding}${fkType}${isOptional ? "?" : ""}${isUniqueFK ? " @unique" : ""}`);
|
|
288
|
+
let relationDef = `${name}${padding}${rel.target}${isOptional ? "?" : ""}`;
|
|
289
|
+
relationDef += ` @relation(`;
|
|
290
|
+
if (relName) {
|
|
291
|
+
relationDef += `"${relName}", `;
|
|
292
|
+
}
|
|
293
|
+
relationDef += `fields: [${fkBase}], references: [id]`;
|
|
294
|
+
if (rel.onDelete) {
|
|
295
|
+
relationDef += `, onDelete: ${rel.onDelete}`;
|
|
296
|
+
}
|
|
297
|
+
if (rel.onUpdate) {
|
|
298
|
+
relationDef += `, onUpdate: ${rel.onUpdate}`;
|
|
299
|
+
}
|
|
300
|
+
relationDef += ")";
|
|
301
|
+
fields.push(relationDef);
|
|
302
|
+
break;
|
|
303
|
+
case "hasOne":
|
|
304
|
+
fields.push(`${name}${padding}${rel.target}?${relAnnotation}`);
|
|
305
|
+
break;
|
|
306
|
+
case "hasMany":
|
|
307
|
+
const pluralName = rel.name || pluralize(rel.target.toLowerCase());
|
|
308
|
+
const manyPadding = " ".repeat(Math.max(1, 15 - pluralName.length));
|
|
309
|
+
fields.push(`${pluralName}${manyPadding}${rel.target}[]${relAnnotation}`);
|
|
310
|
+
break;
|
|
311
|
+
case "manyToMany":
|
|
312
|
+
const manyName = rel.name || pluralize(rel.target.toLowerCase());
|
|
313
|
+
const m2mPadding = " ".repeat(Math.max(1, 15 - manyName.length));
|
|
314
|
+
fields.push(`${manyName}${m2mPadding}${rel.target}[]${relAnnotation}`);
|
|
315
|
+
break;
|
|
316
|
+
}
|
|
317
|
+
return fields;
|
|
318
|
+
}
|
|
319
|
+
function camelToSnake(str) {
|
|
320
|
+
return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
|
|
321
|
+
}
|
|
322
|
+
function mapTypeToPrisma(type, dbType) {
|
|
323
|
+
if (dbType) {
|
|
324
|
+
if (dbType.startsWith("VARCHAR") || dbType === "TEXT") return "String";
|
|
325
|
+
const typeMap = {
|
|
326
|
+
"INTEGER": "Int",
|
|
327
|
+
"BIGINT": "BigInt",
|
|
328
|
+
"DECIMAL": "Decimal",
|
|
329
|
+
"BOOLEAN": "Boolean",
|
|
330
|
+
"DATE": "DateTime",
|
|
331
|
+
"TIMESTAMP": "DateTime",
|
|
332
|
+
"UUID": "String",
|
|
333
|
+
"JSONB": "Json",
|
|
334
|
+
"JSON": "Json",
|
|
335
|
+
"FLOAT": "Float"
|
|
336
|
+
};
|
|
337
|
+
if (typeMap[dbType]) return typeMap[dbType];
|
|
338
|
+
}
|
|
339
|
+
const typeLower = type.toLowerCase();
|
|
340
|
+
if (typeLower.includes("string") || typeLower.includes("text")) return "String";
|
|
341
|
+
if (typeLower.includes("int")) return "Int";
|
|
342
|
+
if (typeLower.includes("bool")) return "Boolean";
|
|
343
|
+
if (typeLower.includes("date") || typeLower.includes("time")) return "DateTime";
|
|
344
|
+
if (typeLower.includes("float") || typeLower.includes("decimal")) return "Float";
|
|
345
|
+
if (typeLower.includes("json")) return "Json";
|
|
346
|
+
return "String";
|
|
347
|
+
}
|
|
348
|
+
function isAuditField(name) {
|
|
349
|
+
const auditFields = [
|
|
350
|
+
"createdAt",
|
|
351
|
+
"updatedAt",
|
|
352
|
+
"createdBy",
|
|
353
|
+
"updatedBy",
|
|
354
|
+
"created_at",
|
|
355
|
+
"updated_at",
|
|
356
|
+
"created_by",
|
|
357
|
+
"updated_by"
|
|
358
|
+
];
|
|
359
|
+
return auditFields.includes(name);
|
|
360
|
+
}
|
|
361
|
+
function isSoftDeleteField(name) {
|
|
362
|
+
const softDeleteFields = ["deletedAt", "isDeleted", "deleted_at", "is_deleted"];
|
|
363
|
+
return softDeleteFields.includes(name);
|
|
364
|
+
}
|
|
365
|
+
function isVersionField(name) {
|
|
366
|
+
const versionFields = ["version", "versionNumber", "version_number"];
|
|
367
|
+
return versionFields.includes(name);
|
|
368
|
+
}
|
|
369
|
+
export {
|
|
370
|
+
generatePrismaSchema as default
|
|
371
|
+
};
|