@venizia/ignis-docs 0.2.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 (174) hide show
  1. package/README.md +14 -14
  2. package/content/best-practices/api-usage-examples.md +39 -19
  3. package/content/best-practices/architectural-patterns.md +24 -13
  4. package/content/best-practices/architecture-decisions.md +29 -16
  5. package/content/best-practices/code-style-standards/advanced-patterns.md +43 -31
  6. package/content/best-practices/code-style-standards/constants-configuration.md +19 -4
  7. package/content/best-practices/code-style-standards/control-flow.md +33 -1
  8. package/content/best-practices/code-style-standards/documentation.md +28 -8
  9. package/content/best-practices/code-style-standards/function-patterns.md +15 -4
  10. package/content/best-practices/code-style-standards/index.md +7 -2
  11. package/content/best-practices/code-style-standards/naming-conventions.md +27 -3
  12. package/content/best-practices/code-style-standards/route-definitions.md +9 -5
  13. package/content/best-practices/code-style-standards/tooling.md +14 -1
  14. package/content/best-practices/code-style-standards/type-safety.md +39 -6
  15. package/content/best-practices/common-pitfalls.md +59 -35
  16. package/content/best-practices/contribution-workflow.md +8 -4
  17. package/content/best-practices/data-modeling.md +78 -62
  18. package/content/best-practices/deployment-strategies.md +56 -19
  19. package/content/best-practices/error-handling.md +247 -153
  20. package/content/best-practices/index.md +6 -0
  21. package/content/best-practices/performance-optimization.md +26 -13
  22. package/content/best-practices/security-guidelines.md +35 -9
  23. package/content/best-practices/testing-strategies.md +7 -2
  24. package/content/best-practices/troubleshooting-tips.md +27 -17
  25. package/content/extensions/components/api-reference.md +109 -323
  26. package/content/extensions/components/authentication/api.md +489 -602
  27. package/content/extensions/components/authentication/errors.md +135 -498
  28. package/content/extensions/components/authentication/index.md +89 -801
  29. package/content/extensions/components/authentication/usage.md +231 -955
  30. package/content/extensions/components/authorization/api.md +991 -644
  31. package/content/extensions/components/authorization/errors.md +204 -208
  32. package/content/extensions/components/authorization/getting-started.md +227 -0
  33. package/content/extensions/components/authorization/index.md +88 -795
  34. package/content/extensions/components/authorization/usage.md +219 -528
  35. package/content/extensions/components/health-check.md +77 -243
  36. package/content/extensions/components/index.md +24 -90
  37. package/content/extensions/components/mail/api.md +553 -285
  38. package/content/extensions/components/mail/errors.md +76 -62
  39. package/content/extensions/components/mail/index.md +111 -463
  40. package/content/extensions/components/mail/usage.md +139 -173
  41. package/content/extensions/components/request-tracker.md +70 -173
  42. package/content/extensions/components/socket-io/api.md +462 -785
  43. package/content/extensions/components/socket-io/errors.md +49 -51
  44. package/content/extensions/components/socket-io/index.md +69 -372
  45. package/content/extensions/components/socket-io/usage.md +212 -105
  46. package/content/extensions/components/static-asset/api.md +461 -141
  47. package/content/extensions/components/static-asset/errors.md +121 -53
  48. package/content/extensions/components/static-asset/index.md +84 -606
  49. package/content/extensions/components/static-asset/usage.md +184 -299
  50. package/content/extensions/components/template/index.md +3 -3
  51. package/content/extensions/components/websocket/api.md +311 -404
  52. package/content/extensions/components/websocket/errors.md +47 -56
  53. package/content/extensions/components/websocket/index.md +75 -407
  54. package/content/extensions/components/websocket/usage.md +120 -338
  55. package/content/extensions/helpers/cron/index.md +52 -160
  56. package/content/extensions/helpers/crypto/index.md +67 -480
  57. package/content/extensions/helpers/crypto/reference.md +528 -0
  58. package/content/extensions/helpers/env/index.md +64 -178
  59. package/content/extensions/helpers/error/index.md +286 -196
  60. package/content/extensions/helpers/index.md +61 -47
  61. package/content/extensions/helpers/inversion/index.md +76 -550
  62. package/content/extensions/helpers/inversion/reference.md +530 -0
  63. package/content/extensions/helpers/kafka/admin.md +23 -3
  64. package/content/extensions/helpers/kafka/compile-binary.md +83 -52
  65. package/content/extensions/helpers/kafka/consumer.md +65 -28
  66. package/content/extensions/helpers/kafka/examples.md +46 -253
  67. package/content/extensions/helpers/kafka/index.md +55 -617
  68. package/content/extensions/helpers/kafka/producer.md +156 -22
  69. package/content/extensions/helpers/kafka/schema-registry.md +59 -79
  70. package/content/extensions/helpers/logger/hf-logger.md +220 -0
  71. package/content/extensions/helpers/logger/index.md +78 -552
  72. package/content/extensions/helpers/logger/pino.md +105 -0
  73. package/content/extensions/helpers/logger/reference.md +937 -0
  74. package/content/extensions/helpers/network/api.md +276 -195
  75. package/content/extensions/helpers/network/index.md +87 -524
  76. package/content/extensions/helpers/queue/index.md +80 -897
  77. package/content/extensions/helpers/queue/reference.md +494 -0
  78. package/content/extensions/helpers/redis/index.md +86 -640
  79. package/content/extensions/helpers/redis/reference.md +784 -0
  80. package/content/extensions/helpers/secrets/index.md +136 -0
  81. package/content/extensions/helpers/socket-io/api.md +325 -204
  82. package/content/extensions/helpers/socket-io/index.md +79 -429
  83. package/content/extensions/helpers/storage/api.md +584 -464
  84. package/content/extensions/helpers/storage/index.md +80 -573
  85. package/content/extensions/helpers/types/index.md +75 -495
  86. package/content/extensions/helpers/types/reference.md +689 -0
  87. package/content/extensions/helpers/uid/index.md +176 -189
  88. package/content/extensions/helpers/websocket/api.md +366 -214
  89. package/content/extensions/helpers/websocket/index.md +68 -503
  90. package/content/extensions/helpers/worker-thread/index.md +62 -396
  91. package/content/extensions/helpers/worker-thread/reference.md +428 -0
  92. package/content/extensions/index.md +38 -39
  93. package/content/extensions/src-details/mcp-server.md +96 -548
  94. package/content/guides/core-concepts/persistent/datasources.md +2 -2
  95. package/content/guides/core-concepts/persistent/index.md +5 -1
  96. package/content/guides/core-concepts/persistent/models.md +1 -1
  97. package/content/guides/core-concepts/persistent/pglite.md +218 -0
  98. package/content/guides/core-concepts/persistent/postgres-drivers.md +21 -15
  99. package/content/guides/core-concepts/persistent/search-meilisearch.md +22 -12
  100. package/content/guides/core-concepts/persistent/search-typesense.md +28 -16
  101. package/content/guides/core-concepts/persistent/sqlite.md +296 -0
  102. package/content/guides/core-concepts/persistent/transactions.md +12 -11
  103. package/content/guides/core-concepts/secrets-vault.md +177 -0
  104. package/content/guides/core-concepts/services.md +1 -1
  105. package/content/guides/get-started/5-minute-quickstart.md +59 -206
  106. package/content/guides/get-started/philosophy.md +135 -670
  107. package/content/guides/get-started/setup.md +53 -74
  108. package/content/guides/migrations/redis-helpers-migration.md +5 -4
  109. package/content/guides/migrations/scoped-rbac-migration.md +5 -5
  110. package/content/guides/migrations/unified-connectors-migration.md +8 -7
  111. package/content/guides/tutorials/ecommerce-api.md +3 -8
  112. package/content/guides/tutorials/realtime-chat.md +1 -1
  113. package/content/references/base/application.md +64 -22
  114. package/content/references/base/components.md +3 -3
  115. package/content/references/base/connectors.md +79 -136
  116. package/content/references/base/controllers.md +12 -12
  117. package/content/references/base/datasources-reference.md +600 -0
  118. package/content/references/base/datasources.md +84 -444
  119. package/content/references/base/dependency-injection.md +25 -46
  120. package/content/references/base/filter-system/application-usage.md +95 -123
  121. package/content/references/base/filter-system/array-operators.md +33 -43
  122. package/content/references/base/filter-system/comparison-operators.md +55 -63
  123. package/content/references/base/filter-system/default-filter.md +144 -350
  124. package/content/references/base/filter-system/fields-order-pagination.md +116 -148
  125. package/content/references/base/filter-system/index.md +114 -253
  126. package/content/references/base/filter-system/json-filtering.md +52 -181
  127. package/content/references/base/filter-system/list-operators.md +28 -44
  128. package/content/references/base/filter-system/logical-operators.md +69 -114
  129. package/content/references/base/filter-system/null-operators.md +41 -98
  130. package/content/references/base/filter-system/pattern-matching.md +48 -51
  131. package/content/references/base/filter-system/quick-reference.md +93 -196
  132. package/content/references/base/filter-system/range-operators.md +22 -38
  133. package/content/references/base/filter-system/tips.md +70 -133
  134. package/content/references/base/filter-system/use-cases.md +173 -232
  135. package/content/references/base/grpc-controllers.md +53 -17
  136. package/content/references/base/index.md +5 -3
  137. package/content/references/base/middlewares.md +43 -28
  138. package/content/references/base/models-reference.md +886 -0
  139. package/content/references/base/models.md +81 -1452
  140. package/content/references/base/providers.md +8 -8
  141. package/content/references/base/repositories/advanced.md +281 -419
  142. package/content/references/base/repositories/index.md +84 -644
  143. package/content/references/base/repositories/mixins.md +25 -21
  144. package/content/references/base/repositories/relations.md +194 -375
  145. package/content/references/base/repositories/soft-deletable.md +67 -55
  146. package/content/references/base/secrets.md +267 -0
  147. package/content/references/base/services.md +8 -6
  148. package/content/references/configuration/environment-variables.md +73 -21
  149. package/content/references/configuration/index.md +51 -31
  150. package/content/references/index.md +1 -1
  151. package/content/references/quick-reference.md +3 -16
  152. package/content/references/utilities/crypto.md +35 -76
  153. package/content/references/utilities/date.md +33 -73
  154. package/content/references/utilities/duration.md +85 -0
  155. package/content/references/utilities/index.md +6 -2
  156. package/content/references/utilities/jsx-reference.md +298 -0
  157. package/content/references/utilities/jsx.md +82 -525
  158. package/content/references/utilities/module.md +81 -61
  159. package/content/references/utilities/parse.md +34 -64
  160. package/content/references/utilities/performance.md +33 -58
  161. package/content/references/utilities/promise.md +28 -62
  162. package/content/references/utilities/request.md +58 -218
  163. package/content/references/utilities/retry.md +139 -0
  164. package/content/references/utilities/schema.md +43 -137
  165. package/content/references/utilities/statuses-reference.md +361 -0
  166. package/content/references/utilities/statuses.md +63 -667
  167. package/dist/mcp-server/index.js +0 -0
  168. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
  169. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts.map +1 -1
  170. package/dist/mcp-server/tools/github/list-project-files.tool.js +3 -3
  171. package/dist/mcp-server/tools/github/search-code.tool.js +1 -1
  172. package/dist/mcp-server/tools/github/verify-dependencies.tool.js +1 -1
  173. package/dist/mcp-server/tools/github/view-source-file.tool.js +1 -1
  174. package/package.json +24 -23
