@triophore/falcon-cli 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 (56) hide show
  1. package/README.md +62 -0
  2. package/auth/basic.js +8 -0
  3. package/auth/cookie.js +10 -0
  4. package/auth/jwks.js +6 -0
  5. package/auth/jwt.js +9 -0
  6. package/auth/openid.js +5 -0
  7. package/auth/webscoket.js +6 -0
  8. package/builder/EnvBuilder.js +0 -0
  9. package/builder/createCjsModule.js +95 -0
  10. package/builder/editModelInteractive.js +159 -0
  11. package/builder/interactiveModelBuilder.js +215 -0
  12. package/builder/interactiveMongobuilder.js +189 -0
  13. package/builder/interactiveUnifiedBuilder.js +277 -0
  14. package/builder/joiValidatorBuilder.js +218 -0
  15. package/builder/mongooseModelBuilder.js +290 -0
  16. package/builder/mongooseModelBuilder2.js +313 -0
  17. package/builder/runMigrations.js +106 -0
  18. package/builder/sequelizeModelBuilder.js +180 -0
  19. package/cli.js +60 -0
  20. package/commands/create.js +57 -0
  21. package/commands/generate.js +74 -0
  22. package/dev/Uset.schema.json +18 -0
  23. package/dev/buildSchemaInteractive.js +189 -0
  24. package/dev/buildSequelizeSchemaInteractive.js +128 -0
  25. package/dev/createJoiSchemaFromJson.js +137 -0
  26. package/dev/createModelFromJson.js +280 -0
  27. package/dev/generateAllFiles.js +45 -0
  28. package/dev/generateJoiFile.js +95 -0
  29. package/dev/generateSequelizeFiles.js +167 -0
  30. package/dev/interactiveJoiBuilder.js +177 -0
  31. package/dev/ra.js +22 -0
  32. package/dev/rj.js +18 -0
  33. package/dev/run.js +16 -0
  34. package/dev/run_seq.js +18 -0
  35. package/dev/tracker.js +23 -0
  36. package/editJsConfig.js +188 -0
  37. package/index.js +548 -0
  38. package/lib/ModelGenerator.js +203 -0
  39. package/lib/ProjectGenerator.js +246 -0
  40. package/lib/utils.js +100 -0
  41. package/logo.js +3 -0
  42. package/package.json +35 -0
  43. package/readme.md +2 -0
  44. package/schema.json +42 -0
  45. package/templates/auth_vals.json +3 -0
  46. package/templates/config.js +0 -0
  47. package/templates/example-route.js +94 -0
  48. package/templates/example-service.js +63 -0
  49. package/templates/example-validator.js +15 -0
  50. package/templates/example-worker.js +83 -0
  51. package/templates/index.txt +41 -0
  52. package/templates/post-init.js +78 -0
  53. package/templates/settings.js +192 -0
  54. package/templates/template1.settings.txt +15 -0
  55. package/templates/templatev1.json +38 -0
  56. package/validateJsConfig.js +125 -0
