@venizia/ignis-docs 0.2.1-0 → 0.2.1-1

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 (145) hide show
  1. package/content/best-practices/architectural-patterns.md +3 -3
  2. package/content/best-practices/code-style-standards/naming-conventions.md +1 -1
  3. package/content/best-practices/contribution-workflow.md +2 -2
  4. package/content/best-practices/error-handling.md +94 -89
  5. package/content/best-practices/security-guidelines.md +5 -5
  6. package/content/extensions/components/api-reference.md +22 -21
  7. package/content/extensions/components/authentication/api.md +64 -28
  8. package/content/extensions/components/authentication/errors.md +19 -5
  9. package/content/extensions/components/authentication/index.md +19 -18
  10. package/content/extensions/components/authentication/usage.md +55 -30
  11. package/content/extensions/components/authorization/api.md +351 -84
  12. package/content/extensions/components/authorization/errors.md +51 -17
  13. package/content/extensions/components/authorization/getting-started.md +227 -0
  14. package/content/extensions/components/authorization/index.md +24 -16
  15. package/content/extensions/components/authorization/usage.md +45 -21
  16. package/content/extensions/components/health-check.md +15 -9
  17. package/content/extensions/components/index.md +24 -90
  18. package/content/extensions/components/mail/api.md +105 -54
  19. package/content/extensions/components/mail/errors.md +12 -10
  20. package/content/extensions/components/mail/index.md +32 -13
  21. package/content/extensions/components/mail/usage.md +26 -18
  22. package/content/extensions/components/request-tracker.md +18 -14
  23. package/content/extensions/components/socket-io/api.md +377 -882
  24. package/content/extensions/components/socket-io/errors.md +49 -51
  25. package/content/extensions/components/socket-io/index.md +72 -88
  26. package/content/extensions/components/socket-io/usage.md +107 -117
  27. package/content/extensions/components/static-asset/api.md +83 -31
  28. package/content/extensions/components/static-asset/errors.md +18 -7
  29. package/content/extensions/components/static-asset/index.md +18 -11
  30. package/content/extensions/components/static-asset/usage.md +11 -6
  31. package/content/extensions/components/template/index.md +3 -3
  32. package/content/extensions/components/websocket/api.md +58 -27
  33. package/content/extensions/components/websocket/errors.md +3 -3
  34. package/content/extensions/components/websocket/index.md +8 -7
  35. package/content/extensions/components/websocket/usage.md +21 -8
  36. package/content/extensions/helpers/cron/index.md +8 -7
  37. package/content/extensions/helpers/crypto/index.md +16 -8
  38. package/content/extensions/helpers/crypto/reference.md +96 -24
  39. package/content/extensions/helpers/env/index.md +14 -10
  40. package/content/extensions/helpers/error/index.md +99 -23
  41. package/content/extensions/helpers/index.md +61 -47
  42. package/content/extensions/helpers/inversion/index.md +23 -6
  43. package/content/extensions/helpers/inversion/reference.md +30 -22
  44. package/content/extensions/helpers/kafka/admin.md +3 -2
  45. package/content/extensions/helpers/kafka/compile-binary.md +69 -44
  46. package/content/extensions/helpers/kafka/consumer.md +24 -21
  47. package/content/extensions/helpers/kafka/examples.md +22 -234
  48. package/content/extensions/helpers/kafka/index.md +30 -62
  49. package/content/extensions/helpers/kafka/producer.md +31 -26
  50. package/content/extensions/helpers/kafka/schema-registry.md +19 -14
  51. package/content/extensions/helpers/logger/hf-logger.md +49 -22
  52. package/content/extensions/helpers/logger/index.md +37 -12
  53. package/content/extensions/helpers/logger/pino.md +31 -11
  54. package/content/extensions/helpers/logger/reference.md +243 -52
  55. package/content/extensions/helpers/network/api.md +65 -30
  56. package/content/extensions/helpers/network/index.md +32 -11
  57. package/content/extensions/helpers/queue/index.md +17 -6
  58. package/content/extensions/helpers/queue/reference.md +52 -25
  59. package/content/extensions/helpers/redis/index.md +29 -11
  60. package/content/extensions/helpers/redis/reference.md +85 -28
  61. package/content/extensions/helpers/secrets/index.md +82 -12
  62. package/content/extensions/helpers/socket-io/api.md +40 -21
  63. package/content/extensions/helpers/socket-io/index.md +26 -10
  64. package/content/extensions/helpers/storage/api.md +42 -24
  65. package/content/extensions/helpers/storage/index.md +10 -7
  66. package/content/extensions/helpers/types/index.md +20 -7
  67. package/content/extensions/helpers/types/reference.md +53 -14
  68. package/content/extensions/helpers/uid/index.md +166 -10
  69. package/content/extensions/helpers/websocket/api.md +52 -13
  70. package/content/extensions/helpers/websocket/index.md +7 -4
  71. package/content/extensions/helpers/worker-thread/index.md +9 -5
  72. package/content/extensions/helpers/worker-thread/reference.md +20 -20
  73. package/content/extensions/index.md +38 -39
  74. package/content/extensions/src-details/mcp-server.md +96 -548
  75. package/content/guides/core-concepts/persistent/datasources.md +2 -2
  76. package/content/guides/core-concepts/persistent/index.md +5 -1
  77. package/content/guides/core-concepts/persistent/pglite.md +218 -0
  78. package/content/guides/core-concepts/persistent/postgres-drivers.md +21 -15
  79. package/content/guides/core-concepts/persistent/search-meilisearch.md +22 -12
  80. package/content/guides/core-concepts/persistent/search-typesense.md +26 -14
  81. package/content/guides/core-concepts/persistent/sqlite.md +296 -0
  82. package/content/guides/core-concepts/persistent/transactions.md +12 -11
  83. package/content/guides/get-started/5-minute-quickstart.md +59 -206
  84. package/content/guides/get-started/philosophy.md +135 -670
  85. package/content/guides/get-started/setup.md +53 -74
  86. package/content/guides/migrations/redis-helpers-migration.md +4 -3
  87. package/content/guides/migrations/scoped-rbac-migration.md +5 -5
  88. package/content/guides/migrations/unified-connectors-migration.md +7 -6
  89. package/content/guides/tutorials/realtime-chat.md +1 -1
  90. package/content/references/base/application.md +63 -21
  91. package/content/references/base/components.md +3 -3
  92. package/content/references/base/connectors.md +14 -14
  93. package/content/references/base/controllers.md +12 -12
  94. package/content/references/base/datasources-reference.md +32 -31
  95. package/content/references/base/datasources.md +6 -6
  96. package/content/references/base/dependency-injection.md +12 -11
  97. package/content/references/base/filter-system/application-usage.md +54 -30
  98. package/content/references/base/filter-system/array-operators.md +24 -46
  99. package/content/references/base/filter-system/comparison-operators.md +47 -67
  100. package/content/references/base/filter-system/default-filter.md +59 -53
  101. package/content/references/base/filter-system/fields-order-pagination.md +92 -146
  102. package/content/references/base/filter-system/index.md +25 -13
  103. package/content/references/base/filter-system/json-filtering.md +45 -184
  104. package/content/references/base/filter-system/list-operators.md +23 -53
  105. package/content/references/base/filter-system/logical-operators.md +63 -121
  106. package/content/references/base/filter-system/null-operators.md +34 -104
  107. package/content/references/base/filter-system/pattern-matching.md +40 -55
  108. package/content/references/base/filter-system/quick-reference.md +86 -198
  109. package/content/references/base/filter-system/range-operators.md +18 -46
  110. package/content/references/base/filter-system/tips.md +6 -6
  111. package/content/references/base/filter-system/use-cases.md +33 -15
  112. package/content/references/base/grpc-controllers.md +53 -17
  113. package/content/references/base/index.md +5 -3
  114. package/content/references/base/middlewares.md +11 -10
  115. package/content/references/base/models-reference.md +17 -17
  116. package/content/references/base/models.md +4 -3
  117. package/content/references/base/providers.md +8 -8
  118. package/content/references/base/repositories/advanced.md +224 -326
  119. package/content/references/base/repositories/index.md +19 -6
  120. package/content/references/base/repositories/mixins.md +5 -5
  121. package/content/references/base/repositories/relations.md +160 -293
  122. package/content/references/base/repositories/soft-deletable.md +16 -6
  123. package/content/references/base/secrets.md +17 -13
  124. package/content/references/base/services.md +6 -4
  125. package/content/references/configuration/environment-variables.md +31 -23
  126. package/content/references/configuration/index.md +6 -4
  127. package/content/references/index.md +1 -1
  128. package/content/references/utilities/duration.md +85 -0
  129. package/content/references/utilities/index.md +5 -1
  130. package/content/references/utilities/jsx-reference.md +11 -11
  131. package/content/references/utilities/jsx.md +2 -2
  132. package/content/references/utilities/module.md +78 -25
  133. package/content/references/utilities/request.md +2 -1
  134. package/content/references/utilities/retry.md +139 -0
  135. package/content/references/utilities/schema.md +2 -2
  136. package/content/references/utilities/statuses-reference.md +4 -4
  137. package/content/references/utilities/statuses.md +5 -5
  138. package/dist/mcp-server/index.js +0 -0
  139. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
  140. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts.map +1 -1
  141. package/dist/mcp-server/tools/github/list-project-files.tool.js +3 -3
  142. package/dist/mcp-server/tools/github/search-code.tool.js +1 -1
  143. package/dist/mcp-server/tools/github/verify-dependencies.tool.js +1 -1
  144. package/dist/mcp-server/tools/github/view-source-file.tool.js +1 -1
  145. package/package.json +17 -16
