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,475 @@
1
+ /**
2
+ * Advanced CRUD Code Generator
3
+ * Generates production-ready CRUD operations with:
4
+ * - Pagination, filtering, sorting, search
5
+ * - Bulk operations
6
+ * - Advanced validation
7
+ * - Proper error handling
8
+ */
9
+
10
+ export function generateAdvancedCrudModel(resourceName, fields = [], hasAuth = false, relations = []) {
11
+ const modelName = resourceName.charAt(0).toUpperCase() + resourceName.slice(1).replace(/s$/, '');
12
+ const collectionName = resourceName.toLowerCase();
13
+
14
+ // Debug logging
15
+ console.log(`\n[DEBUG] Generating model for: ${resourceName}`);
16
+ console.log(`[DEBUG] Model name: ${modelName}`);
17
+ console.log(`[DEBUG] Fields: ${JSON.stringify(fields)}`);
18
+ console.log(`[DEBUG] Relations: ${JSON.stringify(relations)}`);
19
+
20
+ const fieldDefinitions = generateAdvancedFieldDefinitions(fields, relations);
21
+ console.log(`[DEBUG] Field definitions generated (length: ${fieldDefinitions.length} chars)`);
22
+
23
+ const indexes = generateIndexes(fields, modelName);
24
+
25
+ return `import mongoose from 'mongoose';
26
+
27
+ const ${modelName}Schema = new mongoose.Schema(
28
+ {
29
+ ${fieldDefinitions},
30
+ // Metadata
31
+ isActive: {
32
+ type: Boolean,
33
+ default: true,
34
+ index: true
35
+ },
36
+ isDeleted: {
37
+ type: Boolean,
38
+ default: false,
39
+ index: true
40
+ },
41
+ metadata: {
42
+ createdBy: mongoose.Schema.Types.ObjectId,
43
+ updatedBy: mongoose.Schema.Types.ObjectId,
44
+ version: { type: Number, default: 1 },
45
+ tags: [String]
46
+ }
47
+ },
48
+ {
49
+ timestamps: true,
50
+ collection: '${collectionName}'
51
+ }
52
+ );
53
+
54
+ // ============================================================
55
+ // INDEXES FOR PERFORMANCE
56
+ // ============================================================
57
+ ${indexes}
58
+
59
+ // ============================================================
60
+ // HOOKS
61
+ // ============================================================
62
+
63
+ ${modelName}Schema.pre('save', function(next) {
64
+ if (this.isModified()) {
65
+ this.metadata.version = (this.metadata.version || 0) + 1;
66
+ }
67
+ next();
68
+ });
69
+
70
+ // ============================================================
71
+ // QUERY HELPERS
72
+ // ============================================================
73
+
74
+ ${modelName}Schema.query.active = function() {
75
+ return this.find({ isActive: true, isDeleted: false });
76
+ };
77
+
78
+ // ============================================================
79
+ // STATIC METHODS
80
+ // ============================================================
81
+
82
+ ${modelName}Schema.statics.findAllActive = async function(options = {}) {
83
+ return this.find({ isActive: true, isDeleted: false })
84
+ .sort(options.sort || { createdAt: -1 })
85
+ .limit(options.limit || 100)
86
+ .skip(options.skip || 0);
87
+ };
88
+
89
+ ${modelName}Schema.statics.softDelete = async function(id) {
90
+ return this.findByIdAndUpdate(
91
+ id,
92
+ { isDeleted: true },
93
+ { new: true }
94
+ );
95
+ };
96
+
97
+ // ============================================================
98
+ // INSTANCE METHODS
99
+ // ============================================================
100
+
101
+ ${modelName}Schema.methods.toJSON = function() {
102
+ const obj = this.toObject();
103
+ delete obj.__v;
104
+ return obj;
105
+ };
106
+
107
+ const ${modelName} = mongoose.model('${modelName}', ${modelName}Schema);
108
+
109
+ export default ${modelName};
110
+ `;
111
+ }
112
+
113
+ function generateAdvancedFieldDefinitions(fields, relations = []) {
114
+ const fieldDefs = [];
115
+ const relationMap = new Map(relations.map(r => [r.field, r]));
116
+
117
+ for (const field of fields) {
118
+ // Skip MongoDB's automatic _id field and id variations
119
+ if (field === '_id' || field === 'id' || field.toLowerCase() === 'id') {
120
+ continue;
121
+ }
122
+
123
+ const normalized = field.toLowerCase();
124
+ const relation = relationMap.get(field);
125
+
126
+ let fieldDef = '';
127
+
128
+ // Check if this field has a defined relation
129
+ if (relation) {
130
+ fieldDef = ` ${field}: { type: mongoose.Schema.Types.ObjectId, ref: '${relation.ref}', required: ${relation.required || false} }`;
131
+ }
132
+ // Email
133
+ else if (normalized.includes('email')) {
134
+ fieldDef = ` ${field}: { type: String, match: /.+@.+\\..+/, unique: true, sparse: true, lowercase: true, trim: true }`;
135
+ }
136
+ // Price/Amount/Cost
137
+ else if (normalized.match(/^(price|cost|amount|total|revenue|salary)$/)) {
138
+ fieldDef = ` ${field}: { type: Number, min: 0, default: 0 }`;
139
+ }
140
+ // Rating/Score
141
+ else if (normalized.match(/^(rating|score|points?)$/)) {
142
+ fieldDef = ` ${field}: { type: Number, min: 0, max: 100, default: 0 }`;
143
+ }
144
+ // Status
145
+ else if (normalized === 'status') {
146
+ fieldDef = ` ${field}: { type: String, enum: ['active', 'inactive', 'pending', 'archived', 'draft'], default: 'active' }`;
147
+ }
148
+ // Count/Views/Likes
149
+ else if (normalized.match(/^(count|views|likes|clicks|impressions)$/)) {
150
+ fieldDef = ` ${field}: { type: Number, default: 0, min: 0 }`;
151
+ }
152
+ // Boolean flags
153
+ else if (normalized.match(/^(is_|active|published|completed|verified|confirmed)/) ||
154
+ normalized.match(/(active|published|completed|verified|confirmed)$/i)) {
155
+ fieldDef = ` ${field}: { type: Boolean, default: false, index: true }`;
156
+ }
157
+ // Date fields
158
+ else if (normalized.match(/date|time|at$/)) {
159
+ fieldDef = ` ${field}: { type: Date, default: Date.now }`;
160
+ }
161
+ // Phone
162
+ else if (normalized.includes('phone')) {
163
+ fieldDef = ` ${field}: { type: String, match: /^\\+?[\\d\\s\\-()]+$/ }`;
164
+ }
165
+ // Arrays
166
+ else if (normalized.endsWith('s') && !normalized.endsWith('ss')) {
167
+ fieldDef = ` ${field}: [{ type: String }]`;
168
+ }
169
+ // Default to string
170
+ else {
171
+ fieldDef = ` ${field}: { type: String, trim: true }`;
172
+ }
173
+
174
+ fieldDefs.push(fieldDef);
175
+ }
176
+
177
+ return fieldDefs.length > 0 ? fieldDefs.join(',\n') : ' // Fields defined';
178
+ }
179
+
180
+ function generateIndexes(fields, modelName) {
181
+ const indexes = [
182
+ `${modelName}Schema.index({ createdAt: -1 });`,
183
+ `${modelName}Schema.index({ updatedAt: -1 });`,
184
+ `${modelName}Schema.index({ isActive: 1 });`,
185
+ `${modelName}Schema.index({ isDeleted: 1, createdAt: -1 });`
186
+ ];
187
+
188
+ // Add indexes for common query fields
189
+ for (const field of fields) {
190
+ const normalized = field.toLowerCase();
191
+ if (normalized.match(/^(status|type|category|user|owner)$/)) {
192
+ indexes.push(`${modelName}Schema.index({ ${field}: 1, createdAt: -1 });`);
193
+ }
194
+ }
195
+
196
+ return indexes.join('\n');
197
+ }
198
+
199
+ /**
200
+ * Generate Advanced Routes with Pagination, Filtering, Sorting
201
+ */
202
+ export function generateAdvancedCrudRoutes(resourceName, fields = [], hasAuth = false, relations = [], actions = []) {
203
+ const modelName = resourceName.charAt(0).toUpperCase() + resourceName.slice(1).replace(/s$/, '');
204
+ const searchFields = fields.filter(f =>
205
+ !f.toLowerCase().match(/^(id|createdby|lastupdated)$/)
206
+ ).slice(0, 3).join("', '");
207
+
208
+ const filterFields = fields.filter(f =>
209
+ f.toLowerCase().match(/^(status|type|category|isactive)$/)
210
+ ).map(f => `'${f}'`).join(', ');
211
+
212
+ // Debug logging
213
+ console.log(`\n[DEBUG] Generating routes for: ${resourceName}`);
214
+ console.log(`[DEBUG] Model name: ${modelName}`);
215
+ console.log(`[DEBUG] Fields: ${JSON.stringify(fields)}`);
216
+ console.log(`[DEBUG] Search fields string: "${searchFields}"`);
217
+ console.log(`[DEBUG] Filter fields string: "${filterFields}"`);
218
+
219
+ const validators = generateValidators(fields);
220
+ const updateValidators = generateUpdateValidators(fields);
221
+
222
+ return `import express from 'express';
223
+ import { query, body, param } from 'express-validator';
224
+ import { validateErrors } from '../middleware/validation.js';
225
+ import { PaginationHelper } from '../utils/pagination.js';
226
+ import { ResponseHelper } from '../utils/helper.js';
227
+ import ${modelName} from '../models/${modelName}.js';
228
+
229
+ const router = express.Router();
230
+
231
+ // ============================================================
232
+ // GET ALL - WITH PAGINATION, FILTERING, SORTING, SEARCH
233
+ // ============================================================
234
+ router.get(
235
+ '/',
236
+ [
237
+ query('page').optional().isInt({ min: 1 }).toInt(),
238
+ query('limit').optional().isInt({ min: 1, max: 100 }).toInt(),
239
+ query('sort').optional().isString(),
240
+ query('search').optional().isString().trim(),
241
+ validateErrors
242
+ ],
243
+ async (req, res, next) => {
244
+ try {
245
+ const { page = 1, limit = 10, skip = 0 } = PaginationHelper.getPaginationParams(req);
246
+ const sort = PaginationHelper.getSortObject(req.query.sort);
247
+
248
+ let query = {};
249
+
250
+ // Apply search filter
251
+ if (req.query.search) {
252
+ query = PaginationHelper.buildSearchQuery(req.query.search, ['${searchFields}']);
253
+ }
254
+
255
+ // Apply additional filters
256
+ const filters = PaginationHelper.buildFilterQuery(req, [${filterFields}]);
257
+ query = { ...query, ...filters };
258
+
259
+ // Exclude deleted items by default
260
+ query.isDeleted = false;
261
+
262
+ // Execute paginated query
263
+ const result = await PaginationHelper.paginate(${modelName}, query, { page, limit, skip, sort });
264
+
265
+ return ResponseHelper.paginated(res, result.data, result.pagination, '${resourceName} loaded successfully');
266
+ } catch (error) {
267
+ next(error);
268
+ }
269
+ }
270
+ );
271
+
272
+ // ============================================================
273
+ // GET BY ID
274
+ // ============================================================
275
+ router.get(
276
+ '/:id',
277
+ [param('id').isMongoId(), validateErrors],
278
+ async (req, res, next) => {
279
+ try {
280
+ const item = await ${modelName}.findOne({ _id: req.params.id, isDeleted: false });
281
+
282
+ if (!item) {
283
+ return ResponseHelper.notFound(res, '${modelName}');
284
+ }
285
+
286
+ return ResponseHelper.success(res, item, 'Item retrieved successfully');
287
+ } catch (error) {
288
+ next(error);
289
+ }
290
+ }
291
+ );
292
+
293
+ // ============================================================
294
+ // CREATE
295
+ // ============================================================
296
+ router.post(
297
+ '/',
298
+ [${validators ? validators + ', ' : ''}validateErrors],
299
+ async (req, res, next) => {
300
+ try {
301
+ const newItem = new ${modelName}(req.body);
302
+ const saved = await newItem.save();
303
+
304
+ return ResponseHelper.success(res, saved, '${modelName} created successfully', 201);
305
+ } catch (error) {
306
+ if (error.name === 'ValidationError') {
307
+ const errors = Object.values(error.errors).map(e => e.message);
308
+ return ResponseHelper.validationError(res, errors);
309
+ }
310
+ if (error.code === 11000) {
311
+ return ResponseHelper.error(res, 'Duplicate value for unique field', [], 409);
312
+ }
313
+ next(error);
314
+ }
315
+ }
316
+ );
317
+
318
+ // ============================================================
319
+ // UPDATE (PUT - Full)
320
+ // ============================================================
321
+ router.put(
322
+ '/:id',
323
+ [
324
+ param('id').isMongoId(),
325
+ ${updateValidators ? updateValidators + ',' : ''}
326
+ validateErrors
327
+ ],
328
+ async (req, res, next) => {
329
+ try {
330
+ const updated = await ${modelName}.findByIdAndUpdate(
331
+ req.params.id,
332
+ req.body,
333
+ { new: true, runValidators: true }
334
+ );
335
+
336
+ if (!updated) {
337
+ return ResponseHelper.notFound(res, '${modelName}');
338
+ }
339
+
340
+ return ResponseHelper.success(res, updated, '${modelName} updated successfully');
341
+ } catch (error) {
342
+ if (error.name === 'ValidationError') {
343
+ const errors = Object.values(error.errors).map(e => e.message);
344
+ return ResponseHelper.validationError(res, errors);
345
+ }
346
+ next(error);
347
+ }
348
+ }
349
+ );
350
+
351
+ // ============================================================
352
+ // PARTIAL UPDATE (PATCH)
353
+ // ============================================================
354
+ router.patch(
355
+ '/:id',
356
+ [param('id').isMongoId(), validateErrors],
357
+ async (req, res, next) => {
358
+ try {
359
+ // Don't allow ID changes
360
+ delete req.body._id;
361
+ delete req.body.createdAt;
362
+
363
+ const updated = await ${modelName}.findByIdAndUpdate(
364
+ req.params.id,
365
+ { \$set: req.body },
366
+ { new: true, runValidators: true }
367
+ );
368
+
369
+ if (!updated) {
370
+ return ResponseHelper.notFound(res, '${modelName}');
371
+ }
372
+
373
+ return ResponseHelper.success(res, updated, 'Partial update successful');
374
+ } catch (error) {
375
+ next(error);
376
+ }
377
+ }
378
+ );
379
+
380
+ // ============================================================
381
+ // DELETE (Soft Delete)
382
+ // ============================================================
383
+ router.delete(
384
+ '/:id',
385
+ [param('id').isMongoId(), validateErrors],
386
+ async (req, res, next) => {
387
+ try {
388
+ const deleted = await ${modelName}.softDelete(req.params.id);
389
+
390
+ if (!deleted) {
391
+ return ResponseHelper.notFound(res, '${modelName}');
392
+ }
393
+
394
+ return ResponseHelper.success(res, deleted, '${modelName} deleted successfully');
395
+ } catch (error) {
396
+ next(error);
397
+ }
398
+ }
399
+ );
400
+
401
+ // ============================================================
402
+ // BULK OPERATIONS
403
+ // ============================================================
404
+
405
+ // Bulk Create
406
+ router.post(
407
+ '/bulk/create',
408
+ [body('items').isArray({ min: 1 }), validateErrors],
409
+ async (req, res, next) => {
410
+ try {
411
+ const items = await ${modelName}.insertMany(req.body.items);
412
+ return ResponseHelper.success(res, items, \`Created \${items.length} items\`, 201);
413
+ } catch (error) {
414
+ next(error);
415
+ }
416
+ }
417
+ );
418
+
419
+ // Bulk Delete
420
+ router.delete(
421
+ '/bulk/delete',
422
+ [body('ids').isArray({ min: 1 }), validateErrors],
423
+ async (req, res, next) => {
424
+ try {
425
+ const result = await ${modelName}.deleteMany({ _id: { $in: req.body.ids } });
426
+ return ResponseHelper.success(res, result, \`Deleted \${result.deletedCount} items\`);
427
+ } catch (error) {
428
+ next(error);
429
+ }
430
+ }
431
+ );
432
+
433
+ // ============================================================
434
+ // STATISTICS
435
+ // ============================================================
436
+ router.get(
437
+ '/stats/summary',
438
+ async (req, res, next) => {
439
+ try {
440
+ const total = await ${modelName}.countDocuments({ isDeleted: false });
441
+ const recent = await ${modelName}.find({ isDeleted: false }).sort({ createdAt: -1 }).limit(5);
442
+
443
+ return ResponseHelper.success(res, { total, recent }, 'Statistics retrieved');
444
+ } catch (error) {
445
+ next(error);
446
+ }
447
+ }
448
+ );
449
+
450
+ export default router;
451
+ `;
452
+ }
453
+
454
+ function generateValidators(fields) {
455
+ const validators = [];
456
+ for (const field of fields) {
457
+ const normalized = field.toLowerCase();
458
+ if (normalized === 'email') {
459
+ validators.push(`body('email').isEmail()`);
460
+ } else if (normalized === 'password') {
461
+ validators.push(`body('password').isLength({ min: 8 })`);
462
+ } else if (normalized.match(/^(price|cost|amount)$/)) {
463
+ validators.push(`body('${field}').optional().isFloat({ min: 0 })`);
464
+ } else if (normalized === 'name' || normalized === 'title') {
465
+ validators.push(`body('${field}').isString().isLength({ min: 2, max: 255 })`);
466
+ }
467
+ }
468
+ return validators.join(',\n ');
469
+ }
470
+
471
+ function generateUpdateValidators(fields) {
472
+ return generateValidators(fields) || 'validateErrors';
473
+ }
474
+
475
+ export default { generateAdvancedCrudModel, generateAdvancedCrudRoutes };