@@ -0,0 +1,128 @@
1
+ // buildSequelizeSchemaInteractive.js
2
+ const {
3
+ input,
4
+ confirm,
5
+ select,
6
+ checkbox,
7
+ } = require('@inquirer/prompts');
8
+
9
+ async function buildSequelizeSchemaInteractive() {
10
+ console.log('\nSequelize Model + Migration Builder\n');
11
+
12
+ const modelName = await input({
13
+ message: 'Model name (singular, e.g., User):',
14
+ validate: v => v.trim() ? true : 'Required',
15
+ });
16
+
17
+ const tableName = await input({
18
+ message: 'Table name (or press Enter to use model name):',
19
+ default: modelName.toLowerCase() + 's',
20
+ });
21
+
22
+ const useTimestamps = await confirm({
23
+ message: 'Add timestamps (createdAt, updatedAt)?',
24
+ default: true,
25
+ });
26
+
27
+ const useParanoid = await confirm({
28
+ message: 'Enable soft deletes (deletedAt)?',
29
+ default: false,
30
+ });
31
+
32
+ const schema = {};
33
+ const associations = [];
34
+ const options = {
35
+ timestamps: useTimestamps,
36
+ paranoid: useParanoid,
37
+ tableName: tableName.trim() || undefined,
38
+ freezeTableName: true,
39
+ };
40
+
41
+ // ── Fields
42
+ while (true) {
43
+ const add = await confirm({ message: 'Add field?', default: true });
44
+ if (!add) break;
45
+
46
+ const fieldName = await input({
47
+ message: 'Field name:',
48
+ validate: v => v && !schema[v] ? true : 'Invalid/duplicate',
49
+ });
50
+
51
+ const dataType = await select({
52
+ message: `Data type for "${fieldName}":`,
53
+ choices: [
54
+ 'STRING', 'TEXT', 'INTEGER', 'BIGINT', 'FLOAT', 'DOUBLE',
55
+ 'BOOLEAN', 'DATE', 'DATEONLY', 'JSON', 'ARRAY', 'ENUM'
56
+ ].map(t => ({ name: t, value: t })),
57
+ });
58
+
59
+ const field = { type: dataType };
60
+
61
+ const allowNull = await confirm({ message: 'Allow NULL?', default: true });
62
+ if (!allowNull) field.allowNull = false;
63
+
64
+ const unique = await confirm({ message: 'Unique?', default: false });
65
+ if (unique) field.unique = true;
66
+
67
+ // Default value
68
+ const hasDefault = await confirm({ message: 'Set default?', default: false });
69
+ if (hasDefault) {
70
+ if (dataType === 'BOOLEAN') {
71
+ field.defaultValue = await confirm({ message: 'Default TRUE?', default: true });
72
+ } else if (dataType === 'DATE') {
73
+ field.defaultValue = await confirm({ message: 'Default NOW?', default: true })
74
+ ? 'sequelize.fn("NOW")'
75
+ : await input({ message: 'Default (JS):' });
76
+ } else if (dataType === 'ENUM') {
77
+ const vals = await input({ message: 'Enum values (comma-separated):' });
78
+ field.values = vals.split(',').map(v => v.trim()).filter(Boolean);
79
+ const def = await input({ message: 'Default value:' });
80
+ field.defaultValue = def.trim();
81
+ } else {
82
+ const def = await input({ message: 'Default value (JS):' });
83
+ field.defaultValue = def;
84
+ }
85
+ }
86
+
87
+ // String length
88
+ if (dataType === 'STRING') {
89
+ const len = await input({ message: 'Length (default 255):', default: '255' });
90
+ if (len !== '255') field.type = `STRING(${len})`;
91
+ }
92
+
93
+ // Array items
94
+ if (dataType === 'ARRAY') {
95
+ const itemType = await select({
96
+ message: 'Array item type:',
97
+ choices: ['STRING', 'INTEGER', 'FLOAT', 'BOOLEAN', 'DATE'].map(t => ({ name: t, value: t })),
98
+ });
99
+ field.type = `${itemType}[]`;
100
+ }
101
+
102
+ schema[fieldName] = field;
103
+ }
104
+
105
+ // ── Associations
106
+ while (true) {
107
+ const addAssoc = await confirm({ message: 'Add association?', default: false });
108
+ if (!addAssoc) break;
109
+
110
+ const type = await select({
111
+ message: 'Association type:',
112
+ choices: [
113
+ { name: 'belongsTo', value: 'belongsTo' },
114
+ { name: 'hasMany', value: 'hasMany' },
115
+ ],
116
+ });
117
+
118
+ const targetModel = await input({ message: 'Target model name:' });
119
+ const foreignKey = await input({ message: 'Foreign key (in this table):', default: `${targetModel}Id` });
120
+ const as = await input({ message: 'Alias (as):', default: targetModel.toLowerCase() });
121
+
122
+ associations.push({ type, targetModel, foreignKey, as });
123
+ }
124
+
125
+ return { name: modelName, tableName: tableName.trim(), schema, associations, options };
126
+ }
127
+
128
+ module.exports = { buildSequelizeSchemaInteractive };
@@ -0,0 +1,137 @@
1
+ // createJoiSchemaFromJson.js
2
+ const Joi = require('joi');
3
+
4
+ /**
5
+ * Converts custom JSON schema → Joi schema
6
+ * @param {Object} config - Your JSON config
7
+ * @returns {Joi.ObjectSchema}
8
+ */
9
+ function createJoiSchemaFromJson(config) {
10
+ if (!config || !config.schema) {
11
+ throw new Error('Config must have "schema"');
12
+ }
13
+
14
+ const schemaDef = config.schema;
15
+ const joiDef = {};
16
+
17
+ for (const [fieldName, field] of Object.entries(schemaDef)) {
18
+ let joiField = null;
19
+
20
+ const type = (field.type || '').toLowerCase();
21
+
22
+ // Base type
23
+ switch (type) {
24
+ case 'string':
25
+ joiField = Joi.string();
26
+ if (field.trim) joiField = joiField.trim();
27
+ if (field.lowercase) joiField = joiField.lowercase();
28
+ if (field.uppercase) joiField = joiField.uppercase();
29
+ if (field.email) joiField = joiField.email();
30
+ if (field.min) joiField = joiField.min(field.min);
31
+ if (field.max) joiField = joiField.max(field.max);
32
+ if (field.regex) joiField = joiField.pattern(new RegExp(field.regex));
33
+ if (field.enum) joiField = joiField.valid(...field.enum);
34
+ break;
35
+
36
+ case 'number':
37
+ joiField = Joi.number();
38
+ if (field.min !== undefined) joiField = joiField.min(field.min);
39
+ if (field.max !== undefined) joiField = joiField.max(field.max);
40
+ if (field.integer) joiField = joiField.integer();
41
+ break;
42
+
43
+ case 'boolean':
44
+ joiField = Joi.boolean();
45
+ break;
46
+
47
+ case 'date':
48
+ joiField = Joi.date();
49
+ if (field.min) joiField = joiField.min(field.min);
50
+ if (field.max) joiField = joiField.max(field.max);
51
+ break;
52
+
53
+ case 'array':
54
+ const items = convertItem(field.items || { type: 'string' });
55
+ joiField = Joi.array().items(items);
56
+ if (field.min) joiField = joiField.min(field.min);
57
+ if (field.max) joiField = joiField.max(field.max);
58
+ if (field.unique) joiField = joiField.unique();
59
+ break;
60
+
61
+ case 'object':
62
+ joiField = Joi.object(createNestedJoi(field.properties || {}));
63
+ break;
64
+
65
+ case 'objectid':
66
+ joiField = field.ref
67
+ ? Joi.any().meta({ ref: field.ref }) // custom tag
68
+ : Joi.string().length(24).hex(); // MongoID
69
+ break;
70
+
71
+ case 'mixed':
72
+ default:
73
+ joiField = Joi.any();
74
+ }
75
+
76
+ // Required
77
+ if (field.required) {
78
+ joiField = joiField.required();
79
+ } else {
80
+ joiField = joiField.optional();
81
+ }
82
+
83
+ // Default
84
+ if (field.default !== undefined) {
85
+ const def = field.default === 'now' ? new Date() : field.default;
86
+ joiField = joiField.default(def);
87
+ }
88
+
89
+ joiDef[fieldName] = joiField;
90
+ }
91
+
92
+ return Joi.object(joiDef).options({ stripUnknown: true });
93
+ }
94
+
95
+ // Helper: recursive nested object
96
+ function createNestedJoi(properties) {
97
+ const nested = {};
98
+ for (const [key, field] of Object.entries(properties)) {
99
+ nested[key] = convertFieldToJoi(field);
100
+ }
101
+ return nested;
102
+ }
103
+
104
+ // Convert single field (used in array items & nested)
105
+ function convertFieldToJoi(field) {
106
+ const type = (field.type || '').toLowerCase();
107
+ let joi = null;
108
+
109
+ switch (type) {
110
+ case 'string': joi = Joi.string(); break;
111
+ case 'number': joi = Joi.number(); break;
112
+ case 'boolean': joi = Joi.boolean(); break;
113
+ case 'date': joi = Joi.date(); break;
114
+ case 'objectid': joi = Joi.string().hex().length(24); break;
115
+ case 'array':
116
+ const items = convertFieldToJoi(field.items || { type: 'string' });
117
+ joi = Joi.array().items(items);
118
+ break;
119
+ case 'object':
120
+ joi = Joi.object(createNestedJoi(field.properties || {}));
121
+ break;
122
+ default: joi = Joi.any();
123
+ }
124
+
125
+ if (field.required) joi = joi.required();
126
+ if (field.default !== undefined) joi = joi.default(field.default === 'now' ? new Date() : field.default);
127
+ if (field.enum) joi = joi.valid(...field.enum);
128
+
129
+ return joi;
130
+ }
131
+
132
+ // Helper for array items
133
+ function convertItem(item) {
134
+ return convertFieldToJoi(item);
135
+ }
136
+
137
+ module.exports = { createJoiSchemaFromJson };
@@ -0,0 +1,280 @@
1
+ // interactiveUnifiedBuilder.js
2
+ const {
3
+ input,
4
+ confirm,
5
+ select,
6
+ checkbox,
7
+ } = require('@inquirer/prompts');
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+
11
+ async function interactiveUnifiedBuilder(baseDir) {
12
+ console.log('\nUnified Model Builder (Mongoose / Sequelize)\n');
13
+
14
+ // Ensure base directory exists
15
+ if (!fs.existsSync(baseDir)) {
16
+ console.log(`Creating base directory: ${baseDir}`);
17
+ fs.mkdirSync(baseDir, { recursive: true });
18
+ }
19
+
20
+ // Define subdirectories
21
+ const mongoDir = path.join(baseDir, 'mongo');
22
+ const sequelizeDir = path.join(baseDir, 'sequelize');
23
+
24
+ // Create mongo and sequelize folders if they don't exist
25
+ [mongoDir, sequelizeDir].forEach(dir => {
26
+ if (!fs.existsSync(dir)) {
27
+ console.log(`Creating directory: ${dir}`);
28
+ fs.mkdirSync(dir, { recursive: true });
29
+ }
30
+ });
31
+
32
+ // 1. Choose DB type
33
+ const dbType = await select({
34
+ message: 'Database type:',
35
+ choices: [
36
+ { name: 'MongoDB (Mongoose)', value: 'mongodb' },
37
+ { name: 'SQL (Sequelize)', value: 'sequelize' },
38
+ ],
39
+ });
40
+
41
+ const modelName = await input({
42
+ message: 'Model name:',
43
+ validate: v => v.trim() ? true : 'Required',
44
+ });
45
+
46
+ const tableName = dbType === 'sequelize' ? await input({
47
+ message: 'Table name (or Enter for default):',
48
+ default: modelName.toLowerCase() + 's',
49
+ }) : undefined;
50
+
51
+ const schema = {};
52
+ const associations = [];
53
+ const options = { timestamps: true };
54
+
55
+ if (dbType === 'sequelize') {
56
+ options.paranoid = await confirm({ message: 'Enable soft deletes?', default: false });
57
+ }
58
+
59
+ // Load models from correct subdir
60
+ const modelsSubDir = dbType === 'mongodb' ? mongoDir : sequelizeDir;
61
+ const availableModels = loadModelsFromDir(modelsSubDir);
62
+
63
+ console.log('\nAdd fields:\n');
64
+
65
+ while (true) {
66
+ const add = await confirm({ message: 'Add field?', default: true });
67
+ if (!add) break;
68
+
69
+ const fieldName = await input({
70
+ message: 'Field name:',
71
+ validate: v => v && !schema[v] ? true : 'Invalid/duplicate',
72
+ });
73
+
74
+ const typeChoices = dbType === 'mongodb'
75
+ ? ['string', 'number', 'boolean', 'date', 'objectid', 'array', 'object', 'mixed']
76
+ : ['STRING', 'TEXT', 'INTEGER', 'FLOAT', 'BOOLEAN', 'DATE', 'JSON', 'ARRAY', 'ENUM'];
77
+
78
+ const fieldType = await select({
79
+ message: `Type for "${fieldName}":`,
80
+ choices: typeChoices.map(t => ({ name: t, value: t })),
81
+ });
82
+
83
+ const field = { type: fieldType };
84
+
85
+ const required = await confirm({ message: 'Required?', default: false });
86
+ if (required) {
87
+ field[dbType === 'mongodb' ? 'required' : 'allowNull'] = dbType === 'mongodb' ? true : false;
88
+ }
89
+
90
+ const unique = await confirm({ message: 'Unique?', default: false });
91
+ if (unique) field.unique = true;
92
+
93
+ const hasDefault = await confirm({ message: 'Set default?', default: false });
94
+ if (hasDefault) {
95
+ if (fieldType.toLowerCase().includes('boolean')) {
96
+ field.default = await confirm({ message: 'Default TRUE?', default: true });
97
+ } else if (fieldType.toLowerCase().includes('date')) {
98
+ field.default = await confirm({ message: 'Default NOW?', default: true }) ? 'now' : await input({ message: 'Default:' });
99
+ } else {
100
+ field.default = await input({ message: 'Default value:' });
101
+ }
102
+ }
103
+
104
+ if (fieldType === 'STRING' && dbType === 'sequelize') {
105
+ const len = await input({ message: 'Length (default 255):', default: '255' });
106
+ if (len !== '255') field.type = `STRING(${len})`;
107
+ }
108
+
109
+ if (fieldType.toUpperCase() === 'ENUM') {
110
+ const vals = await input({ message: 'Comma-separated values:' });
111
+ field.values = vals.split(',').map(v => v.trim()).filter(Boolean);
112
+ }
113
+
114
+ if (fieldType.toLowerCase() === 'array') {
115
+ const itemType = await select({
116
+ message: 'Array item type:',
117
+ choices: dbType === 'mongodb'
118
+ ? ['string', 'number', 'object', 'objectid']
119
+ : ['STRING', 'INTEGER', 'FLOAT'],
120
+ });
121
+ field.items = { type: itemType };
122
+ }
123
+
124
+ if (fieldType.toLowerCase() === 'object') {
125
+ field.properties = await buildNestedObject(dbType);
126
+ }
127
+
128
+ schema[fieldName] = field;
129
+ }
130
+
131
+ // Associations
132
+ if (availableModels.length > 0) {
133
+ while (true) {
134
+ const addAssoc = await confirm({ message: 'Add association?', default: false });
135
+ if (!addAssoc) break;
136
+
137
+ const assocType = await select({
138
+ message: 'Association type:',
139
+ choices: dbType === 'mongodb'
140
+ ? ['hasMany', 'belongsTo']
141
+ : ['hasMany', 'belongsTo', 'hasOne'],
142
+ });
143
+
144
+ const targetModel = await select({
145
+ message: 'Target model:',
146
+ choices: availableModels.map(m => ({ name: m, value: m })),
147
+ });
148
+
149
+ const fk = await input({
150
+ message: 'Foreign key:',
151
+ default: `${targetModel.toLowerCase()}Id`,
152
+ });
153
+
154
+ const as = await input({ message: 'Alias (as):', default: targetModel.toLowerCase() });
155
+
156
+ associations.push({ type: assocType, targetModel, foreignKey: fk, as });
157
+ }
158
+ }
159
+
160
+ // Build final config
161
+ const config = {
162
+ name: modelName,
163
+ type: dbType,
164
+ tableName: tableName?.trim(),
165
+ schema,
166
+ associations,
167
+ options,
168
+ };
169
+
170
+ // Generate migration only for Sequelize
171
+ let migration = null;
172
+ if (dbType === 'sequelize') {
173
+ migration = generateMigration(config);
174
+ }
175
+
176
+ return {
177
+ config,
178
+ migration, // { fileName, code } or null
179
+ };
180
+ }
181
+
182
+ // Load model names from directory
183
+ function loadModelsFromDir(dir) {
184
+ if (!fs.existsSync(dir)) {
185
+ return [];
186
+ }
187
+
188
+ return fs.readdirSync(dir)
189
+ .filter(file => file.endsWith('.json'))
190
+ .map(file => {
191
+ try {
192
+ const filePath = path.join(dir, file);
193
+ const content = JSON.parse(fs.readFileSync(filePath, 'utf8'));
194
+ return content.name || path.basename(file, '.json');
195
+ } catch (err) {
196
+ console.warn(`Failed to read ${file}:`, err.message);
197
+ return null;
198
+ }
199
+ })
200
+ .filter(Boolean);
201
+ }
202
+
203
+ // Nested object builder
204
+ async function buildNestedObject(dbType) {
205
+ const props = {};
206
+ while (true) {
207
+ const add = await confirm({ message: 'Add nested field?', default: true });
208
+ if (!add) break;
209
+ const name = await input({ message: 'Name:' });
210
+ const type = await select({
211
+ message: 'Type:',
212
+ choices: dbType === 'mongodb'
213
+ ? ['string', 'number', 'boolean', 'date', 'objectid']
214
+ : ['STRING', 'INTEGER', 'BOOLEAN', 'DATE'],
215
+ });
216
+ props[name] = { type };
217
+ }
218
+ return props;
219
+ }
220
+
221
+ // ── Generate Migration String (Sequelize only)
222
+ function generateMigration(config) {
223
+ const timestamp = new Date().toISOString().replace(/[-:T.]/g, '').slice(0, 14);
224
+ const table = config.tableName || `${config.name.toLowerCase()}s`;
225
+ const fileName = `${timestamp}-create-${table}.js`;
226
+
227
+ let code = `'use strict';\n\nmodule.exports = {\n`;
228
+ code += ` up: async (queryInterface, Sequelize) => {\n`;
229
+ code += ` await queryInterface.createTable('${table}', {\n`;
230
+ code += ` id: {\n`;
231
+ code += ` allowNull: false,\n`;
232
+ code += ` autoIncrement: true,\n`;
233
+ code += ` primaryKey: true,\n`;
234
+ code += ` type: Sequelize.INTEGER\n`;
235
+ code += ` },\n`;
236
+
237
+ for (const [field, def] of Object.entries(config.schema)) {
238
+ code += ` ${field}: {\n`;
239
+ code += ` type: ${formatType(def.type, def.values)},\n`;
240
+ if (def.allowNull === false) code += ` allowNull: false,\n`;
241
+ if (def.unique) code += ` unique: true,\n`;
242
+ if (def.default !== undefined) {
243
+ code += ` defaultValue: ${formatDefault(def.default)},\n`;
244
+ }
245
+ code = code.replace(/,\n$/, '\n') + ` },\n`;
246
+ }
247
+
248
+ if (config.options.timestamps) {
249
+ code += ` createdAt: { allowNull: false, type: Sequelize.DATE, defaultValue: Sequelize.fn('NOW') },\n`;
250
+ code += ` updatedAt: { allowNull: false, type: Sequelize.DATE, defaultValue: Sequelize.fn('NOW') },\n`;
251
+ }
252
+ if (config.options.paranoid) {
253
+ code += ` deletedAt: { type: Sequelize.DATE },\n`;
254
+ }
255
+
256
+ code += ` });\n`;
257
+ code += ` },\n\n`;
258
+ code += ` down: async (queryInterface) => {\n`;
259
+ code += ` await queryInterface.dropTable('${table}');\n`;
260
+ code += ` }\n};\n`;
261
+
262
+ return { fileName, code };
263
+ }
264
+
265
+ function formatType(type, values) {
266
+ if (type.includes('[]')) return `Sequelize.ARRAY(Sequelize.${type.replace('[]', '')})`;
267
+ if (type.includes('(')) return `Sequelize.${type}`;
268
+ if (type === 'ENUM') return `Sequelize.ENUM(${values.map(v => `'${v}'`).join(', ')})`;
269
+ return `Sequelize.${type}`;
270
+ }
271
+
272
+ function formatDefault(val) {
273
+ if (val === 'now') return `Sequelize.fn('NOW')`;
274
+ if (val === true) return 'true';
275
+ if (val === false) return 'false';
276
+ if (!isNaN(val)) return val;
277
+ return `'${val}'`;
278
+ }
279
+
280
+ module.exports = { interactiveUnifiedBuilder };
@@ -0,0 +1,45 @@
1
+ // generateAllFiles.js
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const { createModelFromJson } = require('./createModelFromJson');
5
+ const { createJoiSchemaFromJson } = require('./createJoiSchemaFromJson');
6
+
7
+ function generateAllFiles(config) {
8
+ const baseDir = 'generated';
9
+ const modelDir = path.join(baseDir, config.type);
10
+ const joiDir = path.join(baseDir, 'joi');
11
+ const migDir = path.join(baseDir, 'migrations');
12
+
13
+ [baseDir, modelDir, joiDir, migDir].forEach(dir => {
14
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
15
+ });
16
+
17
+ // 1. Model (Mongoose or Sequelize)
18
+ const result = createModelFromJson(config);
19
+ const modelPath = path.join(modelDir, `${config.name}.js`);
20
+ fs.writeFileSync(modelPath, generateModelCode(result));
21
+
22
+ // 2. Joi Schema
23
+ const joiSchema = createJoiSchemaFromJson({ schema: config.joi });
24
+ const joiCode = `const Joi = require('joi');\nmodule.exports = ${joiSchema.toString().replace('Joi.object()', 'Joi.object')};`;
25
+ fs.writeFileSync(path.join(joiDir, `${config.name}.js`), joiCode);
26
+
27
+ // 3. Migration (Sequelize only)
28
+ if (config.type === 'sequelize' && result.migration) {
29
+ fs.writeFileSync(path.join(migDir, result.migration.fileName), result.migration.code);
30
+ }
31
+
32
+ console.log(`Model: ${modelPath}`);
33
+ console.log(`Joi: ${joiDir}/${config.name}.js`);
34
+ if (result.migration) console.log(`Migration: ${migDir}/${result.migration.fileName}`);
35
+ }
36
+
37
+ function generateModelCode(result) {
38
+ if (result.type === 'mongoose') {
39
+ return `const mongoose = require('mongoose');\nconst schema = new mongoose.Schema(${JSON.stringify(result.schema.tree, null, 2)}, { timestamps: true });\nmodule.exports = mongoose.model('${result.modelName}', schema);`;
40
+ } else {
41
+ return `const { DataTypes } = require('sequelize');\nmodule.exports = (sequelize) => {\n return sequelize.define('${result.modelName}', ${JSON.stringify(result.Model.rawAttributes, null, 2)}, { tableName: '${result.Model.getTableName()}', timestamps: true });\n};`;
42
+ }
43
+ }
44
+
45
+ module.exports = { generateAllFiles };
@@ -0,0 +1,95 @@
1
+ // generateJoiFile.js
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const Joi = require('joi');
5
+
6
+ function generateJoiFile(config, outputDir = './joi') {
7
+ const { name, schema } = config;
8
+
9
+ // Build Joi schema
10
+ const joiSchema = buildJoiSchema(schema);
11
+
12
+ // Generate code
13
+ let code = `const Joi = require('joi');\n\n`;
14
+ code += `const ${name} = ${joiSchema.toString().replace(/Joi\.object\(\)/g, 'Joi.object')}\n\n`;
15
+ code += `module.exports = ${name};\n`;
16
+
17
+ // Ensure directory
18
+ if (!fs.existsSync(outputDir)) {
19
+ fs.mkdirSync(outputDir, { recursive: true });
20
+ }
21
+
22
+ // Write file
23
+ const filePath = path.join(outputDir, `${name}.js`);
24
+ fs.writeFileSync(filePath, code);
25
+
26
+ console.log(`Joi schema saved: ${filePath}`);
27
+ }
28
+
29
+ function buildJoiSchema(def) {
30
+ const schema = {};
31
+
32
+ for (const [key, field] of Object.entries(def)) {
33
+ let joiField;
34
+
35
+ switch (field.type) {
36
+ case 'string':
37
+ joiField = Joi.string();
38
+ if (field.email) joiField = joiField.email();
39
+ if (field.trim) joiField = joiField.trim();
40
+ if (field.lowercase) joiField = joiField.lowercase();
41
+ if (field.uppercase) joiField = joiField.uppercase();
42
+ if (field.alphanum) joiField = joiField.alphanum();
43
+ if (field.uuid) joiField = joiField.uuid();
44
+ if (field.min) joiField = joiField.min(field.min);
45
+ if (field.max) joiField = joiField.max(field.max);
46
+ if (field.regex) joiField = joiField.pattern(new RegExp(field.regex));
47
+ if (field.enum) joiField = joiField.valid(...field.enum);
48
+ break;
49
+
50
+ case 'number':
51
+ joiField = Joi.number();
52
+ if (field.integer) joiField = joiField.integer();
53
+ if (field.positive) joiField = joiField.positive();
54
+ if (field.negative) joiField = joiField.negative();
55
+ if (field.min) joiField = joiField.min(field.min);
56
+ if (field.max) joiField = joiField.max(field.max);
57
+ break;
58
+
59
+ case 'boolean':
60
+ joiField = Joi.boolean();
61
+ break;
62
+
63
+ case 'date':
64
+ joiField = Joi.date();
65
+ break;
66
+
67
+ case 'array':
68
+ const items = buildJoiSchema({ item: field.items }).item;
69
+ joiField = Joi.array().items(items);
70
+ if (field.min) joiField = joiField.min(field.min);
71
+ if (field.max) joiField = joiField.max(field.max);
72
+ if (field.unique) joiField = joiField.unique();
73
+ break;
74
+
75
+ case 'object':
76
+ joiField = Joi.object(buildJoiSchema(field.properties));
77
+ break;
78
+
79
+ default:
80
+ joiField = Joi.any();
81
+ }
82
+
83
+ if (field.required) joiField = joiField.required();
84
+ if (field.default !== undefined) {
85
+ const def = field.default === 'Date.now()' ? Date.now : field.default;
86
+ joiField = joiField.default(def);
87
+ }
88
+
89
+ schema[key] = joiField;
90
+ }
91
+
92
+ return Joi.object(schema);
93
+ }
94
+
95
+ module.exports = { generateJoiFile };