@compilr-dev/factory 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +131 -0
  2. package/dist/factory/file-writer.d.ts +11 -0
  3. package/dist/factory/file-writer.js +21 -0
  4. package/dist/factory/list-toolkits-tool.d.ts +12 -0
  5. package/dist/factory/list-toolkits-tool.js +35 -0
  6. package/dist/factory/registry.d.ts +15 -0
  7. package/dist/factory/registry.js +28 -0
  8. package/dist/factory/scaffold-tool.d.ts +21 -0
  9. package/dist/factory/scaffold-tool.js +95 -0
  10. package/dist/factory/skill.d.ts +14 -0
  11. package/dist/factory/skill.js +146 -0
  12. package/dist/factory/tools.d.ts +14 -0
  13. package/dist/factory/tools.js +17 -0
  14. package/dist/index.d.ts +22 -0
  15. package/dist/index.js +25 -0
  16. package/dist/model/defaults.d.ts +10 -0
  17. package/dist/model/defaults.js +68 -0
  18. package/dist/model/naming.d.ts +12 -0
  19. package/dist/model/naming.js +80 -0
  20. package/dist/model/operations-entity.d.ts +35 -0
  21. package/dist/model/operations-entity.js +110 -0
  22. package/dist/model/operations-field.d.ts +33 -0
  23. package/dist/model/operations-field.js +104 -0
  24. package/dist/model/operations-relationship.d.ts +19 -0
  25. package/dist/model/operations-relationship.js +90 -0
  26. package/dist/model/operations-section.d.ts +32 -0
  27. package/dist/model/operations-section.js +35 -0
  28. package/dist/model/operations.d.ts +12 -0
  29. package/dist/model/operations.js +63 -0
  30. package/dist/model/persistence.d.ts +19 -0
  31. package/dist/model/persistence.js +40 -0
  32. package/dist/model/schema.d.ts +15 -0
  33. package/dist/model/schema.js +269 -0
  34. package/dist/model/tools.d.ts +14 -0
  35. package/dist/model/tools.js +380 -0
  36. package/dist/model/types.d.ts +70 -0
  37. package/dist/model/types.js +8 -0
  38. package/dist/toolkits/react-node/api.d.ts +8 -0
  39. package/dist/toolkits/react-node/api.js +198 -0
  40. package/dist/toolkits/react-node/config.d.ts +9 -0
  41. package/dist/toolkits/react-node/config.js +120 -0
  42. package/dist/toolkits/react-node/dashboard.d.ts +8 -0
  43. package/dist/toolkits/react-node/dashboard.js +60 -0
  44. package/dist/toolkits/react-node/entity.d.ts +8 -0
  45. package/dist/toolkits/react-node/entity.js +469 -0
  46. package/dist/toolkits/react-node/helpers.d.ts +27 -0
  47. package/dist/toolkits/react-node/helpers.js +71 -0
  48. package/dist/toolkits/react-node/index.d.ts +8 -0
  49. package/dist/toolkits/react-node/index.js +52 -0
  50. package/dist/toolkits/react-node/router.d.ts +8 -0
  51. package/dist/toolkits/react-node/router.js +49 -0
  52. package/dist/toolkits/react-node/seed.d.ts +11 -0
  53. package/dist/toolkits/react-node/seed.js +144 -0
  54. package/dist/toolkits/react-node/shared.d.ts +7 -0
  55. package/dist/toolkits/react-node/shared.js +119 -0
  56. package/dist/toolkits/react-node/shell.d.ts +8 -0
  57. package/dist/toolkits/react-node/shell.js +152 -0
  58. package/dist/toolkits/react-node/static.d.ts +8 -0
  59. package/dist/toolkits/react-node/static.js +105 -0
  60. package/dist/toolkits/react-node/types-gen.d.ts +8 -0
  61. package/dist/toolkits/react-node/types-gen.js +31 -0
  62. package/dist/toolkits/types.d.ts +22 -0
  63. package/dist/toolkits/types.js +6 -0
  64. package/package.json +51 -0
