@specverse/engines 4.2.0 → 4.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/templates/default/specs/main.specly +65 -0
- package/dist/libs/instance-factories/CURVED-INTERFACE.md +278 -0
- package/dist/libs/instance-factories/README.md +73 -0
- package/dist/libs/instance-factories/applications/README.md +51 -0
- package/dist/libs/instance-factories/applications/generic-app.yaml +52 -0
- package/dist/libs/instance-factories/applications/react-app-runtime.yaml +139 -0
- package/dist/libs/instance-factories/applications/react-app-starter.yaml +143 -0
- package/dist/libs/instance-factories/applications/templates/react/env-example-generator.js +24 -2
- package/dist/libs/instance-factories/applications/templates/react/vite-config-generator.js +54 -33
- package/dist/libs/instance-factories/applications/templates/react-starter/README.md +211 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/api-types-starter-generator.js +69 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/app-tsx-generator.js +1 -1
- package/dist/libs/instance-factories/applications/templates/react-starter/belongs-to.js +40 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/dashboard-body-composer.js +11 -3
- package/dist/libs/instance-factories/applications/templates/react-starter/detail-body-composer.js +18 -16
- package/dist/libs/instance-factories/applications/templates/react-starter/form-body-composer.js +50 -23
- package/dist/libs/instance-factories/applications/templates/react-starter/helpers-emitter.js +9 -3
- package/dist/libs/instance-factories/applications/templates/react-starter/list-body-composer.js +17 -7
- package/dist/libs/instance-factories/applications/templates/react-starter/orchestrator.js +16 -5
- package/dist/libs/instance-factories/applications/templates/react-starter/skeletons/dashboard.tsx.template +49 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/skeletons/detail.tsx.template +96 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/skeletons/form.tsx.template +116 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/skeletons/list.tsx.template +74 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/use-api-hooks-starter-generator.js +95 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/view-emitter.js +26 -1
- package/dist/libs/instance-factories/archived/fastify-prisma.yaml +104 -0
- package/dist/libs/instance-factories/cli/README.md +43 -0
- package/dist/libs/instance-factories/cli/commander-js.yaml +55 -0
- package/dist/libs/instance-factories/cli/templates/commander/command-generator.js +49 -1
- package/dist/libs/instance-factories/communication/README.md +47 -0
- package/dist/libs/instance-factories/communication/event-emitter.yaml +60 -0
- package/dist/libs/instance-factories/communication/rabbitmq-events.yaml +87 -0
- package/dist/libs/instance-factories/controllers/README.md +42 -0
- package/dist/libs/instance-factories/controllers/fastify.yaml +139 -0
- package/dist/libs/instance-factories/controllers/templates/fastify/server-generator.js +29 -2
- package/dist/libs/instance-factories/infrastructure/README.md +29 -0
- package/dist/libs/instance-factories/infrastructure/docker-k8s.yaml +61 -0
- package/dist/libs/instance-factories/orms/README.md +54 -0
- package/dist/libs/instance-factories/orms/prisma.yaml +89 -0
- package/dist/libs/instance-factories/orms/templates/prisma/schema-generator.js +2 -2
- package/dist/libs/instance-factories/scaffolding/README.md +49 -0
- package/dist/libs/instance-factories/scaffolding/generic-scaffold.yaml +65 -0
- package/dist/libs/instance-factories/sdks/README.md +28 -0
- package/dist/libs/instance-factories/sdks/python-sdk.yaml +66 -0
- package/dist/libs/instance-factories/sdks/typescript-sdk.yaml +59 -0
- package/dist/libs/instance-factories/services/README.md +55 -0
- package/dist/libs/instance-factories/services/prisma-services.yaml +71 -0
- package/dist/libs/instance-factories/storage/README.md +34 -0
- package/dist/libs/instance-factories/storage/mongodb.yaml +79 -0
- package/dist/libs/instance-factories/storage/postgresql.yaml +75 -0
- package/dist/libs/instance-factories/storage/redis.yaml +79 -0
- package/dist/libs/instance-factories/testing/README.md +40 -0
- package/dist/libs/instance-factories/testing/vitest-tests.yaml +63 -0
- package/dist/libs/instance-factories/tools/README.md +70 -0
- package/dist/libs/instance-factories/tools/mcp.yaml +36 -0
- package/dist/libs/instance-factories/tools/vscode.yaml +35 -0
- package/dist/libs/instance-factories/validation/README.md +38 -0
- package/dist/libs/instance-factories/validation/zod.yaml +56 -0
- package/dist/realize/engines/code-generator.d.ts.map +1 -1
- package/dist/realize/engines/code-generator.js +3 -0
- package/dist/realize/engines/code-generator.js.map +1 -1
- package/libs/instance-factories/applications/react-app-starter.yaml +10 -17
- package/libs/instance-factories/applications/templates/react/env-example-generator.ts +24 -2
- package/libs/instance-factories/applications/templates/react/vite-config-generator.ts +54 -33
- package/libs/instance-factories/applications/templates/react-starter/__tests__/detail-body-composer.test.ts +5 -4
- package/libs/instance-factories/applications/templates/react-starter/__tests__/form-body-composer.test.ts +18 -5
- package/libs/instance-factories/applications/templates/react-starter/__tests__/orchestrator.test.ts +83 -62
- package/libs/instance-factories/applications/templates/react-starter/api-types-starter-generator.ts +98 -0
- package/libs/instance-factories/applications/templates/react-starter/app-tsx-generator.ts +1 -1
- package/libs/instance-factories/applications/templates/react-starter/belongs-to.ts +82 -0
- package/libs/instance-factories/applications/templates/react-starter/dashboard-body-composer.ts +20 -5
- package/libs/instance-factories/applications/templates/react-starter/detail-body-composer.ts +33 -33
- package/libs/instance-factories/applications/templates/react-starter/form-body-composer.ts +107 -30
- package/libs/instance-factories/applications/templates/react-starter/helpers-emitter.ts +9 -3
- package/libs/instance-factories/applications/templates/react-starter/list-body-composer.ts +34 -8
- package/libs/instance-factories/applications/templates/react-starter/orchestrator.ts +41 -26
- package/libs/instance-factories/applications/templates/react-starter/skeletons/dashboard.tsx.template +2 -0
- package/libs/instance-factories/applications/templates/react-starter/skeletons/detail.tsx.template +2 -0
- package/libs/instance-factories/applications/templates/react-starter/skeletons/form.tsx.template +2 -0
- package/libs/instance-factories/applications/templates/react-starter/skeletons/list.tsx.template +2 -0
- package/libs/instance-factories/applications/templates/react-starter/use-api-hooks-starter-generator.ts +124 -0
- package/libs/instance-factories/applications/templates/react-starter/view-emitter.ts +58 -0
- package/libs/instance-factories/cli/templates/commander/command-generator.ts +49 -1
- package/libs/instance-factories/controllers/fastify.yaml +7 -0
- package/libs/instance-factories/controllers/templates/fastify/server-generator.ts +36 -2
- package/libs/instance-factories/orms/templates/prisma/schema-generator.ts +11 -4
- package/package.json +2 -1
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
function pluralize(s) {
|
|
2
|
+
if (/y$/.test(s) && !/[aeiou]y$/.test(s)) return s.replace(/y$/, "ies");
|
|
3
|
+
if (/(s|x|z|ch|sh)$/.test(s)) return s + "es";
|
|
4
|
+
return s + "s";
|
|
5
|
+
}
|
|
6
|
+
async function generate(context) {
|
|
7
|
+
const models = Object.keys(context.spec.models ?? {});
|
|
8
|
+
const importsAndTypes = `/**
|
|
9
|
+
* useApi \u2014 typed per-model React Query hooks (ReactAppStarter)
|
|
10
|
+
*
|
|
11
|
+
* Safe to edit. Users who want to reshape their API client or add
|
|
12
|
+
* request/response interceptors should start here. Each hook calls
|
|
13
|
+
* the sibling \`apiClient\` which does the actual fetch; edit
|
|
14
|
+
* \`apiClient.ts\` to change transport concerns (headers, auth, etc.).
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
18
|
+
${models.map((m) => `import type { ${m} } from '../types/api';`).join("\n")}
|
|
19
|
+
|
|
20
|
+
const API_BASE = import.meta.env.VITE_API_BASE_URL || '';
|
|
21
|
+
|
|
22
|
+
async function fetchJSON<T = unknown>(url: string, init?: RequestInit): Promise<T> {
|
|
23
|
+
const res = await fetch(url, {
|
|
24
|
+
headers: { 'Content-Type': 'application/json' },
|
|
25
|
+
...init,
|
|
26
|
+
});
|
|
27
|
+
if (!res.ok) {
|
|
28
|
+
throw new Error(\`\${res.status} \${res.statusText} \u2014 \${url}\`);
|
|
29
|
+
}
|
|
30
|
+
if (res.status === 204) return undefined as T;
|
|
31
|
+
return (await res.json()) as T;
|
|
32
|
+
}
|
|
33
|
+
`;
|
|
34
|
+
const hookBlocks = models.map((m) => generateModelHooks(m)).join("\n\n");
|
|
35
|
+
return importsAndTypes + "\n" + hookBlocks + "\n";
|
|
36
|
+
}
|
|
37
|
+
function generateModelHooks(model) {
|
|
38
|
+
const plural = pluralize(model);
|
|
39
|
+
const resource = plural.toLowerCase();
|
|
40
|
+
const modelLower = model.toLowerCase();
|
|
41
|
+
return `// \u2500\u2500\u2500 ${model} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
42
|
+
|
|
43
|
+
export function use${plural}Query() {
|
|
44
|
+
return useQuery({
|
|
45
|
+
queryKey: ['${resource}'],
|
|
46
|
+
queryFn: () => fetchJSON<${model}[]>(\`\${API_BASE}/api/${resource}\`),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function use${model}Query(id: string | number | undefined) {
|
|
51
|
+
return useQuery({
|
|
52
|
+
queryKey: ['${resource}', id],
|
|
53
|
+
queryFn: () => fetchJSON<${model}>(\`\${API_BASE}/api/${resource}/\${id}\`),
|
|
54
|
+
enabled: id !== undefined && id !== null,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function useCreate${model}Mutation() {
|
|
59
|
+
const qc = useQueryClient();
|
|
60
|
+
return useMutation({
|
|
61
|
+
mutationFn: (data: Partial<${model}>) =>
|
|
62
|
+
fetchJSON<${model}>(\`\${API_BASE}/api/${resource}\`, {
|
|
63
|
+
method: 'POST',
|
|
64
|
+
body: JSON.stringify(data),
|
|
65
|
+
}),
|
|
66
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['${resource}'] }),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function useUpdate${model}Mutation() {
|
|
71
|
+
const qc = useQueryClient();
|
|
72
|
+
return useMutation({
|
|
73
|
+
mutationFn: ({ id, data }: { id: string | number; data: Partial<${model}> }) =>
|
|
74
|
+
fetchJSON<${model}>(\`\${API_BASE}/api/${resource}/\${id}\`, {
|
|
75
|
+
method: 'PATCH',
|
|
76
|
+
body: JSON.stringify(data),
|
|
77
|
+
}),
|
|
78
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['${resource}'] }),
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function useDelete${model}Mutation() {
|
|
83
|
+
const qc = useQueryClient();
|
|
84
|
+
return useMutation({
|
|
85
|
+
mutationFn: (id: string | number) =>
|
|
86
|
+
fetchJSON<void>(\`\${API_BASE}/api/${resource}/\${id}\`, { method: 'DELETE' }),
|
|
87
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['${resource}'] }),
|
|
88
|
+
});
|
|
89
|
+
}`;
|
|
90
|
+
}
|
|
91
|
+
var stdin_default = generate;
|
|
92
|
+
export {
|
|
93
|
+
stdin_default as default,
|
|
94
|
+
generate
|
|
95
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from "fs";
|
|
2
2
|
import { join, dirname } from "path";
|
|
3
3
|
import { fileURLToPath } from "url";
|
|
4
|
+
import { extractBelongsToTargets, pluralize as pluralizeShared } from "./belongs-to.js";
|
|
4
5
|
function emitView(context) {
|
|
5
6
|
const skeleton = loadSkeleton(context.view.type);
|
|
6
7
|
const bodyJsx = context.renderBody(context);
|
|
@@ -30,12 +31,36 @@ function loadSkeleton(viewType) {
|
|
|
30
31
|
function buildSubstitutions(context, body) {
|
|
31
32
|
const modelName = context.model.name;
|
|
32
33
|
const pluralModel = pluralize(modelName);
|
|
34
|
+
const { imports, hooks } = buildBelongsToWiring(context);
|
|
33
35
|
return {
|
|
34
36
|
MODEL_NAME: modelName,
|
|
35
37
|
PLURAL_MODEL: pluralModel,
|
|
36
38
|
PLURAL_LOWER: pluralModel.toLowerCase(),
|
|
37
39
|
SINGULAR_LOWER: modelName.toLowerCase(),
|
|
38
|
-
BODY: body
|
|
40
|
+
BODY: body,
|
|
41
|
+
RELATED_IMPORTS: imports,
|
|
42
|
+
RELATED_HOOKS: hooks
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function buildBelongsToWiring(context) {
|
|
46
|
+
const rels = extractBelongsToTargets(context.model);
|
|
47
|
+
if (rels.length === 0) return { imports: "", hooks: "" };
|
|
48
|
+
const hookImportNames = /* @__PURE__ */ new Set();
|
|
49
|
+
for (const rel of rels) {
|
|
50
|
+
hookImportNames.add(`use${pluralizeShared(rel.target)}Query`);
|
|
51
|
+
}
|
|
52
|
+
const helperImport = context.view.type.toLowerCase() === "form" ? "getEntityDisplayName" : "resolveEntityDisplayName";
|
|
53
|
+
const importLines = [];
|
|
54
|
+
importLines.push(
|
|
55
|
+
`import { ${[...hookImportNames].join(", ")} } from '../hooks/useApi';`
|
|
56
|
+
);
|
|
57
|
+
importLines.push(`import { ${helperImport} } from '../lib/entity-display';`);
|
|
58
|
+
const hookLines = rels.map(
|
|
59
|
+
(rel) => ` const { data: ${rel.name}Options = [] } = use${pluralizeShared(rel.target)}Query();`
|
|
60
|
+
);
|
|
61
|
+
return {
|
|
62
|
+
imports: importLines.join("\n"),
|
|
63
|
+
hooks: hookLines.join("\n")
|
|
39
64
|
};
|
|
40
65
|
}
|
|
41
66
|
function applySubstitutions(template, subs) {
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
name: FastifyPrismaAPI
|
|
2
|
+
version: "1.0.0"
|
|
3
|
+
category: controller
|
|
4
|
+
description: "Fastify server with Prisma ORM, Zod validation, and JWT authentication"
|
|
5
|
+
|
|
6
|
+
metadata:
|
|
7
|
+
author: "SpecVerse Team"
|
|
8
|
+
license: "MIT"
|
|
9
|
+
tags: [fastify, prisma, typescript, rest-api]
|
|
10
|
+
|
|
11
|
+
compatibility:
|
|
12
|
+
specverse: ">=3.3.0"
|
|
13
|
+
node: ">=18.0.0"
|
|
14
|
+
|
|
15
|
+
capabilities:
|
|
16
|
+
provides:
|
|
17
|
+
- "api.rest"
|
|
18
|
+
- "api.rest.crud"
|
|
19
|
+
- "api.websocket"
|
|
20
|
+
requires: []
|
|
21
|
+
|
|
22
|
+
technology:
|
|
23
|
+
runtime: "node"
|
|
24
|
+
language: "typescript"
|
|
25
|
+
framework: "fastify"
|
|
26
|
+
orm: "prisma"
|
|
27
|
+
validation: "zod"
|
|
28
|
+
authentication: "jwt"
|
|
29
|
+
|
|
30
|
+
dependencies:
|
|
31
|
+
runtime:
|
|
32
|
+
- name: "fastify"
|
|
33
|
+
version: "^4.24.0"
|
|
34
|
+
- name: "@fastify/cors"
|
|
35
|
+
version: "^8.4.0"
|
|
36
|
+
- name: "@fastify/jwt"
|
|
37
|
+
version: "^7.2.0"
|
|
38
|
+
- name: "@fastify/helmet"
|
|
39
|
+
version: "^11.1.0"
|
|
40
|
+
- name: "@fastify/rate-limit"
|
|
41
|
+
version: "^9.0.0"
|
|
42
|
+
- name: "@prisma/client"
|
|
43
|
+
version: "^5.5.0"
|
|
44
|
+
- name: "zod"
|
|
45
|
+
version: "^3.22.0"
|
|
46
|
+
|
|
47
|
+
dev:
|
|
48
|
+
- name: "prisma"
|
|
49
|
+
version: "^5.5.0"
|
|
50
|
+
- name: "@types/node"
|
|
51
|
+
version: "^20.8.0"
|
|
52
|
+
- name: "typescript"
|
|
53
|
+
version: "^5.2.0"
|
|
54
|
+
- name: "tsx"
|
|
55
|
+
version: "^4.0.0"
|
|
56
|
+
|
|
57
|
+
codeTemplates:
|
|
58
|
+
routes:
|
|
59
|
+
engine: typescript
|
|
60
|
+
generator: "libs/instance-factories/backend/templates/fastify/routes-generator.ts"
|
|
61
|
+
outputPattern: "routes/{controller}.ts"
|
|
62
|
+
|
|
63
|
+
services:
|
|
64
|
+
engine: typescript
|
|
65
|
+
generator: "libs/instance-factories/backend/templates/prisma/services-generator.ts"
|
|
66
|
+
outputPattern: "services/{model}.service.ts"
|
|
67
|
+
|
|
68
|
+
schema:
|
|
69
|
+
engine: typescript
|
|
70
|
+
generator: "libs/instance-factories/backend/templates/prisma/schema-generator.ts"
|
|
71
|
+
outputPattern: "prisma/schema.prisma"
|
|
72
|
+
|
|
73
|
+
configuration:
|
|
74
|
+
server:
|
|
75
|
+
port: 3000
|
|
76
|
+
host: "0.0.0.0"
|
|
77
|
+
trustProxy: true
|
|
78
|
+
requestIdLogLabel: "reqId"
|
|
79
|
+
logger:
|
|
80
|
+
level: "info"
|
|
81
|
+
prettyPrint: true
|
|
82
|
+
|
|
83
|
+
orm:
|
|
84
|
+
provider: "postgresql"
|
|
85
|
+
relationMode: "foreignKeys"
|
|
86
|
+
log: ["query", "error", "warn"]
|
|
87
|
+
|
|
88
|
+
middleware:
|
|
89
|
+
cors:
|
|
90
|
+
origin: true
|
|
91
|
+
credentials: true
|
|
92
|
+
helmet:
|
|
93
|
+
enabled: true
|
|
94
|
+
rateLimit:
|
|
95
|
+
max: 100
|
|
96
|
+
timeWindow: "1 minute"
|
|
97
|
+
jwt:
|
|
98
|
+
secret: "${JWT_SECRET}"
|
|
99
|
+
sign:
|
|
100
|
+
expiresIn: "7d"
|
|
101
|
+
|
|
102
|
+
validation:
|
|
103
|
+
stripUnknown: true
|
|
104
|
+
abortEarly: false
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# CLI Factory
|
|
2
|
+
|
|
3
|
+
Generates Commander.js CLI applications from spec command definitions.
|
|
4
|
+
|
|
5
|
+
## Definition
|
|
6
|
+
|
|
7
|
+
| File | Description |
|
|
8
|
+
|------|-------------|
|
|
9
|
+
| `commander-js.yaml` | Commander.js v12 CLI with argument parsing, help text, and command routing |
|
|
10
|
+
|
|
11
|
+
## Generated Output
|
|
12
|
+
|
|
13
|
+
| Generator | Output | Purpose |
|
|
14
|
+
|-----------|--------|---------|
|
|
15
|
+
| `cli-entry-generator` | `backend/src/cli/index.ts` | Main CLI entry point — registers all commands from spec |
|
|
16
|
+
| `command-generator` | `backend/src/cli/commands/{command}.ts` | Individual command files (one per spec command) |
|
|
17
|
+
|
|
18
|
+
The entry generator reads the spec's `commands` section and registers each as a
|
|
19
|
+
Commander.js command with arguments, flags, and help text. Each command file
|
|
20
|
+
delegates to its corresponding service for business logic.
|
|
21
|
+
|
|
22
|
+
## Technology
|
|
23
|
+
|
|
24
|
+
- **Framework**: Commander.js ^12.0.0
|
|
25
|
+
- **Runtime**: Node.js with tsx for development
|
|
26
|
+
- **Language**: TypeScript
|
|
27
|
+
|
|
28
|
+
## Capabilities
|
|
29
|
+
|
|
30
|
+
| Provides | Requires |
|
|
31
|
+
|----------|----------|
|
|
32
|
+
| `cli.commands` | `service.controller` |
|
|
33
|
+
| `cli.parser` | |
|
|
34
|
+
| `cli.help` | |
|
|
35
|
+
|
|
36
|
+
## Spec Integration
|
|
37
|
+
|
|
38
|
+
Commands are defined in the spec with:
|
|
39
|
+
- `arguments` — positional parameters
|
|
40
|
+
- `flags` — optional switches
|
|
41
|
+
- `returns` — output format
|
|
42
|
+
- `exitCodes` — process exit codes
|
|
43
|
+
- `subcommands` — nested command trees
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
name: CommanderJS
|
|
2
|
+
version: "1.0.0"
|
|
3
|
+
category: cli
|
|
4
|
+
description: "Node.js CLI generation using Commander.js"
|
|
5
|
+
|
|
6
|
+
metadata:
|
|
7
|
+
author: "SpecVerse Team"
|
|
8
|
+
license: "MIT"
|
|
9
|
+
tags: [cli, commander, node, typescript]
|
|
10
|
+
|
|
11
|
+
compatibility:
|
|
12
|
+
specverse: ">=3.3.0"
|
|
13
|
+
node: ">=18.0.0"
|
|
14
|
+
|
|
15
|
+
capabilities:
|
|
16
|
+
provides:
|
|
17
|
+
- "cli.commands"
|
|
18
|
+
- "cli.parser"
|
|
19
|
+
- "cli.help"
|
|
20
|
+
requires:
|
|
21
|
+
- "service.controller"
|
|
22
|
+
|
|
23
|
+
technology:
|
|
24
|
+
runtime: "node"
|
|
25
|
+
language: "typescript"
|
|
26
|
+
framework: "commander"
|
|
27
|
+
version: "^12.0.0"
|
|
28
|
+
|
|
29
|
+
dependencies:
|
|
30
|
+
runtime:
|
|
31
|
+
- name: "commander"
|
|
32
|
+
version: "^12.0.0"
|
|
33
|
+
|
|
34
|
+
dev:
|
|
35
|
+
- name: "@types/node"
|
|
36
|
+
version: "^20.8.0"
|
|
37
|
+
- name: "typescript"
|
|
38
|
+
version: "^5.2.0"
|
|
39
|
+
- name: "tsx"
|
|
40
|
+
version: "^4.0.0"
|
|
41
|
+
|
|
42
|
+
codeTemplates:
|
|
43
|
+
cli-entry:
|
|
44
|
+
engine: typescript
|
|
45
|
+
generator: "libs/instance-factories/cli/templates/commander/cli-entry-generator.ts"
|
|
46
|
+
outputPattern: "{backendDir}/src/cli/index.ts"
|
|
47
|
+
|
|
48
|
+
cli-commands:
|
|
49
|
+
engine: typescript
|
|
50
|
+
generator: "libs/instance-factories/cli/templates/commander/command-generator.ts"
|
|
51
|
+
outputPattern: "{backendDir}/src/cli/commands/{command}.ts"
|
|
52
|
+
|
|
53
|
+
configuration:
|
|
54
|
+
outputStructure: "monorepo"
|
|
55
|
+
backendDir: "backend"
|
|
@@ -505,8 +505,56 @@ import { fileURLToPath } from 'url';`,
|
|
|
505
505
|
}
|
|
506
506
|
|
|
507
507
|
copyDir(templateDir, destDir);
|
|
508
|
+
|
|
509
|
+
// If the copied template didn't ship a specs/main.specly, load
|
|
510
|
+
// the canonical default spec from @specverse/engines/assets \u2014
|
|
511
|
+
// same file app-demo's Server Manager "new spec" action uses,
|
|
512
|
+
// so both entry points start a user on the same footing. The
|
|
513
|
+
// template can opt out of this by shipping its own spec.
|
|
514
|
+
const specDestPath = join(destDir, 'specs', 'main.specly');
|
|
515
|
+
if (!existsSync(specDestPath)) {
|
|
516
|
+
try {
|
|
517
|
+
const { createRequire } = await import('module');
|
|
518
|
+
const require = createRequire(import.meta.url);
|
|
519
|
+
const enginesPkg = require.resolve('@specverse/engines/package.json');
|
|
520
|
+
const canonicalSpec = join(
|
|
521
|
+
dirname(enginesPkg),
|
|
522
|
+
'assets', 'templates', 'default', 'specs', 'main.specly'
|
|
523
|
+
);
|
|
524
|
+
if (existsSync(canonicalSpec)) {
|
|
525
|
+
let content = readFileSync(canonicalSpec, 'utf8');
|
|
526
|
+
for (const [key, val] of Object.entries(vars)) {
|
|
527
|
+
content = content.split(key).join(val);
|
|
528
|
+
}
|
|
529
|
+
mkdirSync(join(destDir, 'specs'), { recursive: true });
|
|
530
|
+
writeFileSync(specDestPath, content);
|
|
531
|
+
}
|
|
532
|
+
} catch { /* engines not installed or template missing \u2014 proceed */ }
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// --static: flip any ReactAppRuntime mappings in the copied
|
|
536
|
+
// manifests to ReactAppStarter so the user gets standalone-starter
|
|
537
|
+
// output on first \`spv realize\` without having to re-pass the
|
|
538
|
+
// flag. Same semantic as \`spv realize --static\` \u2014 just applied
|
|
539
|
+
// at init time. Idempotent; no-op for templates without a frontend.
|
|
540
|
+
if (options.static) {
|
|
541
|
+
const manifestsDir = join(destDir, 'manifests');
|
|
542
|
+
if (existsSync(manifestsDir)) {
|
|
543
|
+
for (const f of readdirSync(manifestsDir)) {
|
|
544
|
+
if (!f.endsWith('.yaml') && !f.endsWith('.yml')) continue;
|
|
545
|
+
const p = join(manifestsDir, f);
|
|
546
|
+
const before = readFileSync(p, 'utf8');
|
|
547
|
+
const after = before.replace(
|
|
548
|
+
/instanceFactory:\\s*["']?ReactAppRuntime["']?/g,
|
|
549
|
+
'instanceFactory: "ReactAppStarter"'
|
|
550
|
+
);
|
|
551
|
+
if (after !== before) writeFileSync(p, after, 'utf8');
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
508
556
|
console.log('Project created: ' + destDir);
|
|
509
|
-
console.log('Template: ' + templateName);
|
|
557
|
+
console.log('Template: ' + templateName + (options.static ? ' (static / ReactAppStarter)' : ''));
|
|
510
558
|
console.log('');
|
|
511
559
|
|
|
512
560
|
// Build the "Next steps" hint from the actual scripts the
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Communication Factory
|
|
2
|
+
|
|
3
|
+
Event-driven messaging infrastructure with publisher/subscriber generation.
|
|
4
|
+
|
|
5
|
+
## Definitions
|
|
6
|
+
|
|
7
|
+
| File | Description |
|
|
8
|
+
|------|-------------|
|
|
9
|
+
| `event-emitter.yaml` | In-memory event bus using EventEmitter3 (development/testing) |
|
|
10
|
+
| `rabbitmq-events.yaml` | RabbitMQ AMQP bus for production pub/sub messaging |
|
|
11
|
+
|
|
12
|
+
## Generated Output
|
|
13
|
+
|
|
14
|
+
### EventEmitter (`eventemitter/`)
|
|
15
|
+
|
|
16
|
+
| Generator | Output | Purpose |
|
|
17
|
+
|-----------|--------|---------|
|
|
18
|
+
| `bus-generator` | `src/events/eventBus.ts` | Singleton event bus with typed event names |
|
|
19
|
+
| `publisher-generator` | `src/events/publishers/{event}Publisher.ts` | Per-event publisher (one per spec event) |
|
|
20
|
+
| `subscriber-generator` | `src/events/subscribers/{event}Subscriber.ts` | Per-event subscriber with handler logic |
|
|
21
|
+
|
|
22
|
+
### RabbitMQ (`rabbitmq/` — defined, templates pending)
|
|
23
|
+
|
|
24
|
+
| Generator | Output | Purpose |
|
|
25
|
+
|-----------|--------|---------|
|
|
26
|
+
| `publisher-generator` | `src/events/publishers/{event}Publisher.ts` | AMQP publisher with confirms |
|
|
27
|
+
| `subscriber-generator` | `src/events/subscribers/{event}Subscriber.ts` | AMQP consumer with ack/nack |
|
|
28
|
+
| `connection-generator` | `src/events/connection.ts` | RabbitMQ connection management |
|
|
29
|
+
|
|
30
|
+
## Technology
|
|
31
|
+
|
|
32
|
+
- **EventEmitter**: eventemitter3 ^5.0.0 (zero-dependency, in-process)
|
|
33
|
+
- **RabbitMQ**: amqplib ^0.10.3 (topic exchange, durable queues, prefetch)
|
|
34
|
+
|
|
35
|
+
## Capabilities
|
|
36
|
+
|
|
37
|
+
| Provides | Requires |
|
|
38
|
+
|----------|----------|
|
|
39
|
+
| `messaging.pubsub` | (none) |
|
|
40
|
+
| `messaging.events` | |
|
|
41
|
+
| `messaging.inmemory` (EventEmitter) | |
|
|
42
|
+
| `messaging.queue` (RabbitMQ) | |
|
|
43
|
+
|
|
44
|
+
## Configuration
|
|
45
|
+
|
|
46
|
+
EventEmitter: `maxListeners: 100`, `captureRejections: true`
|
|
47
|
+
RabbitMQ: topic exchange, durable queues, prefetch 10, persistent messages, confirm-select
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
name: EventEmitter
|
|
2
|
+
version: "2.0.0"
|
|
3
|
+
category: communication
|
|
4
|
+
description: "Typed in-memory event bus with WebSocket bridge for real-time frontend updates"
|
|
5
|
+
|
|
6
|
+
metadata:
|
|
7
|
+
author: "SpecVerse Team"
|
|
8
|
+
license: "MIT"
|
|
9
|
+
tags: [events, in-memory, pubsub, websocket, eventemitter, development]
|
|
10
|
+
|
|
11
|
+
compatibility:
|
|
12
|
+
specverse: ">=3.3.0"
|
|
13
|
+
node: ">=18.0.0"
|
|
14
|
+
|
|
15
|
+
capabilities:
|
|
16
|
+
provides:
|
|
17
|
+
- "messaging.pubsub"
|
|
18
|
+
- "messaging.events"
|
|
19
|
+
- "messaging.inmemory"
|
|
20
|
+
- "messaging.websocket"
|
|
21
|
+
requires: []
|
|
22
|
+
|
|
23
|
+
technology:
|
|
24
|
+
runtime: "node"
|
|
25
|
+
language: "typescript"
|
|
26
|
+
framework: "eventemitter3"
|
|
27
|
+
version: "^5.0.0"
|
|
28
|
+
|
|
29
|
+
dependencies:
|
|
30
|
+
runtime:
|
|
31
|
+
- name: "eventemitter3"
|
|
32
|
+
version: "^5.0.0"
|
|
33
|
+
- name: "@fastify/websocket"
|
|
34
|
+
version: "^11.0.0"
|
|
35
|
+
|
|
36
|
+
dev:
|
|
37
|
+
- name: "typescript"
|
|
38
|
+
version: "^5.2.0"
|
|
39
|
+
|
|
40
|
+
codeTemplates:
|
|
41
|
+
types:
|
|
42
|
+
engine: typescript
|
|
43
|
+
generator: "libs/instance-factories/communication/templates/eventemitter/types-generator.ts"
|
|
44
|
+
outputPattern: "{backendDir}/src/events/event-types.ts"
|
|
45
|
+
|
|
46
|
+
bus:
|
|
47
|
+
engine: typescript
|
|
48
|
+
generator: "libs/instance-factories/communication/templates/eventemitter/bus-generator.ts"
|
|
49
|
+
outputPattern: "{backendDir}/src/events/eventBus.ts"
|
|
50
|
+
|
|
51
|
+
websocket:
|
|
52
|
+
engine: typescript
|
|
53
|
+
generator: "libs/instance-factories/communication/templates/eventemitter/websocket-bridge-generator.ts"
|
|
54
|
+
outputPattern: "{backendDir}/src/events/websocket-bridge.ts"
|
|
55
|
+
|
|
56
|
+
configuration:
|
|
57
|
+
maxListeners: 100
|
|
58
|
+
captureRejections: true
|
|
59
|
+
errorHandling: "log"
|
|
60
|
+
websocket: true
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
name: RabbitMQEvents
|
|
2
|
+
version: "1.0.0"
|
|
3
|
+
category: communication
|
|
4
|
+
description: "RabbitMQ event bus for pub/sub messaging and event-driven architecture"
|
|
5
|
+
|
|
6
|
+
metadata:
|
|
7
|
+
author: "SpecVerse Team"
|
|
8
|
+
license: "MIT"
|
|
9
|
+
tags: [rabbitmq, events, messaging, pubsub, amqp]
|
|
10
|
+
|
|
11
|
+
compatibility:
|
|
12
|
+
specverse: ">=3.3.0"
|
|
13
|
+
node: ">=18.0.0"
|
|
14
|
+
|
|
15
|
+
capabilities:
|
|
16
|
+
provides:
|
|
17
|
+
- "messaging.pubsub"
|
|
18
|
+
- "messaging.events"
|
|
19
|
+
- "messaging.queue"
|
|
20
|
+
requires: []
|
|
21
|
+
|
|
22
|
+
technology:
|
|
23
|
+
runtime: "node"
|
|
24
|
+
language: "typescript"
|
|
25
|
+
framework: "amqplib"
|
|
26
|
+
version: "^0.10.3"
|
|
27
|
+
|
|
28
|
+
dependencies:
|
|
29
|
+
runtime:
|
|
30
|
+
- name: "amqplib"
|
|
31
|
+
version: "^0.10.3"
|
|
32
|
+
- name: "uuid"
|
|
33
|
+
version: "^9.0.0"
|
|
34
|
+
|
|
35
|
+
dev:
|
|
36
|
+
- name: "@types/amqplib"
|
|
37
|
+
version: "^0.10.4"
|
|
38
|
+
- name: "@types/uuid"
|
|
39
|
+
version: "^9.0.0"
|
|
40
|
+
- name: "typescript"
|
|
41
|
+
version: "^5.2.0"
|
|
42
|
+
|
|
43
|
+
codeTemplates:
|
|
44
|
+
publisher:
|
|
45
|
+
engine: typescript
|
|
46
|
+
generator: "libs/instance-factories/communication/templates/rabbitmq/publisher-generator.ts"
|
|
47
|
+
outputPattern: "src/events/publishers/{event}Publisher.ts"
|
|
48
|
+
|
|
49
|
+
subscriber:
|
|
50
|
+
engine: typescript
|
|
51
|
+
generator: "libs/instance-factories/communication/templates/rabbitmq/subscriber-generator.ts"
|
|
52
|
+
outputPattern: "src/events/subscribers/{event}Subscriber.ts"
|
|
53
|
+
|
|
54
|
+
connection:
|
|
55
|
+
engine: typescript
|
|
56
|
+
generator: "libs/instance-factories/communication/templates/rabbitmq/connection-generator.ts"
|
|
57
|
+
outputPattern: "src/events/connection.ts"
|
|
58
|
+
|
|
59
|
+
configuration:
|
|
60
|
+
connection:
|
|
61
|
+
protocol: "amqp"
|
|
62
|
+
hostname: "localhost"
|
|
63
|
+
port: 5672
|
|
64
|
+
username: "guest"
|
|
65
|
+
password: "guest"
|
|
66
|
+
vhost: "/"
|
|
67
|
+
heartbeat: 60
|
|
68
|
+
|
|
69
|
+
exchange:
|
|
70
|
+
type: "topic"
|
|
71
|
+
durable: true
|
|
72
|
+
autoDelete: false
|
|
73
|
+
|
|
74
|
+
queue:
|
|
75
|
+
durable: true
|
|
76
|
+
exclusive: false
|
|
77
|
+
autoDelete: false
|
|
78
|
+
|
|
79
|
+
publishing:
|
|
80
|
+
persistent: true
|
|
81
|
+
mandatory: true
|
|
82
|
+
confirmSelect: true
|
|
83
|
+
|
|
84
|
+
consuming:
|
|
85
|
+
prefetch: 10
|
|
86
|
+
noAck: false
|
|
87
|
+
exclusive: false
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Controllers Factory
|
|
2
|
+
|
|
3
|
+
Fastify REST API route handlers and server bootstrap generation.
|
|
4
|
+
|
|
5
|
+
## Definition
|
|
6
|
+
|
|
7
|
+
| File | Description |
|
|
8
|
+
|------|-------------|
|
|
9
|
+
| `fastify.yaml` | Fastify v4 REST API server with CORS, Helmet, and rate limiting |
|
|
10
|
+
|
|
11
|
+
## Generated Output
|
|
12
|
+
|
|
13
|
+
| Generator | Output | Purpose |
|
|
14
|
+
|-----------|--------|---------|
|
|
15
|
+
| `routes-generator` | `backend/src/routes/{controller}.ts` | Per-model route handler with CRUD endpoints |
|
|
16
|
+
| `meta-routes-generator` | `backend/src/routes/MetaRoutes.ts` | Meta API for spec/view metadata (serves frontend) |
|
|
17
|
+
| `server-generator` | `backend/src/server.ts` | Server bootstrap — registers all model routes |
|
|
18
|
+
|
|
19
|
+
The routes generator reads each controller from the spec and creates Fastify route
|
|
20
|
+
handlers. The server generator auto-imports and registers all model route files.
|
|
21
|
+
Meta routes expose spec and view metadata for the view-renderer architecture.
|
|
22
|
+
|
|
23
|
+
## Technology
|
|
24
|
+
|
|
25
|
+
- **Framework**: Fastify ^4.24.0
|
|
26
|
+
- **Middleware**: @fastify/cors, @fastify/helmet, @fastify/rate-limit
|
|
27
|
+
- **Language**: TypeScript with tsx for development
|
|
28
|
+
|
|
29
|
+
## Capabilities
|
|
30
|
+
|
|
31
|
+
| Provides | Requires |
|
|
32
|
+
|----------|----------|
|
|
33
|
+
| `api.rest` | `storage.database` |
|
|
34
|
+
| `api.rest.crud` | `validation.runtime` |
|
|
35
|
+
| `api.websocket` | |
|
|
36
|
+
|
|
37
|
+
## Configuration
|
|
38
|
+
|
|
39
|
+
- Server: port 3000, host 0.0.0.0, trust proxy, structured logging
|
|
40
|
+
- CORS: origin true, credentials true
|
|
41
|
+
- Rate limit: 100 requests per minute
|
|
42
|
+
- Scripts: `dev` (tsx watch), `start` (node dist)
|