@@ -1,245 +1,85 @@
1
- # Request Utility
2
-
3
- The Request utility provides functions for handling HTTP request data, such as parsing multipart form data, and utilities for creating secure Content-Disposition headers.
4
-
5
- ## `parseMultipartBody`
6
-
7
- The `parseMultipartBody` function is an asynchronous utility for parsing `multipart/form-data` request bodies, which is essential for handling file uploads. It can store the uploaded files in memory or on disk.
8
-
9
- ### `parseMultipartBody(opts)`
10
-
11
- - `opts` (object):
12
- - `context` (object with `req` property): The Hono context object for the current request. Uses `context.req.formData()` internally.
13
- - `storage` (`'memory'` | `'disk'`, optional): The storage strategy for uploaded files. Defaults to `'memory'`.
14
- - `uploadDir` (string, optional): The directory to save files to when using the `'disk'` storage strategy. Defaults to `'./uploads'`. The directory is created recursively if it does not exist.
15
-
16
- The function returns a `Promise` that resolves to an array of `IParsedFile` objects. String form fields are skipped (only `File` entries are processed).
17
-
18
- ### `IParsedFile` Interface
1
+ ---
2
+ title: Request Utility
3
+ description: Multipart form parsing and secure Content-Disposition header helpers for file uploads and downloads
4
+ difficulty: beginner
5
+ lastUpdated: 2026-07-16
6
+ ---
19
7
 