@@ -0,0 +1,380 @@
1
+ /**
2
+ * Model Tools
3
+ *
4
+ * app_model_get — scoped reads of the Application Model
5
+ * app_model_update — semantic operations on the model
6
+ * app_model_validate — check model health
7
+ */
8
+ import { defineTool, createSuccessResult, createErrorResult } from '@compilr-dev/agents';
9
+ import { ModelStore } from './persistence.js';
10
+ import { applyOperation } from './operations.js';
11
+ import { validateModel } from './schema.js';
12
+ import { createDefaultModel } from './defaults.js';
13
+ // =============================================================================
14
+ // Helpers
15
+ // =============================================================================
16
+ function getProjectId(config, inputProjectId) {
17
+ const projectId = inputProjectId ?? config.context.currentProjectId;
18
+ if (!projectId) {
19
+ throw new Error('No active project. Use project_create or project_get first.');
20
+ }
21
+ return projectId;
22
+ }
23
+ function createStore(config, projectId) {
24
+ return new ModelStore({
25
+ documents: config.context.documents,
26
+ projectId,
27
+ });
28
+ }
29
+ function createSummary(model) {
30
+ return {
31
+ name: model.identity.name,
32
+ description: model.identity.description,
33
+ entityCount: model.entities.length,
34
+ entities: model.entities.map((e) => ({
35
+ name: e.name,
36
+ fieldCount: e.fields.length,
37
+ views: e.views,
38
+ relationshipCount: e.relationships.length,
39
+ })),
40
+ features: model.features,
41
+ toolkit: model.techStack.toolkit,
42
+ revision: model.meta.revision,
43
+ };
44
+ }
45
+ function createAppModelGetTool(config) {
46
+ return defineTool({
47
+ name: 'app_model_get',
48
+ description: 'Read the Application Model for the current project. Supports scoped reads: no params = full model, scope="summary" for overview, scope="identity"/"features"/"layout"/"theme"/"techStack" for sections, entity="Name" for a single entity.',
49
+ inputSchema: {
50
+ type: 'object',
51
+ properties: {
52
+ scope: {
53
+ type: 'string',
54
+ enum: ['identity', 'summary', 'features', 'layout', 'theme', 'techStack'],
55
+ description: 'Which section to return. Omit for full model. Use "summary" for a lightweight overview.',
56
+ },
57
+ entity: {
58
+ type: 'string',
59
+ description: 'Return only this entity (by PascalCase name). Overrides scope.',
60
+ },
61
+ project_id: {
62
+ type: 'number',
63
+ description: 'Project ID. Uses active project if omitted.',
64
+ },
65
+ },
66
+ required: [],
67
+ },
68
+ execute: async (input) => {
69
+ try {
70
+ const projectId = getProjectId(config, input.project_id);
71
+ const store = createStore(config, projectId);
72
+ const model = await store.get();
73
+ if (!model) {
74
+ return createSuccessResult({
75
+ exists: false,
76
+ message: 'No Application Model found. Use app_model_update with op: "addEntity" to start building one, or use the factory-scaffold skill.',
77
+ });
78
+ }
79
+ // Entity-scoped read
80
+ if (input.entity) {
81
+ const entity = model.entities.find((e) => e.name === input.entity);
82
+ if (!entity) {
83
+ return createErrorResult(`Entity "${input.entity}" not found`);
84
+ }
85
+ return createSuccessResult({ entity, meta: model.meta });
86
+ }
87
+ // Section-scoped read
88
+ switch (input.scope) {
89
+ case 'identity':
90
+ return createSuccessResult({ identity: model.identity, meta: model.meta });
91
+ case 'summary':
92
+ return createSuccessResult(createSummary(model));
93
+ case 'features':
94
+ return createSuccessResult({ features: model.features, meta: model.meta });
95
+ case 'layout':
96
+ return createSuccessResult({ layout: model.layout, meta: model.meta });
97
+ case 'theme':
98
+ return createSuccessResult({ theme: model.theme, meta: model.meta });
99
+ case 'techStack':
100
+ return createSuccessResult({ techStack: model.techStack, meta: model.meta });
101
+ default:
102
+ return createSuccessResult(model);
103
+ }
104
+ }
105
+ catch (error) {
106
+ return createErrorResult(error instanceof Error ? error.message : String(error));
107
+ }
108
+ },
109
+ readonly: true,
110
+ });
111
+ }
112
+ function createAppModelUpdateTool(config) {
113
+ return defineTool({
114
+ name: 'app_model_update',
115
+ description: `Apply a semantic operation to the Application Model. Operations: addEntity, updateEntity, removeEntity, renameEntity, reorderEntities, addField, updateField, removeField, renameField, addRelationship, removeRelationship, updateIdentity, updateLayout, updateFeatures, updateTheme, updateTechStack. If no model exists, one is created automatically with the first addEntity.`,
116
+ inputSchema: {
117
+ type: 'object',
118
+ properties: {
119
+ op: {
120
+ type: 'string',
121
+ enum: [
122
+ 'addEntity',
123
+ 'updateEntity',
124
+ 'removeEntity',
125
+ 'renameEntity',
126
+ 'reorderEntities',
127
+ 'addField',
128
+ 'updateField',
129
+ 'removeField',
130
+ 'renameField',
131
+ 'addRelationship',
132
+ 'removeRelationship',
133
+ 'updateIdentity',
134
+ 'updateLayout',
135
+ 'updateFeatures',
136
+ 'updateTheme',
137
+ 'updateTechStack',
138
+ ],
139
+ description: 'The operation to perform.',
140
+ },
141
+ revision: {
142
+ type: 'number',
143
+ description: 'Expected current revision for optimistic locking. Optional.',
144
+ },
145
+ entity: {
146
+ description: 'Entity name (string) or full entity object (for addEntity).',
147
+ },
148
+ field: {
149
+ description: 'Field name (string) or full field object (for addField).',
150
+ },
151
+ relationship: {
152
+ type: 'object',
153
+ description: 'Relationship object for addRelationship.',
154
+ properties: {
155
+ type: { type: 'string', enum: ['belongsTo', 'hasMany'] },
156
+ target: { type: 'string' },
157
+ fieldName: { type: 'string' },
158
+ },
159
+ },
160
+ updates: {
161
+ type: 'object',
162
+ description: 'Partial updates for update operations.',
163
+ },
164
+ newName: {
165
+ type: 'string',
166
+ description: 'New name for renameEntity or renameField.',
167
+ },
168
+ order: {
169
+ type: 'array',
170
+ items: { type: 'string' },
171
+ description: 'Entity name order for reorderEntities.',
172
+ },
173
+ target: {
174
+ type: 'string',
175
+ description: 'Relationship target entity name for removeRelationship.',
176
+ },
177
+ force: {
178
+ type: 'boolean',
179
+ description: 'Force removal with cascading cleanup.',
180
+ },
181
+ project_id: {
182
+ type: 'number',
183
+ description: 'Project ID. Uses active project if omitted.',
184
+ },
185
+ },
186
+ required: ['op'],
187
+ },
188
+ execute: async (input) => {
189
+ try {
190
+ const projectId = getProjectId(config, input.project_id);
191
+ const store = createStore(config, projectId);
192
+ // Load current model or create default
193
+ let model = await store.get();
194
+ if (!model) {
195
+ model = createDefaultModel();
196
+ }
197
+ // Optimistic locking
198
+ if (input.revision !== undefined && input.revision !== model.meta.revision) {
199
+ return createErrorResult(`Revision conflict: expected ${String(input.revision)}, current is ${String(model.meta.revision)}. Re-read the model and retry.`);
200
+ }
201
+ // Build the operation from the flat input
202
+ const operation = buildOperation(input);
203
+ // Apply
204
+ const updated = applyOperation(model, operation);
205
+ // Validate result
206
+ const validation = validateModel(updated);
207
+ if (!validation.valid) {
208
+ const msgs = validation.errors.map((e) => `${e.path}: ${e.message}`).join('; ');
209
+ return createErrorResult(`Operation would produce invalid model: ${msgs}`);
210
+ }
211
+ // Save
212
+ await store.save(updated);
213
+ return createSuccessResult({
214
+ op: input.op,
215
+ revision: updated.meta.revision,
216
+ message: `Operation "${input.op}" applied successfully.`,
217
+ });
218
+ }
219
+ catch (error) {
220
+ return createErrorResult(error instanceof Error ? error.message : String(error));
221
+ }
222
+ },
223
+ });
224
+ }
225
+ function buildOperation(input) {
226
+ switch (input.op) {
227
+ case 'addEntity': {
228
+ if (!input.entity || typeof input.entity !== 'object') {
229
+ throw new Error('addEntity requires entity object');
230
+ }
231
+ const entity = input.entity;
232
+ return { op: 'addEntity', entity };
233
+ }
234
+ case 'updateEntity':
235
+ if (typeof input.entity !== 'string')
236
+ throw new Error('updateEntity requires entity name (string)');
237
+ if (!input.updates)
238
+ throw new Error('updateEntity requires updates object');
239
+ return { op: 'updateEntity', entity: input.entity, updates: input.updates };
240
+ case 'removeEntity':
241
+ if (typeof input.entity !== 'string')
242
+ throw new Error('removeEntity requires entity name (string)');
243
+ return { op: 'removeEntity', entity: input.entity, force: input.force };
244
+ case 'renameEntity':
245
+ if (typeof input.entity !== 'string')
246
+ throw new Error('renameEntity requires entity name (string)');
247
+ if (!input.newName)
248
+ throw new Error('renameEntity requires newName');
249
+ return { op: 'renameEntity', entity: input.entity, newName: input.newName };
250
+ case 'reorderEntities':
251
+ if (!input.order)
252
+ throw new Error('reorderEntities requires order array');
253
+ return { op: 'reorderEntities', order: input.order };
254
+ case 'addField': {
255
+ if (typeof input.entity !== 'string')
256
+ throw new Error('addField requires entity name (string)');
257
+ if (!input.field || typeof input.field !== 'object')
258
+ throw new Error('addField requires field object');
259
+ const field = input.field;
260
+ return { op: 'addField', entity: input.entity, field };
261
+ }
262
+ case 'updateField':
263
+ if (typeof input.entity !== 'string')
264
+ throw new Error('updateField requires entity name (string)');
265
+ if (typeof input.field !== 'string')
266
+ throw new Error('updateField requires field name (string)');
267
+ if (!input.updates)
268
+ throw new Error('updateField requires updates object');
269
+ return {
270
+ op: 'updateField',
271
+ entity: input.entity,
272
+ field: input.field,
273
+ updates: input.updates,
274
+ };
275
+ case 'removeField':
276
+ if (typeof input.entity !== 'string')
277
+ throw new Error('removeField requires entity name (string)');
278
+ if (typeof input.field !== 'string')
279
+ throw new Error('removeField requires field name (string)');
280
+ return { op: 'removeField', entity: input.entity, field: input.field };
281
+ case 'renameField':
282
+ if (typeof input.entity !== 'string')
283
+ throw new Error('renameField requires entity name (string)');
284
+ if (typeof input.field !== 'string')
285
+ throw new Error('renameField requires field name (string)');
286
+ if (!input.newName)
287
+ throw new Error('renameField requires newName');
288
+ return {
289
+ op: 'renameField',
290
+ entity: input.entity,
291
+ field: input.field,
292
+ newName: input.newName,
293
+ };
294
+ case 'addRelationship': {
295
+ if (typeof input.entity !== 'string')
296
+ throw new Error('addRelationship requires entity name (string)');
297
+ if (!input.relationship)
298
+ throw new Error('addRelationship requires relationship object');
299
+ const relationship = input.relationship;
300
+ return { op: 'addRelationship', entity: input.entity, relationship };
301
+ }
302
+ case 'removeRelationship':
303
+ if (typeof input.entity !== 'string')
304
+ throw new Error('removeRelationship requires entity name (string)');
305
+ if (!input.target)
306
+ throw new Error('removeRelationship requires target');
307
+ return { op: 'removeRelationship', entity: input.entity, target: input.target };
308
+ case 'updateIdentity':
309
+ if (!input.updates)
310
+ throw new Error('updateIdentity requires updates object');
311
+ return { op: 'updateIdentity', updates: input.updates };
312
+ case 'updateLayout':
313
+ if (!input.updates)
314
+ throw new Error('updateLayout requires updates object');
315
+ return { op: 'updateLayout', updates: input.updates };
316
+ case 'updateFeatures':
317
+ if (!input.updates)
318
+ throw new Error('updateFeatures requires updates object');
319
+ return { op: 'updateFeatures', updates: input.updates };
320
+ case 'updateTheme':
321
+ if (!input.updates)
322
+ throw new Error('updateTheme requires updates object');
323
+ return { op: 'updateTheme', updates: input.updates };
324
+ case 'updateTechStack':
325
+ if (!input.updates)
326
+ throw new Error('updateTechStack requires updates object');
327
+ return { op: 'updateTechStack', updates: input.updates };
328
+ default:
329
+ throw new Error(`Unknown operation: ${input.op}`);
330
+ }
331
+ }
332
+ function createAppModelValidateTool(config) {
333
+ return defineTool({
334
+ name: 'app_model_validate',
335
+ description: 'Validate the Application Model for the current project. Returns validation errors or confirms the model is valid.',
336
+ inputSchema: {
337
+ type: 'object',
338
+ properties: {
339
+ project_id: {
340
+ type: 'number',
341
+ description: 'Project ID. Uses active project if omitted.',
342
+ },
343
+ },
344
+ required: [],
345
+ },
346
+ execute: async (input) => {
347
+ try {
348
+ const projectId = getProjectId(config, input.project_id);
349
+ const store = createStore(config, projectId);
350
+ const model = await store.get();
351
+ if (!model) {
352
+ return createSuccessResult({
353
+ valid: false,
354
+ errors: [{ path: '', message: 'No Application Model found' }],
355
+ });
356
+ }
357
+ const result = validateModel(model);
358
+ return createSuccessResult({
359
+ valid: result.valid,
360
+ errors: result.errors,
361
+ revision: model.meta.revision,
362
+ });
363
+ }
364
+ catch (error) {
365
+ return createErrorResult(error instanceof Error ? error.message : String(error));
366
+ }
367
+ },
368
+ readonly: true,
369
+ });
370
+ }
371
+ // =============================================================================
372
+ // Public API
373
+ // =============================================================================
374
+ export function createModelTools(config) {
375
+ return [
376
+ createAppModelGetTool(config),
377
+ createAppModelUpdateTool(config),
378
+ createAppModelValidateTool(config),
379
+ ];
380
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Application Model Types
3
+ *
4
+ * The Application Model is the central data structure of the factory system.
5
+ * It describes WHAT an application is — entities, fields, relationships,
6
+ * layout, features, theme — independently of how it's built.
7
+ */
8
+ export interface ApplicationModel {
9
+ readonly identity: Identity;
10
+ readonly entities: readonly Entity[];
11
+ readonly layout: Layout;
12
+ readonly features: Features;
13
+ readonly theme: Theme;
14
+ readonly techStack: TechStack;
15
+ readonly meta: Meta;
16
+ }
17
+ export interface Identity {
18
+ readonly name: string;
19
+ readonly description: string;
20
+ readonly version: string;
21
+ }
22
+ export interface Entity {
23
+ readonly name: string;
24
+ readonly pluralName: string;
25
+ readonly description?: string;
26
+ readonly icon: string;
27
+ readonly fields: readonly Field[];
28
+ readonly views: readonly View[];
29
+ readonly relationships: readonly Relationship[];
30
+ }
31
+ export type FieldType = 'string' | 'number' | 'boolean' | 'date' | 'enum';
32
+ export interface Field {
33
+ readonly name: string;
34
+ readonly label: string;
35
+ readonly type: FieldType;
36
+ readonly required: boolean;
37
+ readonly enumValues?: readonly string[];
38
+ readonly defaultValue?: string | number | boolean;
39
+ }
40
+ export type View = 'card' | 'list' | 'detail';
41
+ export type RelationshipType = 'belongsTo' | 'hasMany';
42
+ export interface Relationship {
43
+ readonly type: RelationshipType;
44
+ readonly target: string;
45
+ readonly fieldName?: string;
46
+ }
47
+ export type ShellType = 'sidebar-header' | 'header-only';
48
+ export interface Layout {
49
+ readonly shell: ShellType;
50
+ }
51
+ export interface Features {
52
+ readonly dashboard: boolean;
53
+ readonly auth: boolean;
54
+ readonly userProfiles: boolean;
55
+ readonly settings: boolean;
56
+ readonly darkMode: boolean;
57
+ }
58
+ export interface Theme {
59
+ readonly primaryColor: string;
60
+ }
61
+ export interface TechStack {
62
+ readonly toolkit: string;
63
+ }
64
+ export interface Meta {
65
+ readonly revision: number;
66
+ readonly createdAt: string;
67
+ readonly updatedAt: string;
68
+ readonly factoryVersion: string;
69
+ readonly generatedAt?: string;
70
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Application Model Types
3
+ *
4
+ * The Application Model is the central data structure of the factory system.
5
+ * It describes WHAT an application is — entities, fields, relationships,
6
+ * layout, features, theme — independently of how it's built.
7
+ */
8
+ export {};
@@ -0,0 +1,8 @@
1
+ /**
2
+ * React+Node Toolkit — API Generator
3
+ *
4
+ * Generates: server/index.ts, server/routes/{entity}.ts, server/data/{entity}.ts
5
+ */
6
+ import type { ApplicationModel } from '../../model/types.js';
7
+ import type { FactoryFile } from '../types.js';
8
+ export declare function generateApiFiles(model: ApplicationModel): FactoryFile[];
@@ -0,0 +1,198 @@
1
+ /**
2
+ * React+Node Toolkit — API Generator
3
+ *
4
+ * Generates: server/index.ts, server/routes/{entity}.ts, server/data/{entity}.ts
5
+ */
6
+ import { toCamelCase, toKebabCase } from '../../model/naming.js';
7
+ import { tsType, fkFieldName, belongsToRels } from './helpers.js';
8
+ import { generateSeedData } from './seed.js';
9
+ export function generateApiFiles(model) {
10
+ return [
11
+ generateServerIndex(model),
12
+ ...model.entities.flatMap((entity) => [
13
+ generateDataStore(model, entity),
14
+ generateRoutes(model, entity),
15
+ ]),
16
+ ];
17
+ }
18
+ // =============================================================================
19
+ // Server Entry Point
20
+ // =============================================================================
21
+ function generateServerIndex(model) {
22
+ const routeImports = model.entities
23
+ .map((e) => {
24
+ const varName = toCamelCase(e.pluralName) + 'Router';
25
+ const fileName = toKebabCase(e.pluralName).toLowerCase();
26
+ return `import { ${varName} } from './routes/${fileName}.js';`;
27
+ })
28
+ .join('\n');
29
+ const routeMounts = model.entities
30
+ .map((e) => {
31
+ const varName = toCamelCase(e.pluralName) + 'Router';
32
+ const apiRoute = '/api/' + toKebabCase(e.pluralName).toLowerCase();
33
+ return `app.use('${apiRoute}', ${varName});`;
34
+ })
35
+ .join('\n');
36
+ return {
37
+ path: 'server/index.ts',
38
+ content: `import express from 'express';
39
+ import cors from 'cors';
40
+ ${routeImports}
41
+
42
+ const app = express();
43
+ const PORT = process.env.PORT ?? 3001;
44
+
45
+ app.use(cors());
46
+ app.use(express.json());
47
+
48
+ ${routeMounts}
49
+
50
+ app.listen(PORT, () => {
51
+ console.log(\`API server running on http://localhost:\${String(PORT)}\`);
52
+ });
53
+ `,
54
+ };
55
+ }
56
+ // =============================================================================
57
+ // Data Store
58
+ // =============================================================================
59
+ function generateDataStore(model, entity) {
60
+ const fileName = toKebabCase(entity.pluralName).toLowerCase();
61
+ const varName = toCamelCase(entity.pluralName);
62
+ const typeName = entity.name;
63
+ // Generate type definition for the store
64
+ const typeFields = [' id: number;'];
65
+ for (const field of entity.fields) {
66
+ const optional = field.required ? '' : '?';
67
+ typeFields.push(` ${field.name}${optional}: ${tsType(field)};`);
68
+ }
69
+ for (const rel of belongsToRels(entity)) {
70
+ typeFields.push(` ${fkFieldName(rel)}: number;`);
71
+ }
72
+ const seedData = generateSeedData(model, entity);
73
+ return {
74
+ path: `server/data/${fileName}.ts`,
75
+ content: `// In-memory data store for ${entity.pluralName}
76
+
77
+ export interface ${typeName}Record {
78
+ ${typeFields.join('\n')}
79
+ }
80
+
81
+ ${seedData}
82
+
83
+ let items: ${typeName}Record[] = [...${varName}SeedData];
84
+ let nextId = items.length + 1;
85
+
86
+ export function getAll(): ${typeName}Record[] {
87
+ return items;
88
+ }
89
+
90
+ export function getById(id: number): ${typeName}Record | undefined {
91
+ return items.find((item) => item.id === id);
92
+ }
93
+
94
+ export function create(data: Omit<${typeName}Record, 'id'>): ${typeName}Record {
95
+ const item = { ...data, id: nextId++ };
96
+ items.push(item);
97
+ return item;
98
+ }
99
+
100
+ export function update(id: number, data: Partial<${typeName}Record>): ${typeName}Record | undefined {
101
+ const index = items.findIndex((item) => item.id === id);
102
+ if (index === -1) return undefined;
103
+ items[index] = { ...items[index], ...data, id };
104
+ return items[index];
105
+ }
106
+
107
+ export function remove(id: number): boolean {
108
+ const index = items.findIndex((item) => item.id === id);
109
+ if (index === -1) return false;
110
+ items.splice(index, 1);
111
+ return true;
112
+ }
113
+ `,
114
+ };
115
+ }
116
+ // =============================================================================
117
+ // Routes
118
+ // =============================================================================
119
+ function generateRoutes(model, entity) {
120
+ const fileName = toKebabCase(entity.pluralName).toLowerCase();
121
+ const varName = toCamelCase(entity.pluralName) + 'Router';
122
+ const dataImport = fileName;
123
+ const btoRels = belongsToRels(entity);
124
+ // Populate belongsTo entities
125
+ const populateImports = btoRels
126
+ .map((rel) => {
127
+ const targetEntity = model.entities.find((e) => e.name === rel.target);
128
+ if (!targetEntity)
129
+ return '';
130
+ const targetFile = toKebabCase(targetEntity.pluralName).toLowerCase();
131
+ return `import { getById as get${rel.target}ById } from '../data/${targetFile}.js';`;
132
+ })
133
+ .filter(Boolean)
134
+ .join('\n');
135
+ const populateLogic = btoRels.length > 0
136
+ ? `
137
+ function populate(item: Record<string, unknown>): Record<string, unknown> {
138
+ const result = { ...item };
139
+ ${btoRels
140
+ .map((rel) => {
141
+ const fk = fkFieldName(rel);
142
+ const targetVar = rel.target.charAt(0).toLowerCase() + rel.target.slice(1);
143
+ return ` if (result.${fk}) result.${targetVar} = get${rel.target}ById(result.${fk} as number);`;
144
+ })
145
+ .join('\n')}
146
+ return result;
147
+ }
148
+ `
149
+ : '';
150
+ const getListReturn = btoRels.length > 0
151
+ ? 'res.json(items.map((item) => populate(item as unknown as Record<string, unknown>)));'
152
+ : 'res.json(items);';
153
+ const getByIdReturn = btoRels.length > 0
154
+ ? 'res.json(populate(item as unknown as Record<string, unknown>));'
155
+ : 'res.json(item);';
156
+ return {
157
+ path: `server/routes/${fileName}.ts`,
158
+ content: `import { Router } from 'express';
159
+ import { getAll, getById, create, update, remove } from '../data/${dataImport}.js';
160
+ ${populateImports}
161
+
162
+ export const ${varName} = Router();
163
+ ${populateLogic}
164
+ // GET all
165
+ ${varName}.get('/', (_req, res) => {
166
+ const items = getAll();
167
+ ${getListReturn}
168
+ });
169
+
170
+ // GET by ID
171
+ ${varName}.get('/:id', (req, res) => {
172
+ const item = getById(Number(req.params.id));
173
+ if (!item) return res.status(404).json({ error: 'Not found' });
174
+ ${getByIdReturn}
175
+ });
176
+
177
+ // POST create
178
+ ${varName}.post('/', (req, res) => {
179
+ const item = create(req.body);
180
+ res.status(201).json(item);
181
+ });
182
+
183
+ // PUT update
184
+ ${varName}.put('/:id', (req, res) => {
185
+ const item = update(Number(req.params.id), req.body);
186
+ if (!item) return res.status(404).json({ error: 'Not found' });
187
+ res.json(item);
188
+ });
189
+
190
+ // DELETE
191
+ ${varName}.delete('/:id', (req, res) => {
192
+ const success = remove(Number(req.params.id));
193
+ if (!success) return res.status(404).json({ error: 'Not found' });
194
+ res.status(204).send();
195
+ });
196
+ `,
197
+ };
198
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * React+Node Toolkit — Configuration Files Generator
3
+ *
4
+ * Generates: package.json, vite.config.ts, tailwind.config.js,
5
+ * tsconfig.json, postcss.config.js
6
+ */
7
+ import type { ApplicationModel } from '../../model/types.js';
8
+ import type { FactoryFile } from '../types.js';
9
+ export declare function generateConfigFiles(model: ApplicationModel): FactoryFile[];