offbyt 1.0.0

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 (103) hide show
  1. package/README.md +2 -0
  2. package/cli/index.js +2 -0
  3. package/cli.js +206 -0
  4. package/core/detector/detectAxios.js +107 -0
  5. package/core/detector/detectFetch.js +148 -0
  6. package/core/detector/detectForms.js +55 -0
  7. package/core/detector/detectSocket.js +341 -0
  8. package/core/generator/generateControllers.js +17 -0
  9. package/core/generator/generateModels.js +25 -0
  10. package/core/generator/generateRoutes.js +17 -0
  11. package/core/generator/generateServer.js +18 -0
  12. package/core/generator/generateSocket.js +160 -0
  13. package/core/index.js +14 -0
  14. package/core/ir/IRTypes.js +25 -0
  15. package/core/ir/buildIR.js +83 -0
  16. package/core/parser/parseJS.js +26 -0
  17. package/core/parser/parseTS.js +27 -0
  18. package/core/rules/relationRules.js +38 -0
  19. package/core/rules/resourceRules.js +32 -0
  20. package/core/rules/schemaInference.js +26 -0
  21. package/core/scanner/scanProject.js +58 -0
  22. package/deploy/cloudflare.js +41 -0
  23. package/deploy/cloudflareWorker.js +122 -0
  24. package/deploy/connect.js +198 -0
  25. package/deploy/flyio.js +51 -0
  26. package/deploy/index.js +322 -0
  27. package/deploy/netlify.js +29 -0
  28. package/deploy/railway.js +215 -0
  29. package/deploy/render.js +195 -0
  30. package/deploy/utils.js +383 -0
  31. package/deploy/vercel.js +29 -0
  32. package/index.js +18 -0
  33. package/lib/generator/advancedCrudGenerator.js +475 -0
  34. package/lib/generator/crudCodeGenerator.js +486 -0
  35. package/lib/generator/irBasedGenerator.js +360 -0
  36. package/lib/ir-builder/index.js +16 -0
  37. package/lib/ir-builder/irBuilder.js +330 -0
  38. package/lib/ir-builder/rulesEngine.js +353 -0
  39. package/lib/ir-builder/templateEngine.js +193 -0
  40. package/lib/ir-builder/templates/index.js +14 -0
  41. package/lib/ir-builder/templates/model.template.js +47 -0
  42. package/lib/ir-builder/templates/routes-generic.template.js +66 -0
  43. package/lib/ir-builder/templates/routes-user.template.js +105 -0
  44. package/lib/ir-builder/templates/routes.template.js +102 -0
  45. package/lib/ir-builder/templates/validation.template.js +15 -0
  46. package/lib/ir-integration.js +349 -0
  47. package/lib/modes/benchmark.js +162 -0
  48. package/lib/modes/configBasedGenerator.js +2258 -0
  49. package/lib/modes/connect.js +1125 -0
  50. package/lib/modes/doctorAi.js +172 -0
  51. package/lib/modes/generateApi.js +435 -0
  52. package/lib/modes/interactiveSetup.js +548 -0
  53. package/lib/modes/offline.clean.js +14 -0
  54. package/lib/modes/offline.enhanced.js +787 -0
  55. package/lib/modes/offline.js +295 -0
  56. package/lib/modes/offline.v2.js +13 -0
  57. package/lib/modes/sync.js +629 -0
  58. package/lib/scanner/apiEndpointExtractor.js +387 -0
  59. package/lib/scanner/authPatternDetector.js +54 -0
  60. package/lib/scanner/frontendScanner.js +642 -0
  61. package/lib/utils/apiClientGenerator.js +242 -0
  62. package/lib/utils/apiScanner.js +95 -0
  63. package/lib/utils/codeInjector.js +350 -0
  64. package/lib/utils/doctor.js +381 -0
  65. package/lib/utils/envGenerator.js +36 -0
  66. package/lib/utils/loadTester.js +61 -0
  67. package/lib/utils/performanceAnalyzer.js +298 -0
  68. package/lib/utils/resourceDetector.js +281 -0
  69. package/package.json +20 -0
  70. package/templates/.env.template +31 -0
  71. package/templates/advanced.model.template.js +201 -0
  72. package/templates/advanced.route.template.js +341 -0
  73. package/templates/auth.middleware.template.js +87 -0
  74. package/templates/auth.routes.template.js +238 -0
  75. package/templates/auth.user.model.template.js +78 -0
  76. package/templates/cache.middleware.js +34 -0
  77. package/templates/chat.models.template.js +260 -0
  78. package/templates/chat.routes.template.js +478 -0
  79. package/templates/compression.middleware.js +19 -0
  80. package/templates/database.config.js +74 -0
  81. package/templates/errorHandler.middleware.js +54 -0
  82. package/templates/express/controller.ejs +26 -0
  83. package/templates/express/model.ejs +9 -0
  84. package/templates/express/route.ejs +18 -0
  85. package/templates/express/server.ejs +16 -0
  86. package/templates/frontend.env.template +14 -0
  87. package/templates/model.template.js +86 -0
  88. package/templates/package.production.json +51 -0
  89. package/templates/package.template.json +41 -0
  90. package/templates/pagination.utility.js +110 -0
  91. package/templates/production.server.template.js +233 -0
  92. package/templates/rateLimiter.middleware.js +36 -0
  93. package/templates/requestLogger.middleware.js +19 -0
  94. package/templates/response.helper.js +179 -0
  95. package/templates/route.template.js +130 -0
  96. package/templates/security.middleware.js +78 -0
  97. package/templates/server.template.js +91 -0
  98. package/templates/socket.server.template.js +433 -0
  99. package/templates/utils.helper.js +157 -0
  100. package/templates/validation.middleware.js +63 -0
  101. package/templates/validation.schema.js +128 -0
  102. package/utils/fileWriter.js +15 -0
  103. package/utils/logger.js +18 -0