20
- - `fieldname`: The name of the form field.
21
- - `originalname`: The original name of the uploaded file.
22
- - `encoding`: The file's encoding (always `'utf8'`).
23
- - `mimetype`: The MIME type of the file.
24
- - `size`: The size of the file in bytes.
25
- - `buffer` (Buffer, optional): The file's content as a Buffer (if `storage` is `'memory'`).
26
- - `filename` (string, optional): The generated name of the file on disk (if `storage` is `'disk'`). Format: `{timestamp}-{randomString}-{sanitizedOriginalName}`.
27
- - `path` (string, optional): The full path to the file on disk (if `storage` is `'disk'`).
8
+ # Request Utility
28
9
 
29
- ### Example
10
+ Functions for handling HTTP request data: parsing `multipart/form-data` bodies for file uploads, and building safe, RFC-compliant `Content-Disposition` headers for downloads.
30
11
 
31
- Here is an example of how to use `parseMultipartBody` in a controller to handle a file upload.
12
+ ## In one example
32
13
 
33
14
  ```typescript
34
15
  import { BaseRestController, controller } from '@venizia/ignis';
35
- import { parseMultipartBody, HTTP } from '@venizia/ignis-helpers';
16
+ import { parseMultipartBody, createContentDispositionHeader, HTTP } from '@venizia/ignis-helpers';
36
17
 
37
18
  @controller({ path: '/files' })
38
19
  export class FileController extends BaseRestController {
39
- // ...
40
20
  override binding() {
41
- this.defineRoute({
42
- configs: {
43
- path: '/upload',
44
- method: 'post',
45
- // Note: You would typically define a request body schema
46
- // for multipart/form-data in your OpenAPI spec.
21
+ this.bindRoute({ configs: { path: '/upload', method: 'post' } }).to({
22
+ handler: async (ctx) => {
23
+ const files = await parseMultipartBody({ context: ctx, storage: 'disk', uploadDir: './uploads' });
24
+ return ctx.json(
25
+ { message: 'Uploaded', files: files.map(f => ({ name: f.originalname, size: f.size })) },
26
+ HTTP.ResultCodes.RS_2.Ok,
27
+ );
47
28
  },
48
- handler: async (c) => {
49
- try {
50
- const files = await parseMultipartBody({
51
- context: c,
52
- storage: 'disk', // or 'memory'
53
- uploadDir: './my-uploads',
54
- });
55
-
56
- console.log('Uploaded files:', files);
57
-
58
- return c.json(
59
- { message: `${files.length} file(s) uploaded successfully.` },
60
- HTTP.ResultCodes.RS_2.Ok,
61
- );
62
- } catch (error) {
63
- return c.json(
64
- { message: 'Failed to upload files', error: error.message },
65
- HTTP.ResultCodes.RS_5.InternalServerError,
66
- );
67
- }
29
+ });
30
+
31
+ this.bindRoute({ configs: { path: '/:filename', method: 'get' } }).to({
32
+ handler: (ctx) => {
33
+ const { filename } = ctx.req.valid('param');
34
+ ctx.header(
35
+ 'content-disposition',
36
+ createContentDispositionHeader({ filename, type: 'attachment' }),
37
+ );
38
+ // ... stream the file
68
39
  },
69
40
  });
70
41
  }
71
42
  }
72
43
  ```
