@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,94 @@
1
+ /**
2
+ * Example route handler
3
+ * This file demonstrates the route structure expected by Falcon.js
4
+ */
5
+
6
+ module.exports.route = async function (server, context) {
7
+
8
+ // Example GET route
9
+ server.route({
10
+ method: 'GET',
11
+ path: '/api/example',
12
+ options: {
13
+ tags: ['api', 'example'],
14
+ description: 'Example GET endpoint',
15
+ notes: 'Returns example data'
16
+ },
17
+ handler: async (request, h) => {
18
+ return {
19
+ success: true,
20
+ message: 'Hello from Falcon.js!',
21
+ timestamp: new Date().toISOString()
22
+ };
23
+ }
24
+ });
25
+
26
+ // Example POST route with validation
27
+ server.route({
28
+ method: 'POST',
29
+ path: '/api/example',
30
+ options: {
31
+ tags: ['api', 'example'],
32
+ description: 'Example POST endpoint',
33
+ validate: {
34
+ payload: context.validators?.ExamplePayload || undefined
35
+ }
36
+ },
37
+ handler: async (request, h) => {
38
+ const { name, email } = request.payload;
39
+
40
+ // Example: Save to database if models are available
41
+ if (context.models.example) {
42
+ const result = await context.models.example.create({
43
+ name,
44
+ email,
45
+ createdAt: new Date()
46
+ });
47
+
48
+ return {
49
+ success: true,
50
+ data: result,
51
+ message: 'Data saved successfully'
52
+ };
53
+ }
54
+
55
+ return {
56
+ success: true,
57
+ message: 'Data received',
58
+ data: { name, email }
59
+ };
60
+ }
61
+ });
62
+
63
+ // Example route that sends job to worker
64
+ server.route({
65
+ method: 'POST',
66
+ path: '/api/example/job',
67
+ options: {
68
+ tags: ['api', 'jobs'],
69
+ description: 'Queue a job for processing'
70
+ },
71
+ handler: async (request, h) => {
72
+ const jobData = request.payload;
73
+
74
+ // Send job to worker via MQTT (if available)
75
+ if (context.mqtt_client) {
76
+ context.mqtt_client.publish('worker_example_job', JSON.stringify({
77
+ jobId: Date.now().toString(),
78
+ data: jobData,
79
+ timestamp: new Date().toISOString()
80
+ }));
81
+
82
+ return {
83
+ success: true,
84
+ message: 'Job queued for processing'
85
+ };
86
+ }
87
+
88
+ return {
89
+ success: false,
90
+ message: 'Job queue not available'
91
+ };
92
+ }
93
+ });
94
+ };
@@ -0,0 +1,63 @@
1
+ const { FalconBaseService } = require('falconjs');
2
+
3
+ /**
4
+ * Example service - rename and customize as needed
5
+ */
6
+ class ExampleService extends FalconBaseService {
7
+
8
+ constructor() {
9
+ super('example'); // Service ID for MQTT topics
10
+ }
11
+
12
+ /**
13
+ * Handle incoming MQTT messages
14
+ */
15
+ async onMessage(topic, msg) {
16
+ try {
17
+ const message = JSON.parse(msg.toString());
18
+
19
+ switch (topic) {
20
+ case 'service_example':
21
+ await this.handleRequest(message);
22
+ break;
23
+ default:
24
+ console.log(`Unhandled topic: ${topic}`);
25
+ }
26
+ } catch (error) {
27
+ console.error('Error processing message:', error);
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Handle service requests
33
+ */
34
+ async handleRequest(request) {
35
+ console.log('Processing request:', request);
36
+
37
+ // Your service logic here
38
+
39
+ // Send response back via MQTT
40
+ this.publish({
41
+ status: 'completed',
42
+ result: 'success',
43
+ timestamp: new Date().toISOString()
44
+ }, 'service_example_response');
45
+ }
46
+
47
+ /**
48
+ * Service main logic
49
+ */
50
+ async run() {
51
+ console.log('Example service is running...');
52
+
53
+ // Your initialization logic here
54
+ }
55
+ }
56
+
57
+ // Start the service if this file is run directly
58
+ if (require.main === module) {
59
+ const service = new ExampleService();
60
+ service.init().catch(console.error);
61
+ }
62
+
63
+ module.exports = ExampleService;
@@ -0,0 +1,15 @@
1
+ const Joi = require('joi');
2
+
3
+ /**
4
+ * Example Joi validator for API endpoints
5
+ * This validator can be automatically matched with routes by name
6
+ */
7
+ module.exports = Joi.object({
8
+ name: Joi.string().min(2).max(50).required().description('User name'),
9
+ email: Joi.string().email().required().description('User email address'),
10
+ age: Joi.number().integer().min(18).max(120).optional().description('User age'),
11
+ preferences: Joi.object({
12
+ newsletter: Joi.boolean().default(false),
13
+ notifications: Joi.boolean().default(true)
14
+ }).optional().description('User preferences')
15
+ }).description('Example payload validation schema');
@@ -0,0 +1,83 @@
1
+ const { FalconBaseWorker } = require('falconjs');
2
+
3
+ /**
4
+ * Example worker - rename and customize as needed
5
+ */
6
+ class ExampleWorker extends FalconBaseWorker {
7
+
8
+ constructor() {
9
+ super('example'); // Worker ID for MQTT topics
10
+ }
11
+
12
+ /**
13
+ * Handle incoming MQTT messages
14
+ */
15
+ async onMessage(topic, msg) {
16
+ try {
17
+ const message = JSON.parse(msg.toString());
18
+
19
+ switch (topic) {
20
+ case 'worker_example_job':
21
+ await this.processJob(message);
22
+ break;
23
+ default:
24
+ console.log(`Unhandled topic: ${topic}`);
25
+ }
26
+ } catch (error) {
27
+ console.error('Error processing message:', error);
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Process a job
33
+ */
34
+ async processJob(job) {
35
+ const { jobId, data } = job;
36
+
37
+ console.log(`Processing job ${jobId}:`, data);
38
+
39
+ // Simulate work
40
+ await new Promise(resolve => setTimeout(resolve, 2000));
41
+
42
+ // Update job status in database if available
43
+ if (this.models.job) {
44
+ await this.models.job.findByIdAndUpdate(jobId, {
45
+ status: 'completed',
46
+ completedAt: new Date(),
47
+ result: { processed: true }
48
+ });
49
+ }
50
+
51
+ // Send completion notification
52
+ await this.publish('worker_example_complete', {
53
+ jobId: jobId,
54
+ status: 'completed',
55
+ timestamp: new Date().toISOString()
56
+ });
57
+ }
58
+
59
+ /**
60
+ * Worker main logic
61
+ */
62
+ async run() {
63
+ const args = this.parseArgs();
64
+ console.log('Example worker started with args:', args);
65
+
66
+ // If specific job passed as argument, process it immediately
67
+ if (args.jobId) {
68
+ await this.processJob(args);
69
+ process.exit(0); // Exit after processing single job
70
+ }
71
+
72
+ // Otherwise, keep running and wait for MQTT jobs
73
+ console.log('Waiting for jobs...');
74
+ }
75
+ }
76
+
77
+ // Start the worker if this file is run directly
78
+ if (require.main === module) {
79
+ const worker = new ExampleWorker();
80
+ worker.init().catch(console.error);
81
+ }
82
+
83
+ module.exports = ExampleWorker;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Falcon.js Application Entry Point
3
+ */
4
+ require('dotenv').config();
5
+ const { FalconServer } = require("falconjs");
6
+
7
+ async function main() {
8
+ try {
9
+ /**
10
+ * Create the server instance with path to settings
11
+ */
12
+ const server = new FalconServer(__dirname);
13
+
14
+ /**
15
+ * Optional: Set custom authentication strategy
16
+ */
17
+ // server.setAuthStrategy(async (context) => {
18
+ // // Custom auth setup here
19
+ // });
20
+
21
+ /**
22
+ * Initialize the server and related modules
23
+ */
24
+ await server.init();
25
+
26
+ /**
27
+ * Start the HTTP server
28
+ */
29
+ await server.runServer();
30
+
31
+ console.log(`🚀 Falcon server running on http://${server.SETTINGS.http.host}:${server.SETTINGS.http.port}`);
32
+ console.log(`📚 API Documentation: http://${server.SETTINGS.http.host}:${server.SETTINGS.http.port}/documentation`);
33
+ console.log(`❤️ Health Check: http://${server.SETTINGS.http.host}:${server.SETTINGS.http.port}/health`);
34
+
35
+ } catch (error) {
36
+ console.error('❌ Failed to start server:', error);
37
+ process.exit(1);
38
+ }
39
+ }
40
+
41
+ main();
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Post-initialization script
3
+ * This runs after all Falcon components are initialized
4
+ */
5
+
6
+ module.exports.run = async function(context) {
7
+ const { logger, mqtt_client, models, server } = context;
8
+
9
+ logger.info("Running post-initialization script...");
10
+
11
+ // Example: Set up MQTT subscriptions for the main server
12
+ if (mqtt_client) {
13
+ // Subscribe to service responses
14
+ mqtt_client.subscribe("service_*_response", (err) => {
15
+ if (err) {
16
+ logger.error("Failed to subscribe to service responses:", err);
17
+ } else {
18
+ logger.info("Subscribed to service response topics");
19
+ }
20
+ });
21
+
22
+ // Subscribe to worker completions
23
+ mqtt_client.subscribe("worker_*_complete", (err) => {
24
+ if (err) {
25
+ logger.error("Failed to subscribe to worker completions:", err);
26
+ } else {
27
+ logger.info("Subscribed to worker completion topics");
28
+ }
29
+ });
30
+
31
+ // Handle incoming messages
32
+ mqtt_client.on("message", async function (topic, msg) {
33
+ try {
34
+ const message = JSON.parse(msg.toString());
35
+ logger.info(`Received message on ${topic}:`, message);
36
+
37
+ // Handle different message types
38
+ if (topic.includes('_response')) {
39
+ // Handle service responses
40
+ logger.info("Service response received:", message);
41
+ } else if (topic.includes('_complete')) {
42
+ // Handle worker completions
43
+ logger.info("Worker job completed:", message);
44
+ }
45
+ } catch (error) {
46
+ logger.error("Error processing MQTT message:", error);
47
+ }
48
+ });
49
+ }
50
+
51
+ // Example: Add custom routes dynamically
52
+ if (server) {
53
+ server.route({
54
+ method: 'GET',
55
+ path: '/api/status',
56
+ options: {
57
+ tags: ['system'],
58
+ description: 'System status endpoint'
59
+ },
60
+ handler: async (request, h) => {
61
+ return {
62
+ status: 'running',
63
+ timestamp: new Date().toISOString(),
64
+ uptime: process.uptime(),
65
+ memory: process.memoryUsage(),
66
+ version: require('../package.json').version
67
+ };
68
+ }
69
+ });
70
+ }
71
+
72
+ // Example: Initialize background tasks
73
+ setInterval(() => {
74
+ logger.debug("Heartbeat - System is running");
75
+ }, 60000); // Every minute
76
+
77
+ logger.info("Post-initialization completed successfully");
78
+ };
@@ -0,0 +1,192 @@
1
+ module.exports.settings = {
2
+ name: "falcon-app",
3
+ http: {
4
+ host: process.env.HTTP_HOST || "localhost",
5
+ port: process.env.HTTP_PORT || 3000
6
+ },
7
+ database: {
8
+ mongodb: {
9
+ database: process.env.MONGODB_URL || "mongodb://localhost:27017/falcon_db"
10
+ }
11
+ },
12
+ mqtt: {
13
+ internal: true,
14
+ external: false
15
+ },
16
+ log: {
17
+ appenders: {
18
+ file: {
19
+ type: "file",
20
+ filename: process.env.LOG_FILE_NAME || "logs/app.log",
21
+ maxLogSize: process.env.LOG_MAX_SIZE || "10M",
22
+ backups: 3
23
+ },
24
+ console: {
25
+ type: "console"
26
+ }
27
+ },
28
+ categories: {
29
+ default: {
30
+ appenders: ["file", "console"],
31
+ level: process.env.LOG_LEVEL || "info"
32
+ }
33
+ }
34
+ },
35
+ swagger: {
36
+ enabled: true,
37
+ path: "/documentation"
38
+ },
39
+ auth: {
40
+ // Custom Authentication (API Keys, etc.)
41
+ custom: {
42
+ name: 'api-key',
43
+ validate: async (token, request, context) => {
44
+ // Custom validation logic with access to context (logger, redis, models)
45
+ context.logger.info('Validating API key');
46
+
47
+ // Check cache first
48
+ const cacheKey = `api-key:${token}`;
49
+ if (context.redis) {
50
+ const cached = await context.redis.get(cacheKey);
51
+ if (cached) return JSON.parse(cached);
52
+ }
53
+
54
+ // Validate against database or external service
55
+ if (context.models.apiKey) {
56
+ const apiKey = await context.models.apiKey.findOne({ key: token, active: true });
57
+ if (apiKey) {
58
+ const user = { id: apiKey.userId, roles: apiKey.roles };
59
+ // Cache for 5 minutes
60
+ if (context.redis) {
61
+ await context.redis.setEx(cacheKey, 300, JSON.stringify(user));
62
+ }
63
+ return user;
64
+ }
65
+ }
66
+
67
+ return null;
68
+ },
69
+ extractToken: (request) => {
70
+ // Custom token extraction
71
+ return request.headers['x-api-key'] || request.query.apiKey;
72
+ }
73
+ },
74
+
75
+ // JWT Authentication
76
+ jwt: {
77
+ secret: process.env.JWT_SECRET || "your-secret-key-here",
78
+ algorithms: ['HS256'],
79
+ cache: {
80
+ enabled: true,
81
+ ttl: 300 // 5 minutes
82
+ },
83
+ validate: async (decoded, request, context) => {
84
+ context.logger.info(`JWT validation for user: ${decoded.sub}`);
85
+
86
+ // Check if user exists and is active
87
+ if (context.models.user) {
88
+ const user = await context.models.user.findById(decoded.sub).select('-password');
89
+ if (user && user.active) {
90
+ return {
91
+ id: user._id,
92
+ email: user.email,
93
+ roles: user.roles || ['user']
94
+ };
95
+ }
96
+ }
97
+
98
+ // Fallback for development
99
+ return {
100
+ id: decoded.sub,
101
+ email: decoded.email,
102
+ roles: decoded.roles || ['user']
103
+ };
104
+ }
105
+ },
106
+
107
+ // Cookie Authentication
108
+ cookie: {
109
+ password: process.env.COOKIE_SECRET || "your-32-char-cookie-password-here",
110
+ name: "falcon-session",
111
+ ttl: 24 * 60 * 60 * 1000, // 24 hours
112
+ isSecure: process.env.NODE_ENV === "production",
113
+ isHttpOnly: true,
114
+ validate: async (request, session, context) => {
115
+ context.logger.info(`Session validation for user: ${session.userId}`);
116
+
117
+ if (context.models.user) {
118
+ const user = await context.models.user.findById(session.userId).select('-password');
119
+ if (user && user.active) {
120
+ return {
121
+ id: user._id,
122
+ email: user.email,
123
+ roles: user.roles || ['user']
124
+ };
125
+ }
126
+ }
127
+
128
+ return session.user;
129
+ }
130
+ },
131
+
132
+ // JWKS Authentication (External Identity Providers)
133
+ jwks: {
134
+ jwksUri: process.env.JWKS_URI || "https://your-auth0-domain/.well-known/jwks.json",
135
+ issuer: process.env.JWT_ISSUER || "https://your-auth0-domain/",
136
+ audience: process.env.JWT_AUDIENCE || "your-api-identifier",
137
+ algorithms: ['RS256'],
138
+ cache: {
139
+ enabled: true,
140
+ ttl: 3600 // 1 hour
141
+ },
142
+ validate: async (decoded, request, context) => {
143
+ context.logger.info(`JWKS validation for user: ${decoded.sub}`);
144
+
145
+ // Map external identity to internal user
146
+ if (context.models.user) {
147
+ let user = await context.models.user.findOne({ externalId: decoded.sub });
148
+
149
+ if (!user && decoded.email) {
150
+ // Create user if doesn't exist
151
+ user = await context.models.user.create({
152
+ externalId: decoded.sub,
153
+ email: decoded.email,
154
+ name: decoded.name,
155
+ roles: ['user'],
156
+ active: true
157
+ });
158
+ }
159
+
160
+ if (user) {
161
+ return {
162
+ id: user._id,
163
+ email: user.email,
164
+ roles: user.roles || ['user']
165
+ };
166
+ }
167
+ }
168
+
169
+ // Fallback
170
+ return {
171
+ id: decoded.sub,
172
+ email: decoded.email,
173
+ roles: decoded['https://yourapp.com/roles'] || ['user']
174
+ };
175
+ }
176
+ },
177
+
178
+ // Socket.IO Configuration
179
+ socketio: {
180
+ enabled: true,
181
+ timeout: 5000
182
+ }
183
+ },
184
+ services: [],
185
+ workers: [],
186
+ models: [],
187
+ routes: [],
188
+ crud: {
189
+ exclude: []
190
+ },
191
+ postInit: "post"
192
+ };
@@ -0,0 +1,15 @@
1
+ module.exports.settings = {
2
+ crud: {
3
+ exclude: [
4
+ ]
5
+ },
6
+ auth:{},
7
+ services:[],
8
+ workers:[],
9
+ preInit:"",
10
+ postInit:"",
11
+ routes: [
12
+ ],
13
+ models: [
14
+ ]
15
+ }
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "falcon-app",
3
+ "version": "1.0.0",
4
+ "description": "Falcon.js application",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "start": "node index.js",
8
+ "dev": "nodemon index.js",
9
+ "test": "echo \"Error: no test specified\" && exit 1"
10
+ },
11
+ "keywords": [
12
+ "falcon",
13
+ "nodejs",
14
+ "api"
15
+ ],
16
+ "author": "",
17
+ "license": "ISC",
18
+ "type": "commonjs",
19
+ "dependencies": {
20
+ "falconjs": "@triophore/falconjs",
21
+ "@hapi/boom": "^10.0.1",
22
+ "@hapi/inert": "^7.1.0",
23
+ "@hapi/vision": "^7.0.3",
24
+ "joi": "^17.13.3",
25
+ "jose": "^5.2.0",
26
+ "mongoose": "^8.3.1",
27
+ "redis": "^4.7.0",
28
+ "mqtt": "^5.13.3",
29
+ "log4js": "^6.9.1",
30
+ "rate-limiter-flexible": "^2.4.2",
31
+ "hapi-alive": "^2.0.4",
32
+ "hapi-swagger": "^17.2.1",
33
+ "dotenv": "^16.0.0"
34
+ },
35
+ "devDependencies": {
36
+ "nodemon": "^3.1.10"
37
+ }
38
+ }