@venizia/ignis-docs 0.0.8 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/README.md +7 -7
  2. package/content/best-practices/api-usage-examples.md +15 -12
  3. package/content/best-practices/architectural-patterns.md +70 -78
  4. package/content/best-practices/architecture-decisions.md +91 -60
  5. package/content/best-practices/code-style-standards/advanced-patterns.md +56 -44
  6. package/content/best-practices/code-style-standards/constants-configuration.md +11 -11
  7. package/content/best-practices/code-style-standards/control-flow.md +5 -2
  8. package/content/best-practices/code-style-standards/documentation.md +13 -13
  9. package/content/best-practices/code-style-standards/function-patterns.md +9 -10
  10. package/content/best-practices/code-style-standards/index.md +1 -1
  11. package/content/best-practices/code-style-standards/naming-conventions.md +10 -8
  12. package/content/best-practices/code-style-standards/route-definitions.md +30 -12
  13. package/content/best-practices/code-style-standards/tooling.md +8 -5
  14. package/content/best-practices/code-style-standards/type-safety.md +13 -12
  15. package/content/best-practices/common-pitfalls.md +56 -37
  16. package/content/best-practices/contribution-workflow.md +13 -14
  17. package/content/best-practices/data-modeling.md +46 -22
  18. package/content/best-practices/deployment-strategies.md +28 -27
  19. package/content/best-practices/error-handling.md +48 -24
  20. package/content/best-practices/index.md +5 -5
  21. package/content/best-practices/performance-optimization.md +40 -31
  22. package/content/best-practices/security-guidelines.md +52 -23
  23. package/content/best-practices/testing-strategies.md +65 -51
  24. package/content/best-practices/troubleshooting-tips.md +24 -24
  25. package/content/extensions/components/{swagger.md → api-reference.md} +40 -31
  26. package/content/extensions/components/authentication/api.md +19 -19
  27. package/content/extensions/components/authentication/errors.md +7 -7
  28. package/content/extensions/components/authentication/index.md +10 -8
  29. package/content/extensions/components/authentication/usage.md +101 -6
  30. package/content/extensions/components/authorization/api.md +45 -25
  31. package/content/extensions/components/authorization/errors.md +6 -6
  32. package/content/extensions/components/authorization/index.md +11 -10
  33. package/content/extensions/components/authorization/usage.md +21 -21
  34. package/content/extensions/components/health-check.md +1 -1
  35. package/content/extensions/components/index.md +5 -5
  36. package/content/extensions/components/mail/errors.md +15 -15
  37. package/content/extensions/components/mail/index.md +1 -2
  38. package/content/extensions/components/mail/usage.md +1 -1
  39. package/content/extensions/components/request-tracker.md +1 -1
  40. package/content/extensions/components/socket-io/api.md +9 -9
  41. package/content/extensions/components/socket-io/errors.md +5 -5
  42. package/content/extensions/components/socket-io/index.md +8 -8
  43. package/content/extensions/components/socket-io/usage.md +1 -1
  44. package/content/extensions/components/static-asset/api.md +17 -4
  45. package/content/extensions/components/static-asset/errors.md +4 -4
  46. package/content/extensions/components/static-asset/index.md +26 -28
  47. package/content/extensions/components/static-asset/usage.md +13 -12
  48. package/content/extensions/components/template/index.md +2 -2
  49. package/content/extensions/components/template/setup-page.md +1 -1
  50. package/content/extensions/components/websocket/api.md +3 -3
  51. package/content/extensions/components/websocket/errors.md +5 -5
  52. package/content/extensions/components/websocket/index.md +5 -5
  53. package/content/extensions/components/websocket/usage.md +3 -3
  54. package/content/extensions/helpers/cron/index.md +2 -2
  55. package/content/extensions/helpers/crypto/index.md +1 -1
  56. package/content/extensions/helpers/env/index.md +27 -12
  57. package/content/extensions/helpers/error/index.md +81 -25
  58. package/content/extensions/helpers/index.md +2 -3
  59. package/content/extensions/helpers/inversion/index.md +15 -7
  60. package/content/extensions/helpers/kafka/compile-binary.md +92 -0
  61. package/content/extensions/helpers/kafka/examples.md +1 -1
  62. package/content/extensions/helpers/kafka/index.md +3 -0
  63. package/content/extensions/helpers/logger/index.md +32 -2
  64. package/content/extensions/helpers/network/index.md +6 -0
  65. package/content/extensions/helpers/queue/index.md +14 -17
  66. package/content/extensions/helpers/redis/index.md +548 -323
  67. package/content/extensions/helpers/socket-io/index.md +14 -10
  68. package/content/extensions/helpers/storage/api.md +44 -8
  69. package/content/extensions/helpers/storage/index.md +43 -7
  70. package/content/extensions/helpers/template/index.md +6 -3
  71. package/content/extensions/helpers/types/index.md +11 -8
  72. package/content/extensions/helpers/websocket/api.md +9 -9
  73. package/content/extensions/helpers/websocket/index.md +7 -7
  74. package/content/extensions/helpers/worker-thread/index.md +2 -2
  75. package/content/extensions/index.md +3 -4
  76. package/content/extensions/src-details/mcp-server.md +18 -24
  77. package/content/guides/core-concepts/application/bootstrapping.md +11 -14
  78. package/content/guides/core-concepts/application/index.md +3 -3
  79. package/content/guides/core-concepts/components.md +19 -10
  80. package/content/guides/core-concepts/dependency-injection.md +6 -3
  81. package/content/guides/core-concepts/grpc-controllers.md +6 -5
  82. package/content/guides/core-concepts/persistent/datasources.md +42 -43
  83. package/content/guides/core-concepts/persistent/index.md +16 -7
  84. package/content/guides/core-concepts/persistent/models.md +24 -20
  85. package/content/guides/core-concepts/persistent/postgres-drivers.md +201 -0
  86. package/content/guides/core-concepts/persistent/repositories.md +40 -23
  87. package/content/guides/core-concepts/persistent/search-meilisearch.md +185 -0
  88. package/content/guides/core-concepts/persistent/search-typesense.md +431 -0
  89. package/content/guides/core-concepts/persistent/transactions.md +61 -25
  90. package/content/guides/core-concepts/rest-controllers.md +12 -9
  91. package/content/guides/core-concepts/services.md +330 -60
  92. package/content/guides/get-started/5-minute-quickstart.md +15 -15
  93. package/content/guides/get-started/philosophy.md +36 -36
  94. package/content/guides/get-started/setup.md +3 -3
  95. package/content/guides/index.md +3 -3
  96. package/content/guides/migrations/redis-helpers-migration.md +177 -0
  97. package/content/guides/migrations/scoped-rbac-migration.md +17 -17
  98. package/content/guides/migrations/unified-connectors-migration.md +113 -0
  99. package/content/guides/reference/glossary.md +19 -12
  100. package/content/guides/reference/mcp-docs-server.md +22 -18
  101. package/content/guides/tutorials/building-a-crud-api.md +37 -44
  102. package/content/guides/tutorials/complete-installation.md +17 -17
  103. package/content/guides/tutorials/ecommerce-api.md +163 -124
  104. package/content/guides/tutorials/realtime-chat.md +181 -135
  105. package/content/guides/tutorials/testing.md +65 -523
  106. package/content/index.md +2 -180
  107. package/content/public/apple-touch-icon.png +0 -0
  108. package/content/public/og-image.png +0 -0
  109. package/content/public/site.webmanifest +11 -0
  110. package/content/references/base/application.md +4 -5
  111. package/content/references/base/bootstrapping.md +18 -5
  112. package/content/references/base/components.md +149 -120
  113. package/content/references/base/connectors.md +178 -0
  114. package/content/references/base/controllers.md +41 -30
  115. package/content/references/base/datasources.md +163 -92
  116. package/content/references/base/dependency-injection.md +34 -22
  117. package/content/references/base/filter-system/application-usage.md +17 -14
  118. package/content/references/base/filter-system/array-operators.md +7 -2
  119. package/content/references/base/filter-system/comparison-operators.md +3 -0
  120. package/content/references/base/filter-system/default-filter.md +89 -71
  121. package/content/references/base/filter-system/fields-order-pagination.md +22 -22
  122. package/content/references/base/filter-system/index.md +6 -3
  123. package/content/references/base/filter-system/json-filtering.md +20 -1
  124. package/content/references/base/filter-system/list-operators.md +1 -1
  125. package/content/references/base/filter-system/logical-operators.md +33 -1
  126. package/content/references/base/filter-system/null-operators.md +30 -1
  127. package/content/references/base/filter-system/quick-reference.md +23 -4
  128. package/content/references/base/filter-system/tips.md +5 -5
  129. package/content/references/base/filter-system/use-cases.md +12 -12
  130. package/content/references/base/grpc-controllers.md +13 -13
  131. package/content/references/base/index.md +24 -12
  132. package/content/references/base/middlewares.md +265 -327
  133. package/content/references/base/models.md +63 -49
  134. package/content/references/base/providers.md +136 -130
  135. package/content/references/base/repositories/advanced.md +59 -58
  136. package/content/references/base/repositories/index.md +115 -91
  137. package/content/references/base/repositories/mixins.md +55 -291
  138. package/content/references/base/repositories/relations.md +54 -64
  139. package/content/references/base/repositories/soft-deletable.md +31 -30
  140. package/content/references/base/services.md +296 -93
  141. package/content/references/configuration/environment-variables.md +49 -31
  142. package/content/references/configuration/index.md +6 -6
  143. package/content/references/index.md +17 -12
  144. package/content/references/quick-reference.md +65 -106
  145. package/content/references/utilities/crypto.md +65 -23
  146. package/content/references/utilities/index.md +3 -3
  147. package/content/references/utilities/jsx.md +6 -4
  148. package/content/references/utilities/module.md +68 -20
  149. package/content/references/utilities/parse.md +4 -14
  150. package/content/references/utilities/promise.md +9 -7
  151. package/content/references/utilities/schema.md +5 -3
  152. package/dist/mcp-server/common/guards.d.ts +8 -0
  153. package/dist/mcp-server/common/guards.d.ts.map +1 -0
  154. package/dist/mcp-server/common/guards.js +14 -0
  155. package/dist/mcp-server/common/guards.js.map +1 -0
  156. package/dist/mcp-server/common/index.d.ts +1 -0
  157. package/dist/mcp-server/common/index.d.ts.map +1 -1
  158. package/dist/mcp-server/common/index.js +1 -0
  159. package/dist/mcp-server/common/index.js.map +1 -1
  160. package/dist/mcp-server/helpers/docs.helper.d.ts.map +1 -1
  161. package/dist/mcp-server/helpers/docs.helper.js +4 -2
  162. package/dist/mcp-server/helpers/docs.helper.js.map +1 -1
  163. package/dist/mcp-server/helpers/github.helper.js +1 -1
  164. package/dist/mcp-server/index.js +7 -2
  165. package/dist/mcp-server/index.js.map +1 -1
  166. package/dist/mcp-server/tools/base.tool.d.ts +6 -2
  167. package/dist/mcp-server/tools/base.tool.d.ts.map +1 -1
  168. package/dist/mcp-server/tools/base.tool.js.map +1 -1
  169. package/dist/mcp-server/tools/docs/search-documents.tool.d.ts +1 -1
  170. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
  171. package/dist/mcp-server/tools/github/search-code.tool.d.ts +1 -1
  172. package/dist/mcp-server/tools/github/search-code.tool.d.ts.map +1 -1
  173. package/dist/mcp-server/tools/github/search-code.tool.js +4 -1
  174. package/dist/mcp-server/tools/github/search-code.tool.js.map +1 -1
  175. package/dist/mcp-server/tools/github/verify-dependencies.tool.d.ts.map +1 -1
  176. package/dist/mcp-server/tools/github/verify-dependencies.tool.js +3 -1
  177. package/dist/mcp-server/tools/github/verify-dependencies.tool.js.map +1 -1
  178. package/package.json +9 -9
  179. package/content/extensions/helpers/testing/index.md +0 -510
  180. package/content/references/base/middleware.md +0 -347