73
44
 
74
- ---
75
-
76
- ## Content-Disposition Utilities
77
-
78
- These utilities help create secure, RFC-compliant `Content-Disposition` headers for file downloads.
79
-
80
- ### `createContentDispositionHeader`
81
-
82
- Creates a safe Content-Disposition header with proper filename encoding for file downloads.
83
-
84
- #### `createContentDispositionHeader(opts)`
85
-
86
- - `opts` (object):
87
- - `filename` (string): The filename to use in the Content-Disposition header.
88
- - `type` (`'attachment'` | `'inline'`): The disposition type.
89
-
90
- The function returns a properly formatted `Content-Disposition` header string with both ASCII and UTF-8 encoded filenames for maximum browser compatibility.
91
-
92
- **Features:**
93
- - Automatic filename sanitization via `sanitizeFilename()`
94
- - UTF-8 encoding support via `encodeRFC5987()`
95
- - RFC 5987 compliant
96
- - Dual `filename` / `filename*` for browser compatibility
97
-
98
- **Example:**
99
-
100
- ```typescript
101
- import { createContentDispositionHeader } from '@venizia/ignis-helpers';
102
-
103
- // Attachment (file download)
104
- ctx.header('content-disposition', createContentDispositionHeader({
105
- filename: 'my-document.pdf',
106
- type: 'attachment',
107
- }));
108
- // Output: attachment; filename="my-document.pdf"; filename*=UTF-8''my-document.pdf
109
-
110
- // Inline (display in browser)
111
- ctx.header('content-disposition', createContentDispositionHeader({
112
- filename: 'report.pdf',
113
- type: 'inline',
114
- }));
115
- // Output: inline; filename="report.pdf"; filename*=UTF-8''report.pdf
116
- ```
117
-
118
- ---
119
-
120
- ### `sanitizeFilename`
121
-
122
- Sanitizes a filename for safe use, removing path components and dangerous characters. Useful for HTTP headers (e.g., Content-Disposition) and general file handling.
123
-
124
- #### `sanitizeFilename(filename: string): string`
125
-
126
- - `filename` (string): The filename to sanitize.
127
-
128
- Returns a safe filename suitable for use in headers or filesystem operations.
129
-
130
- **Features:**
131
- - Removes path components via `path.basename()` (prevents directory traversal attacks)
132
- - Allows only word characters (`\w`), spaces, hyphens, underscores, and dots
133
- - Replaces dangerous characters with underscores
134
- - Removes leading dots (prevents hidden files)
135
- - Replaces consecutive dots with a single dot
136
- - Removes ".." patterns (additional path traversal protection)
137
- - Returns `'download'` for empty, suspicious, or invalid filenames
138
-
139
- **Example:**
140
-
141
- ```typescript
142
- import { sanitizeFilename } from '@venizia/ignis-helpers';
143
-
144
- sanitizeFilename('../../etc/passwd'); // Returns: 'passwd'
145
- sanitizeFilename('my<file>name.txt'); // Returns: 'my_file_name.txt'
146
- sanitizeFilename('.hidden'); // Returns: 'hidden'
147
- sanitizeFilename('file...txt'); // Returns: 'file.txt'
148
- sanitizeFilename(''); // Returns: 'download'
149
- sanitizeFilename('..'); // Returns: 'download'
150
- ```
151
-
152
-
153
- ### `encodeRFC5987`
154
-
155
- Encodes a filename according to RFC 5987 for use in HTTP headers. Encodes using `encodeURIComponent` and additionally escapes single quotes, parentheses, and asterisks.
156
-
157
- #### `encodeRFC5987(filename: string): string`
158
-
159
- - `filename` (string): The filename to encode.
160
-
161
- Returns an RFC 5987 encoded string suitable for the `filename*` parameter in Content-Disposition headers.
162
-
163
- **Example:**
164
-
165
- ```typescript
166
- import { encodeRFC5987 } from '@venizia/ignis-helpers';
167
-
168
- encodeRFC5987('my document.pdf'); // Returns: 'my%20document.pdf'
169
- ```
170
-
45
+ ## Functions
171
46
 
172
- ## `IRequestedRemark` Interface
47
+ | Function | Signature | What it does |
48
+ |----------|-----------|---------------|
49
+ | `parseMultipartBody` | `parseMultipartBody(opts: { context: { req: any }; storage?: 'memory' \| 'disk'; uploadDir?: string }): Promise<IParsedFile[]>` | Parses a `multipart/form-data` body via `context.req.formData()`. String fields are skipped - only `File` entries are returned. |
50
+ | `sanitizeFilename` | `sanitizeFilename(filename: string): string` | Strips path components and dangerous characters from `filename`. Returns `'download'` for empty or suspicious input. |
51
+ | `encodeRFC5987` | `encodeRFC5987(filename: string): string` | RFC 5987 encodes `filename` for the `filename*` header parameter (`encodeURIComponent` plus escaped `'`, `(`, `)`, `*`). |
52
+ | `createContentDispositionHeader` | `createContentDispositionHeader(opts: { filename: string; type: 'attachment' \| 'inline' }): string` | Builds a full `Content-Disposition` value: sanitizes the filename, then emits both the ASCII `filename=` and UTF-8 `filename*=` forms. |
173
53
 
174
- The Request utility also exports the `IRequestedRemark` interface, which describes a request remark object:
54
+ ## Parsed file shape
175
55
 
