@venizia/ignis-docs 0.0.7 → 0.0.8-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.
- package/dist/mcp-server/common/paths.d.ts +4 -2
- package/dist/mcp-server/common/paths.d.ts.map +1 -1
- package/dist/mcp-server/common/paths.js +8 -6
- package/dist/mcp-server/common/paths.js.map +1 -1
- package/dist/mcp-server/tools/docs/get-document-content.tool.d.ts +1 -1
- package/dist/mcp-server/tools/docs/get-document-content.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/docs/get-document-content.tool.js +7 -7
- package/dist/mcp-server/tools/docs/get-document-metadata.tool.js +3 -3
- package/dist/mcp-server/tools/docs/get-package-overview.tool.d.ts +1 -1
- package/dist/mcp-server/tools/docs/get-package-overview.tool.js +1 -1
- package/package.json +1 -1
- package/wiki/best-practices/api-usage-examples.md +9 -9
- package/wiki/best-practices/architectural-patterns.md +19 -3
- package/wiki/best-practices/architecture-decisions.md +6 -6
- package/wiki/best-practices/code-style-standards/advanced-patterns.md +1 -1
- package/wiki/best-practices/code-style-standards/control-flow.md +1 -1
- package/wiki/best-practices/code-style-standards/function-patterns.md +2 -2
- package/wiki/best-practices/code-style-standards/index.md +2 -2
- package/wiki/best-practices/code-style-standards/naming-conventions.md +1 -1
- package/wiki/best-practices/code-style-standards/route-definitions.md +4 -4
- package/wiki/best-practices/data-modeling.md +1 -1
- package/wiki/best-practices/deployment-strategies.md +1 -1
- package/wiki/best-practices/error-handling.md +2 -2
- package/wiki/best-practices/performance-optimization.md +3 -3
- package/wiki/best-practices/security-guidelines.md +2 -2
- package/wiki/best-practices/troubleshooting-tips.md +1 -1
- package/wiki/{references → extensions}/components/authentication/api.md +12 -20
- package/wiki/{references → extensions}/components/authentication/errors.md +1 -1
- package/wiki/{references → extensions}/components/authentication/index.md +5 -8
- package/wiki/{references → extensions}/components/authentication/usage.md +20 -36
- package/wiki/{references → extensions}/components/authorization/api.md +62 -13
- package/wiki/{references → extensions}/components/authorization/errors.md +12 -7
- package/wiki/{references → extensions}/components/authorization/index.md +93 -6
- package/wiki/{references → extensions}/components/authorization/usage.md +42 -4
- package/wiki/{references → extensions}/components/health-check.md +5 -4
- package/wiki/{references → extensions}/components/index.md +2 -0
- package/wiki/{references → extensions}/components/mail/index.md +1 -1
- package/wiki/{references → extensions}/components/request-tracker.md +1 -1
- package/wiki/{references → extensions}/components/socket-io/api.md +2 -2
- package/wiki/{references → extensions}/components/socket-io/errors.md +2 -0
- package/wiki/{references → extensions}/components/socket-io/index.md +24 -20
- package/wiki/{references → extensions}/components/socket-io/usage.md +2 -2
- package/wiki/{references → extensions}/components/static-asset/api.md +14 -15
- package/wiki/{references → extensions}/components/static-asset/errors.md +3 -1
- package/wiki/{references → extensions}/components/static-asset/index.md +158 -89
- package/wiki/{references → extensions}/components/static-asset/usage.md +8 -5
- package/wiki/{references → extensions}/components/swagger.md +3 -3
- package/wiki/{references → extensions}/components/template/index.md +4 -4
- package/wiki/{references → extensions}/components/template/setup-page.md +1 -1
- package/wiki/{references → extensions}/components/template/single-page.md +1 -1
- package/wiki/{references → extensions}/components/websocket/api.md +7 -6
- package/wiki/{references → extensions}/components/websocket/errors.md +17 -3
- package/wiki/{references → extensions}/components/websocket/index.md +17 -11
- package/wiki/{references → extensions}/components/websocket/usage.md +2 -2
- package/wiki/{references → extensions}/helpers/crypto/index.md +1 -1
- package/wiki/{references → extensions}/helpers/env/index.md +9 -5
- package/wiki/{references → extensions}/helpers/error/index.md +2 -7
- package/wiki/{references → extensions}/helpers/index.md +18 -6
- package/wiki/{references → extensions}/helpers/kafka/admin.md +13 -1
- package/wiki/{references → extensions}/helpers/kafka/consumer.md +28 -28
- package/wiki/{references → extensions}/helpers/kafka/examples.md +19 -19
- package/wiki/{references → extensions}/helpers/kafka/index.md +51 -48
- package/wiki/{references → extensions}/helpers/kafka/producer.md +18 -18
- package/wiki/{references → extensions}/helpers/kafka/schema-registry.md +25 -25
- package/wiki/{references → extensions}/helpers/logger/index.md +2 -2
- package/wiki/{references → extensions}/helpers/queue/index.md +400 -4
- package/wiki/{references → extensions}/helpers/storage/api.md +170 -10
- package/wiki/{references → extensions}/helpers/storage/index.md +44 -8
- package/wiki/{references → extensions}/helpers/template/index.md +1 -1
- package/wiki/{references → extensions}/helpers/testing/index.md +4 -4
- package/wiki/{references → extensions}/helpers/types/index.md +63 -16
- package/wiki/{references → extensions}/helpers/websocket/index.md +1 -1
- package/wiki/extensions/index.md +48 -0
- package/wiki/guides/core-concepts/application/bootstrapping.md +55 -37
- package/wiki/guides/core-concepts/application/index.md +95 -35
- package/wiki/guides/core-concepts/components-guide.md +23 -19
- package/wiki/guides/core-concepts/components.md +34 -10
- package/wiki/guides/core-concepts/dependency-injection.md +99 -34
- package/wiki/guides/core-concepts/grpc-controllers.md +295 -0
- package/wiki/guides/core-concepts/persistent/datasources.md +27 -8
- package/wiki/guides/core-concepts/persistent/models.md +43 -1
- package/wiki/guides/core-concepts/persistent/repositories.md +75 -8
- package/wiki/guides/core-concepts/persistent/transactions.md +38 -8
- package/wiki/guides/core-concepts/{controllers.md → rest-controllers.md} +30 -33
- package/wiki/guides/core-concepts/services.md +19 -5
- package/wiki/guides/get-started/5-minute-quickstart.md +6 -7
- package/wiki/guides/get-started/philosophy.md +1 -1
- package/wiki/guides/index.md +2 -2
- package/wiki/guides/reference/glossary.md +7 -7
- package/wiki/guides/reference/mcp-docs-server.md +1 -1
- package/wiki/guides/tutorials/building-a-crud-api.md +2 -2
- package/wiki/guides/tutorials/complete-installation.md +17 -14
- package/wiki/guides/tutorials/ecommerce-api.md +18 -18
- package/wiki/guides/tutorials/realtime-chat.md +8 -8
- package/wiki/guides/tutorials/testing.md +2 -2
- package/wiki/index.md +4 -3
- package/wiki/references/base/application.md +341 -21
- package/wiki/references/base/bootstrapping.md +43 -13
- package/wiki/references/base/components.md +259 -8
- package/wiki/references/base/controllers.md +556 -253
- package/wiki/references/base/datasources.md +159 -79
- package/wiki/references/base/dependency-injection.md +299 -48
- package/wiki/references/base/filter-system/application-usage.md +18 -2
- package/wiki/references/base/filter-system/array-operators.md +14 -6
- package/wiki/references/base/filter-system/comparison-operators.md +9 -3
- package/wiki/references/base/filter-system/default-filter.md +28 -3
- package/wiki/references/base/filter-system/fields-order-pagination.md +17 -13
- package/wiki/references/base/filter-system/index.md +169 -11
- package/wiki/references/base/filter-system/json-filtering.md +51 -18
- package/wiki/references/base/filter-system/list-operators.md +4 -3
- package/wiki/references/base/filter-system/logical-operators.md +7 -2
- package/wiki/references/base/filter-system/null-operators.md +50 -0
- package/wiki/references/base/filter-system/quick-reference.md +82 -243
- package/wiki/references/base/filter-system/range-operators.md +7 -1
- package/wiki/references/base/filter-system/tips.md +34 -7
- package/wiki/references/base/filter-system/use-cases.md +6 -5
- package/wiki/references/base/grpc-controllers.md +984 -0
- package/wiki/references/base/index.md +32 -24
- package/wiki/references/base/middleware.md +347 -0
- package/wiki/references/base/models.md +390 -46
- package/wiki/references/base/providers.md +14 -14
- package/wiki/references/base/repositories/advanced.md +84 -69
- package/wiki/references/base/repositories/index.md +447 -12
- package/wiki/references/base/repositories/mixins.md +103 -98
- package/wiki/references/base/repositories/relations.md +129 -45
- package/wiki/references/base/repositories/soft-deletable.md +104 -23
- package/wiki/references/base/services.md +94 -14
- package/wiki/references/index.md +12 -10
- package/wiki/references/quick-reference.md +98 -65
- package/wiki/references/utilities/crypto.md +21 -4
- package/wiki/references/utilities/date.md +25 -7
- package/wiki/references/utilities/index.md +26 -24
- package/wiki/references/utilities/jsx.md +54 -54
- package/wiki/references/utilities/module.md +8 -6
- package/wiki/references/utilities/parse.md +16 -9
- package/wiki/references/utilities/performance.md +22 -7
- package/wiki/references/utilities/promise.md +19 -16
- package/wiki/references/utilities/request.md +48 -26
- package/wiki/references/utilities/schema.md +69 -6
- package/wiki/references/utilities/statuses.md +131 -140
- /package/wiki/{references → extensions}/components/mail/api.md +0 -0
- /package/wiki/{references → extensions}/components/mail/errors.md +0 -0
- /package/wiki/{references → extensions}/components/mail/usage.md +0 -0
- /package/wiki/{references → extensions}/components/template/api-page.md +0 -0
- /package/wiki/{references → extensions}/components/template/errors-page.md +0 -0
- /package/wiki/{references → extensions}/components/template/usage-page.md +0 -0
- /package/wiki/{references → extensions}/helpers/cron/index.md +0 -0
- /package/wiki/{references → extensions}/helpers/inversion/index.md +0 -0
- /package/wiki/{references → extensions}/helpers/network/api.md +0 -0
- /package/wiki/{references → extensions}/helpers/network/index.md +0 -0
- /package/wiki/{references → extensions}/helpers/redis/index.md +0 -0
- /package/wiki/{references → extensions}/helpers/socket-io/api.md +0 -0
- /package/wiki/{references → extensions}/helpers/socket-io/index.md +0 -0
- /package/wiki/{references → extensions}/helpers/template/single-page.md +0 -0
- /package/wiki/{references → extensions}/helpers/uid/index.md +0 -0
- /package/wiki/{references → extensions}/helpers/websocket/api.md +0 -0
- /package/wiki/{references → extensions}/helpers/worker-thread/index.md +0 -0
- /package/wiki/{references → extensions}/src-details/mcp-server.md +0 -0
|
@@ -89,7 +89,7 @@ const pageContent = htmlContent({
|
|
|
89
89
|
|
|
90
90
|
## htmlResponse()
|
|
91
91
|
|
|
92
|
-
Creates a standard OpenAPI response object for HTML endpoints, including success (200 OK) HTML response and JSON error
|
|
92
|
+
Creates a standard OpenAPI response object for HTML endpoints, including a success (200 OK) HTML response and a JSON error response for `4xx | 5xx` status codes using the `ErrorSchema`.
|
|
93
93
|
|
|
94
94
|
### Signature
|
|
95
95
|
|
|
@@ -120,7 +120,7 @@ function htmlResponse(opts: {
|
|
|
120
120
|
### Returns
|
|
121
121
|
|
|
122
122
|
Returns an OpenAPI responses object with:
|
|
123
|
-
- `200`: Success response with HTML content
|
|
123
|
+
- `200`: Success response with HTML content (via `htmlContent()`)
|
|
124
124
|
- `4xx | 5xx`: Error responses with JSON error schema
|
|
125
125
|
|
|
126
126
|
### Example
|
|
@@ -157,14 +157,16 @@ this.defineRoute({
|
|
|
157
157
|
### Basic HTML Route
|
|
158
158
|
|
|
159
159
|
```typescript
|
|
160
|
-
import {
|
|
160
|
+
import { BaseRestController, get, htmlResponse } from '@venizia/ignis';
|
|
161
161
|
|
|
162
|
-
export class PageController extends
|
|
162
|
+
export class PageController extends BaseRestController {
|
|
163
163
|
@get({
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
164
|
+
configs: {
|
|
165
|
+
path: '/home',
|
|
166
|
+
responses: htmlResponse({
|
|
167
|
+
description: 'Home page HTML',
|
|
168
|
+
}),
|
|
169
|
+
},
|
|
168
170
|
})
|
|
169
171
|
async getHomePage() {
|
|
170
172
|
return this.context.html(
|
|
@@ -184,7 +186,7 @@ export class PageController extends BaseController {
|
|
|
184
186
|
### HTML Email Preview
|
|
185
187
|
|
|
186
188
|
```typescript
|
|
187
|
-
import {
|
|
189
|
+
import { BaseRestController, get, htmlResponse, TRouteContext, z } from '@venizia/ignis';
|
|
188
190
|
import { HTTP } from '@venizia/ignis-helpers';
|
|
189
191
|
|
|
190
192
|
const EmailRoutes = {
|
|
@@ -200,7 +202,7 @@ const EmailRoutes = {
|
|
|
200
202
|
},
|
|
201
203
|
} as const;
|
|
202
204
|
|
|
203
|
-
export class EmailController extends
|
|
205
|
+
export class EmailController extends BaseRestController {
|
|
204
206
|
@get({ configs: EmailRoutes.PREVIEW })
|
|
205
207
|
async previewTemplate(c: TRouteContext) {
|
|
206
208
|
const { templateId } = c.req.valid<{ templateId: string }>('param');
|
|
@@ -223,7 +225,7 @@ export class EmailController extends BaseController {
|
|
|
223
225
|
### Documentation Page
|
|
224
226
|
|
|
225
227
|
```typescript
|
|
226
|
-
import {
|
|
228
|
+
import { BaseRestController, get, htmlResponse, TRouteContext, z } from '@venizia/ignis';
|
|
227
229
|
import { HTTP } from '@venizia/ignis-helpers';
|
|
228
230
|
|
|
229
231
|
const DocsRoutes = {
|
|
@@ -239,7 +241,7 @@ const DocsRoutes = {
|
|
|
239
241
|
},
|
|
240
242
|
} as const;
|
|
241
243
|
|
|
242
|
-
export class DocsController extends
|
|
244
|
+
export class DocsController extends BaseRestController {
|
|
243
245
|
@get({ configs: DocsRoutes.GET_SECTION })
|
|
244
246
|
async getDocumentation(c: TRouteContext) {
|
|
245
247
|
const { section } = c.req.valid<{ section: string }>('param');
|
|
@@ -270,16 +272,17 @@ export class DocsController extends BaseController {
|
|
|
270
272
|
### Admin Dashboard
|
|
271
273
|
|
|
272
274
|
```typescript
|
|
273
|
-
import {
|
|
274
|
-
import { authenticate } from '../middleware/auth';
|
|
275
|
+
import { BaseRestController, get, htmlResponse } from '@venizia/ignis';
|
|
275
276
|
|
|
276
|
-
export class AdminController extends
|
|
277
|
+
export class AdminController extends BaseRestController {
|
|
277
278
|
@get({
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
279
|
+
configs: {
|
|
280
|
+
path: '/admin',
|
|
281
|
+
middleware: [authenticate({ role: 'admin' })],
|
|
282
|
+
responses: htmlResponse({
|
|
283
|
+
description: 'Admin dashboard',
|
|
284
|
+
}),
|
|
285
|
+
},
|
|
283
286
|
})
|
|
284
287
|
async getDashboard() {
|
|
285
288
|
const stats = await this.statsService.getAdminStats();
|
|
@@ -337,7 +340,7 @@ export class AdminController extends BaseController {
|
|
|
337
340
|
### 1. Use for Server-Side Rendering
|
|
338
341
|
|
|
339
342
|
```typescript
|
|
340
|
-
//
|
|
343
|
+
// Good: Use htmlResponse for SSR routes
|
|
341
344
|
const ProfileConfig = {
|
|
342
345
|
method: HTTP.Methods.GET,
|
|
343
346
|
path: '/profile/:userId',
|
|
@@ -352,29 +355,16 @@ async getUserProfile(c: TRouteContext) {
|
|
|
352
355
|
return c.html(<UserProfile user={user} />);
|
|
353
356
|
}
|
|
354
357
|
|
|
355
|
-
//
|
|
356
|
-
const BadConfig = {
|
|
357
|
-
method: HTTP.Methods.GET,
|
|
358
|
-
path: '/api/users/:userId',
|
|
359
|
-
request: { params: z.object({ userId: z.string() }) },
|
|
360
|
-
responses: htmlResponse({ description: 'User data' }), // Wrong!
|
|
361
|
-
} as const;
|
|
362
|
-
|
|
363
|
-
@get({ configs: BadConfig })
|
|
364
|
-
async getUser(c: TRouteContext) {
|
|
365
|
-
const { userId } = c.req.valid<{ userId: string }>('param');
|
|
366
|
-
return { id: userId, name: 'John' }; // Should use jsonResponse
|
|
367
|
-
}
|
|
358
|
+
// Bad: Don't use htmlResponse for API endpoints — use jsonResponse instead
|
|
368
359
|
```
|
|
369
360
|
|
|
370
361
|
### 2. Combine with Authentication
|
|
371
362
|
|
|
372
363
|
```typescript
|
|
373
|
-
// ✅ Good: Protect HTML routes with auth
|
|
374
364
|
const SettingsConfig = {
|
|
375
365
|
method: HTTP.Methods.GET,
|
|
376
366
|
path: '/admin/settings',
|
|
377
|
-
|
|
367
|
+
authenticate: { strategies: ['jwt'] },
|
|
378
368
|
responses: htmlResponse({ description: 'Settings page' }),
|
|
379
369
|
} as const;
|
|
380
370
|
|
|
@@ -490,8 +480,10 @@ export const Layout = (props: { title: string; children: any }) => {
|
|
|
490
480
|
import { Layout } from './components/Layout';
|
|
491
481
|
|
|
492
482
|
@get({
|
|
493
|
-
|
|
494
|
-
|
|
483
|
+
configs: {
|
|
484
|
+
path: '/',
|
|
485
|
+
responses: htmlResponse({ description: 'Home page' }),
|
|
486
|
+
},
|
|
495
487
|
})
|
|
496
488
|
async getHome() {
|
|
497
489
|
return this.context.html(
|
|
@@ -509,19 +501,23 @@ async getHome() {
|
|
|
509
501
|
### Pitfall 1: Missing HTML Wrapper
|
|
510
502
|
|
|
511
503
|
```typescript
|
|
512
|
-
//
|
|
504
|
+
// Bad: Incomplete HTML
|
|
513
505
|
@get({
|
|
514
|
-
|
|
515
|
-
|
|
506
|
+
configs: {
|
|
507
|
+
path: '/page',
|
|
508
|
+
responses: htmlResponse({ description: 'Page' }),
|
|
509
|
+
},
|
|
516
510
|
})
|
|
517
511
|
async getPage() {
|
|
518
512
|
return this.context.html(<div>Hello</div>); // Missing <html>, <head>, <body>
|
|
519
513
|
}
|
|
520
514
|
|
|
521
|
-
//
|
|
515
|
+
// Good: Complete HTML document
|
|
522
516
|
@get({
|
|
523
|
-
|
|
524
|
-
|
|
517
|
+
configs: {
|
|
518
|
+
path: '/page',
|
|
519
|
+
responses: htmlResponse({ description: 'Page' }),
|
|
520
|
+
},
|
|
525
521
|
})
|
|
526
522
|
async getPage() {
|
|
527
523
|
return this.context.html(
|
|
@@ -536,22 +532,26 @@ async getPage() {
|
|
|
536
532
|
### Pitfall 2: Using htmlResponse for APIs
|
|
537
533
|
|
|
538
534
|
```typescript
|
|
539
|
-
//
|
|
535
|
+
// Bad: HTML response for API
|
|
540
536
|
@get({
|
|
541
|
-
|
|
542
|
-
|
|
537
|
+
configs: {
|
|
538
|
+
path: '/api/users',
|
|
539
|
+
responses: htmlResponse({ description: 'Users' }),
|
|
540
|
+
},
|
|
543
541
|
})
|
|
544
542
|
async getUsers() {
|
|
545
543
|
return { users: [...] }; // Should return HTML or use jsonResponse
|
|
546
544
|
}
|
|
547
545
|
|
|
548
|
-
//
|
|
546
|
+
// Good: Use jsonResponse for APIs
|
|
549
547
|
@get({
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
548
|
+
configs: {
|
|
549
|
+
path: '/api/users',
|
|
550
|
+
responses: jsonResponse({
|
|
551
|
+
description: 'Users list',
|
|
552
|
+
schema: z.object({ users: z.array(UserSchema) }),
|
|
553
|
+
}),
|
|
554
|
+
},
|
|
555
555
|
})
|
|
556
556
|
async getUsers() {
|
|
557
557
|
return { users: await this.userService.findAll() };
|
|
@@ -564,7 +564,7 @@ async getUsers() {
|
|
|
564
564
|
- **Related References:**
|
|
565
565
|
- [Schema Utility](./schema.md) - JSON content and response helpers
|
|
566
566
|
- [Controllers](../base/controllers.md) - Defining routes and handlers
|
|
567
|
-
- [OpenAPI Component](
|
|
567
|
+
- [OpenAPI Component](/extensions/components/swagger) - API documentation
|
|
568
568
|
|
|
569
569
|
- **External Resources:**
|
|
570
570
|
- [Hono JSX Documentation](https://hono.dev/guides/jsx)
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
# Module Utility
|
|
2
2
|
|
|
3
|
-
The Module utility provides a
|
|
3
|
+
The Module utility provides a function to validate the existence of Node.js modules at runtime. It uses `createRequire` from `node:module` rooted at the application's `process.cwd()/node_modules`, so peer dependencies in the consuming application are properly resolved even though this utility lives inside `packages/helpers`.
|
|
4
4
|
|
|
5
5
|
## `validateModule`
|
|
6
6
|
|
|
7
|
-
The `validateModule` function checks if a list of modules can be resolved. If a module is not found, it throws a descriptive error, prompting the developer to install it. This is particularly useful for features that have optional peer dependencies.
|
|
7
|
+
The `validateModule` function checks if a list of modules can be resolved. If a module is not found, it logs the error and throws a descriptive error, prompting the developer to install it. This is particularly useful for features that have optional peer dependencies.
|
|
8
8
|
|
|
9
9
|
### `validateModule(opts)`
|
|
10
10
|
|
|
11
11
|
- `opts` (object):
|
|
12
|
-
- `scope` (string, optional): A string to identify the feature or component that requires the module, making the error message more informative.
|
|
13
|
-
- `modules` (Array<string>): An array of module names to validate.
|
|
12
|
+
- `scope` (string, optional): A string to identify the feature or component that requires the module, making the error message more informative. Defaults to empty string.
|
|
13
|
+
- `modules` (Array<string>): An array of module names to validate. Defaults to empty array.
|
|
14
|
+
|
|
15
|
+
This is an `async` function, though it performs synchronous resolution internally.
|
|
14
16
|
|
|
15
17
|
### Example
|
|
16
18
|
|
|
@@ -24,7 +26,7 @@ export class SwaggerComponent extends BaseComponent {
|
|
|
24
26
|
|
|
25
27
|
override async binding() {
|
|
26
28
|
// This will throw an error if '@hono/swagger-ui' is not installed
|
|
27
|
-
validateModule({ scope: SwaggerComponent.name, modules: ['@hono/swagger-ui'] });
|
|
29
|
+
await validateModule({ scope: SwaggerComponent.name, modules: ['@hono/swagger-ui'] });
|
|
28
30
|
|
|
29
31
|
const { swaggerUI } = await import('@hono/swagger-ui');
|
|
30
32
|
|
|
@@ -33,7 +35,7 @@ export class SwaggerComponent extends BaseComponent {
|
|
|
33
35
|
}
|
|
34
36
|
```
|
|
35
37
|
|
|
36
|
-
If the module is missing, the application will fail
|
|
38
|
+
If the module is missing, the application will fail with an error message like:
|
|
37
39
|
|
|
38
40
|
```
|
|
39
41
|
[validateModule] @hono/swagger-ui is required for SwaggerComponent. Please install '@hono/swagger-ui'
|
|
@@ -9,17 +9,19 @@ The Parse utility provides a collection of functions for data type checking, con
|
|
|
9
9
|
|
|
10
10
|
## Type Conversion
|
|
11
11
|
|
|
12
|
-
- **`int(input)`**: Parses a value to an integer. Handles
|
|
13
|
-
- **`float(input, digit = 2)`**: Parses a value to a float, rounding to a specified number of digits. Handles
|
|
14
|
-
- **`toBoolean(input)`**: Converts
|
|
15
|
-
- **`toStringDecimal(input, digit = 2)`**: Formats a number to a string with a specified number of decimal places
|
|
12
|
+
- **`int(input)`**: Parses a value to an integer. Handles strings with commas and defaults to `0` if the input is invalid.
|
|
13
|
+
- **`float(input, digit = 2)`**: Parses a value to a float, rounding to a specified number of digits. Handles strings with commas and defaults to `0` if the input is invalid.
|
|
14
|
+
- **`toBoolean(input)`**: Converts a value to a boolean. Returns `false` for empty string, `'false'`, `'0'`, `false`, `0`, `null`, and `undefined`. Returns `true` for everything else.
|
|
15
|
+
- **`toStringDecimal(input, digit = 2, options?)`**: Formats a number to a string with a specified number of decimal places. By default uses locale-specific formatting (`useLocaleFormat: true`). When `useLocaleFormat` is `false`, uses `toFixed()` instead.
|
|
16
16
|
|
|
17
17
|
```typescript
|
|
18
|
-
import { int, float, toBoolean } from '@venizia/ignis-helpers';
|
|
18
|
+
import { int, float, toBoolean, toStringDecimal } from '@venizia/ignis-helpers';
|
|
19
19
|
|
|
20
20
|
const myInt = int('1,000'); // => 1000
|
|
21
21
|
const myFloat = float('1,234.567', 2); // => 1234.57
|
|
22
22
|
const myBool = toBoolean('true'); // => true
|
|
23
|
+
const formatted = toStringDecimal(1234.5, 2); // => '1,234.50'
|
|
24
|
+
const fixed = toStringDecimal(1234.5, 2, { useLocaleFormat: false }); // => '1234.50'
|
|
23
25
|
```
|
|
24
26
|
|
|
25
27
|
## String and Object Transformation
|
|
@@ -59,17 +61,22 @@ getNumberValue('1.234,56', { method: 'float', locale: 'eu' }); // => 1234.56
|
|
|
59
61
|
|
|
60
62
|
## Array Transformation
|
|
61
63
|
|
|
62
|
-
- **`parseArrayToRecordWithKey(opts)`**: Transforms an array of objects into a record (plain object), using a specified property of the objects as keys.
|
|
63
|
-
- **`parseArrayToMapWithKey(arr, keyMap)`**: Transforms an array of objects into a `Map`, using a specified property of the objects as keys.
|
|
64
|
+
- **`parseArrayToRecordWithKey(opts)`**: Transforms an array of objects into a record (plain object), using a specified property of the objects as keys. Takes an options object with `arr` and `keyMap` properties. Throws an error if `keyMap` is not found in an element. Last element wins on duplicate keys.
|
|
65
|
+
- **`parseArrayToMapWithKey(arr, keyMap)`**: Transforms an array of objects into a `Map`, using a specified property of the objects as keys. Takes positional arguments (not an options object). Throws an error if `keyMap` is not found in an element. Last element wins on duplicate keys.
|
|
64
66
|
|
|
65
67
|
```typescript
|
|
66
|
-
import { parseArrayToMapWithKey } from '@venizia/ignis-helpers';
|
|
68
|
+
import { parseArrayToRecordWithKey, parseArrayToMapWithKey } from '@venizia/ignis-helpers';
|
|
67
69
|
|
|
68
70
|
const users = [
|
|
69
71
|
{ id: 1, name: 'Alice' },
|
|
70
72
|
{ id: 2, name: 'Bob' },
|
|
71
73
|
];
|
|
72
74
|
|
|
75
|
+
// Record (options object pattern)
|
|
76
|
+
const usersRecord = parseArrayToRecordWithKey({ arr: users, keyMap: 'id' });
|
|
77
|
+
// => { 1: { id: 1, name: 'Alice' }, 2: { id: 2, name: 'Bob' } }
|
|
78
|
+
|
|
79
|
+
// Map (positional arguments)
|
|
73
80
|
const usersMap = parseArrayToMapWithKey(users, 'id');
|
|
74
81
|
// => Map { 1 => { id: 1, name: 'Alice' }, 2 => { id: 2, name: 'Bob' } }
|
|
75
82
|
|
|
@@ -79,7 +86,7 @@ const user = usersMap.get(1);
|
|
|
79
86
|
|
|
80
87
|
## Unique ID
|
|
81
88
|
|
|
82
|
-
- **`getUID()`**: Generates a simple, short unique ID string.
|
|
89
|
+
- **`getUID()`**: Generates a simple, short unique ID string based on `Math.random()`, returned in uppercase.
|
|
83
90
|
|
|
84
91
|
```typescript
|
|
85
92
|
import { getUID } from '@venizia/ignis-helpers';
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
# Performance Utility
|
|
2
2
|
|
|
3
|
-
The Performance utility provides functions for measuring and logging the execution time of code blocks, which is useful for identifying bottlenecks and optimizing your application.
|
|
3
|
+
The Performance utility provides functions for measuring and logging the execution time of code blocks, which is useful for identifying bottlenecks and optimizing your application. All timing uses `performance.now()` (milliseconds).
|
|
4
4
|
|
|
5
5
|
## `executeWithPerformanceMeasure`
|
|
6
6
|
|
|
7
|
-
This is a higher-order function that wraps a task (a function
|
|
7
|
+
This is a higher-order function that wraps a task (a function), automatically logging its start time, end time, and total execution duration. Returns a Promise that resolves to the task's return value.
|
|
8
8
|
|
|
9
9
|
### `executeWithPerformanceMeasure(opts)`
|
|
10
10
|
|
|
11
11
|
- `opts` (object):
|
|
12
|
-
- `logger` (
|
|
13
|
-
- `
|
|
12
|
+
- `logger` (Logger, optional): A logger instance to use for logging. Defaults to `console`.
|
|
13
|
+
- `level` (string, optional): The log level to use (e.g., `'debug'`, `'info'`). Defaults to `'debug'`.
|
|
14
|
+
- `description` (string, optional): A description of the task being measured. Defaults to `'Executing'`.
|
|
14
15
|
- `scope` (string): A scope to identify the context of the measurement.
|
|
15
|
-
- `task` (Function): The
|
|
16
|
+
- `task` (Function): The function to be executed and measured. Can be sync or async (wrapped with `Promise.resolve()`).
|
|
17
|
+
- `args` (any, optional): Arguments to include in the log output for debugging purposes. When provided, they are logged as JSON (`%j` format).
|
|
16
18
|
|
|
17
19
|
### Example
|
|
18
20
|
|
|
@@ -43,12 +45,21 @@ When `registerComponents` is called, it will produce log output similar to this:
|
|
|
43
45
|
[RegisterComponents] DONE | Register application components | Took: 12.3456 (ms)
|
|
44
46
|
```
|
|
45
47
|
|
|
48
|
+
With `args` provided:
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
[RegisterComponents] START | Register application components... | Args: {"name":"auth"}
|
|
52
|
+
[RegisterComponents] DONE | Register application components | Args: {"name":"auth"} | Took: 12.3456 (ms)
|
|
53
|
+
```
|
|
54
|
+
|
|
46
55
|
## Low-Level Utilities
|
|
47
56
|
|
|
48
57
|
For more granular measurements, you can use the lower-level functions:
|
|
49
58
|
|
|
50
|
-
- **`getPerformanceCheckpoint()`**: Returns a high-resolution timestamp
|
|
51
|
-
- **`getExecutedPerformance(opts)`**: Calculates the elapsed time in milliseconds since a given checkpoint.
|
|
59
|
+
- **`getPerformanceCheckpoint()`**: Returns a high-resolution timestamp from `performance.now()`, which you can use as a starting point.
|
|
60
|
+
- **`getExecutedPerformance(opts)`**: Calculates the elapsed time in milliseconds since a given checkpoint. Rounds to 6 decimal places by default.
|
|
61
|
+
- `opts.from` (number): The starting checkpoint from `getPerformanceCheckpoint()`.
|
|
62
|
+
- `opts.digit` (number, optional): Number of decimal places for rounding. Defaults to `6`.
|
|
52
63
|
|
|
53
64
|
### Example
|
|
54
65
|
|
|
@@ -61,4 +72,8 @@ const start = getPerformanceCheckpoint();
|
|
|
61
72
|
|
|
62
73
|
const duration = getExecutedPerformance({ from: start });
|
|
63
74
|
console.log(`The work took ${duration} ms.`);
|
|
75
|
+
|
|
76
|
+
// With custom precision
|
|
77
|
+
const preciseDuration = getExecutedPerformance({ from: start, digit: 2 });
|
|
78
|
+
console.log(`The work took ${preciseDuration} ms.`);
|
|
64
79
|
```
|
|
@@ -11,7 +11,7 @@ This function executes an array of asynchronous tasks concurrently, but with a s
|
|
|
11
11
|
- `opts` (object):
|
|
12
12
|
- `tasks` (Array<() => Promise<T>>): An array of functions that each return a Promise.
|
|
13
13
|
- `limit` (number): The maximum number of promises to execute in parallel.
|
|
14
|
-
- `onTaskDone` (<R>(opts: { result: R }) => ValueOrPromise<void>, optional): A callback function that is executed whenever a task is completed.
|
|
14
|
+
- `onTaskDone` (<R>(opts: { result: R }) => ValueOrPromise<void>, optional): A callback function that is executed whenever a task is completed (specifically, when the concurrency limit is reached and a task finishes to make room).
|
|
15
15
|
|
|
16
16
|
### Example
|
|
17
17
|
|
|
@@ -38,9 +38,22 @@ const results = await executePromiseWithLimit({
|
|
|
38
38
|
console.log('All tasks finished:', results);
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
+
## `transformValueOrPromise`
|
|
42
|
+
|
|
43
|
+
This async function applies a transformation function to a value that might be a direct value or a Promise. It always returns a Promise.
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
import { transformValueOrPromise } from '@venizia/ignis-helpers';
|
|
47
|
+
|
|
48
|
+
const double = (n: number) => n * 2;
|
|
49
|
+
|
|
50
|
+
const result1 = await transformValueOrPromise(5, double); // => 10
|
|
51
|
+
const result2 = await transformValueOrPromise(Promise.resolve(5), double); // => 10
|
|
52
|
+
```
|
|
53
|
+
|
|
41
54
|
## `isPromiseLike`
|
|
42
55
|
|
|
43
|
-
A type guard function to check if a given value is a Promise-like object (i.e., it has a `then` method).
|
|
56
|
+
A type guard function to check if a given value is a Promise-like object (i.e., it has a `then` method). Checks that the value is non-null, is an object or function, and has a `then` property that is a function.
|
|
44
57
|
|
|
45
58
|
```typescript
|
|
46
59
|
import { isPromiseLike } from '@venizia/ignis-helpers';
|
|
@@ -57,22 +70,9 @@ if (isPromiseLike(b)) {
|
|
|
57
70
|
}
|
|
58
71
|
```
|
|
59
72
|
|
|
60
|
-
## `transformValueOrPromise`
|
|
61
|
-
|
|
62
|
-
This function applies a transformation function to a value that might be a direct value or a Promise.
|
|
63
|
-
|
|
64
|
-
```typescript
|
|
65
|
-
import { transformValueOrPromise, isPromiseLike } from '@venizia/ignis-helpers';
|
|
66
|
-
|
|
67
|
-
const double = (n: number) => n * 2;
|
|
68
|
-
|
|
69
|
-
const result1 = await transformValueOrPromise(5, double); // => 10
|
|
70
|
-
const result2 = await transformValueOrPromise(Promise.resolve(5), double); // => 10
|
|
71
|
-
```
|
|
72
|
-
|
|
73
73
|
## `getDeepProperty`
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
Traverses a dot-separated property path on an object and returns the value. It throws an error if any intermediate part of the path is null or undefined.
|
|
76
76
|
|
|
77
77
|
```typescript
|
|
78
78
|
import { getDeepProperty } from '@venizia/ignis-helpers';
|
|
@@ -80,4 +80,7 @@ import { getDeepProperty } from '@venizia/ignis-helpers';
|
|
|
80
80
|
const obj = { a: { b: { c: 'hello' } } };
|
|
81
81
|
|
|
82
82
|
const value = getDeepProperty(obj, 'a.b.c'); // => 'hello'
|
|
83
|
+
|
|
84
|
+
// Throws: Cannot read property 'x' of undefined
|
|
85
|
+
getDeepProperty(obj, 'a.x.y');
|
|
83
86
|
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Request Utility
|
|
2
2
|
|
|
3
|
-
The Request utility provides functions for handling HTTP request data, such as parsing multipart form data.
|
|
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
4
|
|
|
5
5
|
## `parseMultipartBody`
|
|
6
6
|
|
|
@@ -9,21 +9,21 @@ The `parseMultipartBody` function is an asynchronous utility for parsing `multip
|
|
|
9
9
|
### `parseMultipartBody(opts)`
|
|
10
10
|
|
|
11
11
|
- `opts` (object):
|
|
12
|
-
- `context` (
|
|
13
|
-
- `storage` ('memory' | 'disk'
|
|
14
|
-
- `uploadDir` (string, optional): The directory to save files to when using the `'disk'` storage strategy. Defaults to `'./uploads'`.
|
|
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
15
|
|
|
16
|
-
The function returns a `Promise` that resolves to an array of `IParsedFile` objects.
|
|
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
17
|
|
|
18
18
|
### `IParsedFile` Interface
|
|
19
19
|
|
|
20
20
|
- `fieldname`: The name of the form field.
|
|
21
21
|
- `originalname`: The original name of the uploaded file.
|
|
22
|
-
- `encoding`: The file's encoding.
|
|
22
|
+
- `encoding`: The file's encoding (always `'utf8'`).
|
|
23
23
|
- `mimetype`: The MIME type of the file.
|
|
24
24
|
- `size`: The size of the file in bytes.
|
|
25
25
|
- `buffer` (Buffer, optional): The file's content as a Buffer (if `storage` is `'memory'`).
|
|
26
|
-
- `filename` (string, optional): The name of the file on disk (if `storage` is `'disk'`).
|
|
26
|
+
- `filename` (string, optional): The generated name of the file on disk (if `storage` is `'disk'`). Format: `{timestamp}-{randomString}-{sanitizedOriginalName}`.
|
|
27
27
|
- `path` (string, optional): The full path to the file on disk (if `storage` is `'disk'`).
|
|
28
28
|
|
|
29
29
|
### Example
|
|
@@ -31,11 +31,11 @@ The function returns a `Promise` that resolves to an array of `IParsedFile` obje
|
|
|
31
31
|
Here is an example of how to use `parseMultipartBody` in a controller to handle a file upload.
|
|
32
32
|
|
|
33
33
|
```typescript
|
|
34
|
-
import {
|
|
34
|
+
import { BaseRestController, controller } from '@venizia/ignis';
|
|
35
35
|
import { parseMultipartBody, HTTP } from '@venizia/ignis-helpers';
|
|
36
36
|
|
|
37
37
|
@controller({ path: '/files' })
|
|
38
|
-
export class FileController extends
|
|
38
|
+
export class FileController extends BaseRestController {
|
|
39
39
|
// ...
|
|
40
40
|
override binding() {
|
|
41
41
|
this.defineRoute({
|
|
@@ -81,27 +81,38 @@ These utilities help create secure, RFC-compliant `Content-Disposition` headers
|
|
|
81
81
|
|
|
82
82
|
Creates a safe Content-Disposition header with proper filename encoding for file downloads.
|
|
83
83
|
|
|
84
|
-
#### `createContentDispositionHeader(
|
|
84
|
+
#### `createContentDispositionHeader(opts)`
|
|
85
85
|
|
|
86
|
-
- `
|
|
86
|
+
- `opts` (object):
|
|
87
|
+
- `filename` (string): The filename to use in the Content-Disposition header.
|
|
88
|
+
- `type` (`'attachment'` | `'inline'`): The disposition type.
|
|
87
89
|
|
|
88
90
|
The function returns a properly formatted `Content-Disposition` header string with both ASCII and UTF-8 encoded filenames for maximum browser compatibility.
|
|
89
91
|
|
|
90
92
|
**Features:**
|
|
91
|
-
- Automatic filename sanitization (
|
|
92
|
-
- UTF-8 encoding support
|
|
93
|
+
- Automatic filename sanitization via `sanitizeFilename()`
|
|
94
|
+
- UTF-8 encoding support via `encodeRFC5987()`
|
|
93
95
|
- RFC 5987 compliant
|
|
94
|
-
-
|
|
96
|
+
- Dual `filename` / `filename*` for browser compatibility
|
|
95
97
|
|
|
96
98
|
**Example:**
|
|
97
99
|
|
|
98
100
|
```typescript
|
|
99
101
|
import { createContentDispositionHeader } from '@venizia/ignis-helpers';
|
|
100
102
|
|
|
101
|
-
//
|
|
102
|
-
ctx.header('content-disposition', createContentDispositionHeader(
|
|
103
|
-
|
|
103
|
+
// Attachment (file download)
|
|
104
|
+
ctx.header('content-disposition', createContentDispositionHeader({
|
|
105
|
+
filename: 'my-document.pdf',
|
|
106
|
+
type: 'attachment',
|
|
107
|
+
}));
|
|
104
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
|
|
105
116
|
```
|
|
106
117
|
|
|
107
118
|
---
|
|
@@ -117,13 +128,13 @@ Sanitizes a filename for safe use, removing path components and dangerous charac
|
|
|
117
128
|
Returns a safe filename suitable for use in headers or filesystem operations.
|
|
118
129
|
|
|
119
130
|
**Features:**
|
|
120
|
-
- Removes path components (prevents directory traversal attacks)
|
|
121
|
-
- Allows only
|
|
131
|
+
- Removes path components via `path.basename()` (prevents directory traversal attacks)
|
|
132
|
+
- Allows only word characters (`\w`), spaces, hyphens, underscores, and dots
|
|
122
133
|
- Replaces dangerous characters with underscores
|
|
123
134
|
- Removes leading dots (prevents hidden files)
|
|
124
135
|
- Replaces consecutive dots with a single dot
|
|
125
136
|
- Removes ".." patterns (additional path traversal protection)
|
|
126
|
-
-
|
|
137
|
+
- Returns `'download'` for empty, suspicious, or invalid filenames
|
|
127
138
|
|
|
128
139
|
**Example:**
|
|
129
140
|
|
|
@@ -134,7 +145,6 @@ sanitizeFilename('../../etc/passwd'); // Returns: 'passwd'
|
|
|
134
145
|
sanitizeFilename('my<file>name.txt'); // Returns: 'my_file_name.txt'
|
|
135
146
|
sanitizeFilename('.hidden'); // Returns: 'hidden'
|
|
136
147
|
sanitizeFilename('file...txt'); // Returns: 'file.txt'
|
|
137
|
-
sanitizeFilename('документ.pdf'); // Returns: '_________.pdf'
|
|
138
148
|
sanitizeFilename(''); // Returns: 'download'
|
|
139
149
|
sanitizeFilename('..'); // Returns: 'download'
|
|
140
150
|
```
|
|
@@ -142,7 +152,7 @@ sanitizeFilename('..'); // Returns: 'download'
|
|
|
142
152
|
|
|
143
153
|
### `encodeRFC5987`
|
|
144
154
|
|
|
145
|
-
Encodes a filename according to RFC 5987 for use in HTTP headers.
|
|
155
|
+
Encodes a filename according to RFC 5987 for use in HTTP headers. Encodes using `encodeURIComponent` and additionally escapes single quotes, parentheses, and asterisks.
|
|
146
156
|
|
|
147
157
|
#### `encodeRFC5987(filename: string): string`
|
|
148
158
|
|
|
@@ -156,22 +166,31 @@ Returns an RFC 5987 encoded string suitable for the `filename*` parameter in Con
|
|
|
156
166
|
import { encodeRFC5987 } from '@venizia/ignis-helpers';
|
|
157
167
|
|
|
158
168
|
encodeRFC5987('my document.pdf'); // Returns: 'my%20document.pdf'
|
|
159
|
-
encodeRFC5987('файл.txt'); // Returns: '%D1%84%D0%B0%D0%B9%D0%BB.txt'
|
|
160
169
|
```
|
|
161
170
|
|
|
162
171
|
|
|
172
|
+
## `IRequestedRemark` Interface
|
|
173
|
+
|
|
174
|
+
The Request utility also exports the `IRequestedRemark` interface, which describes a request remark object:
|
|
175
|
+
|
|
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.
|
|
180
|
+
|
|
181
|
+
|
|
163
182
|
## Complete File Download Example
|
|
164
183
|
|
|
165
184
|
Here's a complete example combining multipart upload parsing with secure file downloads:
|
|
166
185
|
|
|
167
186
|
```typescript
|
|
168
|
-
import {
|
|
187
|
+
import { BaseRestController, controller } from '@venizia/ignis';
|
|
169
188
|
import { parseMultipartBody, createContentDispositionHeader, HTTP } from '@venizia/ignis-helpers';
|
|
170
189
|
import fs from 'node:fs';
|
|
171
190
|
import path from 'node:path';
|
|
172
191
|
|
|
173
192
|
@controller({ path: '/files' })
|
|
174
|
-
export class FileController extends
|
|
193
|
+
export class FileController extends BaseRestController {
|
|
175
194
|
override binding() {
|
|
176
195
|
// Upload endpoint
|
|
177
196
|
this.bindRoute({
|
|
@@ -209,7 +228,10 @@ export class FileController extends BaseController {
|
|
|
209
228
|
// Set secure headers
|
|
210
229
|
ctx.header('content-type', 'application/octet-stream');
|
|
211
230
|
ctx.header('content-length', fileStat.size.toString());
|
|
212
|
-
ctx.header('content-disposition', createContentDispositionHeader(
|
|
231
|
+
ctx.header('content-disposition', createContentDispositionHeader({
|
|
232
|
+
filename,
|
|
233
|
+
type: 'attachment',
|
|
234
|
+
}));
|
|
213
235
|
ctx.header('x-content-type-options', 'nosniff');
|
|
214
236
|
|
|
215
237
|
return new Response(fileStream, {
|