@venizia/ignis-docs 0.0.4 → 0.0.5

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.
@@ -193,5 +193,7 @@ CREATE INDEX idx_metadata_gin ON "Product" USING GIN ("metadata");
193
193
  // This is safe - no errors, just no matches
194
194
  { where: { 'metadata.nonexistent.field': 'value' } }
195
195
  // SQL: "metadata" #>> '{nonexistent,field}' = 'value'
196
- // Result: No rows (NULL != 'value')
197
- ```
196
+ ## See Also
197
+
198
+ - [Nested JSON Updates](../repositories/advanced.md#nested-json-updates) - Updating JSON fields
199
+
@@ -7,7 +7,7 @@ lastUpdated: 2026-01-03
7
7
 
8
8
  # Middlewares Reference
9
9
 
10
- IGNIS provides a collection of built-in middlewares for common application needs including error handling, request logging, request normalization, and favicon serving.
10
+ IGNIS provides a collection of built-in middlewares for common application needs including error handling, request logging, and favicon serving.
11
11
 
12
12
  **Files:**
13
13
  - `packages/core/src/base/middlewares/*.ts`
@@ -24,16 +24,14 @@ IGNIS provides a collection of built-in middlewares for common application needs
24
24
  |------------|---------|-------------|
25
25
  | `appErrorHandler` | Catches and formats application errors | `logger` |
26
26
  | `notFoundHandler` | Handles 404 Not Found responses | `logger` |
27
- | `requestNormalize` | Pre-parses JSON request bodies | None |
28
- | `RequestSpyMiddleware` | Logs request lifecycle and timing | None |
27
+ | `RequestSpyMiddleware` | Logs request lifecycle, timing, and parses request body | None |
29
28
  | `emojiFavicon` | Serves an emoji as favicon | `icon` |
30
29
 
31
30
  ## Table of Contents
32
31
 
33
32
  - [Error Handler (`appErrorHandler`)](#error-handler-apporerrorhandler)
34
33
  - [Not Found Handler (`notFoundHandler`)](#not-found-handler-notfoundhandler)
35
- - [Request Normalizer (`requestNormalize`)](#request-normalizer-requestnormalize)
36
- - [Request Spy (Debug)](#request-spy-debug)
34
+ - [Request Spy (`RequestSpyMiddleware`)](#request-spy-requestspymiddleware)
37
35
  - [Emoji Favicon](#emoji-favicon)
38
36
  - [Creating Custom Middleware](#creating-custom-middleware)
39
37
  - [Middleware Order & Priority](#middleware-order--priority)
@@ -237,65 +235,23 @@ app.notFound(notFoundHandler({
237
235
 
238
236
  ---
239
237
 
240
- ### Request Normalizer (`requestNormalize`)
238
+ ### Request Spy (`RequestSpyMiddleware`)
241
239
 
242
- Pre-parses JSON request bodies to ensure consistent request handling and prevent common parsing issues.
243
-
244
- **File:** `packages/core/src/base/middlewares/request-normalize.middleware.ts`
245
-
246
- #### How It Works
247
-
248
- 1. **Skip for GET/OPTIONS**: No normalization for read-only requests
249
- 2. **Check Content-Length**: Skip if no body is present (`Content-Length: 0`)
250
- 3. **Check Content-Type**: Only process `application/json` requests
251
- 4. **Pre-parse JSON**: Calls `context.req.json()` to cache the parsed body
252
-
253
- #### Benefits
254
-
255
- - Prevents multiple body parsing attempts
256
- - Ensures body is available for all subsequent middleware/handlers
257
- - Catches JSON parsing errors early in the request lifecycle
258
-
259
- #### Usage
260
-
261
- ```typescript
262
- import { requestNormalize } from '@venizia/ignis';
263
-
264
- const app = new IgnisApplication({
265
- // ...
266
- });
267
-
268
- // Register as early middleware
269
- app.use(requestNormalize());
270
- ```
271
-
272
- :::tip Why Pre-parse?
273
- Hono's request body can only be read once. This middleware ensures the body is parsed and cached early, making it available to all downstream handlers.
274
- :::
275
-
276
- #### API Reference
277
-
278
- ##### `requestNormalize()`
279
-
280
- **Parameters:** None
281
-
282
- **Returns:** `MiddlewareHandler` - Hono middleware function
283
-
284
- ---
285
-
286
- ### Request Spy (Debug)
287
-
288
- Logs detailed information about each request including timing, IP address, method, path, and query parameters.
240
+ Logs detailed information about each request including timing, IP address, method, path, query parameters, and request body. Also handles request body parsing for JSON, form data, and text content types.
289
241
 
290
242
  **File:** `packages/core/src/base/middlewares/request-spy.middleware.ts`
291
243
 
292
244
  #### Features
293
245
 
294
- - Request lifecycle logging (START/DONE)
246
+ - Request lifecycle logging (incoming/outgoing)
295
247
  - Performance timing tracking
296
248
  - IP address extraction (supports `x-real-ip` and `x-forwarded-for` headers)
297
249
  - Request ID tracking
298
- - Query and body parameter logging
250
+ - Query and body parameter logging (body only logged in non-production)
251
+ - **Request body parsing**: Automatically parses and caches request bodies:
252
+ - `application/json` → `req.json()`
253
+ - `multipart/form-data`, `application/x-www-form-urlencoded` → `req.parseBody()`
254
+ - Other content types (text, html, xml) → `req.text()`
299
255
 
300
256
  #### Usage
301
257
 
@@ -511,43 +467,39 @@ app.use(cors());
511
467
  // 2. Request ID generation
512
468
  app.use(requestId());
513
469
 
514
- // 3. Request spy/logging
470
+ // 3. Request spy/logging (also handles body parsing)
515
471
  const requestSpy = new RequestSpyMiddleware();
516
472
  app.use(requestSpy.value());
517
473
 
518
- // 4. Request normalization
519
- app.use(requestNormalize());
520
-
521
- // 5. Security middleware (helmet, etc.)
474
+ // 4. Security middleware (helmet, etc.)
522
475
  app.use(helmet());
523
476
 
524
- // 6. Rate limiting
477
+ // 5. Rate limiting
525
478
  app.use(rateLimit());
526
479
 
527
- // 7. Authentication
480
+ // 6. Authentication
528
481
  app.use('/api/*', authenticate());
529
482
 
530
- // 8. Favicon (can be early or late)
483
+ // 7. Favicon (can be early or late)
531
484
  app.use(emojiFavicon({ icon: '🚀' }));
532
485
 
533
- // 9. Application routes
486
+ // 8. Application routes
534
487
  app.mountControllers();
535
488
 
536
- // 10. Error handler (LAST in chain)
489
+ // 9. Error handler (LAST in chain)
537
490
  app.onError(appErrorHandler({ logger: app.logger }));
538
491
 
539
- // 11. Not found handler (AFTER error handler)
492
+ // 10. Not found handler (AFTER error handler)
540
493
  app.notFound(notFoundHandler({ logger: app.logger }));
541
494
  ```
542
495
 
543
496
  ### Key Principles
544
497
 
545
498
  1. **Request ID First**: Generate request ID before logging
546
- 2. **Logging Early**: Log requests before normalization/parsing
547
- 3. **Normalization Before Business Logic**: Parse bodies before they're needed
548
- 4. **Security Middleware Before Routes**: Protect routes with security checks
549
- 5. **Error Handler Last**: Catch all errors from previous middleware
550
- 6. **404 Handler After Error Handler**: Ensure unhandled routes return 404
499
+ 2. **Request Spy Early**: Log and parse request bodies before business logic
500
+ 3. **Security Middleware Before Routes**: Protect routes with security checks
501
+ 4. **Error Handler Last**: Catch all errors from previous middleware
502
+ 5. **404 Handler After Error Handler**: Ensure unhandled routes return 404
551
503
 
552
504
  :::warning Order Matters
553
505
  Placing error handler before routes will prevent it from catching route errors. Always register error handlers last.
@@ -584,7 +536,7 @@ app.use('/api/public/*', rateLimitMiddleware());
584
536
  const apiMiddleware = (): MiddlewareHandler => {
585
537
  return createMiddleware(async (context, next) => {
586
538
  // Run multiple middleware in sequence
587
- await requestNormalize()(context, async () => {
539
+ await rateLimit()(context, async () => {
588
540
  await authenticate()(context, next);
589
541
  });
590
542
  });
@@ -513,6 +513,89 @@ await tx.commit();
513
513
  > See [Default Filter](../filter-system/default-filter.md) for full documentation on configuring model default filters.
514
514
 
515
515
 
516
+ ## Nested JSON Updates
517
+
518
+ Repositories support updating specific fields within `json` or `jsonb` columns without overwriting the entire object. This is achieved using **JSON Path Notation** in the update data.
519
+
520
+ ### Basic Usage
521
+
522
+ Use dot notation keys to target nested properties:
523
+
524
+ ```typescript
525
+ // Assume 'metadata' is a JSONB column
526
+ // Current value: { theme: 'light', notifications: { email: true } }
527
+
528
+ await repo.updateById({
529
+ id: '123',
530
+ data: {
531
+ // Update only the theme, preserving other fields
532
+ 'metadata.theme': 'dark'
533
+ }
534
+ });
535
+
536
+ // New value: { theme: 'dark', notifications: { email: true } }
537
+ ```
538
+
539
+ ### Supported Features
540
+
541
+ - **Deep Nesting:** Update properties at any depth (e.g., `settings.display.font.size`).
542
+ - **Array Access:** Update array elements by index (e.g., `tags[0]`).
543
+ - **Auto-Creation:** Creates missing intermediate keys automatically.
544
+ - **Type Safety:** Validates that the target column is a JSON type.
545
+ - **Multiple Updates:** Chain multiple updates to the same or different columns.
546
+
547
+ ### Examples
548
+
549
+ #### Deeply Nested Updates
550
+
551
+ ```typescript
552
+ await repo.updateById({
553
+ id: '123',
554
+ data: {
555
+ 'metadata.settings.display.fontSize': 16,
556
+ 'metadata.settings.display.showSidebar': true
557
+ }
558
+ });
559
+ ```
560
+
561
+ #### Array Element Updates
562
+
563
+ ```typescript
564
+ await repo.updateById({
565
+ id: '123',
566
+ data: {
567
+ // Set the first address as primary
568
+ 'metadata.addresses[0].primary': true
569
+ }
570
+ });
571
+ ```
572
+
573
+ #### Mixed Updates (Regular + JSON)
574
+
575
+ You can mix regular column updates with JSON path updates:
576
+
577
+ ```typescript
578
+ await repo.updateById({
579
+ id: '123',
580
+ data: {
581
+ status: 'active', // Regular column
582
+ 'metadata.lastLogin': now, // JSON path
583
+ 'preferences.lang': 'en' // Another JSON path
584
+ }
585
+ });
586
+ ```
587
+
588
+ ### Security & Validation
589
+
590
+ The framework validates JSON paths to prevent SQL injection:
591
+ - **Allowed Characters:** Alphanumeric, underscores `_`, hyphens `-`, and brackets `[]`.
592
+ - **Validation:** Invalid paths (e.g., containing SQL commands or special characters) throw an error before reaching the database.
593
+ - **Values:** Values are safely serialized and parameterized.
594
+
595
+ > [!NOTE]
596
+ > This feature uses PostgreSQL's `jsonb_set` function. It is only available for columns defined as `json` or `jsonb`.
597
+
598
+
516
599
  ## Quick Reference
517
600
 
518
601
  | Feature | Code |
@@ -214,7 +214,7 @@ await repo.deleteAll({ where: {}, options: { force: true } });
214
214
 
215
215
  - **Repository Topics:**
216
216
  - [Relations & Includes](./relations) - Loading related data
217
- - [Advanced Features](./advanced) - JSON queries, performance tuning, transactions
217
+ - [Advanced Features](./advanced) - JSON updates, transactions, performance tuning
218
218
  - [Repository Mixins](./mixins) - Soft delete and auditing
219
219
 
220
220
  - **Filtering:**