176
- - `id` (string): The request identifier.
177
- - `url` (string): The request URL.
178
- - `method` (string): The HTTP method.
179
- - `[extra: string | symbol]`: Additional arbitrary properties.
56
+ `parseMultipartBody` resolves to an array of objects (the `IParsedFile` shape, internal to the module - not separately exported):
180
57
 
58
+ | Field | Type | Present when |
59
+ |-------|------|---------------|
60
+ | `fieldname` | `string` | always |
61
+ | `originalname` | `string` | always |
62
+ | `encoding` | `string` | always - hardcoded `'utf8'` |
63
+ | `mimetype` | `string` | always |
64
+ | `size` | `number` | always |
65
+ | `buffer` | `Buffer` | `storage: 'memory'` (default) |
66
+ | `filename` | `string` | `storage: 'disk'` - format `{timestamp}-{randomString}-{sanitizedOriginalName}` |
67
+ | `path` | `string` | `storage: 'disk'` |
181
68
 
182
- ## Complete File Download Example
69
+ ## Notes
183
70
 
184
- Here's a complete example combining multipart upload parsing with secure file downloads:
71
+ - **`storage` defaults to `'memory'`**; `uploadDir` defaults to `'./uploads'` and is created recursively if it does not exist.
72
+ - **`sanitizeFilename` is applied automatically** inside `createContentDispositionHeader` - callers do not need to sanitize twice. It also removes leading dots, collapses repeated dots, and strips `..` sequences to block directory traversal and hidden-file tricks.
73
+ - **`createContentDispositionHeader` always emits both forms** (`filename="..."; filename*=UTF-8''...`) for maximum browser compatibility - older browsers read the ASCII fallback, modern ones read the UTF-8 form.
74
+ - **`IRequestedRemark`** is a separately exported interface for describing a request: `{ id: string; url: string; method: string; [extra: string | symbol]: any }`.
75
+ - It is not consumed internally by `parseMultipartBody` or any other function on this page - it is a general-purpose shape for application code that needs to tag a request with an id, URL, method, and arbitrary extra fields.
185
76
 