@@ -6,7 +6,9 @@ difficulty: intermediate
6
6
 
7
7
  # Socket.IO
8
8
 
9
- `SocketIOServerHelper` and `SocketIOClientHelper` wrap `socket.io` with a mandatory post-connection authentication handshake, room management, and a Redis adapter so events reach clients no matter which server instance they are connected to.
9
+ `SocketIOServerHelper` and `SocketIOClientHelper` wrap `socket.io` with a mandatory post-connection authentication handshake and room management. A Redis adapter makes sure events reach a client no matter which server instance it's connected to.
10
+
11
+ These are the raw classes: construct and `configure()` them yourself. Need the server helper inside an IGNIS app instead? [`SocketIOComponent`](/extensions/components/socket-io/) wires it into DI for you, on both Node.js and Bun.
10
12
 
11
13
  ## In one example
12
14
 
@@ -58,14 +60,28 @@ const client = new SocketIOClientHelper({
58
60
 
59
61
  ## How it works
60
62
 
61
- - **Two independent helpers.** `SocketIOServerHelper` wraps a `socket.io` `Server`; `SocketIOClientHelper` wraps a `socket.io-client` `Socket`. Both extend `BaseHelper`, and the client can talk to any `socket.io` server, not only this one.
63
+ - **Two independent helpers, both extending `BaseHelper`:**
64
+
65
+ | Helper | Wraps |
66
+ |--------|-------|
67
+ | `SocketIOServerHelper` | a `socket.io` `Server` |
68
+ | `SocketIOClientHelper` | a `socket.io-client` `Socket` |
69
+
70
+ - **The client isn't locked to this server.** It can talk to any `socket.io` server, not only this one.
62
71
  - **Runtime-agnostic server.** Pass a Node.js `http.Server` for `runtime: 'node'`, or an `@socket.io/bun-engine` instance for `runtime: 'bun'`.
63
- - **Redis is mandatory server-side.** `configure()` duplicates the parent `redisConnection` into three dedicated clients: `redisPub`/`redisSub` power `@socket.io/redis-adapter` for cross-instance room broadcast, and `redisEmitter` powers `@socket.io/redis-emitter` for `send()`.
64
- - **Boot fails fast on a broken Redis connection.** `configure()` waits for all three clients to reach `ready` and rejects after 30 seconds if any never do, so a broken Redis connection fails boot instead of hanging it.
65
- - **Authentication is a step separate from connecting.** A client connects at the transport level in state `UNAUTHORIZED`, then must emit `'authenticate'`. The server calls `authenticateFn(handshake)`; only `true` moves the client to `AUTHENTICATED` and joins it to `defaultRooms`.
66
- - **Unauthenticated clients time out.** A client that never authenticates within `authenticateTimeout` is disconnected - including one whose `authenticateFn` is still pending when the timeout fires.
67
- - **Heartbeat has no pong check.** Once authenticated, the server pings on `pingInterval` as a keep-alive; a silently dead connection is only caught when the underlying transport itself notices.
68
- - **Custom rooms need `validateRoomFn`.** Room joins beyond `defaultRooms` are rejected unless you supply it - without it, every custom join request is dropped.
72
+ - **Redis is mandatory server-side.** `configure()` duplicates the parent `redisConnection` into three dedicated clients:
73
+
74
+ | Client | Powers |
75
+ |--------|--------|
76
+ | `redisPub` + `redisSub` | `@socket.io/redis-adapter` - cross-instance room broadcast |
77
+ | `redisEmitter` | `@socket.io/redis-emitter` - used by `send()` |
78
+
79
+ - **Boot fails fast on a broken Redis connection.** `configure()` waits for all three clients to reach `ready`. It rejects after 30 seconds if any never do, so a broken Redis connection fails boot instead of hanging it.
80
+ - **Authentication is a step separate from connecting.** A client connects at the transport level in state `UNAUTHORIZED`, then must emit `'authenticate'`. The server then calls `authenticateFn(handshake)`.
81
+ - **Only `true` authenticates.** It moves the client to `AUTHENTICATED` and joins it to `defaultRooms`.
82
+ - **Unauthenticated clients time out.** A client that never authenticates within `authenticateTimeout` is disconnected. That includes a client whose `authenticateFn` is still pending when the timeout fires.
83
+ - **Heartbeat has no pong check.** Once authenticated, the server pings on `pingInterval` as a keep-alive. A silently dead connection is only caught when the underlying transport itself notices.
84
+ - **Custom rooms need `validateRoomFn`.** Room joins beyond `defaultRooms` are rejected unless you supply `validateRoomFn`.
69
85
 
70
86
  **Defaults**
71
87
 
@@ -90,7 +106,7 @@ socketServer.send({
90
106
 
91
107
  ### Listen for a custom event
92
108
 
93
- Register on the server with `on()`; subscribe on the client with `subscribe()`.
109
+ Register on the server with `on()`. Subscribe on the client with `subscribe()`.
94
110
 
95
111
  ```typescript
96
112
  socketServer.on({
@@ -106,7 +122,7 @@ client.subscribe({
106
122
 
107
123
  ### Manage rooms
108
124
 
109
- Clients request rooms with `joinRooms()` / `leaveRooms()`; the server filters join requests through `validateRoomFn`.
125
+ Clients request rooms with `joinRooms()` / `leaveRooms()`. The server filters join requests through `validateRoomFn`.
110
126
 
111
127
  ```typescript
112
128
  const socketServer = new SocketIOServerHelper({
@@ -19,6 +19,22 @@ Exhaustive reference for `BaseStorageHelper`, the three `IStorageHelper` backend
19
19
  - [`packages/helpers/src/modules/storage/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/storage/types.ts) - `IStorageHelper` and every option/result type
20
20
  - [`packages/helpers/src/common/constants/mime.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/common/constants/mime.ts) - `MimeTypes` const-class
21
21
 
22
+ ## Find what you need
23
+
24
+ | You want to | Go to |
25
+ |---|---|
26
+ | See which class implements which backend | [Class and Interface Model](#class-and-interface-model) |
27
+ | Construct a backend and see its options | [MinioHelper](#miniohelper) / [BunS3Helper](#buns3helper) / [DiskHelper](#diskhelper) |
28
+ | Understand what `upload()` validates and how it writes files | [upload (template method)](#upload-template-method-shared-by-every-backend) |
29
+ | Validate a name or path before writing | [isValidName](#isvalidname) / [isValidPath](#isvalidpath) |
30
+ | Look up every error message `upload()` can throw | [validateUploadFiles](#upload-template-method-shared-by-every-backend) |
31
+ | Read a file back as a stream | [MinioHelper#getFile](#getfile) / [BunS3Helper#getFile](#getfile-1) / [DiskHelper#getFile](#getfile-2) |
32
+ | List or delete objects in a bucket | per-backend Methods tables ([MinioHelper](#methods), [BunS3Helper](#methods-1), [DiskHelper](#methods-2)) |
33
+ | Cache values in-process (not bucket storage) | [MemoryStorageHelper](#memorystoragehelper) |
34
+ | Look up a type or option shape | [Types Reference](#types-reference) |
35
+ | Compare backend differences at a glance | [Backend Behavior Matrix](#backend-behavior-matrix) |
36
+ | Fix a thrown error message | [Troubleshooting](#troubleshooting) |
37
+
22
38
  ## Class and Interface Model
23
39
 
24
40
  ```
@@ -30,8 +46,9 @@ BaseHelper
30
46
  └── MemoryStorageHelper -- In-memory key-value store (standalone, not IStorageHelper)
31
47
  ```
32
48
 
33
- - **`upload()` is a template method.** It validates the bucket and every file, then calls two protected hooks each backend supplies: `defaultLinkPrefix` (a getter) and `writeObject()` (the actual write).
34
- - **Every other method is backend-specific.** `isBucketExists`, `getBuckets`, `getBucket`, `createBucket`, `removeBucket`, `getFile`, `getStat`, `removeObject`, `removeObjects`, and `listObjects` are declared `abstract` on `BaseStorageHelper` and fully reimplemented per backend - there is no shared logic between a filesystem read and a MinIO `statObject()` call.
49
+ - **`upload()` is a template method.** It validates the bucket and every file. Then it calls two protected hooks each backend supplies: `defaultLinkPrefix` (a getter) and `writeObject()` (the write itself).
50
+ - **Every other method is backend-specific.** `isBucketExists`, `getBuckets`, `getBucket`, `createBucket`, `removeBucket`, `getFile`, `getStat`, `removeObject`, `removeObjects`, and `listObjects` are declared `abstract` on `BaseStorageHelper`.
51
+ - Each one is fully reimplemented per backend - no logic is shared between a filesystem read and a MinIO `statObject()` call.
35
52
 
36
53
  > [!TIP] Typing rule
37
54
  > Declare parameters and bindings as `IStorageHelper` for `MinioHelper` / `BunS3Helper` / `DiskHelper`. `MemoryStorageHelper` does not implement it and has its own standalone API - see [MemoryStorageHelper](#memorystoragehelper).
@@ -81,7 +98,7 @@ constructor(opts: { scope: string; identifier: string })
81
98
  | `scope` | `string` | Logger scope name. |
82
99
  | `identifier` | `string` | Helper identifier. |
83
100
 
84
- Every concrete backend's own constructor supplies defaults (`options.scope ?? <ClassName>`, `options.identifier ?? <ClassName>`) before calling `super()` - `scope`/`identifier` are required here but optional on every subclass's public options type.
101
+ Every concrete backend's constructor calls `super()` with its own defaults: `scope: options.scope ?? <ClassName>`, `identifier: options.identifier ?? <ClassName>`. That's why `scope` and `identifier` are required here, but optional on every subclass's public options type.
85
102
 
86
103
  ### Static properties
87
104
 
@@ -139,7 +156,7 @@ storage.getMimeType('unknown.xyz'); // 'application/octet-stream'
139
156
  isValidName(name: string): boolean
140
157
  ```
141
158
 
142
- Validates a **single path segment** (bucket name or bare file name - must not contain `/`). Used internally by `isValidPath` to validate each segment. Logs a specific error for whichever rule fails.
159
+ Validates a **single path segment**: a bucket name or a bare file name, which must not contain `/`. Used internally by `isValidPath` to validate each segment. Logs a specific error for whichever rule fails.
143
160
 
144
161
  | Rule (checked in order) | Example rejected | Reason |
145
162
  |---|---|---|
@@ -166,7 +183,7 @@ storage.isValidName('.hidden'); // false -- starts with dot
166
183
  isValidPath(pathStr: string, opts?: { maxDepth?: number }): boolean
167
184
  ```
168
185
 
169
- Validates a **full object path** that may include folder segments (e.g. `2025/uploads/report.pdf`). Trims leading/trailing slashes, splits on `/`, validates each segment with `isValidName`, and enforces a maximum folder depth.
186
+ Validates a **full object path** that may include folder segments, for example `2025/uploads/report.pdf`. It trims leading and trailing slashes, splits on `/`, validates each segment with `isValidName`, and enforces a maximum folder depth. See the exact rules below.
170
187
 
171
188
  | Rule (checked in order) | Description |
172
189
  |---|---|
@@ -191,7 +208,7 @@ storage.isValidPath('a/b/c/d/file.pdf'); // false -- exceeds default max dep
191
208
  getFileType(opts: { mimeType: string }): string
192
209
  ```
193
210
 
194
- Categorizes a MIME type using the `MimeTypes` const-class (`UNKNOWN`, `IMAGE`, `VIDEO`, `TEXT`) by checking whether `mimeType` (lowercased) starts with `image`, `video`, or `text`.
211
+ Categorizes a MIME type using the `MimeTypes` const-class: `UNKNOWN`, `IMAGE`, `VIDEO`, `TEXT`. It lowercases `mimeType` first, then checks whether it starts with `image`, `video`, or `text`.
195
212
 
196
213
  ```typescript
197
214
  storage.getFileType({ mimeType: 'image/png' }); // 'image'
@@ -220,9 +237,9 @@ Implemented once on `BaseStorageHelper`; `MinioHelper`, `BunS3Helper`, and `Disk
220
237
  2. Calls `isBucketExists({ name: bucket })`; throws if the bucket does not exist.
221
238
  3. Validates every file (`validateUploadFiles`, below).
222
239
  4. For each file, in parallel via `Promise.all()`:
223
- - Computes `normalizeName` via `normalizeNameFn` if provided, else the default normalizer (lowercase, spaces to `_`, `{folderPath}/` prefix if set).
224
- - Re-validates `normalizeName` with `isValidPath({ maxDepth: maxFolderDepth })` - this catches a traversal payload returned by a **custom** `normalizeNameFn`, even though `originalName` already passed validation.
225
- - Computes `normalizeLink` via `normalizeLinkFn` if provided, else the default (`{defaultLinkPrefix}{bucket}/{normalizeName}`, each `/`-segment `encodeURIComponent`-ed).
240
+ - Computes `normalizeName` via `normalizeNameFn` if provided. Otherwise the default normalizer lowercases the name, replaces spaces with `_`, and prefixes `{folderPath}/` if set.
241
+ - Re-validates `normalizeName` with `isValidPath({ maxDepth: maxFolderDepth })`. This catches a traversal payload returned by a **custom** `normalizeNameFn`, even though `originalName` already passed validation.
242
+ - Computes `normalizeLink` via `normalizeLinkFn` if provided. Otherwise it builds the default: `{defaultLinkPrefix}{bucket}/{normalizeName}`, with each `/`-segment run through `encodeURIComponent`.
226
243
  - Calls the backend's `writeObject({ bucket, normalizeName, file })`.
227
244
  - Logs an info line with `normalizeName`, `normalizeLink`, `mimeType`, `encoding`, `size`, and elapsed time.
228
245
  5. Returns `{ bucketName, objectName, link }` per file.
@@ -234,7 +251,7 @@ Implemented once on `BaseStorageHelper`; `MinioHelper`, `BunS3Helper`, and `Disk
234
251
  | `isValidName(originalName)` | `'[upload] Invalid original file name'` |
235
252
  | If `folderPath` set: segment count vs. `maxFolderDepth ?? DEFAULT_MAX_FOLDER_DEPTH` | `` `[upload] Invalid folder path | depth: {depth} | max: {max}` `` |
236
253
  | If `folderPath` set: `isValidPath(folderPath, { maxDepth })` | `'[upload] Invalid folder path'` |
237
- | `size` must be a number `>= 0` (`undefined`/`null`/negative rejected; `0` is a legal empty file) | `` `[upload] Invalid file size | size: {size}` `` |
254
+ | `size` must be a number `>= 0`. `undefined`, `null`, and negative values are rejected; `0` is a legal empty file. | `` `[upload] Invalid file size | size: {size}` `` |
238
255
 
239
256
  **Also throws:**
240
257
 
@@ -311,12 +328,12 @@ Creates a `minio.Client` internally and stores it as a private `client` field -
311
328
  | `options.scope` | `string` | `'MinioHelper'` | Logger scope name. |
312
329
  | `options.identifier` | `string` | `'MinioHelper'` | Helper identifier. |
313
330
 
314
- All other `minio.ClientOptions` fields (`region`, `transport`, `sessionToken`, `partSize`, `pathStyle`, ...) are also accepted and passed to the client - see the [minio JavaScript SDK docs](https://min.io/docs/minio/linux/developers/javascript/API.html).
331
+ All other `minio.ClientOptions` fields are also accepted and passed to the client: `region`, `transport`, `sessionToken`, `partSize`, `pathStyle`, and more. See the [minio JavaScript SDK docs](https://min.io/docs/minio/linux/developers/javascript/API.html) for the complete list.
315
332
 
316
333
  ### defaultLinkPrefix and writeObject
317
334
 
318
335
  - `defaultLinkPrefix`: `'/static-assets/'`
319
- - `writeObject`: calls `client.putObject(bucket, normalizeName, buffer, size, metadata)` where `metadata` is `{ originalName, normalizeName, size, encoding, mimeType }` - the full upload metadata is persisted server-side and returned later by `getStat()`.
336
+ - `writeObject`: calls `client.putObject(bucket, normalizeName, buffer, size, metadata)`, where `metadata` is `{ originalName, normalizeName, size, encoding, mimeType }`. The full upload metadata is persisted server-side and returned later by `getStat()`.
320
337
 
321
338
  ### Methods
322
339
 
@@ -366,7 +383,7 @@ const fileStream = await minioStorage.getFile({
366
383
  async getStat(opts: { bucket: string; name: string }): Promise<IFileStat>
367
384
  ```
368
385
 
369
- `client.statObject()`. Returns `size`, `metadata` (MinIO's `metaData` - the full dict written by `writeObject`), `lastModified`, `etag`, and `versionId` (if versioning is enabled).
386
+ `client.statObject()`. Returns `size`, `metadata`, `lastModified`, `etag`, and `versionId`. `metadata` is MinIO's `metaData` field: the full dict written by `writeObject`. `versionId` is set only if versioning is enabled.
370
387
 
371
388
  #### listObjects
372
389
 
@@ -428,13 +445,13 @@ Creates a Bun `S3Client` for object operations and stores `{ accessKey, secretKe
428
445
  ### defaultLinkPrefix and writeObject
429
446
 
430
447
  - `defaultLinkPrefix`: `'/static-assets/'`
431
- - `writeObject`: `client.write(normalizeName, buffer, { bucket, type: mimeType })` - only the content type is persisted; unlike `MinioHelper`, no `originalName`/`encoding`/`size` metadata dictionary is stored.
448
+ - `writeObject`: `client.write(normalizeName, buffer, { bucket, type: mimeType })`. Only the content type is persisted. Unlike `MinioHelper`, no `originalName`/`encoding`/`size` metadata dictionary is stored.
432
449
 
433
450
  ### Methods
434
451
 
435
452
  | Method | Behavior |
436
453
  |---|---|
437
- | `isBucketExists` | Returns `false` if the name fails `isValidName()`. Otherwise attempts `client.list({ maxKeys: 1 }, { bucket: name })`; returns `false` on any error (network, missing bucket, etc.). |
454
+ | `isBucketExists` | Returns `false` if the name fails `isValidName()`. Otherwise attempts `client.list({ maxKeys: 1 }, { bucket: name })`, and returns `false` on any error - for example a network failure or a missing bucket. |
438
455
  | `getBuckets` | Signed `GET /`; parses `<Bucket><Name>...<CreationDate>...` from the XML response. |
439
456
  | `getBucket` | Finds the entry in `getBuckets()`; `null` if not found. |
440
457
  | `createBucket` | Signed `PUT /{name}`. Throws `'[createBucket] Invalid name to create bucket!'` on invalid name, or `` `[createBucket] S3 error: {xml}` `` on a non-OK response. |
@@ -504,7 +521,7 @@ async function buildSignedRequest(opts: {
504
521
  ```
505
522
 
506
523
  - **Internal only.** Not exported from the package barrel.
507
- - **Builds the `Authorization` header from scratch.** Uses `crypto.subtle` (HMAC-SHA256 and SHA-256 digest), following the standard SigV4 derivation: `kDate -> kRegion -> kService -> kSigning`.
524
+ - **Builds the `Authorization` header from scratch.** Uses `crypto.subtle` for HMAC-SHA256 and SHA-256 digests, following the standard SigV4 derivation: `kDate -> kRegion -> kService -> kSigning`.
508
525
  - **Signs four headers.** `host`, `x-amz-content-sha256`, `x-amz-date`, and (if present) `x-amz-security-token`.
509
526
  - **Used exclusively for bucket management.** `getBuckets`, `createBucket`, and `removeBucket` on `BunS3Helper`.
510
527
 
@@ -524,7 +541,7 @@ interface IDiskHelperOptions extends IStorageHelperOptions {
524
541
  }
525
542
  ```
526
543
 
527
- Resolves `basePath` to an absolute path with `path.resolve()` and creates it (`fs.mkdirSync(..., { recursive: true })`) if it does not exist.
544
+ Resolves `basePath` to an absolute path with `path.resolve()`. Creates it via `fs.mkdirSync(..., { recursive: true })` if it does not exist.
528
545
 
529
546
  | Parameter | Type | Default | Description |
530
547
  |-----------|------|---------|-------------|
@@ -546,8 +563,9 @@ app_data/storage/ <-- basePath
546
563
 
547
564
  ### defaultLinkPrefix and writeObject
548
565
 
549
- - `defaultLinkPrefix`: `'/static-resources/'` (the one backend that differs from `/static-assets/`).
550
- - `writeObject`: creates the object's parent directory if missing (`fsp.mkdir(dir, { recursive: true })`), then `fsp.writeFile(objectPath, file.buffer)`. No metadata dictionary is persisted alongside the file - `getStat()` derives `mimetype` from the filename at read time.
566
+ - `defaultLinkPrefix`: `'/static-resources/'` - the one backend that differs from `/static-assets/`.
567
+ - `writeObject`: creates the object's parent directory if missing, via `fsp.mkdir(dir, { recursive: true })`. Then it calls `fsp.writeFile(objectPath, file.buffer)`.
568
+ - No metadata dictionary is persisted alongside the file. `getStat()` derives `mimetype` from the filename at read time instead.
551
569
 
552
570
  ### Methods
553
571
 
@@ -559,7 +577,7 @@ app_data/storage/ <-- basePath
559
577
  | `createBucket` | `fsp.mkdir(bucketPath, { recursive: true })`, then returns `getBucket()`. |
560
578
  | `removeBucket` | `fsp.rmdir(bucketPath)`. |
561
579
  | `removeObject` | Checks the object exists first (`fsp.access`); throws if missing. Otherwise `fsp.unlink(objectPath)`. |
562
- | `removeObjects` | Deletes **sequentially** by calling `removeObject()` per name in a `for` loop - if any file is missing, the error propagates immediately and remaining names are not attempted. |
580
+ | `removeObjects` | Deletes **sequentially** by calling `removeObject()` per name in a `for` loop. If any file is missing, the error propagates immediately and remaining names are not attempted. |
563
581
 
564
582
  **`createBucket` throws:**
565
583
 
@@ -846,7 +864,7 @@ interface IBunS3HelperOptions extends IStorageHelperOptions {
846
864
 
847
865
  ### "[createBucket] Invalid name to create bucket!"
848
866
 
849
- **Cause:** The bucket name failed `isValidName()` - it may contain path traversal characters, start with a dot, contain shell-special characters, or exceed 255 characters.
867
+ **Cause:** The bucket name failed `isValidName()`. See the [validation rules](#isvalidname) for exactly what's rejected.
850
868
 
851
869
  **Fix:**
852
870
 
@@ -865,7 +883,7 @@ await storage.createBucket({ name: 'my-bucket' });
865
883
 
866
884
  ### "[createBucket] Bucket already exists | name: {name}"
867
885
 
868
- **Cause:** `DiskHelper` throws this exact message when `createBucket()` targets a directory that already exists. `MinioHelper` and `BunS3Helper` do not perform this check themselves - an existing bucket instead surfaces whatever the `minio` SDK or the raw S3 `PUT` request returns for that case, which depends on the server.
886
+ **Cause:** `DiskHelper` throws this exact message when `createBucket()` targets a directory that already exists. `MinioHelper` and `BunS3Helper` skip this check. An existing bucket instead surfaces whatever the `minio` SDK or the raw S3 `PUT` request returns, which depends on the server.
869
887
 
870
888
  **Fix:** Check existence first.
871
889
 
@@ -929,7 +947,7 @@ await storage.upload({
929
947
 
930
948
  ### "[upload] Invalid folder path" / "[upload] Invalid folder path | depth: {depth} | max: {max}"
931
949
 
932
- **Cause:** A file's `folderPath` either exceeds `maxFolderDepth` (the depth-specific message) or fails `isValidPath()` for another reason (traversal, invalid segment, the generic message).
950
+ **Cause:** A file's `folderPath` triggers one of two messages. The depth-specific message means it exceeds `maxFolderDepth`. The generic message means it fails `isValidPath()` for another reason, such as a traversal segment.
933
951
 
934
952
  **Fix:** Keep `folderPath` within `maxFolderDepth` (default `2`) segments, and free of `..`/invalid characters.
935
953
 
@@ -950,7 +968,7 @@ const file: IUploadFile = {
950
968
 
951
969
  ### "[upload] Invalid normalized object name | name: {name}"
952
970
 
953
- **Cause:** A custom `normalizeNameFn` returned a value that fails `isValidPath()` - typically a traversal payload (`../../../etc/cron.d/pwn`) or a name exceeding `maxFolderDepth`. This check exists specifically because `originalName` passing validation does not guarantee the function's *output* is safe.
971
+ **Cause:** A custom `normalizeNameFn` returned a value that fails `isValidPath()`. That's typically a traversal payload like `../../../etc/cron.d/pwn`, or a name exceeding `maxFolderDepth`. This check exists because `originalName` passing validation does not guarantee the function's *output* is safe.
954
972
 
955
973
  **Fix:** Ensure `normalizeNameFn` returns a plain relative name/path - no `..` segments, no leading `/`, no more folder segments than `maxFolderDepth` allows.
956
974
 
@@ -10,7 +10,7 @@ Storage gives you one file-storage interface with interchangeable backends for S
10
10
 
11
11
  ## In one example
12
12
 
13
- `DiskHelper` needs no external server, so it is the fastest way to see the shape of the API - create a bucket, upload a file, get a link back.
13
+ `DiskHelper` needs no external server, so it's the fastest way to see the shape of the API. Create a bucket, upload a file, get a link back.
14
14
 
15
15
  ```typescript
16
16
  import { DiskHelper } from '@venizia/ignis-helpers';
@@ -39,12 +39,14 @@ console.log(result);
39
39
 
40
40
  ## How it works
41
41
 
42
- - **`BaseStorageHelper` owns the shared logic.** It's an abstract class implementing `IStorageHelper` - name/path validation, MIME type detection, and the `upload()` orchestration itself (check the bucket exists, validate every file, write in parallel). Each backend only supplies two protected hooks: `defaultLinkPrefix` and `writeObject()`.
43
- - **Everything else is per-backend.** `isBucketExists`, `getBuckets`, `createBucket`, `getFile`, `getStat`, `removeObject`, `listObjects`, and the rest of `IStorageHelper` are implemented independently per backend - a filesystem `stat()` and a MinIO `statObject()` share nothing beyond the return shape.
42
+ - **`BaseStorageHelper` owns the shared logic.** It's an abstract class implementing `IStorageHelper`: name/path validation, MIME type detection, and the `upload()` orchestration itself. Each backend only supplies two protected hooks: `defaultLinkPrefix` and `writeObject()`.
43
+ - **Everything else is per-backend.** `isBucketExists`, `getBuckets`, `createBucket`, `getFile`, `getStat`, `removeObject`, `listObjects`, and the rest of `IStorageHelper` are implemented independently per backend. A filesystem `stat()` and a MinIO `statObject()` share nothing beyond the return shape.
44
44
  - **The three backends are interchangeable.** Write services against `IStorageHelper`, not a concrete class, and swap backends by construction only.
45
- - **`MemoryStorageHelper` is unrelated.** A standalone generic key-value store for in-process caching, extending `BaseHelper` directly - no bucket or file concept.
46
- - **Every write path is validated first.** `originalName` and `folderPath` run through `isValidName()`/`isValidPath()` before touching the filesystem or object store, rejecting path traversal (`../`), shell-injection characters, hidden files, and folder nesting beyond `maxFolderDepth` (default `2`). The same check re-runs on whatever a custom `normalizeNameFn` returns, so a traversal payload smuggled back from application code is rejected too.
47
- - **Two backends stay optional.** `MinioHelper` and `BunS3Helper` live behind separate sub-path exports, so apps that only need `DiskHelper` or `MemoryStorageHelper` don't pull in the `minio` package or require the Bun runtime.
45
+ - **`MemoryStorageHelper` is unrelated.** It's a standalone generic key-value store for in-process caching, extending `BaseHelper` directly - no bucket or file concept.
46
+ - **Every write path is validated first.** `originalName` and `folderPath` run through `isValidName()`/`isValidPath()` before touching the filesystem or object store.
47
+ - **Validation blocks four kinds of bad input:** path traversal (`../`), shell-injection characters, hidden files, and folder nesting beyond `maxFolderDepth` (default `2`).
48
+ - **A custom `normalizeNameFn` doesn't get a free pass.** Its output runs through the same check, so a traversal payload smuggled back from application code is rejected too.
49
+ - **Two backends stay optional.** `MinioHelper` and `BunS3Helper` live behind separate sub-path exports. Apps that only need `DiskHelper` or `MemoryStorageHelper` don't pull in the `minio` package or require the Bun runtime.
48
50
 
49
51
  **Backends**
50
52
 
@@ -124,7 +126,8 @@ import type { IStorageHelper, IUploadFile } from '@venizia/ignis-helpers';
124
126
  class FileService {
125
127
  constructor(private storage: IStorageHelper) {}
126
128
 
127
- uploadFile(bucket: string, file: IUploadFile) {
129
+ uploadFile(opts: { bucket: string; file: IUploadFile }) {
130
+ const { bucket, file } = opts;
128
131
  return this.storage.upload({ bucket, files: [file] });
129
132
  }
130
133
  }
@@ -6,7 +6,7 @@ difficulty: beginner
6
6
 
7
7
  # Types
8
8
 
9
- `@venizia/ignis-helpers` exports the utility types, lazy-value resolvers, and constant classes (`HTTP`, `GRPC`, `RuntimeModules`, ...) that the rest of the Ignis stack builds on.
9
+ `@venizia/ignis-helpers` exports the utility types, lazy-value resolvers, and constant classes (`HTTP`, `GRPC`, `RuntimeModules`, ...) that the rest of the IGNIS stack builds on.
10
10
 
11
11
  ## In one example
12
12
 
@@ -26,10 +26,23 @@ configure({ timeout: () => 5000 });
26
26
 
27
27
  ## How it works
28
28
 
29
- - **Escape hatches, not the norm.** `AnyType` (`any`) and `AnyObject` (`Record<string | symbol | number, any>`) exist for the rare case a shape truly cannot be known at compile time - the framework itself prefers types derived from definitions (`typeof X.schema`) everywhere else.
30
- - **Nullable and async are explicit.** `TNullable<T>` (`T | undefined | null`) and `ValueOrPromise<T>` (`T | Promise<T>`) appear on most framework method signatures, since sync and async implementations share one type.
31
- - **Resolvers defer construction.** `TResolver`/`TAsyncResolver` and their `TValueOrResolver`/`TValueOrAsyncResolver` unions let a config option be given eagerly or lazily. `resolveValue`/`resolveValueAsync` collapse either shape to a value - class constructors are always passed through untouched, detected via `isClass()` (re-exported from `@venizia/ignis-inversion`), never invoked as a resolver.
32
- - **Const classes replace string unions.** A class of `static readonly` fields (`HTTP`, `GRPC`, `RuntimeModules`, `MimeTypes`, `DataTypes`) is both a value namespace and, via `TConstValue<typeof X>`, the source of its own union type - one declaration, no duplicated string literals.
29
+ - **Escape hatches, not the norm.** `AnyType` and `AnyObject` exist for the rare case a shape truly cannot be known at compile time. The framework prefers types derived from definitions, like `typeof X.schema`, everywhere else.
30
+
31
+ | Type | Is |
32
+ |---|---|
33
+ | `AnyType` | `any` |
34
+ | `AnyObject` | `Record<string \| symbol \| number, any>` |
35
+
36
+ - **Nullable and async are explicit.** `TNullable<T>` and `ValueOrPromise<T>` appear on most framework method signatures. One declared type covers both the sync and the async implementation.
37
+
38
+ | Type | Shape |
39
+ |---|---|
40
+ | `TNullable<T>` | `T \| undefined \| null` |
41
+ | `ValueOrPromise<T>` | `T \| Promise<T>` |
42
+
43
+ - **Resolvers defer construction.** `TResolver`/`TAsyncResolver` and their `TValueOrResolver`/`TValueOrAsyncResolver` unions let a config option be given eagerly or lazily. `resolveValue`/`resolveValueAsync` collapse either shape to a value.
44
+ - **Class constructors pass through untouched.** `isClass()` detects them, so the resolver never invokes a class as if it were a function. It's re-exported from `@venizia/ignis-inversion` into `helpers`.
45
+ - **Const classes replace string unions.** A class of `static readonly` fields, for example `HTTP` or `RuntimeModules`, is a value namespace. `TConstValue<typeof X>` derives its own union type from those fields - one declaration, no duplicated string literals.
33
46
 
34
47
  ## Common tasks
35
48
 
@@ -105,7 +118,7 @@ function WithTimestamps<T extends TMixinTarget<BaseEntity>>(Base: T) {
105
118
 
106
119
  ### Flatten an intersection type for readable hover tooltips
107
120
 
108
- `TPrettify<T>` collapses `A & B` into a single flat object type, which IDE tooltips render far more readably than a chain of intersections.
121
+ `TPrettify<T>` collapses `A & B` into a single flat object type. IDE tooltips render that far more readably than a chain of intersections.
109
122
 
110
123
  ```typescript
111
124
  import { TPrettify } from '@venizia/ignis-helpers';
@@ -114,7 +127,7 @@ type Merged = TPrettify<{ id: string } & { name: string }>;
114
127
  // Hovers as { id: string; name: string } instead of { id: string } & { name: string }
115
128
  ```
116
129
 
117
- Every type, resolver function, and constant (including the full `HTTP.Headers`, `HTTP.ResultCodes`, and `GRPC` tables) is in the [Full reference](/extensions/helpers/types/reference).
130
+ See the [Full reference](/extensions/helpers/types/reference) for every type, resolver function, and constant - including the full `HTTP.Headers`, `HTTP.ResultCodes`, and `GRPC` tables.
118
131
 
119
132
  ## See also
120
133
 
@@ -17,6 +17,27 @@ Exhaustive reference for every utility type, resolver function, and constant cla
17
17
  - [`packages/helpers/src/common/constants/mime.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/common/constants/mime.ts) - `MimeTypes`
18
18
  - [`packages/helpers/src/common/constants/index.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/common/constants/index.ts) - constants barrel
19
19
 
20
+ ## Find what you need
21
+
22
+ | Looking for | Go to |
23
+ |---|---|
24
+ | An escape hatch (`any`, a loose object) | [General Purpose Types](#general-purpose-types) |
25
+ | A value that may be absent, or a callback that may be async | [Nullable and Promise Types](#nullable-and-promise-types) |
26
+ | A constructor or class type parameter | [Class and Constructor Types](#class-and-constructor-types) |
27
+ | Telling a class from a plain function at runtime | [isClass](#isclass) |
28
+ | Making object keys optional or required, or flattening `A & B` | [Object Utility Types](#object-utility-types) |
29
+ | A union type derived from a const class | [Const Value Extraction Types](#const-value-extraction-types) |
30
+ | An option that accepts an eager value or a resolver function | [Value Resolution Types and Functions](#value-resolution-types-and-functions) |
31
+ | A config-driven field-to-type mapping | [Field Mapping Types](#field-mapping-types) |
32
+ | DI container getter types, the `configure()` interface | [DI and Lifecycle Types](#di-and-lifecycle-types) |
33
+ | JSX component prop types | [JSX Types](#jsx-types) |
34
+ | Query pagination defaults, the application name | [Defaults](#defaults) |
35
+ | Detecting Bun vs Node at runtime | [RuntimeModules](#runtimemodules) |
36
+ | Schema data type constants | [DataTypes](#datatypes) |
37
+ | HTTP headers, methods, status codes | [HTTP](#http) |
38
+ | gRPC methods, headers, status codes | [GRPC](#grpc) |
39
+ | Content-type classification (image, video, text) | [MimeTypes](#mimetypes) |
40
+
20
41
  ## Import paths
21
42
 
22
43
  ```typescript
@@ -76,7 +97,7 @@ import type {
76
97
  import type { Child, FC, PropsWithChildren } from '@venizia/ignis-helpers';
77
98
  ```
78
99
 
79
- All of the above resolve through the root `@venizia/ignis-helpers` barrel, which re-exports `./common` (and therefore `./common/types` and `./common/constants`) in full.
100
+ All of the above resolve through the root `@venizia/ignis-helpers` barrel. It re-exports `./common` in full, which includes `./common/types` and `./common/constants`.
80
101
 
81
102
  ## General Purpose Types
82
103
 
@@ -142,7 +163,7 @@ function MyMixin<T extends TMixinTarget<BaseClass>>(Base: T) {
142
163
  const isClass: <T>(target: any) => target is TClass<T>;
143
164
  ```
144
165
 
145
- Declared in `@venizia/ignis-inversion` and re-exported by `helpers`. The single predicate that tells a constructor from a resolver function - the boot booters, controller factories, and `resolveValue`/`resolveValueAsync`/`resolveClass` all branch on it.
166
+ Declared in `@venizia/ignis-inversion` and re-exported by `helpers`. It's the single predicate that tells a constructor from a resolver function. The boot booters, controller factories, and `resolveValue`/`resolveValueAsync`/`resolveClass` all branch on it.
146
167
 
147
168
  - **Filters to functions with a `prototype`** - true of every non-arrow function, so this alone is not sufficient.
148
169
  - **Decompiles the function via `Function.prototype.toString`** and regex-tests that the source text literally starts with the `class` keyword.
@@ -209,11 +230,13 @@ type TValueOrAsyncResolver<T> = T | TAsyncResolver<T>;
209
230
  const resolveValue: <T>(valueOrResolver: TValueOrResolver<T>) => T;
210
231
  ```
211
232
 
212
- Synchronously resolves a lazy value:
233
+ Synchronously resolves a lazy value.
213
234
 
214
- - **Non-function values** - returned as-is.
215
- - **Class constructors** - returned as-is (detected via `isClass()`, never invoked).
216
- - **Resolver functions** - invoked and the result returned.
235
+ | Input | Result |
236
+ |---|---|
237
+ | Non-function value | Returned as-is |
238
+ | Class constructor | Returned as-is - detected via `isClass()`, never invoked |
239
+ | Resolver function | Invoked; the return value is returned |
217
240
 
218
241
  ### resolveValueAsync
219
242
 
@@ -231,11 +254,13 @@ const resolveClass: <T>(
231
254
  ) => TClass<T> | string;
232
255
  ```
233
256
 
234
- Resolves lazy class references. Handles three cases:
257
+ Resolves lazy class references.
235
258
 
236
- - **String binding keys** - returned as-is (for DI key lookups).
237
- - **Class constructors** - returned as-is.
238
- - **Resolver functions** - invoked via `resolveValue` and the result returned.
259
+ | Input | Result |
260
+ |---|---|
261
+ | String binding key | Returned as-is, for DI key lookups |
262
+ | Class constructor | Returned as-is |
263
+ | Resolver function | Invoked via `resolveValue`; the result is returned |
239
264
 
240
265
  ### Resolution example
241
266
 
@@ -361,7 +386,13 @@ class RuntimeModules {
361
386
  type TRuntimeModule = TConstValue<typeof RuntimeModules>; // 'node' | 'bun'
362
387
  ```
363
388
 
364
- Runtime detection utility. `detect()` returns `'bun'` if `typeof Bun !== 'undefined'`, `'node'` otherwise. `isBun()` and `isNode()` are convenience methods that call `detect()` internally on every invocation - there is no caching.
389
+ Runtime detection utility.
390
+
391
+ | Method | Behavior |
392
+ |---|---|
393
+ | `detect()` | Returns `'bun'` if `typeof Bun !== 'undefined'`, else `'node'` |
394
+ | `isBun()` | Calls `detect()` and compares to `'bun'` - no caching, runs the check every time |
395
+ | `isNode()` | Calls `detect()` and compares to `'node'` - no caching, runs the check every time |
365
396
 
366
397
  ### DataTypes
367
398
 
@@ -439,7 +470,7 @@ Grouped by category, matching the source file's comments:
439
470
  | **Rate limiting** (de facto standard) | `X_RATELIMIT_LIMIT` | `'x-ratelimit-limit'` |
440
471
  | | `X_RATELIMIT_REMAINING` | `'x-ratelimit-remaining'` |
441
472
  | | `X_RATELIMIT_RESET` | `'x-ratelimit-reset'` |
442
- | **Ignis custom** | `REQUEST_TRACING_ID` | `'x-request-id'` |
473
+ | **IGNIS custom** | `REQUEST_TRACING_ID` | `'x-request-id'` |
443
474
  | | `REQUEST_DEVICE_INFO` | `'x-device-info'` |
444
475
  | | `REQUEST_CHANNEL` | `'x-request-channel'` |
445
476
  | | `REQUEST_COUNT_DATA` | `'x-request-count'` |
@@ -489,7 +520,15 @@ Grouped by category, matching the source file's comments:
489
520
  | `HTTP.Methods.QUERY` | `'query'` (RFC 10008 `QUERY` method) |
490
521
 
491
522
  > [!IMPORTANT]
492
- > All `HTTP.Methods.*` tokens are lowercase - `@hono/zod-openapi` route definitions accept no other case. Each network fetcher uppercases at the wire boundary (`method.toUpperCase()`): undici only normalizes `DELETE`/`GET`/`HEAD`/`OPTIONS`/`POST`/`PUT`, so a lowercase `patch` or `query` sent as-is travels verbatim over Node and the server rejects it. Bun's `fetch` uppercases everything, which hides the bug until the app runs on Node.
523
+ > All `HTTP.Methods.*` tokens are lowercase. `@hono/zod-openapi` route definitions accept no other case.
524
+
525
+ Every IGNIS network fetcher uppercases the method at the wire boundary, via `method.toUpperCase()`. But undici, Node's `fetch` implementation, only normalizes six of the eight methods on its own:
526
+
527
+ | Undici normalizes it on its own | Undici does not |
528
+ |---|---|
529
+ | `delete`, `get`, `head`, `options`, `post`, `put` | `patch`, `query` |
530
+
531
+ A lowercase `patch` or `query` sent as-is travels verbatim over Node, and the server rejects it. Bun's `fetch` uppercases every method, which hides the bug until the app runs on Node.
493
532
 
494
533
  #### HTTP.Protocols
495
534
 
@@ -500,7 +539,7 @@ Grouped by category, matching the source file's comments:
500
539
 
501
540
  #### HTTP.ResultCodes
502
541
 
503
- Status codes are grouped by class under `HTTP.ResultCodes.RS_1` through `HTTP.ResultCodes.RS_5` (e.g. `HTTP.ResultCodes.RS_4.NotFound`).
542
+ Status codes are grouped by class under `HTTP.ResultCodes.RS_1` through `HTTP.ResultCodes.RS_5` - for example, `HTTP.ResultCodes.RS_4.NotFound`.
504
543
 
505
544
  | Group | Constant | Value |
506
545
  |-------|----------|-------|