@@ -0,0 +1,360 @@
1
+ /**
2
+ * IR-Based Generator
3
+ * Main orchestrator: Scanner → IR → Templates → Generated Code
4
+ *
5
+ * This is the professional approach used by Yeoman, create-react-app, etc.
6
+ */
7
+
8
+ import fs from 'fs';
9
+ import path from 'path';
10
+ import { buildIR, validateIR, printIR } from '../ir-builder/irBuilder.js';
11
+ import { renderTemplate } from '../ir-builder/templateEngine.js';
12
+ import { MODEL_TEMPLATE } from '../ir-builder/templates/model.template.js';
13
+ import { ROUTES_USER_TEMPLATE } from '../ir-builder/templates/routes-user.template.js';
14
+ import { ROUTES_GENERIC_TEMPLATE } from '../ir-builder/templates/routes-generic.template.js';
15
+ import { VALIDATION_TEMPLATE } from '../ir-builder/templates/validation.template.js';
16
+
17
+ /**
18
+ * Main entry point: Generate complete backend from scanner output
19
+ *
20
+ * Usage:
21
+ * const generated = await generateBackendFromScanner(detectedApis, options);
22
+ */
23
+ export async function generateBackendFromScanner(detectedApis, options = {}) {
24
+ try {
25
+ console.log('🔄 Starting IR-based generation pipeline...\n');
26
+
27
+ // Step 1: Build IR
28
+ console.log('📊 Step 1: Building Intermediate Representation (IR)...');
29
+ const ir = buildIR(detectedApis, options);
30
+
31
+ // Validate IR
32
+ const validation = validateIR(ir);
33
+ if (!validation.valid) {
34
+ console.error('❌ IR validation failed:');
35
+ validation.errors.forEach(err => console.error(` - ${err}`));
36
+ throw new Error('Invalid IR structure');
37
+ }
38
+ console.log('✅ IR built successfully');
39
+ console.log(` Resources: ${ir.resources.map(r => r.name).join(', ')}\n`);
40
+
41
+ // Step 2: Generate code for each resource
42
+ console.log('🔨 Step 2: Generating code from templates...');
43
+ const generated = {};
44
+
45
+ for (const resource of ir.resources) {
46
+ console.log(` Generating ${resource.name}...`);
47
+
48
+ // Use user routes template for users, generic for others
49
+ const routesTemplate = resource.name === 'user' ? ROUTES_USER_TEMPLATE : ROUTES_GENERIC_TEMPLATE;
50
+
51
+ generated[resource.name] = {
52
+ model: renderTemplate(MODEL_TEMPLATE, ir, resource.name),
53
+ routes: resource.name === 'user' ? routesTemplate : renderTemplate(routesTemplate, ir, resource.name),
54
+ validation: renderTemplate(VALIDATION_TEMPLATE, ir, resource.name),
55
+ ir: JSON.stringify(ir, null, 2)
56
+ };
57
+ }
58
+
59
+ console.log(`✅ Generated ${ir.resources.length} models, routes, and validators\n`);
60
+
61
+ // Step 3: Return complete structure
62
+ return {
63
+ success: true,
64
+ ir,
65
+ generated,
66
+ resources: ir.resources,
67
+ metadata: {
68
+ totalResources: ir.resources.length,
69
+ totalEndpoints: ir.resources.reduce((sum, r) => sum + r.endpoints.length, 0),
70
+ timestamp: new Date().toISOString()
71
+ }
72
+ };
73
+ } catch (error) {
74
+ console.error(`❌ Generation failed: ${error.message}`);
75
+ throw error;
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Write generated files to disk
81
+ */
82
+ export async function writeGeneratedFiles(generated, outputDir, projectType = 'express') {
83
+ try {
84
+ console.log(`\n📁 Writing files to ${outputDir}...\n`);
85
+
86
+ // Create directory structure
87
+ const dirs = [
88
+ `${outputDir}/models`,
89
+ `${outputDir}/routes`,
90
+ `${outputDir}/validations`,
91
+ `${outputDir}/ir-schemas`
92
+ ];
93
+
94
+ for (const dir of dirs) {
95
+ if (!fs.existsSync(dir)) {
96
+ fs.mkdirSync(dir, { recursive: true });
97
+ console.log(` 📂 Created ${path.relative(process.cwd(), dir)}`);
98
+ }
99
+ }
100
+
101
+ // Write files
102
+ let fileCount = 0;
103
+
104
+ for (const [resourceName, code] of Object.entries(generated)) {
105
+ if (typeof code === 'object') {
106
+ // Model
107
+ const modelPath = `${outputDir}/models/${capitalize(resourceName)}.model.js`;
108
+ fs.writeFileSync(modelPath, code.model);
109
+ console.log(` ✅ ${path.relative(process.cwd(), modelPath)}`);
110
+ fileCount++;
111
+
112
+ // Routes
113
+ const routesPath = `${outputDir}/routes/${resourceName}.routes.js`;
114
+ fs.writeFileSync(routesPath, code.routes);
115
+ console.log(` ✅ ${path.relative(process.cwd(), routesPath)}`);
116
+ fileCount++;
117
+
118
+ // Validation
119
+ const validationPath = `${outputDir}/validations/${resourceName}.validation.js`;
120
+ fs.writeFileSync(validationPath, code.validation);
121
+ console.log(` ✅ ${path.relative(process.cwd(), validationPath)}`);
122
+ fileCount++;
123
+
124
+ // IR Schema (for reference)
125
+ const schemaPath = `${outputDir}/ir-schemas/${resourceName}.ir.json`;
126
+ fs.writeFileSync(schemaPath, code.ir);
127
+ fileCount++;
128
+ }
129
+ }
130
+
131
+ console.log(`\n✅ Generated ${fileCount} files successfully!\n`);
132
+ return { success: true, filesWritten: fileCount };
133
+ } catch (error) {
134
+ console.error(`❌ Failed to write files: ${error.message}`);
135
+ throw error;
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Generate complete backend structure (scaffold)
141
+ */
142
+ export async function generateBackendScaffold(ir, outputDir) {
143
+ const scaffoldFiles = {
144
+ 'server.js': generateServerFile(ir),
145
+ 'package.json': generatePackageJson(ir),
146
+ '.env.example': generateEnvFile(ir),
147
+ 'config/db.js': generateDbConfig(ir),
148
+ 'middleware/errorHandler.js': generateErrorHandler(),
149
+ 'middleware/requestLogger.js': generateRequestLogger()
150
+ };
151
+
152
+ console.log('\n🏗️ Creating backend scaffold...\n');
153
+
154
+ for (const [filePath, content] of Object.entries(scaffoldFiles)) {
155
+ const fullPath = `${outputDir}/${filePath}`;
156
+ const dir = path.dirname(fullPath);
157
+
158
+ if (!fs.existsSync(dir)) {
159
+ fs.mkdirSync(dir, { recursive: true });
160
+ }
161
+
162
+ fs.writeFileSync(fullPath, content);
163
+ console.log(` ✅ ${path.relative(process.cwd(), fullPath)}`);
164
+ }
165
+
166
+ console.log('\n✅ Backend scaffold created!\n');
167
+ }
168
+
169
+ /**
170
+ * Helper: Generate server.js
171
+ */
172
+ function generateServerFile(ir) {
173
+ const imports = ir.resources
174
+ .map(r => `import ${capitalize(r.singular)}Routes from './routes/${r.name}.routes.js';`)
175
+ .join('\n');
176
+
177
+ const mountRoutes = ir.resources
178
+ .map(r => `app.use('/api/${r.plural}', ${capitalize(r.singular)}Routes);`)
179
+ .join('\n');
180
+
181
+ return `import express from 'express';
182
+ import cors from 'cors';
183
+ import mongoose from 'mongoose';
184
+ import dotenv from 'dotenv';
185
+
186
+ dotenv.config();
187
+
188
+ const app = express();
189
+
190
+ // Middleware
191
+ app.use(express.json());
192
+ app.use(cors());
193
+
194
+ // Connect to MongoDB
195
+ mongoose.connect(process.env.MONGODB_URI)
196
+ .then(() => console.log('✅ Connected to MongoDB'))
197
+ .catch(err => console.error('❌ MongoDB connection failed:', err));
198
+
199
+ // Routes
200
+ ${imports}
201
+
202
+ ${mountRoutes}
203
+
204
+ // Error handling
205
+ app.use((err, req, res, next) => {
206
+ console.error(err.stack);
207
+ res.status(err.status || 500).json({
208
+ success: false,
209
+ error: err.message
210
+ });
211
+ });
212
+
213
+ const PORT = process.env.PORT || 3000;
214
+ app.listen(PORT, () => {
215
+ console.log(\`🚀 Server running on port \${PORT}\`);
216
+ });
217
+ `;
218
+ }
219
+
220
+ /**
221
+ * Helper: Generate package.json
222
+ */
223
+ function generatePackageJson(ir) {
224
+ const pkg = {
225
+ name: 'offbyt-generated',
226
+ version: '1.0.0',
227
+ type: 'module',
228
+ scripts: {
229
+ start: 'node server.js',
230
+ dev: 'nodemon server.js',
231
+ test: 'echo "Error: no test specified" && exit 1'
232
+ },
233
+ dependencies: {
234
+ express: '^4.18.0',
235
+ mongoose: '^8.0.0',
236
+ cors: '^2.8.5',
237
+ dotenv: '^16.3.1',
238
+ joi: '^17.11.0'
239
+ },
240
+ devDependencies: {
241
+ nodemon: '^3.0.1'
242
+ }
243
+ };
244
+
245
+ return JSON.stringify(pkg, null, 2);
246
+ }
247
+
248
+ /**
249
+ * Helper: Generate .env file
250
+ */
251
+ function generateEnvFile(ir) {
252
+ return `# Database
253
+ MONGODB_URI=mongodb://localhost:27017/offbyt-${ir.settings.apiVersion}
254
+
255
+ # Server
256
+ PORT=3000
257
+ NODE_ENV=development
258
+
259
+ # API
260
+ API_VERSION=${ir.settings.apiVersion || 'v1'}
261
+
262
+ # Auth (if needed)
263
+ JWT_SECRET=your-secret-key-here
264
+ `;
265
+ }
266
+
267
+ /**
268
+ * Helper: Generate database config
269
+ */
270
+ function generateDbConfig(ir) {
271
+ return `import mongoose from 'mongoose';
272
+
273
+ const connectDB = async () => {
274
+ try {
275
+ await mongoose.connect(process.env.MONGODB_URI, {
276
+ useNewUrlParser: true,
277
+ useUnifiedTopology: true,
278
+ });
279
+ console.log('✅ MongoDB connected');
280
+ } catch (error) {
281
+ console.error('❌ MongoDB connection failed:', error);
282
+ process.exit(1);
283
+ }
284
+ };
285
+
286
+ export default connectDB;
287
+ `;
288
+ }
289
+
290
+ /**
291
+ * Helper: Generate error handler
292
+ */
293
+ function generateErrorHandler() {
294
+ return `export const errorHandler = (err, req, res, next) => {
295
+ const status = err.status || 500;
296
+ const message = err.message || 'Internal Server Error';
297
+
298
+ console.error(\`[\${new Date().toISOString()}] \${status} - \${message}\`);
299
+
300
+ res.status(status).json({
301
+ success: false,
302
+ status,
303
+ error: message,
304
+ ...(process.env.NODE_ENV === 'development' && { stack: err.stack })
305
+ });
306
+ };
307
+ `;
308
+ }
309
+
310
+ /**
311
+ * Helper: Generate request logger
312
+ */
313
+ function generateRequestLogger() {
314
+ return `export const requestLogger = (req, res, next) => {
315
+ const start = Date.now();
316
+
317
+ res.on('finish', () => {
318
+ const duration = Date.now() - start;
319
+ console.log(
320
+ \`[\${new Date().toISOString()}] \${req.method} \${req.path} - \${res.statusCode} (\${duration}ms)\`
321
+ );
322
+ });
323
+
324
+ next();
325
+ };
326
+ `;
327
+ }
328
+
329
+ /**
330
+ * Helper: Capitalize
331
+ */
332
+ function capitalize(str) {
333
+ return str.charAt(0).toUpperCase() + str.slice(1);
334
+ }
335
+
336
+ /**
337
+ * Print generation summary
338
+ */
339
+ export function printGenerationSummary(result) {
340
+ console.log('\n' + '='.repeat(60));
341
+ console.log('🎉 BACKEND GENERATION COMPLETE!');
342
+ console.log('='.repeat(60));
343
+ console.log(`\n📊 Generated Resources:`);
344
+
345
+ result.resources.forEach(resource => {
346
+ console.log(`\n ${capitalize(resource.singular)}:`);
347
+ console.log(` • Model: ${resource.name}.model.js`);
348
+ console.log(` • Routes: ${resource.name}.routes.js`);
349
+ console.log(` • Validation: ${resource.name}.validation.js`);
350
+ console.log(` • Fields: ${resource.fields.map(f => f.name).join(', ')}`);
351
+ console.log(` • Endpoints: ${resource.endpoints.map(e => `${e.method} ${e.path}`).join(', ')}`);
352
+ });
353
+
354
+ console.log(`\n📈 Statistics:`);
355
+ console.log(` • Total Resources: ${result.metadata.totalResources}`);
356
+ console.log(` • Total Endpoints: ${result.metadata.totalEndpoints}`);
357
+ console.log(` • Generated At: ${result.metadata.timestamp}`);
358
+ console.log('\n' + '='.repeat(60) + '\n');
359
+ }
360
+
@@ -0,0 +1,16 @@
1
+ /**
2
+ * IR Builder - Main Export
3
+ * Complete IR generation pipeline
4
+ */
5
+
6
+ export { buildIR, validateIR, printIR } from './irBuilder.js';
7
+ export {
8
+ detectFieldType,
9
+ buildFieldConfig,
10
+ shouldIndex,
11
+ detectRelationship,
12
+ getAllRules,
13
+ addCustomRule,
14
+ getRule
15
+ } from './rulesEngine.js';
16
+ export { renderTemplate, renderAllTemplates, validateTemplate } from './templateEngine.js';
@@ -0,0 +1,330 @@
1
+ /**
2
+ * IR Builder - Converts Scanner Output to Intermediate Representation
3
+ * Creates structured, language-agnostic description of backend
4
+ */
5
+
6
+ import { buildFieldConfig, detectRelationship } from './rulesEngine.js';
7
+
8
+ /**
9
+ * Build IR from scanner detected APIs
10
+ * @param {Array} detectedApis - Output from scanner
11
+ * @param {Object} options - Configuration options
12
+ * @returns {Object} - Complete IR
13
+ */
14
+ export function buildIR(detectedApis = [], options = {}) {
15
+ const resources = [];
16
+ const relationships = [];
17
+ const globalSettings = {
18
+ hasAuth: options.hasAuth || false,
19
+ dbType: options.dbType || 'mongodb',
20
+ apiVersion: options.apiVersion || 'v1',
21
+ ...options
22
+ };
23
+
24
+ // Group APIs by resource
25
+ const resourceMap = groupApisByResource(detectedApis);
26
+
27
+ // PASS 1: Build basic resource info (names only)
28
+ const resourceNames = [];
29
+ for (const resourceName of Object.keys(resourceMap)) {
30
+ const normalized = normalizeResourceName(resourceName);
31
+ resourceNames.push({
32
+ name: normalized,
33
+ singular: singularize(resourceName),
34
+ plural: pluralize(resourceName)
35
+ });
36
+ }
37
+
38
+ // PASS 2: Build complete resource definitions with smart relationship detection
39
+ for (const [resourceName, apis] of Object.entries(resourceMap)) {
40
+ const resource = buildResourceIR(resourceName, apis, options, resourceNames);
41
+ resources.push(resource);
42
+
43
+ // Track relationships
44
+ resource.fields.forEach(field => {
45
+ if (field.relationship) {
46
+ relationships.push({
47
+ from: resource.name,
48
+ to: field.relationship.ref,
49
+ field: field.name,
50
+ type: 'reference'
51
+ });
52
+ }
53
+ });
54
+ }
55
+
56
+ return {
57
+ version: '1.0',
58
+ metadata: {
59
+ createdAt: new Date().toISOString(),
60
+ generator: 'offbyt-ir-builder'
61
+ },
62
+ settings: globalSettings,
63
+ resources,
64
+ relationships,
65
+ hooks: generateHooks(resources)
66
+ };
67
+ }
68
+
69
+ /**
70
+ * Build single resource IR
71
+ */
72
+ function buildResourceIR(resourceName, apis, options, resourceNames = []) {
73
+ const methods = new Set(apis.map(api => api.method));
74
+
75
+ // Infer fields from API documentation or use defaults
76
+ // Pass resourceNames so relationship detection can work
77
+ const fields = inferFieldsFromApis(apis, options, resourceNames);
78
+
79
+ // Add audit fields if auth enabled
80
+ if (options.hasAuth) {
81
+ if (!fields.find(f => f.name === 'userId')) {
82
+ fields.push({
83
+ name: 'userId',
84
+ type: 'ObjectId',
85
+ ref: 'User',
86
+ isRequired: true,
87
+ relationship: { ref: 'User' }
88
+ });
89
+ }
90
+ }
91
+
92
+ return {
93
+ name: normalizeResourceName(resourceName),
94
+ singular: singularize(resourceName),
95
+ plural: pluralize(resourceName),
96
+ description: `${capitalizeFirst(resourceName)} resource`,
97
+ fields,
98
+ routes: Array.from(methods),
99
+ endpoints: apis.map(api => ({
100
+ method: api.method,
101
+ path: api.path,
102
+ action: inferAction(api)
103
+ })),
104
+ validations: generateValidations(fields),
105
+ middleware: generateMiddleware(fields, options),
106
+ timestamps: true
107
+ };
108
+ }
109
+
110
+ /**
111
+ * Group APIs by resource name
112
+ */
113
+ function groupApisByResource(apis) {
114
+ const grouped = {};
115
+
116
+ for (const api of apis) {
117
+ let resource = null;
118
+
119
+ // First check if api object has resource property explicitly
120
+ if (api.resource) {
121
+ resource = api.resource;
122
+ } else {
123
+ // Extract resource from path
124
+ // Handle both /api/users and /api/v1/users patterns
125
+ let match = api.path.match(/\/api\/(?:v\d+\/)?(\w+)/);
126
+ if (match) {
127
+ resource = match[1];
128
+ }
129
+ }
130
+
131
+ if (resource) {
132
+ if (!grouped[resource]) grouped[resource] = [];
133
+ grouped[resource].push(api);
134
+ }
135
+ }
136
+
137
+ return grouped;
138
+ }
139
+
140
+ /**
141
+ * Infer fields from APIs (or use smart defaults)
142
+ */
143
+ function inferFieldsFromApis(apis, options, resourceNames = []) {
144
+ const fields = [];
145
+ const fieldSet = new Set();
146
+
147
+ // Get common field names for this resource
148
+ const commonFields = getCommonFieldsForResource(apis[0]?.path);
149
+
150
+ for (const fieldName of commonFields) {
151
+ if (!fieldSet.has(fieldName)) {
152
+ const config = buildFieldConfig(fieldName, '', resourceNames);
153
+ fields.push(config);
154
+ fieldSet.add(fieldName);
155
+ }
156
+ }
157
+
158
+ return fields;
159
+ }
160
+
161
+ /**
162
+ * Get smart default fields for resource based on type
163
+ * Enhanced with automatic relationship fields
164
+ */
165
+ function getCommonFieldsForResource(apiPath) {
166
+ // Smart defaults based on resource type with relationship fields
167
+ const defaults = {
168
+ users: ['username', 'firstName', 'lastName', 'email', 'password', 'phone', 'avatar', 'bio', 'role', 'active'],
169
+ products: ['name', 'description', 'price', 'category', 'stock', 'image', 'rating', 'seller'],
170
+ posts: ['title', 'content', 'author', 'tags', 'published', 'views', 'likes'],
171
+ comments: ['text', 'author', 'post', 'likes', 'approved'],
172
+ categories: ['name', 'description', 'parent', 'active'],
173
+ events: ['title', 'description', 'club', 'date', 'location', 'capacity', 'organizer', 'image'],
174
+ clubs: ['name', 'description', 'admin', 'members', 'image'],
175
+ registrations: ['event', 'user', 'status'],
176
+ orders: ['items', 'total', 'status', 'user', 'deliveryAddress', 'paymentStatus'],
177
+ reviews: ['rating', 'text', 'author', 'product', 'verified']
178
+ };
179
+
180
+ // Try to match resource type from API path
181
+ for (const [type, fields] of Object.entries(defaults)) {
182
+ if (apiPath?.includes(`/${type}`)) {
183
+ return fields;
184
+ }
185
+ }
186
+
187
+ // Default fields for any resource
188
+ return ['name', 'description', 'active'];
189
+ }
190
+
191
+ /**
192
+ * Infer action from API endpoint
193
+ */
194
+ function inferAction(api) {
195
+ const methodToAction = {
196
+ 'GET': 'read',
197
+ 'POST': 'create',
198
+ 'PUT': 'update',
199
+ 'PATCH': 'update',
200
+ 'DELETE': 'delete'
201
+ };
202
+
203
+ return methodToAction[api.method] || 'custom';
204
+ }
205
+
206
+ /**
207
+ * Generate validation rules from fields
208
+ */
209
+ function generateValidations(fields) {
210
+ const validations = {};
211
+
212
+ for (const field of fields) {
213
+ const fieldValidations = [];
214
+
215
+ if (field.isRequired) fieldValidations.push('required');
216
+ if (field.validators) fieldValidations.push(...field.validators);
217
+
218
+ if (fieldValidations.length > 0) {
219
+ validations[field.name] = fieldValidations;
220
+ }
221
+ }
222
+
223
+ return validations;
224
+ }
225
+
226
+ /**
227
+ * Generate required middleware based on fields
228
+ */
229
+ function generateMiddleware(fields, options) {
230
+ const middleware = [];
231
+
232
+ // Add auth middleware if needed
233
+ if (options.hasAuth) {
234
+ middleware.push('authenticate');
235
+ }
236
+
237
+ // Add validation middleware
238
+ middleware.push('validateInput');
239
+
240
+ // Add error handling
241
+ middleware.push('errorHandler');
242
+
243
+ return middleware;
244
+ }
245
+
246
+ /**
247
+ * Generate hooks needed for fields
248
+ */
249
+ function generateHooks(resources) {
250
+ const hooks = {};
251
+
252
+ for (const resource of resources) {
253
+ hooks[resource.name] = [];
254
+
255
+ for (const field of resource.fields) {
256
+ if (field.hooks) {
257
+ hooks[resource.name].push(...field.hooks);
258
+ }
259
+ }
260
+
261
+ // Remove duplicates
262
+ hooks[resource.name] = [...new Set(hooks[resource.name])];
263
+ }
264
+
265
+ return hooks;
266
+ }
267
+
268
+ /**
269
+ * Helper: Normalize resource name
270
+ */
271
+ function normalizeResourceName(name) {
272
+ return name.toLowerCase().replace(/s$/, '').replace(/ies$/, 'y');
273
+ }
274
+
275
+ /**
276
+ * Helper: Singularize
277
+ */
278
+ function singularize(word) {
279
+ if (word.endsWith('ies')) return word.slice(0, -3) + 'y';
280
+ if (word.endsWith('s')) return word.slice(0, -1);
281
+ return word;
282
+ }
283
+
284
+ /**
285
+ * Helper: Pluralize
286
+ */
287
+ function pluralize(word) {
288
+ if (word.endsWith('y')) return word.slice(0, -1) + 'ies';
289
+ return word + 's';
290
+ }
291
+
292
+ /**
293
+ * Helper: Capitalize first letter
294
+ */
295
+ function capitalizeFirst(word) {
296
+ return word.charAt(0).toUpperCase() + word.slice(1);
297
+ }
298
+
299
+ /**
300
+ * Validate IR structure
301
+ */
302
+ export function validateIR(ir) {
303
+ const errors = [];
304
+
305
+ if (!ir.version) errors.push('Missing IR version');
306
+ if (!ir.resources || !Array.isArray(ir.resources)) {
307
+ errors.push('Missing or invalid resources array');
308
+ }
309
+ if (!ir.settings) errors.push('Missing settings');
310
+
311
+ ir.resources?.forEach((resource, idx) => {
312
+ if (!resource.name) errors.push(`Resource ${idx} missing name`);
313
+ if (!resource.fields || !Array.isArray(resource.fields)) {
314
+ errors.push(`Resource ${resource.name} missing fields`);
315
+ }
316
+ });
317
+
318
+ return {
319
+ valid: errors.length === 0,
320
+ errors
321
+ };
322
+ }
323
+
324
+ /**
325
+ * Pretty print IR (for debugging)
326
+ */
327
+ export function printIR(ir) {
328
+ return JSON.stringify(ir, null, 2);
329
+ }
330
+