186
- ```typescript
187
- import { BaseRestController, controller } from '@venizia/ignis';
188
- import { parseMultipartBody, createContentDispositionHeader, HTTP } from '@venizia/ignis-helpers';
189
- import fs from 'node:fs';
190
- import path from 'node:path';
77
+ ## See also
191
78
 
192
- @controller({ path: '/files' })
193
- export class FileController extends BaseRestController {
194
- override binding() {
195
- // Upload endpoint
196
- this.bindRoute({
197
- configs: { path: '/upload', method: 'post' },
198
- }).to({
199
- handler: async (ctx) => {
200
- const files = await parseMultipartBody({
201
- context: ctx,
202
- storage: 'disk',
203
- uploadDir: './uploads',
204
- });
79
+ - [Utilities Overview](/references/utilities/) - all utility functions
80
+ - [Static Asset Component](/extensions/components/static-asset/) - built-in upload/download CRUD built on this utility
81
+ - [Request Tracker Component](/extensions/components/request-tracker) - `x-request-id` header and request body parsing
205
82
 
206
- return ctx.json(
207
- {
208
- message: 'Files uploaded successfully',
209
- files: files.map(f => ({ name: f.originalname, size: f.size })),
210
- },
211
- HTTP.ResultCodes.RS_2.Ok,
212
- );
213
- },
214
- });
83
+ **Files:**
215
84
 
216
- // Download endpoint
217
- this.bindRoute({
218
- configs: { path: '/:filename', method: 'get' },
219
- }).to({
220
- handler: async (ctx) => {
221
- const { filename } = ctx.req.valid('param');
222
- const filePath = path.join('./uploads', filename);
223
-
224
- // Read file
225
- const fileStat = fs.statSync(filePath);
226
- const fileStream = fs.createReadStream(filePath);
227
-
228
- // Set secure headers
229
- ctx.header('content-type', 'application/octet-stream');
230
- ctx.header('content-length', fileStat.size.toString());
231
- ctx.header('content-disposition', createContentDispositionHeader({
232
- filename,
233
- type: 'attachment',
234
- }));
235
- ctx.header('x-content-type-options', 'nosniff');
236
-
237
- return new Response(fileStream, {
238
- headers: ctx.res.headers,
239
- status: HTTP.ResultCodes.RS_2.Ok,
240
- });
241
- },
242
- });
243
- }
244
- }
245
- ```
85
+ - [`packages/helpers/src/utilities/request.utility.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/utilities/request.utility.ts)
@@ -0,0 +1,139 @@
1
+ ---
2
+ title: Retry Utility
3
+ description: Backoff-driven retry helpers - executeWithRetry retries on errors, executeWithRetryUntil retries until a result looks right
4
+ difficulty: intermediate
5
+ lastUpdated: 2026-07-18
6
+ ---
7
+
8
+ # Retry Utility
9
+
10
+ Two retry helpers. One retries when a call **throws**. The other retries when a call **succeeds but the result is not what you want yet**.
11
+
12
+ ## In one example
13
+
14
+ ```typescript
15
+ import { executeWithRetry, executeWithRetryUntil } from '@venizia/ignis-helpers';
16
+
17
+ // Retries because the call THREW
18
+ const data = await executeWithRetry({
19
+ operation: 'fetch-remote-config',
20
+ execution: () => fetchConfig(),
21
+ maxAttempts: 5,
22
+ });
23
+
24
+ // Retries because the result is not YET what we want
25
+ const order = await executeWithRetryUntil({
26
+ operation: 'wait-for-paid-order',
27
+ execution: () => orderRepository.findById({ id: orderId }),
28
+ until: result => result?.status === 'PAID',
29
+ maxAttempts: 5,
30
+ });
31
+ ```
32
+
33
+ ## Which one do I need?
34
+
35
+ | Function | Retries when | Typical use |
36
+ |---|---|---|
37
+ | `executeWithRetry` | `execution` throws | Flaky network calls, connection setup |
38
+ | `executeWithRetryUntil` | `until(result)` returns `false` | Polling until data is fresh or a job is done. Powers the repository [`retry` option](/references/base/repositories/advanced#read-retry-replica-lag). |
39
+
40
+ Both share the same backoff engine and the same habit: on exhaustion, log one `logger.warn` and hand back the LAST outcome.
41
+
42
+ ## `executeWithRetry`
43
+
44
+ ```typescript
45
+ const executeWithRetry: <T>(opts: {
46
+ operation: string;
47
+ execution: (context: { attempt: number; signal?: AbortSignal }) => ValueOrPromise<T>;
48
+ maxAttempts?: number; // default 3
49
+ maxTotalMs?: number; // total budget across attempts and sleeps
50
+ perAttemptTimeoutMs?: number; // race each attempt against a timeout
51
+ backoff?: IRetryBackoffOptions;
52
+ shouldRetry?: (context: IRetryContext) => boolean;
53
+ onRetry?: (context: IRetryContext & { nextDelayMs: number }) => ValueOrPromise<void>;
54
+ signal?: AbortSignal;
55
+ logger?: ILogger;
56
+ }) => Promise<T>;
57
+ ```
58
+
59
+ The rules:
60
+
61
+ - Every thrown error retries, unless `shouldRetry` returns `false` - then it rethrows immediately. Use this for permanent errors like a `400`.
62
+ - Out of attempts or budget? The LAST error is thrown.
63
+ - `signal` aborts between attempts and during sleeps. It is also passed to `execution` - a running promise cannot be cancelled from outside, so honor it inside if you can.
64
+
65
+ ## `executeWithRetryUntil`
66
+
67
+ ```typescript
68
+ const executeWithRetryUntil: <T>(opts: {
69
+ operation: string;
70
+ execution: (context: { attempt: number; signal?: AbortSignal }) => ValueOrPromise<T>;
71
+ until: (result: T) => boolean; // return true to stop: "the result is good"
72
+ maxAttempts?: number; // default 3
73
+ maxTotalMs?: number; // stop starting NEW attempts after this much time
74
+ backoff?: IRetryBackoffOptions;
75
+ signal?: AbortSignal;
76
+ logger?: ILogger;
77
+ }) => Promise<T>;
78
+ ```
79
+
80
+ The rules:
81
+
82
+ - A thrown error is never retried. It rethrows immediately. Only a successful call with a "not yet" result retries.
83
+ - Out of attempts or budget? The LAST result is returned as-is. No error.
84
+ - `maxTotalMs` never cuts a running read short. It only stops NEW attempts from starting. Zero or negative just means "no retries" - one call still runs.
85
+ - `maxAttempts` below `1` throws before anything runs.
86
+ - An aborted `signal` rejects the call - a cancelled caller does not want a stale result.
87
+
88
+ Use it for any polling: waiting for a job status to flip, for a downstream service to come up, for a replica to catch up.
89
+
90
+ ## Backoff and jitter
91
+
92
+ Both helpers wait between attempts using `IRetryBackoffOptions`:
93
+
94
+ ```typescript
95
+ interface IRetryBackoffOptions {
96
+ strategy?: 'fixed' | 'linear' | 'exponential' | 'schedule'; // default exponential
97
+ initialDelayMs?: number; // default 250
98
+ multiplier?: number; // exponential growth factor, default 2
99
+ maxDelayMs?: number; // cap before jitter, default 30000
100
+ scheduleMs?: readonly number[]; // required for 'schedule'
101
+ jitter?: 'none' | 'full' | 'equal'; // default full
102
+ }
103
+ ```
104
+
105
+ | Strategy | Delay for attempt N |
106
+ |---|---|
107
+ | `fixed` | `initialDelayMs` |
108
+ | `linear` | `initialDelayMs * N` |
109
+ | `exponential` | `initialDelayMs * multiplier ** (N - 1)` |
110
+ | `schedule` | `scheduleMs[N - 1]`, last entry repeats |
111
+
112
+ | Jitter | Effect |
113
+ |---|---|
114
+ | `none` | delay used as-is |
115
+ | `full` | random in `[0, delay)` |
116
+ | `equal` | random in `[delay/2, delay)` |
117
+
118
+ Prefer named constants? `RetryBackoffStrategies.EXPONENTIAL`, `RetryJitterModes.EQUAL`, etc.
119
+
120
+ > [!NOTE]
121
+ > These defaults (250ms, 30s cap) suit network retries. The repository `retry` option uses its own tighter defaults (50ms up to 500ms) - see [Read Retry](/references/base/repositories/advanced#read-retry-replica-lag).
122
+
123
+ ## Other exports
124
+
125
+ | Export | What it does |
126
+ |---|---|
127
+ | `runWithTimeout({ operation, timeoutMs, execution })` | Races `execution` against a timeout. Omitted or `<= 0` means no timeout. |
128
+ | `isRetryTimeoutError(error)` | `true` when the error is a timeout from `runWithTimeout`/`executeWithRetry`. |
129
+ | `computeBackoffDelayMs({ attempt, backoff })` | The delay both helpers use, exposed for your own loops. |
130
+
131
+ ## See also
132
+
133
+ - [Read Retry](/references/base/repositories/advanced#read-retry-replica-lag) - the repository `retry` option built on `executeWithRetryUntil`
134
+ - [Repository Read Retry changelog](/changelogs/2026-07-18-repository-read-retry) - what shipped and why
135
+ - [Utilities Overview](/references/utilities/)
136
+
137
+ **Files:**
138
+
139
+ - [`packages/helpers/src/utilities/retry.utility.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/utilities/retry.utility.ts)
@@ -1,162 +1,68 @@
1
- # Schema Utility
2
-
3
- The Schema utility provides a set of helper functions and predefined schemas for working with `zod` and `@hono/zod-openapi`. These utilities simplify the process of defining API request/response schemas and improve consistency in your API documentation.
4
-
5
- ## `jsonContent`
6
-
7
- The `jsonContent` function creates a standard OpenAPI content object for `application/json` payloads.
1
+ ---
2
+ title: Schema Utility
3
+ description: Zod and OpenAPI helpers for JSON response schemas, path params, string constraints, and case conversion
4
+ difficulty: beginner
5
+ lastUpdated: 2026-07-16
6
+ ---
8
7
 