@@ -17,7 +17,7 @@ import {
17
17
  SocketIOClientHelper,
18
18
  SocketIOConstants,
19
19
  SocketIOClientStates,
20
- } from '@venizia/ignis-helpers';
20
+ } from '@venizia/ignis-helpers/socket-io';
21
21
 
22
22
  import type {
23
23
  TSocketIOServerOptions,
@@ -33,7 +33,7 @@ import type {
33
33
  TSocketIOClientConnectedFn,
34
34
  TSocketIOEventHandler,
35
35
  TSocketIOClientState,
36
- } from '@venizia/ignis-helpers';
36
+ } from '@venizia/ignis-helpers/socket-io';
37
37
  ```
38
38
 
39
39
  ## Creating an Instance
@@ -43,16 +43,17 @@ import type {
43
43
  `SocketIOServerHelper` requires a Redis connection for the pub/sub adapter, an HTTP server (Node.js) or Bun engine instance, and an authentication function.
44
44
 
45
45
  ```typescript
46
- import { SocketIOServerHelper } from '@venizia/ignis-helpers';
47
- import { DefaultRedisHelper } from '@venizia/ignis-helpers';
46
+ import { SocketIOServerHelper } from '@venizia/ignis-helpers/socket-io';
47
+ import { RedisSingleHelper } from '@venizia/ignis-helpers';
48
48
  import { createServer } from 'node:http';
