langaro-api 1.2.2 → 1.2.4

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.
@@ -117,8 +117,15 @@ function runWatch() {
117
117
  async function runInit() {
118
118
  const ok = await confirm('\n\x1b[33mThis will create project files in the current directory.\x1b[0m Are you sure?\n');
119
119
  if (!ok) { console.log('\nAborted.\n'); process.exit(0); }
120
- const init = require('../lib/cli/init');
121
- await init();
120
+ const { run } = require('../lib/cli/init');
121
+ await run();
122
+ }
123
+
124
+ function runUpdateDocs() {
125
+ const { updateDocumentation } = require('../lib/cli/init');
126
+ const root = process.cwd();
127
+ updateDocumentation(root);
128
+ console.log('\n \x1b[32m✔\x1b[0m documentation/ updated successfully.\n');
122
129
  }
123
130
 
124
131
  async function runMigrate() {
@@ -143,6 +150,7 @@ async function main() {
143
150
  if (command === 'init') return runInit();
144
151
  if (command === 'migrate') return runMigrate();
145
152
  if (command === 'new') return runNew();
153
+ if (command === 'update-docs') { runUpdateDocs(); return process.exit(0); }
146
154
  if (command === '--watch') return runWatch();
147
155
  if (command === 'generate' || command === 'types') {
148
156
  runGenerateTypes();
@@ -163,6 +171,7 @@ async function main() {
163
171
  { label: 'New resource', value: 'new', desc: 'Scaffold a new controller, service, model, or route' },
164
172
  { label: 'Migrate project', value: 'migrate', desc: 'Replace index.js files with langaro-api loaders' },
165
173
  { label: 'Init new project', value: 'init', desc: 'Scaffold a full project from scratch' },
174
+ { label: 'Update docs', value: 'update-docs', desc: 'Overwrite documentation/ with latest version' },
166
175
  ]);
167
176
 
168
177
  console.log('');
@@ -171,6 +180,7 @@ async function main() {
171
180
  if (choice === 'new') return runNew();
172
181
  if (choice === 'migrate') return runMigrate();
173
182
  if (choice === 'init') return runInit();
183
+ if (choice === 'update-docs') { runUpdateDocs(); process.exit(0); }
174
184
 
175
185
  return undefined;
176
186
  }
@@ -0,0 +1,240 @@
1
+ # Architecture Overview
2
+
3
+ ## Three-Layer Stack
4
+
5
+ This project is built on a three-layer architecture:
6
+
7
+ ```
8
+ ┌─────────────────────────────────────────────────┐
9
+ │ PROJECT CODE (this repo) │
10
+ │ Controllers, Services, Models, Routes, Jobs, │
11
+ │ Tasks, Middlewares, Integrations, Utils │
12
+ ├─────────────────────────────────────────────────┤
13
+ │ LANGARO-API (framework) │
14
+ │ Loaders, CLI, Type Generation, Scaffolding │
15
+ ├─────────────────────────────────────────────────┤
16
+ │ KNEX-EXTENDED-CRUD (library) │
17
+ │ CRUD class, Validation, Caching, Relations │
18
+ ├─────────────────────────────────────────────────┤
19
+ │ KNEX + MySQL2 + Redis + BullMQ │
20
+ └─────────────────────────────────────────────────┘
21
+ ```
22
+
23
+ **knex-extended-crud** provides the `CRUD` class — a database abstraction with built-in validation, caching, pagination, relationships, and batch operations. Every database table in the project gets a CRUD instance.
24
+
25
+ **langaro-api** provides loaders that auto-discover and instantiate all components from their directories, a CLI for scaffolding projects and resources, and a type generation system for IDE IntelliSense.
26
+
27
+ **Project code** is where business logic lives. It follows the conventions established by the framework: factory functions, naming conventions, directory structure.
28
+
29
+ ---
30
+
31
+ ## Boot Sequence
32
+
33
+ When `npm run dev` executes, the following happens in order:
34
+
35
+ ```
36
+ 1. server.js
37
+ ├── Load .env (dotenv)
38
+ ├── Create Knex instance (database/connection.js)
39
+ ├── Call getAppInstance(knex) → new App(knex)
40
+ └── Listen on PORT
41
+
42
+ 2. App.constructor → App.init()
43
+ ├── Set query parser to 'extended'
44
+ ├── Apply readiness middleware (blocks requests until boot completes)
45
+ ├── Apply middlewares (CORS, morgan, bodyParser, multer)
46
+ └── Call this.routes()
47
+
48
+ 3. App.routes()
49
+ ├── Create Socket.io server
50
+ │ └── On connection: join rooms by companyId and userId
51
+
52
+ ├── loadModels(knex, dir, { redis })
53
+ │ ├── Query information_schema.tables for all table names
54
+ │ ├── For each table: load optional {table}.model.js config
55
+ │ ├── For each table: create class extends CRUD { constructor() { super({knex, table, ...config}) } }
56
+ │ └── Return { models, tableNames }
57
+
58
+ ├── loadServices(models, io, dir)
59
+ │ ├── For each model: load optional {table}.services.js factory
60
+ │ ├── If factory exists: call factory(model, models, io) → custom class extends model
61
+ │ ├── If no factory: create class extends model (bare CRUD wrapper)
62
+ │ ├── Also scan for custom services (no DB table)
63
+ │ ├── Instantiate all services
64
+ │ └── Return ServicesMap { PascalCaseServices: instance }
65
+
66
+ ├── initializeQueues(services, io, bullBoardSetQueues)
67
+ │ ├── Create Redis connection
68
+ │ ├── loadJobs(services, dir) → scan src/jobs/ recursively
69
+ │ ├── Register all job configs in jobConfigs Map
70
+ │ ├── Scan Redis for existing queues with pending jobs → start workers
71
+ │ ├── Start inactivity cleanup interval (every 1 min)
72
+ │ └── Return Queue { add(name, data, options) }
73
+
74
+ ├── loadControllers(services, Queue, io, dir)
75
+ │ ├── Scan for {table}.controller.js files (flat or nested in subdirs)
76
+ │ ├── For each: create ServicesClass with { this.service, this.services, this.Queue, this.io }
77
+ │ ├── Call factory(ServicesClass) → custom class extends ServicesClass
78
+ │ ├── Instantiate controller
79
+ │ └── Return ControllersMap { PascalCaseController: instance }
80
+
81
+ ├── attachRouters(express, controllers, services, dir)
82
+ │ ├── Scan src/routes/ for {table}.router.js files
83
+ │ ├── Convert snake_case filename to kebab-case URL path
84
+ │ ├── Call factory(controllers, services) → Express Router
85
+ │ └── Mount: express.use('/kebab-path', router)
86
+
87
+ └── loadTasks(services, Queue, dir) [skipped in test env]
88
+ ├── Scan src/tasks/ recursively
89
+ ├── Call factory(services, Queue) → { task, cronTime, isActive, environments }
90
+ ├── If isActive && NODE_ENV in environments
91
+ └── Start CronJob (timezone: America/Sao_Paulo)
92
+
93
+ 4. App.handleErrors()
94
+ ├── Sentry error handler
95
+ ├── Axios network errors → 422
96
+ ├── Safe errors (err.safe = true) → err.code + err.data
97
+ └── Unsafe errors → 500 + Sentry capture
98
+
99
+ 5. App.isReady = true → release queued requests
100
+ ```
101
+
102
+ ---
103
+
104
+ ## Inheritance Chain
105
+
106
+ The core inheritance pattern flows from database to HTTP:
107
+
108
+ ```
109
+ CRUD (knex-extended-crud)
110
+ │ Provides: get, getWhere, create, batchCreate, updateWhere,
111
+ │ batchUpdate, deleteWhere, search, count, createTransaction,
112
+ │ appendItems, validation, caching
113
+
114
+
115
+ Model (auto-generated per table by loadModels)
116
+ │ class extends CRUD { constructor() { super({knex, table, ...modelConfig}) } }
117
+ │ Configured by: src/database/models/{table}.model.js
118
+
119
+
120
+ Service (factory from loadServices)
121
+ │ module.exports = (model, models, io) => class extends model { ... }
122
+ │ File: src/database/services/{table}.services.js
123
+ │ Has access to: all CRUD methods + custom business logic
124
+
125
+
126
+ ServicesClass (created by loadControllers)
127
+ │ class { constructor() { this.service = serviceInstance;
128
+ │ this.services = allServicesMap; this.Queue = queueAPI; this.io = socketIo; } }
129
+
130
+
131
+ Controller (factory from loadControllers)
132
+ │ module.exports = (ServicesClass) => class extends ServicesClass { ... }
133
+ │ File: src/controllers/{table}/{table}.controller.js
134
+ │ Has access to: this.service, this.services, this.Queue, this.io
135
+ ```
136
+
137
+ **Key insight**: Controllers do NOT inherit CRUD methods directly. They access database operations through `this.service.methodName()` or `this.services.OtherServices.methodName()`.
138
+
139
+ ---
140
+
141
+ ## Dependency Injection Pattern
142
+
143
+ The framework uses **factory functions** instead of traditional DI containers:
144
+
145
+ ```javascript
146
+ // Service factory receives dependencies as arguments
147
+ module.exports = (model, models, io) => class extends model {
148
+ // model = CRUD class for this table
149
+ // models = map of all model constructors
150
+ // io = Socket.io instance
151
+ };
152
+
153
+ // Controller factory receives dependencies via ServicesClass
154
+ module.exports = (ServicesClass) => class extends ServicesClass {
155
+ // this.service = service instance for this controller's table
156
+ // this.services = map of all service instances
157
+ // this.Queue = { add(name, data, options) }
158
+ // this.io = Socket.io instance
159
+ };
160
+
161
+ // Router factory receives controllers and services
162
+ module.exports = (controllers, services) => {
163
+ // controllers = map of all controller instances
164
+ // services = map of all service instances (for middleware)
165
+ };
166
+
167
+ // Job factory receives services
168
+ module.exports = (services) => ({
169
+ handle: async (data, job, queue, io) => { ... }
170
+ });
171
+
172
+ // Task factory receives services and Queue
173
+ module.exports = (services, Queue) => ({
174
+ task: async () => { ... },
175
+ cronTime: '...',
176
+ isActive: true
177
+ });
178
+ ```
179
+
180
+ The loaders wire everything together at boot time. No manual dependency resolution needed.
181
+
182
+ ---
183
+
184
+ ## Directory Structure
185
+
186
+ ```
187
+ src/
188
+ ├── config/
189
+ │ ├── server.js # Entry point: dotenv, knex, startServer, graceful shutdown
190
+ │ ├── app.js # App class: middleware, routes, error handling
191
+ │ ├── queues.js # BullMQ queue management
192
+ │ ├── instrument.js # Sentry initialization
193
+ │ └── tests/ # Test setup files
194
+ │ ├── global-setup-tests.js
195
+ │ └── setup-tests.js
196
+ ├── database/
197
+ │ ├── connection.js # Knex MySQL2 connection factory
198
+ │ ├── redis-config.js # Redis connection parameters
199
+ │ ├── redis-cache.js # RedisCache singleton (get/set/delete)
200
+ │ ├── models/ # Model config files ({table}.model.js)
201
+ │ │ └── index.js # loadModels loader
202
+ │ ├── services/ # Service files ({table}.services.js)
203
+ │ │ └── index.js # loadServices loader
204
+ │ └── tests/ # Database test utilities
205
+ ├── controllers/ # Controller files ({table}/{table}.controller.js)
206
+ │ └── index.js # loadControllers loader
207
+ ├── routes/ # Router files ({table}.router.js)
208
+ │ └── index.js # attachRouters loader
209
+ ├── jobs/ # Job files ({name}.js, supports subdirectories)
210
+ │ └── index.js # loadJobs loader
211
+ ├── tasks/ # Task files ({name}.js, supports subdirectories)
212
+ │ └── index.js # loadTasks loader
213
+ ├── middlewares/ # Middleware files ({name}.middleware.js)
214
+ │ └── auth.middleware.js # Authentication & authorization
215
+ ├── integrations/ # External service wrappers
216
+ ├── utils/ # Utility functions
217
+ │ └── index.js # Re-exports common utilities
218
+ ├── static/ # Static files
219
+ └── temp/ # Temporary file storage
220
+ ```
221
+
222
+ ---
223
+
224
+ ## Naming Conventions Quick Reference
225
+
226
+ | Component | File Pattern | Key Name | Example |
227
+ |-----------|-------------|----------|---------|
228
+ | Model | `{table}.model.js` | — | `users.model.js` |
229
+ | Service | `{table}.services.js` | `PascalCase + Services` | `UsersServices` |
230
+ | Controller | `{table}/{table}.controller.js` | `PascalCase + Controller` | `UsersController` |
231
+ | Route | `{table}.router.js` | URL: `/kebab-case` | `/users`, `/products-invoices` |
232
+ | Job | `{name}.js` (kebab-case) | `kebab-case` | `send-mail` |
233
+ | Task | `{name}.js` (kebab-case) | — | `update-currencies.js` |
234
+ | Middleware | `{name}.middleware.js` | — | `auth.middleware.js` |
235
+
236
+ **Table names** use `snake_case` (matching the MySQL table name): `users`, `products_invoices`, `companies`.
237
+
238
+ **File-to-URL conversion**: `products_invoices.router.js` → URL path `/products-invoices` (underscore to hyphen).
239
+
240
+ **File-to-key conversion**: `products_invoices` → `ProductsInvoicesServices` / `ProductsInvoicesController` (snake_case to PascalCase + suffix).