9
- ### `jsonContent(opts)`
10
-
11
- - `opts` (object):
12
- - `schema` (ZodType): The Zod schema describing the JSON payload.
13
- - `description` (string): A description of the content.
14
- - `required` (boolean, optional): Whether the content is required.
15
-
16
- ```typescript
17
- import { jsonContent } from '@venizia/ignis';
18
- import { z } from '@hono/zod-openapi';
19
-
20
- const UserSchema = z.object({
21
- id: z.number(),
22
- name: z.string(),
23
- });
24
-
25
- const userResponse = {
26
- description: 'A single user object',
27
- ...jsonContent({ schema: UserSchema, description: 'User data' }),
28
- };
29
- ```
30
-
31
- ## `jsonResponse`
32
-
33
- The `jsonResponse` function generates a standard OpenAPI response object that includes a success (200 OK) response and a default error response for `4xx | 5xx` status codes. The error response uses the `ErrorSchema`.
8
+ # Schema Utility
34
9
 
35
- ### `jsonResponse(opts)`
10
+ Helper functions and predefined schemas for working with `zod` and `@hono/zod-openapi` - building JSON request/response schemas, path-param schemas, and case-converting schemas for API routes.
36
11
 
37
- - `opts` (object):
38
- - `schema` (ZodType): The Zod schema for the success response body.
39
- - `description` (string, optional): A description for the success response. Defaults to `'Success Response'`.
40
- - `required` (boolean, optional): Whether the content is required.
41
- - `headers` (Record&lt;string, THeaderObject&gt;, optional): Custom response headers to include in the success response.
12
+ ## In one example
42
13
 
43
14
  ```typescript
44
- import { jsonResponse } from '@venizia/ignis';
15
+ import { jsonResponse, requiredString, idParamsSchema } from '@venizia/ignis';
45
16
  import { z } from '@hono/zod-openapi';
46
17
 
47
- const UserSchema = z.object({
48
- id: z.number(),
49
- name: z.string(),
50
- });
18
+ const UserSchema = z.object({ id: z.number(), name: requiredString({ min: 1, max: 50 }) });
51
19
 
52
20
  this.defineRoute({
53
21
  configs: {
54
- path: '/',
22
+ path: '/{id}',
55
23
  method: 'get',
56
- responses: jsonResponse({
57
- description: 'A single user object',
58
- schema: UserSchema,
59
- }),
24
+ request: { params: idParamsSchema({ idType: 'number' }) },
25
+ responses: jsonResponse({ schema: UserSchema, description: 'A single user object' }),
60
26
  },
61
- // ...
62
- });
63
-
64
- // With custom headers
65
- this.defineRoute({
66
- configs: {
67
- path: '/list',
68
- method: 'get',
69
- responses: jsonResponse({
70
- schema: z.array(UserSchema),
71
- description: 'User list',
72
- headers: {
73
- 'x-total-count': {
74
- description: 'Total number of records',
75
- schema: { type: 'string', examples: ['100'] },
76
- },
77
- },
78
- }),
27
+ handler: (c) => {
28
+ const { id } = c.req.valid('param');
29
+ // ...
79
30
  },
80
- // ...
81
31
  });
82
32
  ```
83
33
 
84
- ## `requiredString`
34
+ ## Functions
85
35
 
86
- This function creates a `zod` string schema that is non-empty (`nonempty()`) and can be further constrained by length.
36
+ | Function | Signature | What it does |
37
+ |----------|-----------|---------------|
38
+ | `jsonContent` | `jsonContent(opts: { schema: ZodType; description: string; required?: boolean }): { description; content; required }` | Builds a standard OpenAPI content object for an `application/json` payload. |
39
+ | `jsonResponse` | `jsonResponse(opts: { schema: ZodType; description?: string; required?: boolean; headers?: Record<string, THeaderObject> }): Record<number \| string, ...>` | Builds a full response map: `200` success (via `jsonContent`, description defaults to `'Success Response'`) plus a `'4xx \| 5xx'` error entry using `ErrorSchema`. |
40
+ | `requiredString` | `requiredString(opts?: { min?: number; max?: number; fixed?: number }): ZodString` | A non-empty (`.nonempty()`) Zod string, optionally constrained by `min`, `max`, or an exact `fixed` length. |
41
+ | `idParamsSchema` | `idParamsSchema(opts?: { idType?: 'number' \| 'string' }): ZodObject` | Builds a path-param schema for `{ id }`, typed and OpenAPI-documented as `number` (default) or `string`. Throws on any other `idType`. |
42
+ | `snakeToCamel` | `snakeToCamel<T extends ZodRawShape>(shape: T): ZodEffects` | Wraps a Zod object `shape` so it accepts `snake_case` input keys and produces a `camelCase`-keyed output, via `.transform()` piped into a camelCase-shaped schema. |
87
43
 