49
49
 
50
50
  const httpServer = createServer();
51
51
 
52
- const redisHelper = new DefaultRedisHelper({
52
+ const redisHelper = new RedisSingleHelper({
53
53
  name: 'socket-redis',
54
54
  host: 'localhost',
55
55
  port: 6379,
56
+ password: '',
56
57
  });
57
58
 
58
59
  const socketServer = new SocketIOServerHelper({
@@ -91,8 +92,8 @@ A discriminated union based on the `runtime` field:
91
92
  | `runtime` | `'node' \| 'bun'` | -- | Runtime environment. Determines which server field is required |
92
93
  | `server` | `HTTPServer` | -- | Node.js HTTP server instance. **Required when `runtime` is `'node'`** |
93
94
  | `engine` | `any` | -- | `@socket.io/bun-engine` Server instance. **Required when `runtime` is `'bun'`** |
94
- | `serverOptions` | `Partial<ServerOptions>` | `{}` | Socket.IO `ServerOptions` (cors, path, transports, etc.) |
95
- | `redisConnection` | `DefaultRedisHelper` | -- | **Required.** Redis helper used to create pub, sub, and emitter clients |
95
+ | `serverOptions` | `Partial<ServerOptions>` | -- | **Required by the type.** Socket.IO `ServerOptions` (cors, path, transports, etc.). Pass `{}` when no options are needed; nullish values fall back to `{}` at runtime |
96
+ | `redisConnection` | `IRedisHelper` | -- | **Required.** Redis helper used to create pub, sub, and emitter clients |
96
97
  | `authenticateFn` | `TSocketIOAuthenticateFn` | -- | **Required.** Called with the client's handshake data. Return `true` to accept, `false` to reject |
97
98
  | `validateRoomFn` | `TSocketIOValidateRoomFn` | `undefined` | Called when a client requests to join rooms. Return the allowed subset |
98
99
  | `clientConnectedFn` | `TSocketIOClientConnectedFn` | `undefined` | Called after a client is fully authenticated and has joined default rooms |
@@ -108,7 +109,7 @@ A discriminated union based on the `runtime` field:
108
109
  `SocketIOClientHelper` connects to a Socket.IO server. Configuration is done entirely via the constructor -- `configure()` is called automatically.
109
110
 
110
111
  ```typescript
111
- import { SocketIOClientHelper } from '@venizia/ignis-helpers';
112
+ import { SocketIOClientHelper } from '@venizia/ignis-helpers/socket-io';
112
113
 
113
114
  const socketClient = new SocketIOClientHelper({
114
115
  identifier: 'my-client',
@@ -191,7 +192,7 @@ httpServer.listen(3000, () => {
191
192
  For Bun, pass the `@socket.io/bun-engine` instance instead of an HTTP server:
192
193
 
193
194
  ```typescript
194
- import { SocketIOServerHelper } from '@venizia/ignis-helpers';
195
+ import { SocketIOServerHelper } from '@venizia/ignis-helpers/socket-io';
195
196
 
196
197
  const socketServer = new SocketIOServerHelper({
197
198
  identifier: 'my-bun-server',
@@ -425,6 +426,9 @@ The server uses `@socket.io/redis-adapter` and `@socket.io/redis-emitter` for ho
425
426
 
426
427
  All three connections are initialized and awaited during `configure()`. If the parent client uses `lazyConnect`, the duplicated clients will connect automatically.
427
428
 
429
+ > [!NOTE]
430
+ > `configure()` fails fast: if any of the three Redis clients never reaches the `ready` state, it rejects after **30 seconds** rather than hanging boot indefinitely.
431
+
428
432
  ```typescript
429
433
  // Messages sent via send() use the Redis emitter,
430
434
  // so they reach clients on ANY server instance
@@ -481,7 +485,7 @@ client.shutdown();
481
485
 
482
486
  **Cause:** The `redisConnection` option is missing, `null`, or `undefined`.
483
487
 
484
- **Fix:** Pass a valid `DefaultRedisHelper` instance.
488
+ **Fix:** Pass a valid `IRedisHelper` instance (e.g., `RedisSingleHelper` or `RedisClusterHelper`).
485
489
 
486
490
  ### `[on] Invalid topic to start binding handler`
487
491
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Architecture
4
4
 
5
- The storage system uses a class hierarchy with an abstract base class providing shared logic and two concrete implementations for different backends. `MemoryStorageHelper` is a separate, standalone class that does not participate in the `IStorageHelper` hierarchy.
5
+ The storage system uses a class hierarchy with an abstract base class providing shared logic and three concrete implementations for different backends. `MemoryStorageHelper` is a separate, standalone class that does not participate in the `IStorageHelper` hierarchy.
6
6
 
7
7
  ```
8
8
  BaseHelper
@@ -83,11 +83,11 @@ Returns the MIME type for a filename based on its extension. Extracts the extens
83
83
  isValidName(name: string): boolean
84
84
  ```
85
85
 
86
- Validates a bucket or file name against security rules. Logs specific error messages for each validation failure.
86
+ Validates a **single path segment** (bucket name or bare file name - no slashes) against security rules. Used internally by `isValidPath` to validate each segment. Logs specific error messages for each validation failure.
87
87
 
88
88
  | Parameter | Type | Description |
89
89
  |-----------|------|-------------|
90
- | `name` | `string` | Name to validate. |
90
+ | `name` | `string` | Single-segment name to validate (must not contain `/`). |
91
91
 
92
92
  **Returns:** `true` if the name passes all checks, `false` otherwise.
93
93
 
@@ -102,6 +102,30 @@ Validates a bucket or file name against security rules. Logs specific error mess
102
102
  7. Must not exceed 255 characters (DoS prevention)
103
103
  8. Must not be whitespace-only
104
104
 
105
+ #### isValidPath
106
+
107
+ ```typescript
108
+ isValidPath(pathStr: string, opts?: { maxDepth?: number }): boolean
109
+ ```
110
+
111
+ Validates a **full object path** that may include folder segments (e.g., `2025/uploads/report.pdf`). Splits the path on `/`, validates each segment with `isValidName`, and enforces a maximum folder depth. Used for object name validation where paths with folder structure are allowed.
112
+
113
+ | Parameter | Type | Description |
114
+ |-----------|------|-------------|
115
+ | `pathStr` | `string` | Path string to validate (may contain `/` separators). |
116
+ | `opts.maxDepth` | `number` | Maximum folder depth. Default: `2`. |
117
+
118
+ **Returns:** `true` if the path and all its segments are valid, `false` otherwise.
119
+
120
+ **Validation rules (checked in order):**
121
+
122
+ 1. Must be a non-empty string
123
+ 2. After stripping leading/trailing slashes, must not be empty
124
+ 3. Must not contain empty segments (double slashes, e.g., `a//b`)
125
+ 4. Folder depth must not exceed `maxDepth` (depth = number of `/` separators)
126
+ 5. Every segment must pass `isValidName()`
127
+ 6. Total path length must not exceed 1024 characters
128
+
105
129
  #### getFileType
106
130
 
107
131
  ```typescript
@@ -132,6 +156,7 @@ abstract upload(opts: {
132
156
  files: IUploadFile[];
133
157
  normalizeNameFn?: (opts: { originalName: string; folderPath?: string }) => string;
134
158
  normalizeLinkFn?: (opts: { bucketName: string; normalizeName: string }) => string;
159
+ maxFolderDepth?: number;
135
160
  }): Promise<IUploadResult[]>;
136
161
 
137
162
  abstract getFile(opts: { bucket: string; name: string; options?: any }): Promise<Readable>;
@@ -228,14 +253,19 @@ async upload(opts: {
228
253
  files: IUploadFile[];
229
254
  normalizeNameFn?: (opts: { originalName: string; folderPath?: string }) => string;
230
255
  normalizeLinkFn?: (opts: { bucketName: string; normalizeName: string }) => string;
256
+ maxFolderDepth?: number;
231
257
  }): Promise<IUploadResult[]>
232
258
  ```
233
259
 
234
260
  Uploads files to a MinIO bucket. Returns `[]` if `files` is empty. Validates the bucket exists and all file names/sizes before uploading. Uploads run in parallel via `Promise.all()`.
235
261
 
262
+ | Parameter | Type | Default | Description |
263
+ |-----------|------|---------|-------------|
264
+ | `opts.maxFolderDepth` | `number` | `BaseStorageHelper.DEFAULT_MAX_FOLDER_DEPTH` (`2`) | Folder nesting the caller allows, for both `folderPath` and the normalized object name. |
265
+
236
266
  **Default name normalization:** Lowercased with spaces replaced by `_`. If `folderPath` is set, prepends `{folderPath}/`.
237
267
 
238
- **Default link format:** `/static-assets/{bucket}/{encodeURIComponent(normalizeName)}`
268
+ **Default link format:** `/static-assets/{bucket}/{normalizeName}`, with each `/`-separated segment of `normalizeName` URI-encoded via `encodeURIComponent()`.
239
269
 
240
270
  **Metadata stored:** `originalName`, `normalizeName`, `size`, `encoding`, `mimeType`.
241
271
 
@@ -244,6 +274,7 @@ Uploads files to a MinIO bucket. Returns `[]` if `files` is empty. Validates the
244
274
  - `'[upload] Invalid original file name'`
245
275
  - `'[upload] Invalid folder path'`
246
276
  - `'[upload] Invalid file size'`
277
+ - `'[upload] Invalid normalized object name | name: {name}'` -- the value returned by a custom `normalizeNameFn` is path-validated before use; a traversal payload is rejected here even if the original name passed validation.
247
278
 
248
279
  #### getFile
249
280
 
@@ -401,12 +432,13 @@ async upload(opts: {
401
432
  files: IUploadFile[];
402
433
  normalizeNameFn?: (opts: { originalName: string; folderPath?: string }) => string;
403
434
  normalizeLinkFn?: (opts: { bucketName: string; normalizeName: string }) => string;
435
+ maxFolderDepth?: number;
404
436
  }): Promise<IUploadResult[]>
405
437
  ```
406
438
 
407
- Uploads files using `client.write(name, buffer, { bucket, type })`. Same validation and normalization behavior as MinioHelper.
439
+ Uploads files using `client.write(name, buffer, { bucket, type })`. Same validation and normalization behavior as MinioHelper, including `maxFolderDepth` and the normalized-name path validation.
408
440
 
409
- **Default link format:** `/static-assets/{bucket}/{encodeURIComponent(normalizeName)}`
441
+ **Default link format:** `/static-assets/{bucket}/{normalizeName}`, with each `/`-separated segment of `normalizeName` URI-encoded via `encodeURIComponent()`.
410
442
 
411
443
  #### getFile
412
444
 
@@ -553,6 +585,7 @@ async upload(opts: {
553
585
  files: IUploadFile[];
554
586
  normalizeNameFn?: (opts: { originalName: string; folderPath?: string }) => string;
555
587
  normalizeLinkFn?: (opts: { bucketName: string; normalizeName: string }) => string;
588
+ maxFolderDepth?: number;
556
589
  }): Promise<IUploadResult[]>
557
590
  ```
558
591
 
@@ -560,13 +593,14 @@ Writes files to the bucket directory. Returns `[]` if `files` is empty. Validate
560
593
 
561
594
  **Default name normalization:** Same as MinioHelper -- lowercased with spaces replaced by `_`.
562
595
 
563
- **Default link format:** `/static-resources/{bucket}/{encodeURIComponent(normalizeName)}`
596
+ **Default link format:** `/static-resources/{bucket}/{normalizeName}`, with each `/`-separated segment of `normalizeName` URI-encoded via `encodeURIComponent()`.
564
597
 
565
598
  **Throws:**
566
599
  - `'[upload] Bucket does not exist | name: {bucket}'`
567
600
  - `'[upload] Invalid original file name'`
568
601
  - `'[upload] Invalid folder path'`
569
602
  - `'[upload] Invalid file size'`
603
+ - `'[upload] Invalid normalized object name | name: {name}'` -- the value returned by a custom `normalizeNameFn` is path-validated before it is written to disk; an unvalidated `../../../etc/cron.d/pwn` would otherwise write outside `basePath`.
570
604
 
571
605
  #### getFile
572
606
 
@@ -727,6 +761,7 @@ The unified interface implemented by `MinioHelper`, `BunS3Helper`, and `DiskHelp
727
761
  ```typescript
728
762
  interface IStorageHelper {
729
763
  isValidName(name: string): boolean;
764
+ isValidPath(pathStr: string, opts?: { maxDepth?: number }): boolean;
730
765
 
731
766
  isBucketExists(opts: { name: string }): Promise<boolean>;
732
767
  getBuckets(): Promise<IBucketInfo[]>;
@@ -739,6 +774,7 @@ interface IStorageHelper {
739
774
  files: IUploadFile[];
740
775
  normalizeNameFn?: (opts: { originalName: string; folderPath?: string }) => string;
741
776
  normalizeLinkFn?: (opts: { bucketName: string; normalizeName: string }) => string;
777
+ maxFolderDepth?: number;
742
778
  }): Promise<IUploadResult[]>;
743
779
 
744
780
  getFile(opts: { bucket: string; name: string; options?: any }): Promise<Readable>;
@@ -793,7 +829,7 @@ interface IFileStat {
793
829
  size: number; // File size in bytes
794
830
  metadata: Record<string, any>; // Storage-specific metadata
795
831
  lastModified?: Date; // Last modification date
796
- etag?: string; // Entity tag (MinioHelper only)
832
+ etag?: string; // Entity tag (MinioHelper and BunS3Helper only)
797
833
  versionId?: string; // Version ID (MinioHelper only)
798
834
  }
799
835
  ```
@@ -209,6 +209,23 @@ const results = await storage.upload({
209
209
  });
210
210
  ```
211
211
 
212
+ > [!NOTE]
213
+ > The output of `normalizeNameFn` is validated with `isValidPath()` before it reaches the filesystem or object store - a traversal payload returned from a custom function (e.g. `../../../etc/cron.d/pwn`) is rejected with `'[upload] Invalid normalized object name | name: {name}'`, not trusted just because it came from application code.
214
+
215
+ #### Controlling Folder Depth
216
+
217
+ `upload()` accepts an optional `maxFolderDepth` that overrides `BaseStorageHelper.DEFAULT_MAX_FOLDER_DEPTH` (`2`) for that call. It bounds both the incoming `folderPath` on each file and the folder depth of the resulting normalized object name:
218
+
219
+ ```typescript
220
+ const results = await storage.upload({
221
+ bucket: 'my-bucket',
222
+ files: files,
223
+ maxFolderDepth: 4, // allow up to 4 folder segments instead of the default 2
224
+ });
225
+ ```
226
+
227
+ Omit it to keep the default of `2`.
228
+
212
229
  #### Upload with Folder Path
213
230
 
214
231
  When `folderPath` is provided in an `IUploadFile`, the default normalization creates subdirectory-based paths:
@@ -281,7 +298,7 @@ console.log(stat);
281
298
  // size: 204800,
282
299
  // lastModified: 2025-01-15T10:30:00.000Z,
283
300
  // metadata: { mimetype: 'application/pdf' },
284
- // etag: 'abc123', // MinioHelper only
301
+ // etag: 'abc123', // MinioHelper and BunS3Helper only
285
302
  // versionId: 'v1', // MinioHelper only (if versioning enabled)
286
303
  // }
287
304
  ```
@@ -389,7 +406,14 @@ cache.clear();
389
406
 
390
407
  ### Name Validation
391
408
 
392
- All bucket and file operations validate names using `isValidName()` before execution. The following are rejected:
409
+ Storage helpers use two validation methods depending on context:
410
+
411
+ - **`isValidName(name)`** - validates a single path segment (bucket names, raw file names). Rejects names that contain `/`, `\`, or `..`.
412
+ - **`isValidPath(pathStr)`** - validates a full object path that may include folder segments (e.g., `folder/file.pdf`). Splits on `/` and validates each segment with `isValidName`. Also enforces a max folder depth (default: 2).
413
+
414
+ Bucket operations (create, remove) validate the bucket name with `isValidName()`. `upload()` validates each file's `originalName` with `isValidName()` and, when provided, the file's `folderPath` with `isValidPath()` so that folder structures like `2025/uploads` are accepted. Read and delete operations (`getFile`, `getStat`, `removeObject`, `listObjects`) do not re-validate names.
415
+
416
+ The following single-segment inputs are rejected by `isValidName()`:
393
417
 
394
418
  | Rule | Example | Reason |
395
419
  |------|---------|--------|
@@ -401,9 +425,15 @@ All bucket and file operations validate names using `isValidName()` before execu
401
425
  | Empty or whitespace-only | `""`, `" "` | Invalid input |
402
426
 
403
427
  ```typescript
428
+ // Single-segment name validation (buckets, raw file names)
404
429
  storage.isValidName('my-file.pdf'); // true
405
- storage.isValidName('../etc/passwd'); // false
406
- storage.isValidName('.hidden'); // false
430
+ storage.isValidName('../etc/passwd'); // false - contains path separators
431
+ storage.isValidName('.hidden'); // false - starts with dot
432
+
433
+ // Multi-segment path validation (object names with folder structure)
434
+ storage.isValidPath('folder/file.pdf'); // true
435
+ storage.isValidPath('../etc/passwd'); // false - path traversal
436
+ storage.isValidPath('a/b/c/d/file.pdf'); // false - exceeds max depth (2)
407
437
  ```
408
438
 
409
439
  ### MIME Type Detection
@@ -536,7 +566,7 @@ await storage.removeBucket({ name: 'my-bucket' });
536
566
 
537
567
  ### "[upload] Bucket does not exist | name: {bucket}"
538
568
 
539
- **Cause:** The target bucket does not exist. Both DiskHelper and MinioHelper validate bucket existence before uploading.
569
+ **Cause:** The target bucket does not exist. DiskHelper, MinioHelper, and BunS3Helper all validate bucket existence before uploading.
540
570
 
541
571
  **Fix:** Create the bucket before uploading:
542
572
 
@@ -566,7 +596,7 @@ await storage.upload({
566
596
 
567
597
  ### "[upload] Invalid file size"
568
598
 
569
- **Cause:** A file's `size` property is `0`, `undefined`, or falsy.
599
+ **Cause:** A file's `size` property is `undefined`, `null`, or negative. A zero-byte file is a legal upload - it is not what triggers this error.
570
600
 
571
601
  **Fix:** Ensure every file in the upload array has a valid `size` value:
572
602
 
@@ -575,10 +605,16 @@ const file: IUploadFile = {
575
605
  originalName: 'doc.pdf',
576
606
  mimetype: 'application/pdf',
577
607
  buffer: fileBuffer,
578
- size: fileBuffer.length, // Must be > 0
608
+ size: fileBuffer.length, // Must be a number >= 0
579
609
  };
580
610
  ```
581
611
 
612
+ ### "[upload] Invalid normalized object name | name: {name}"
613
+
614
+ **Cause:** A custom `normalizeNameFn` returned a value that fails `isValidPath()` - typically a path-traversal payload (e.g. `../../../etc/cron.d/pwn`) or a name exceeding `maxFolderDepth`.
615
+
616
+ **Fix:** Ensure `normalizeNameFn` returns a plain relative name/path with no `..` segments, no leading `/`, and no more folder segments than `maxFolderDepth` (default `2`) allows.
617
+
582
618
  ### "[getFile] File not found | bucket: {bucket} | name: {name}"
583
619
 
584
620
  **Cause:** DiskHelper throws when the requested file does not exist on the filesystem.
@@ -1,6 +1,6 @@
1
1
  # Helper Documentation Template
2
2
 
3
- Guide for writing consistent, professional helper reference docs for Ignis.
3
+ Guide for writing consistent, professional helper reference docs for IGNIS.
4
4
 
5
5
  ## Tiers
6
6
 
@@ -21,6 +21,9 @@ Guide for writing consistent, professional helper reference docs for Ignis.
21
21
  | Socket.IO | 2 | Server + Client helpers, Redis integration |
22
22
  | WebSocket | 2 | Largest helper, Server + Emitter, complex internals |
23
23
 
24
+ > [!NOTE]
25
+ > Kafka uses an extended multi-page structure (`index.md` + `producer.md`, `consumer.md`, `admin.md`, `schema-registry.md`, `examples.md`) that goes beyond the Tier 2 two-page pattern.
26
+
24
27
  ## Principles
25
28
 
26
29
  - **Usage-first** -- Show working code early, not abstract API tables
@@ -54,13 +57,13 @@ Use **GitHub-style only**:
54
57
  |----------|---------|
55
58
  | [Single Page](./single-page) | Tier 1 template -- one file per helper |
56
59
 
57
- Tier 2 follows the same two-page pattern as [Component Tier 2](../../components/template/).
60
+ Tier 2 helpers use two pages (`index.md` + `api.md`) -- see the existing Tier 2 helper docs (Network, Storage, Socket.IO, WebSocket) for the pattern. Note this differs from [Component Tier 2](../../components/template/), which is a four-page set.
58
61
 
59
62
  ## Source Paths
60
63
 
61
64
  | Package | Path |
62
65
  |---------|------|
63
- | Helpers | `packages/helpers/src/helpers/{name}/` |
66
+ | Helpers | `packages/helpers/src/modules/{name}/` |
64
67
  | Inversion | `packages/inversion/src/` |
65
68
  | Common types | `packages/helpers/src/common/types.ts` |
66
69
  | Utilities | `packages/helpers/src/utilities/` |
@@ -8,6 +8,7 @@ Shared utility types, resolver functions, and constants exported from `@venizia/
8
8
  |------|-------|
9
9
  | **Package** | `@venizia/ignis-helpers` |
10
10
  | **Module** | Common types and constants |
11
+ | **Source** | `packages/helpers/src/common/types.ts` (types + resolvers), `packages/helpers/src/common/constants/` (constants) |
11
12
  | **Runtimes** | Both |
12
13
 
13
14
  #### Import Paths
@@ -195,7 +196,7 @@ Synchronously resolves a lazy value:
195
196
  - **Class constructors** -- returned as-is (not invoked)
196
197
  - **Resolver functions** -- invoked and result returned
197
198
 
198
- Class constructors are detected via `isClassConstructor()` from `@venizia/ignis-inversion` and are never called as resolver functions.
199
+ Class constructors are detected via `isClass()` (a source-level class-syntax check), which lives in `@venizia/ignis-inversion` and is re-exported by `helpers`; they are never called as resolver functions. `isClassConstructor` does not exist.
199
200
 
200
201
  #### resolveValueAsync
201
202
 
@@ -369,7 +370,7 @@ Schema data type constants with validation. `SCHEME_SET` contains all valid type
369
370
 
370
371
  The `HTTP` class groups all HTTP-related constants into nested objects.
371
372
 
372
- **Headers:**
373
+ **Headers** (commonly used subset -- the full list in `packages/helpers/src/common/constants/http.ts` also covers content negotiation, CORS, security, proxy/forwarding, and rate-limit headers):
373
374
 
374
375
  | Constant | Value |
375
376
  |----------|-------|
@@ -389,7 +390,7 @@ The `HTTP` class groups all HTTP-related constants into nested objects.
389
390
  | `HTTP.Headers.RESPONSE_COUNT_DATA` | `'x-response-count'` |
390
391
  | `HTTP.Headers.RESPONSE_FORMAT` | `'x-response-format'` |
391
392
 
392
- **Header Values:**
393
+ **Header Values** (commonly used subset -- the full list also covers XML, PDF, zip, image, CSS/CSV/HTML, and event-stream types):
393
394
 
394
395
  | Constant | Value |
395
396
  |----------|-------|
@@ -413,11 +414,13 @@ The `HTTP` class groups all HTTP-related constants into nested objects.
413
414
 
414
415
  **Result Codes:**
415
416
 
417
+ Status codes are grouped by class under `HTTP.ResultCodes.RS_1` through `HTTP.ResultCodes.RS_5` (e.g. `HTTP.ResultCodes.RS_4.NotFound`).
418
+
416
419
  | Group | Constant | Value |
417
420
  |-------|----------|-------|
418
- | Top-level | `RS_FAIL` | `0` |
419
- | | `RS_SUCCESS` | `1` |
420
- | | `RS_UNKNOWN_ERROR` | `-199` |
421
+ | **RS_1** (1xx) | `Continue` | `100` |
422
+ | | `SwitchingProtocols` | `101` |
423
+ | | `EarlyHints` | `103` |
421
424
  | **RS_2** (2xx) | `Ok` | `200` |
422
425
  | | `Created` | `201` |
423
426
  | | `Accepted` | `202` |
@@ -470,8 +473,8 @@ The `HTTP` class groups all HTTP-related constants into nested objects.
470
473
  **Derived Types:**
471
474
 
472
475
  ```typescript
473
- type THttpMethod = ValueOf<typeof HTTP.Methods>; // 'get' | 'post' | 'put' | ...
474
- type THttpResultCode = ValueOf<typeof HTTP.ResultCodes>; // 0 | 1 | -199 | { Ok: 200, ... } | ...
476
+ type THttpMethod = ValueOf<typeof HTTP.Methods>; // 'get' | 'post' | 'put' | ...
477
+ type THttpResultCode = ValueOf<typeof HTTP.ResultCodes>; // union of the RS_1..RS_5 group objects
475
478
  ```
476
479
 
477
480
  #### GRPC
@@ -17,8 +17,8 @@ The WebSocket helper provides two classes: `WebSocketServerHelper` for managing
17
17
  | |-- Store callbacks (auth, rooms, messages) |
18
18
  | |-- Apply defaults (rooms, timeouts) |
19
19
  | +-- initRedisClients(redisConnection) |
20
- | +-- redisPub = client.duplicate() |
21
- | +-- redisSub = client.duplicate() |
20
+ | +-- redisPub = duplicateClient() |
21
+ | +-- redisSub = duplicateClient() |
22
22
  | |
23
23
  | configure() [async] |
24
24
  | |-- Connect Redis clients (if lazyConnect) |
@@ -39,7 +39,7 @@ The WebSocket helper provides two classes: `WebSocketServerHelper` for managing
39
39
  WebSocketEmitter
40
40
  +---------------------------------------------------+
41
41
  | constructor(opts) |
42
- | +-- redisPub = client.duplicate() |
42
+ | +-- redisPub = duplicateClient() |
43
43
  | |
44
44
  | configure() [async] |
45
45
  | +-- await Redis ready |
@@ -95,14 +95,14 @@ Client connects via WebSocket upgrade
95
95
  #### Redis 2-Client Architecture
96
96
 
97
97
  ```
98
- RedisHelper (parent -- NOT consumed)
98
+ RedisSingleHelper (parent -- NOT consumed)
99
99
  |
100
- +-- client.duplicate() --> redisPub (publishes cross-instance messages)
100
+ +-- duplicateClient() --> redisPub (publishes cross-instance messages)
101
101
  |
102
- +-- client.duplicate() --> redisSub (subscribes to cross-instance messages)
102
+ +-- duplicateClient() --> redisSub (subscribes to cross-instance messages)
103
103
  ```
104
104
 
105
- Both single-instance `Redis` and `Cluster` connections from ioredis are supported. The parent `RedisHelper` connection remains independent.
105
+ Both single-instance `Redis` and `Cluster` connections from ioredis are supported. The parent `RedisSingleHelper` connection remains independent.
106
106
 
107
107
  ## Server API
108
108
 
@@ -549,7 +549,7 @@ interface IWebSocketServerOptions<
549
549
  > {
550
550
  identifier: string;
551
551
  path?: string; // Default: '/ws'
552
- redisConnection: DefaultRedisHelper;
552
+ redisConnection: IRedisHelper;
553
553
  server: IBunServer;
554
554
  defaultRooms?: string[]; // Default: ['ws-default', 'ws-notification']
555
555
  serverOptions?: IBunWebSocketConfig;
@@ -570,7 +570,7 @@ interface IWebSocketServerOptions<
570
570
 
571
571
  interface IWebSocketEmitterOptions {
572
572
  identifier?: string; // Default: 'WebSocketEmitter'
573
- redisConnection: DefaultRedisHelper;
573
+ redisConnection: IRedisHelper;
574
574
  }
575
575
  ```
576
576
 
@@ -58,7 +58,7 @@ const helper = new WebSocketServerHelper({
58
58
  identifier: 'my-ws-server',
59
59
  path: '/ws',
60
60
  server: bunServerInstance, // Bun.Server
61
- redisConnection: myRedisHelper, // DefaultRedisHelper
61
+ redisConnection: myRedisHelper, // IRedisHelper
62
62
  authenticateFn: async (data) => {
63
63
  const { token } = data as { token: string };
64
64
  const user = await verifyJWT(token);
@@ -89,7 +89,7 @@ await helper.configure();
89
89
  | `identifier` | `string` | Yes | -- | Unique name for this WebSocket server instance |
90
90
  | `path` | `string` | No | `'/ws'` | URL path for WebSocket upgrade requests |
91
91
  | `server` | `IBunServer` | Yes | -- | Bun server instance (provides `publish()` for native pub/sub) |
92
- | `redisConnection` | `DefaultRedisHelper` | Yes | -- | Redis helper for cross-instance messaging. Creates 2 duplicate connections internally |
92
+ | `redisConnection` | `IRedisHelper` | Yes | -- | Redis helper for cross-instance messaging. Creates 2 duplicate connections internally |
93
93
  | `defaultRooms` | `string[]` | No | `['ws-default', 'ws-notification']` | Rooms clients auto-join after authentication |
94
94
  | `serverOptions` | `IBunWebSocketConfig` | No | See defaults below | Bun native WebSocket configuration |
95
95
  | `authTimeout` | `number` | No | `5000` (5s) | Milliseconds before unauthenticated clients are disconnected (close code `4001`) |
@@ -155,7 +155,7 @@ await emitter.configure();
155
155
  | Option | Type | Required | Default | Description |
156
156
  |--------|------|----------|---------|-------------|
157
157
  | `identifier` | `string` | No | `'WebSocketEmitter'` | Unique name for logging |
158
- | `redisConnection` | `DefaultRedisHelper` | Yes | -- | Redis helper. Creates 1 duplicate connection internally |
158
+ | `redisConnection` | `IRedisHelper` | Yes | -- | Redis helper. Creates 1 duplicate connection internally |
159
159
 
160
160
  ## Usage
161
161
 
@@ -555,20 +555,20 @@ Authentication succeeds but the client is immediately closed with code `4004`.
555
555
 
556
556
  ### "[WebSocketServerHelper] Invalid redis connection!"
557
557
 
558
- Thrown during construction when `redisConnection` is `null`/`undefined`. Ensure you pass a valid `DefaultRedisHelper` instance.
558
+ Thrown during construction when `redisConnection` is `null`/`undefined`. Ensure you pass a valid `IRedisHelper` instance (e.g., `RedisSingleHelper`).
559
559
 
560
560
  ### "[WebSocketEmitter] Invalid redis connection!"
561
561
 
562
- Thrown during `WebSocketEmitter` construction when `redisConnection` is `null`/`undefined`. Ensure you pass a valid `DefaultRedisHelper` instance.
562
+ Thrown during `WebSocketEmitter` construction when `redisConnection` is `null`/`undefined`. Ensure you pass a valid `IRedisHelper` instance (e.g., `RedisSingleHelper`).
563
563
 
564
564
  ### "Redis client did not become ready within 30000ms"
565
565
 
566
- Thrown during `configure()` when a Redis client fails to reach `ready` status. Check that the Redis server is reachable and the parent `DefaultRedisHelper` connection is properly configured.
566
+ Thrown during `configure()` when a Redis client fails to reach `ready` status. Check that the Redis server is reachable and the `IRedisHelper` instance is properly configured.
567
567
 
568
568
  ## See Also
569
569
 
570
570
  - [API Reference](./api) -- Full method signatures, types, and constants
571
571
  - [Socket.IO Helper](../socket-io/) -- Socket.IO-based alternative with Node.js support
572
- - [Redis Helper](../redis/) -- `RedisHelper` used for cross-instance messaging
572
+ - [Redis Helper](../redis/) -- `RedisSingleHelper` or `RedisClusterHelper` used for cross-instance messaging
573
573
  - [Crypto Helper](../crypto/) -- ECDH key exchange for WebSocket encryption
574
574
  - [WebSocket Component](/extensions/components/websocket/) -- Component-level lifecycle integration
@@ -94,7 +94,7 @@ const worker = new BaseWorkerHelper<MyMessageType>({
94
94
  | `identifier` | `string` | -- | A unique name for this worker instance, used in log output. Required. |
95
95
  | `path` | `string \| URL` | -- | Path to the worker script file. Required. |
96
96
  | `options` | `WorkerOptions` | -- | Node.js `WorkerOptions` passed directly to `new Worker()`. Required. Supports `workerData`, `transferList`, `env`, etc. |
97
- | `scope` | `string` | `'BaseWorkerHelper'` | Logger scope prefix. |
97
+ | `scope` | `string` | `'BaseWorkerHelper'` | Accepted but currently ignored -- the constructor always sets the logger scope to `'BaseWorkerHelper'`. |
98
98
  | `eventHandlers` | `Partial<Pick<IWorker<MessageType>, ...>>` | `undefined` | Optional overrides for lifecycle event callbacks. Any handler not provided falls back to default logging behavior. |
99
99
 
100
100
  #### Event Handler Overrides
@@ -425,7 +425,7 @@ const bus = thread.getWorkerBus({ key: 'my-bus' });
425
425
 
426
426
  ### "Failed to post message to main | Invalid parentPort!"
427
427
 
428
- **Cause:** `BaseWorkerBusHelper.postMessage()` was called but the `port` property is null or undefined. This typically means the bus was constructed with an invalid `MessagePort`.
428
+ **Cause:** `BaseWorkerBusHelper.postMessage()` was called but the `port` property is null or undefined. This typically means the bus was constructed with an invalid `MessagePort`. Note this is logged at error level, not thrown -- the message is silently dropped.
429
429
 
430
430
  **Fix:** Ensure a valid `MessagePort` (e.g., `parentPort` from `node:worker_threads` or a port from `new MessageChannel()`) is passed to the constructor:
431
431
 
@@ -1,6 +1,6 @@
1
1
  # Extensions
2
2
 
3
- Extensions are optional packages and built-in modules that add functionality on top of the Ignis core framework. They are organized into two categories:
3
+ Extensions are optional packages and built-in modules that add functionality on top of the IGNIS core framework. They are organized into two categories:
4
4
 
5
5
  ## Components
6
6
 
@@ -10,12 +10,12 @@ Components are self-contained feature modules that plug into your application vi
10
10
  | :--- | :--- | :--- |
11
11
  | [Authentication](./components/authentication/) | Identity verification | JWT, Basic, JWKS strategies |
12
12
  | [Authorization](./components/authorization/) | Access control | Casbin-based RBAC, per-route policies |
13
- | [Health Check](./components/health-check) | Liveness & readiness | `/health`, `/health/live`, `/health/ready` |
13
+ | [Health Check](./components/health-check) | Liveness check | `GET /health`, `POST /health/ping` |
14
14
  | [Mail](./components/mail/) | Email delivery | Nodemailer, Mailgun, queue support |
15
15
  | [Request Tracker](./components/request-tracker) | Request tracing | `x-request-id` header, body parsing |
16
16
  | [Socket.IO](./components/socket-io/) | Real-time (Socket.IO) | Redis adapter, room-based messaging |
17
17
  | [Static Asset](./components/static-asset/) | File management | Upload/download, MinIO, Disk, BunS3 |
18
- | [Swagger](./components/swagger) | API docs | OpenAPI UI, Swagger UI, Scalar UI |
18
+ | [Swagger](./components/api-reference) | API docs | OpenAPI UI, Swagger UI, Scalar UI |
19
19
  | [WebSocket](./components/websocket/) | Real-time (native) | Bun native WebSocket, encryption |
20
20
 
21
21
  ## Helpers
@@ -36,7 +36,6 @@ Helpers are standalone utility classes for infrastructure concerns. They extend
36
36
  | [Redis](./helpers/redis/) | Redis client | `ioredis` |
37
37
  | [Socket.IO](./helpers/socket-io/) | Socket.IO server | `socket.io` |
38
38
  | [Storage](./helpers/storage/) | File storage | `minio` (optional) |
39
- | [Testing](./helpers/testing/) | Test utilities | Built-in |
40
39
  | [Types](./helpers/types/) | Shared types | Built-in |
41
40
  | [UID](./helpers/uid/) | Snowflake IDs | Built-in |
42
41
  | [WebSocket](./helpers/websocket/) | WebSocket server | Built-in |