langaro-api 1.2.3 → 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.
- package/bin/langaro-api.js +12 -2
- package/lib/cli/documentation-templates/01-architecture-overview.md +240 -0
- package/lib/cli/documentation-templates/02-crud-layer.md +504 -0
- package/lib/cli/documentation-templates/03-models.md +362 -0
- package/lib/cli/documentation-templates/04-services.md +355 -0
- package/lib/cli/documentation-templates/05-controllers.md +395 -0
- package/lib/cli/documentation-templates/06-routes.md +268 -0
- package/lib/cli/documentation-templates/07-jobs.md +361 -0
- package/lib/cli/documentation-templates/08-tasks.md +265 -0
- package/lib/cli/documentation-templates/09-middlewares.md +238 -0
- package/lib/cli/documentation-templates/10-integrations.md +332 -0
- package/lib/cli/documentation-templates/11-config-and-bootstrap.md +352 -0
- package/lib/cli/documentation-templates/12-queues.md +205 -0
- package/lib/cli/documentation-templates/13-utils.md +281 -0
- package/lib/cli/documentation-templates/14-testing.md +315 -0
- package/lib/cli/documentation-templates/15-cli-and-scaffolding.md +344 -0
- package/lib/cli/documentation-templates/SUMMARY.md +116 -0
- package/lib/cli/init.js +30 -2
- package/package.json +1 -1
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
# CRUD Layer (knex-extended-crud)
|
|
2
|
+
|
|
3
|
+
The `CRUD` class from `knex-extended-crud` is the foundation of all database operations. Every model and service inherits from it.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Constructor Parameters
|
|
8
|
+
|
|
9
|
+
When the loader creates a model, it passes these to the CRUD constructor:
|
|
10
|
+
|
|
11
|
+
```javascript
|
|
12
|
+
{
|
|
13
|
+
knex, // Knex instance (injected by loader)
|
|
14
|
+
table, // Table name (injected by loader)
|
|
15
|
+
hide: [], // Fields never returned in responses
|
|
16
|
+
append: {}, // Relationship definitions
|
|
17
|
+
fields: {}, // Field configuration
|
|
18
|
+
schema: null, // Yup validation schema
|
|
19
|
+
fieldsSpec: [], // Per-field specifications
|
|
20
|
+
forceInteger: [], // Fields stored as int but exposed as float (÷100)
|
|
21
|
+
sortable: false, // Enable manual sorting
|
|
22
|
+
redis: null, // Redis instance for query caching
|
|
23
|
+
cacheEnabled: true, // Master cache toggle
|
|
24
|
+
cacheTimeout: 300000, // Schema cache TTL in ms (5 min)
|
|
25
|
+
singularTableName: null, // Override singular form (for relationship detection)
|
|
26
|
+
pluralTableName: null, // Override plural form
|
|
27
|
+
notSearchableFields: [], // Fields excluded from search
|
|
28
|
+
modelsPath: '', // Path to model files (for append resolution)
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Default Options
|
|
35
|
+
|
|
36
|
+
Every CRUD instance has these defaults for query operations:
|
|
37
|
+
|
|
38
|
+
```javascript
|
|
39
|
+
this.options = {
|
|
40
|
+
perPage: 100, // Records per page
|
|
41
|
+
currentPage: 1, // Starting page
|
|
42
|
+
isLengthAware: true, // Include total count in pagination
|
|
43
|
+
exactMatch: false, // Search uses LIKE %term% by default
|
|
44
|
+
sortBy: 'id', // Default sort column
|
|
45
|
+
sort: 'desc', // Default sort direction
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Read Methods
|
|
52
|
+
|
|
53
|
+
### `get(options, transaction)`
|
|
54
|
+
|
|
55
|
+
Retrieves records with pagination, filtering, searching, and relationship loading.
|
|
56
|
+
|
|
57
|
+
**Options (CRUDGetOptions):**
|
|
58
|
+
|
|
59
|
+
```javascript
|
|
60
|
+
{
|
|
61
|
+
// Pagination
|
|
62
|
+
perPage: 100, // Records per page
|
|
63
|
+
currentPage: 1, // Page number
|
|
64
|
+
|
|
65
|
+
// Sorting
|
|
66
|
+
sortBy: 'created_at', // Sort column
|
|
67
|
+
sort: 'desc', // 'asc' or 'desc'
|
|
68
|
+
|
|
69
|
+
// Field selection
|
|
70
|
+
showOnly: ['id', 'name'], // Return ONLY these fields
|
|
71
|
+
show: ['field1'], // Whitelist (inverse of hide)
|
|
72
|
+
|
|
73
|
+
// Filtering
|
|
74
|
+
where: (query) => { // Knex query builder function
|
|
75
|
+
query.where('status', 'active');
|
|
76
|
+
},
|
|
77
|
+
andWhere: [ // Array-based conditions (see andWhere section)
|
|
78
|
+
['status', 'active'],
|
|
79
|
+
['amount', '>=', 100],
|
|
80
|
+
],
|
|
81
|
+
|
|
82
|
+
// Search
|
|
83
|
+
search: 'john', // Search term
|
|
84
|
+
searchFields: ['name', 'email'],// Fields to search (overrides model config)
|
|
85
|
+
searchExactMatch: false, // true = exact, false = fuzzy (%term%)
|
|
86
|
+
|
|
87
|
+
// Relationships
|
|
88
|
+
append: ['companies', 'users'], // Relations to load
|
|
89
|
+
appendOptions: { // Per-relation options
|
|
90
|
+
companies: { showOnly: ['id', 'name'] },
|
|
91
|
+
users: { show: ['id', 'email'] },
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
// Caching
|
|
95
|
+
cache: true, // Read from Redis cache
|
|
96
|
+
cacheHours: 24, // Write result to cache with TTL
|
|
97
|
+
|
|
98
|
+
// Special
|
|
99
|
+
firstOnly: true, // Return only the first result
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**Returns:**
|
|
104
|
+
```javascript
|
|
105
|
+
{
|
|
106
|
+
success: true,
|
|
107
|
+
data: [...], // Array of records (or single object if firstOnly)
|
|
108
|
+
pagination: { // Only if isLengthAware
|
|
109
|
+
perPage: 100,
|
|
110
|
+
currentPage: 1,
|
|
111
|
+
total: 500,
|
|
112
|
+
lastPage: 5
|
|
113
|
+
},
|
|
114
|
+
cached: true // Present if served from cache
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### `getWhere(prop, value, options, transaction)`
|
|
119
|
+
|
|
120
|
+
Retrieves records matching a single condition. Shorthand for `get()` with a where clause.
|
|
121
|
+
|
|
122
|
+
```javascript
|
|
123
|
+
const { data: user } = await this.service.getWhere('id', userId, {
|
|
124
|
+
firstOnly: true,
|
|
125
|
+
append: ['users_permissions'],
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### `search(field, term, options, transaction)`
|
|
130
|
+
|
|
131
|
+
Full-text search on a specific field. Uses case-insensitive `LOWER()` comparison.
|
|
132
|
+
|
|
133
|
+
```javascript
|
|
134
|
+
const results = await this.service.search('name', 'john', {
|
|
135
|
+
perPage: 50,
|
|
136
|
+
searchExactMatch: false,
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
The `field` must exist in the table and not be in `notSearchableFields`.
|
|
141
|
+
|
|
142
|
+
### `count(options, transaction)`
|
|
143
|
+
|
|
144
|
+
Counts matching records.
|
|
145
|
+
|
|
146
|
+
```javascript
|
|
147
|
+
const { data: total } = await this.service.count({
|
|
148
|
+
andWhere: [['status', 'active']],
|
|
149
|
+
});
|
|
150
|
+
// total = 125
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### `tableInfo(options, transaction)`
|
|
154
|
+
|
|
155
|
+
Returns schema information for the table. Cached in memory.
|
|
156
|
+
|
|
157
|
+
```javascript
|
|
158
|
+
const { data: schema } = await this.service.tableInfo();
|
|
159
|
+
// schema = { id: { type: 'uuid', nullable: false, ... }, name: { type: 'varchar', ... } }
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### `requiredFields(options, transaction)`
|
|
163
|
+
|
|
164
|
+
Returns list of non-nullable field names.
|
|
165
|
+
|
|
166
|
+
```javascript
|
|
167
|
+
const { data: required } = await this.service.requiredFields();
|
|
168
|
+
// required = ['name', 'email', 'company_id']
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Write Methods
|
|
174
|
+
|
|
175
|
+
### `create(data, transaction)`
|
|
176
|
+
|
|
177
|
+
Creates a single record.
|
|
178
|
+
|
|
179
|
+
**Automatic behaviors:**
|
|
180
|
+
- Generates UUID for `id` if not provided
|
|
181
|
+
- Hashes `password` field with bcrypt (10 salt rounds)
|
|
182
|
+
- Converts `forceInteger` fields (multiplies by 100)
|
|
183
|
+
- Validates against Yup schema
|
|
184
|
+
- Clears table query cache
|
|
185
|
+
|
|
186
|
+
```javascript
|
|
187
|
+
const { data: created } = await this.service.create({
|
|
188
|
+
name: 'John',
|
|
189
|
+
email: 'john@example.com',
|
|
190
|
+
company_id: companyId,
|
|
191
|
+
});
|
|
192
|
+
// created = { id: 'uuid-xxx' }
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
**Returns:** `{ success: true, data: { id: 'generated-uuid' } }`
|
|
196
|
+
|
|
197
|
+
### `batchCreate(data[], transaction)`
|
|
198
|
+
|
|
199
|
+
Creates multiple records in a single INSERT.
|
|
200
|
+
|
|
201
|
+
```javascript
|
|
202
|
+
const { data: created } = await this.service.batchCreate([
|
|
203
|
+
{ name: 'John', email: 'john@example.com' },
|
|
204
|
+
{ name: 'Jane', email: 'jane@example.com' },
|
|
205
|
+
]);
|
|
206
|
+
// created = [{ id: 'uuid-1' }, { id: 'uuid-2' }]
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Same auto-behaviors as `create()` applied to each item.
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## Update Methods
|
|
214
|
+
|
|
215
|
+
### `updateWhere(prop, value, data, options, transaction)`
|
|
216
|
+
|
|
217
|
+
Updates records matching a condition.
|
|
218
|
+
|
|
219
|
+
**Parameters:**
|
|
220
|
+
- `prop` — Column to match (e.g., `'id'`)
|
|
221
|
+
- `value` — Value to match
|
|
222
|
+
- `data` — Fields to update
|
|
223
|
+
- `options` — Update options
|
|
224
|
+
- `transaction` — Optional Knex transaction
|
|
225
|
+
|
|
226
|
+
**Options (CRUDUpdateOptions):**
|
|
227
|
+
```javascript
|
|
228
|
+
{
|
|
229
|
+
allowForbiddenUpdates: false, // Allow updating protected fields
|
|
230
|
+
where: (query) => {}, // Additional where clause
|
|
231
|
+
andWhere: [...], // Additional conditions
|
|
232
|
+
skipCacheInvalidation: false, // Don't clear Redis cache
|
|
233
|
+
whenSuccess: (data, value) => {},// Callback after successful update
|
|
234
|
+
extraValidations: [ // Custom validation functions
|
|
235
|
+
async (existingItem, newData, trx) => {
|
|
236
|
+
// Throw to prevent update
|
|
237
|
+
}
|
|
238
|
+
],
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
**Protected fields** (cannot be updated without `allowForbiddenUpdates`):
|
|
243
|
+
- `id`, `created_at`, `updated_at`
|
|
244
|
+
- Fields listed in `fields.notUpdatable`
|
|
245
|
+
|
|
246
|
+
**Automatic behaviors:**
|
|
247
|
+
- Per-field Yup schema validation
|
|
248
|
+
- Password auto-hashing
|
|
249
|
+
- JSON field stringification
|
|
250
|
+
- Foreign key reference validation
|
|
251
|
+
- Cache invalidation
|
|
252
|
+
|
|
253
|
+
```javascript
|
|
254
|
+
await this.service.updateWhere('id', userId, {
|
|
255
|
+
name: 'New Name',
|
|
256
|
+
email: 'new@email.com',
|
|
257
|
+
});
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
**Returns:** `{ success: true, data: { id: 'uuid-xxx' } }`
|
|
261
|
+
|
|
262
|
+
### `batchUpdate(data[], options, transaction)`
|
|
263
|
+
|
|
264
|
+
Updates multiple records with concurrency control.
|
|
265
|
+
|
|
266
|
+
**Options (CRUDBatchUpdateOptions):**
|
|
267
|
+
```javascript
|
|
268
|
+
{
|
|
269
|
+
getItemBy: 'id', // Field to identify each item (default: 'id')
|
|
270
|
+
socket: socketInstance, // Socket.io for progress events
|
|
271
|
+
...CRUDUpdateOptions
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
**Behavior:**
|
|
276
|
+
1. Pre-fetches all items in a single query
|
|
277
|
+
2. Limits concurrency to 10 parallel updates (prevents connection pool exhaustion)
|
|
278
|
+
3. Creates its own transaction if none provided (auto commit/rollback)
|
|
279
|
+
4. Emits Socket.io progress events: `batchUpdate:started`, `batchUpdate:progress`, `batchUpdate:completed`
|
|
280
|
+
|
|
281
|
+
```javascript
|
|
282
|
+
const results = await this.service.batchUpdate([
|
|
283
|
+
{ id: 'uuid-1', name: 'Updated 1' },
|
|
284
|
+
{ id: 'uuid-2', name: 'Updated 2' },
|
|
285
|
+
], { socket: io.sockets.in(userId) });
|
|
286
|
+
// results = [{ id: 'uuid-1', success: true }, { id: 'uuid-2', success: true }]
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## Delete Methods
|
|
292
|
+
|
|
293
|
+
### `deleteWhere(prop, values, options, transaction)`
|
|
294
|
+
|
|
295
|
+
Deletes records matching condition(s).
|
|
296
|
+
|
|
297
|
+
```javascript
|
|
298
|
+
// Single delete
|
|
299
|
+
await this.service.deleteWhere('id', itemId);
|
|
300
|
+
|
|
301
|
+
// Batch delete
|
|
302
|
+
await this.service.deleteWhere('id', [id1, id2, id3]);
|
|
303
|
+
|
|
304
|
+
// Conditional delete
|
|
305
|
+
await this.service.deleteWhere('id', itemId, {
|
|
306
|
+
andWhere: [['company_id', companyId]],
|
|
307
|
+
});
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
**Returns:** `{ success: true, data: { id: [...] } }`
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
## andWhere Query Builder
|
|
315
|
+
|
|
316
|
+
The `andWhere` option provides a declarative way to build complex WHERE clauses:
|
|
317
|
+
|
|
318
|
+
```javascript
|
|
319
|
+
andWhere: [
|
|
320
|
+
// Equality: [column, value]
|
|
321
|
+
['status', 'active'],
|
|
322
|
+
|
|
323
|
+
// Comparison: [column, operator, value]
|
|
324
|
+
['amount', '>=', 100],
|
|
325
|
+
['created_at', '<', '2025-01-01'],
|
|
326
|
+
|
|
327
|
+
// IN operator: [column, 'in', array]
|
|
328
|
+
['status', 'in', ['active', 'pending']],
|
|
329
|
+
|
|
330
|
+
// NOT IN: [column, 'not in', array]
|
|
331
|
+
['role', 'not in', ['banned']],
|
|
332
|
+
|
|
333
|
+
// BETWEEN: [column, 'between', [start, end]]
|
|
334
|
+
['created_at', 'between', ['2025-01-01', '2025-12-31']],
|
|
335
|
+
|
|
336
|
+
// NULL handling
|
|
337
|
+
['deleted_at', null], // WHERE deleted_at IS NULL
|
|
338
|
+
['deleted_at', '<>', null], // WHERE deleted_at IS NOT NULL
|
|
339
|
+
|
|
340
|
+
// LIKE: [column, 'like', pattern]
|
|
341
|
+
['name', 'like', '%john%'],
|
|
342
|
+
|
|
343
|
+
// IN with NULL: [column, 'in', [values, null]]
|
|
344
|
+
['status', 'in', ['active', null]], // WHERE status IN ('active') OR status IS NULL
|
|
345
|
+
|
|
346
|
+
// Boolean columns (TINYINT): use 1/0, NOT true/false
|
|
347
|
+
['is_active', 1], // WHERE is_active = 1
|
|
348
|
+
['is_deleted', 0], // WHERE is_deleted = 0
|
|
349
|
+
]
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
**Supported operators:** `=`, `<>`, `>`, `<`, `>=`, `<=`, `in`, `not in`, `between`, `not between`, `like`
|
|
353
|
+
|
|
354
|
+
**Boolean gotcha:** MySQL stores booleans as `TINYINT(1)`. In `andWhere`, use `1`/`0` — not `true`/`false`. JavaScript `true`/`false` does not work correctly with the query builder.
|
|
355
|
+
|
|
356
|
+
---
|
|
357
|
+
|
|
358
|
+
## Relationship System (append)
|
|
359
|
+
|
|
360
|
+
Relationships are configured in the model file and resolved at query time.
|
|
361
|
+
|
|
362
|
+
### Configuration (in model file)
|
|
363
|
+
|
|
364
|
+
```javascript
|
|
365
|
+
module.exports = {
|
|
366
|
+
append: {
|
|
367
|
+
companies: 'one', // This table has company_id FK → load one company
|
|
368
|
+
posts: 'many', // Other table has this_table_id FK → load many posts
|
|
369
|
+
categories: 'categories_users_join', // Many-to-many via join table
|
|
370
|
+
},
|
|
371
|
+
};
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### Relationship Types
|
|
375
|
+
|
|
376
|
+
**one** — This table has a foreign key pointing to the related table:
|
|
377
|
+
- `users` table has `company_id` → `append: { companies: 'one' }`
|
|
378
|
+
- Returns: each user gets a `companies` object
|
|
379
|
+
|
|
380
|
+
**many** — The related table has a foreign key pointing to this table:
|
|
381
|
+
- `posts` table has `user_id` → on users model: `append: { posts: 'many' }`
|
|
382
|
+
- Returns: each user gets a `posts` array
|
|
383
|
+
|
|
384
|
+
**join table** — Many-to-many via intermediate table:
|
|
385
|
+
- `categories_users_join` table with `category_id` and `user_id`
|
|
386
|
+
- On users model: `append: { categories: 'categories_users_join' }`
|
|
387
|
+
|
|
388
|
+
### Nested Appending
|
|
389
|
+
|
|
390
|
+
Up to 3 levels of nesting:
|
|
391
|
+
```javascript
|
|
392
|
+
append: ['companies', 'companies.subscriptions', 'companies.subscriptions.plans']
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### appendOptions
|
|
396
|
+
|
|
397
|
+
Control which fields are loaded per relation:
|
|
398
|
+
```javascript
|
|
399
|
+
{
|
|
400
|
+
append: ['companies', 'users'],
|
|
401
|
+
appendOptions: {
|
|
402
|
+
companies: { showOnly: ['id', 'name'] }, // Only id and name
|
|
403
|
+
users: { show: ['id', 'email', 'name'] }, // Only these fields
|
|
404
|
+
},
|
|
405
|
+
}
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
## Validation Pipeline
|
|
411
|
+
|
|
412
|
+
When creating or updating records, the CRUD class runs validation in this order:
|
|
413
|
+
|
|
414
|
+
### On Create (`defaultInsertValidations`):
|
|
415
|
+
1. Check for forbidden fields (fields in `hide` list)
|
|
416
|
+
2. Validate required fields (non-nullable fields)
|
|
417
|
+
3. Run `validateAndFormatFields`:
|
|
418
|
+
- Field exists in table schema
|
|
419
|
+
- Nullable check
|
|
420
|
+
- Empty string check (if `notEmpty` spec)
|
|
421
|
+
- Max length check
|
|
422
|
+
- Allowed values check (enum)
|
|
423
|
+
- Yup schema validation (per-field)
|
|
424
|
+
- Unique constraint check
|
|
425
|
+
- Foreign key reference validation
|
|
426
|
+
- Boolean type check
|
|
427
|
+
- Integer type check
|
|
428
|
+
- JSON field handling (auto-stringify objects)
|
|
429
|
+
- Email lowercase normalization
|
|
430
|
+
|
|
431
|
+
### On Update (`defaultUpdateValidations`):
|
|
432
|
+
1. Verify item exists (throws NOT_FOUND)
|
|
433
|
+
2. Check for protected fields (id, created_at, updated_at, notUpdatable fields)
|
|
434
|
+
3. Run custom `extraValidations` if provided
|
|
435
|
+
4. Check for forbidden fields (unless `allowForbiddenUpdates`)
|
|
436
|
+
5. Run same `validateAndFormatFields` pipeline (per changed field only)
|
|
437
|
+
|
|
438
|
+
---
|
|
439
|
+
|
|
440
|
+
## Transaction Management
|
|
441
|
+
|
|
442
|
+
```javascript
|
|
443
|
+
const trx = await this.service.createTransaction();
|
|
444
|
+
try {
|
|
445
|
+
const { data: user } = await this.service.create(userData, trx);
|
|
446
|
+
await this.services.PermissionsServices.batchCreate(permissions, trx);
|
|
447
|
+
await trx.commit();
|
|
448
|
+
return user;
|
|
449
|
+
} catch (error) {
|
|
450
|
+
await trx.rollback();
|
|
451
|
+
throw error;
|
|
452
|
+
}
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
**Rules:**
|
|
456
|
+
- All methods accept an optional `transaction` parameter as their last argument
|
|
457
|
+
- If no transaction is provided, methods use the default knex instance
|
|
458
|
+
- `batchUpdate` creates its own transaction if none is provided
|
|
459
|
+
- Always use try/catch/rollback — there is no auto-rollback
|
|
460
|
+
|
|
461
|
+
---
|
|
462
|
+
|
|
463
|
+
## Redis Query Cache
|
|
464
|
+
|
|
465
|
+
The CRUD layer supports two caching tiers:
|
|
466
|
+
|
|
467
|
+
### In-Memory Schema Cache
|
|
468
|
+
- Stores table schema info (`tableInfo` results)
|
|
469
|
+
- TTL: `cacheTimeout` (default 5 min)
|
|
470
|
+
- Cleared via `clearSchemaCache()` or `clearTableCache(tableName)`
|
|
471
|
+
|
|
472
|
+
### Redis Query Cache
|
|
473
|
+
- Stores full query results
|
|
474
|
+
- Key format: `crud-cache:{table}:{method}:{sha256hash}`
|
|
475
|
+
- **Read from cache**: set `cache: true` in options
|
|
476
|
+
- **Write to cache**: set `cacheHours: N` in options
|
|
477
|
+
- **Invalidation**: Automatic on create/update/delete (unless `skipCacheInvalidation`)
|
|
478
|
+
- **Disable**: `disableCache()` / `enableCache()`
|
|
479
|
+
|
|
480
|
+
**Cache management methods:**
|
|
481
|
+
```javascript
|
|
482
|
+
this.service.clearQueryCache(); // Invalidate this table's cache
|
|
483
|
+
this.service.clearSchemaCache(); // Clear all schema caches
|
|
484
|
+
this.service.clearTableCache('users');// Clear specific table schema
|
|
485
|
+
this.service.refreshCache(trx); // Pre-load caches
|
|
486
|
+
CRUD.clearAllCrudCache(redis); // Global invalidation (static)
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
---
|
|
490
|
+
|
|
491
|
+
## forceInteger Fields
|
|
492
|
+
|
|
493
|
+
For fields that store monetary values as integers (cents) but should be exposed as decimals:
|
|
494
|
+
|
|
495
|
+
```javascript
|
|
496
|
+
// Model config
|
|
497
|
+
{ forceInteger: ['price', 'amount'] }
|
|
498
|
+
|
|
499
|
+
// On create: value * 100 before INSERT
|
|
500
|
+
// price: 29.99 → stored as 2999
|
|
501
|
+
|
|
502
|
+
// On read: value / 100 after SELECT
|
|
503
|
+
// stored 2999 → returned as 29.99
|
|
504
|
+
```
|