88
- ### `requiredString(opts?)`
44
+ ## Predefined schemas and types
89
45
 
90
- - `opts` (object, optional):
91
- - `min` (number, optional): Minimum string length.
92
- - `max` (number, optional): Maximum string length.
93
- - `fixed` (number, optional): Exact string length (uses `.length()`).
46
+ | Export | What it is |
47
+ |--------|------------|
48
+ | `AnyObjectSchema` | `z.object().catchall(z.any())`, OpenAPI-described as `'Unknown schema'` - a permissive object schema. |
49
+ | `TAnyObjectSchema` | Type alias for `z.ZodObject<z.ZodRawShape>`. |
50
+ | `TInferSchema<T>` | Type alias for `z.infer<T>` - the TypeScript type inferred from a Zod schema `T`. |
94
51
 
95
- ```typescript
96
- import { requiredString } from '@venizia/ignis';
52
+ ## Notes
97
53
 
98
- const schema = z.object({
99
- username: requiredString({ min: 3, max: 20 }),
100
- password: requiredString({ min: 8 }),
101
- countryCode: requiredString({ fixed: 2 }),
102
- });
103
- ```
104
-
105
- ## Predefined Schemas
54
+ - **Two source files, one page.** `requiredString`, `AnyObjectSchema`, `TAnyObjectSchema`, and `TInferSchema` live in `schema.utility.ts`; `jsonContent`, `jsonResponse`, `idParamsSchema`, and `snakeToCamel` live in `base/models/common/types.ts`. Both are re-exported from the `@venizia/ignis` root barrel, so the import path is the same either way.
55
+ - **`jsonResponse`'s error branch is fixed** - it always uses `ErrorSchema` under the `'4xx | 5xx'` key; only the success schema, description, and headers are customizable per call.
56
+ - **HTML responses are a separate utility.** For `text/html` routes, use `htmlContent`/`htmlResponse` from the [JSX Utility](/references/utilities/jsx) instead of `jsonContent`/`jsonResponse`.
106
57
 
107
- - **`AnyObjectSchema`**: A flexible schema for any object (`z.object().catchall(z.any())`), with an OpenAPI description of `'Unknown schema'`.
58
+ ## See also
108
59
 
109
- ### Type Utilities
60
+ - [Utilities Overview](/references/utilities/) - all utility functions
61
+ - [JSX Utility](/references/utilities/jsx) - `htmlContent()` / `htmlResponse()` for HTML routes
62
+ - [Parse Utility](/references/utilities/parse) - `toCamel()` / `keysToCamel()`, the transforms `snakeToCamel` builds on
63
+ - [REST Controllers Guide](/guides/core-concepts/rest-controllers) - defining routes with `request`/`responses`
110
64
 
111
- ```typescript
112
- import { TAnyObjectSchema, TInferSchema } from '@venizia/ignis';
65
+ **Files:**
113
66
 
114
- // TAnyObjectSchema = z.ZodObject<z.ZodRawShape>
115
- // TInferSchema<T> = z.infer<T> - infer TypeScript type from a Zod schema
116
-
117
- type UserType = TInferSchema<typeof UserSchema>;
118
- ```
119
-
120
- ## `snakeToCamel`
121
-
122
- Transforms a Zod object shape from snake_case keys to camelCase. Uses `.transform()` and `.pipe()` to create a schema that accepts snake_case input but produces camelCase output.
123
-
124
- ```typescript
125
- import { snakeToCamel } from '@venizia/ignis';
126
-
127
- const schema = snakeToCamel({
128
- first_name: z.string(),
129
- last_name: z.string(),
130
- });
131
-
132
- // Input: { first_name: 'John', last_name: 'Doe' }
133
- // Output: { firstName: 'John', lastName: 'Doe' }
134
- ```
135
-
136
- ## Custom ID Params
137
-
138
- Use the `idParamsSchema()` helper to generate path parameter schemas for resource IDs:
139
-
140
- ### `idParamsSchema(opts?)`
141
-
142
- - `opts` (object, optional):
143
- - `idType` (string): `'number'` (default) or `'string'`.
144
-
145
- ```typescript
146
- import { idParamsSchema } from '@venizia/ignis';
147
-
148
- this.defineRoute({
149
- configs: {
150
- path: '/{id}',
151
- method: 'get',
152
- request: {
153
- params: idParamsSchema({ idType: 'number' }),
154
- },
155
- // ...
156
- },
157
- handler: (c) => {
158
- const { id } = c.req.valid('param');
159
- // ...
160
- },
161
- });
162
- ```
67
+ - [`packages/core-server/src/utilities/schema.utility.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/utilities/schema.utility.ts)
68
+ - [`packages/core-server/src/base/models/common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/base/models/common/types.ts)