@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
|
@@ -350,8 +350,7 @@ import {
|
|
|
350
350
|
extraUserColumns,
|
|
351
351
|
extraRoleColumns,
|
|
352
352
|
extraPermissionColumns,
|
|
353
|
-
|
|
354
|
-
extraUserRoleColumns,
|
|
353
|
+
extraPolicyDefinitionColumns,
|
|
355
354
|
} from '@venizia/ignis';
|
|
356
355
|
import { withSerialId, withTimestamps } from '@venizia/ignis';
|
|
357
356
|
|
|
@@ -379,16 +378,11 @@ export const permissions = pgTable('permissions', {
|
|
|
379
378
|
...extraPermissionColumns(),
|
|
380
379
|
});
|
|
381
380
|
|
|
382
|
-
//
|
|
383
|
-
export const
|
|
384
|
-
...withSerialId(),
|
|
385
|
-
...extraPermissionMappingColumns(),
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
// User-role junction table
|
|
389
|
-
export const userRoles = pgTable('user_roles', {
|
|
381
|
+
// Policy definition table (Casbin-style policies)
|
|
382
|
+
export const policyDefinitions = pgTable('policy_definitions', {
|
|
390
383
|
...withSerialId(),
|
|
391
|
-
...
|
|
384
|
+
...withTimestamps(),
|
|
385
|
+
...extraPolicyDefinitionColumns(),
|
|
392
386
|
});
|
|
393
387
|
```
|
|
394
388
|
|
|
@@ -435,41 +429,31 @@ extraPermissionColumns(opts?: { idType: 'string' | 'number' })
|
|
|
435
429
|
|
|
436
430
|
| Column | Type | Default | Description |
|
|
437
431
|
|--------|------|---------|-------------|
|
|
438
|
-
| `code` | `text` | -- | Unique permission code |
|
|
432
|
+
| `code` | `text` (unique) | -- | Unique permission code |
|
|
439
433
|
| `name` | `text` | -- | Permission name |
|
|
440
434
|
| `subject` | `text` | -- | Permission subject (e.g., `'User'`, `'Order'`) |
|
|
441
|
-
| `pType` | `text` | -- | Permission type (maps to DB column `p_type`) |
|
|
442
435
|
| `action` | `text` | -- | Permitted action (e.g., `'read'`, `'write'`) |
|
|
443
436
|
| `scope` | `text` | -- | Permission scope |
|
|
444
437
|
| `parentId` | `text` or `integer` | `null` | Parent permission ID |
|
|
445
438
|
|
|
446
|
-
###
|
|
447
|
-
|
|
448
|
-
Returns columns for mapping permissions to users or roles.
|
|
449
|
-
|
|
450
|
-
```typescript
|
|
451
|
-
extraPermissionMappingColumns(opts?: { idType: 'string' | 'number' })
|
|
452
|
-
```
|
|
453
|
-
|
|
454
|
-
| Column | Type | Default | Description |
|
|
455
|
-
|--------|------|---------|-------------|
|
|
456
|
-
| `effect` | `text` | `null` | Permission effect (e.g., `'allow'`, `'deny'`) |
|
|
457
|
-
| `userId` | `text` or `integer` | `null` | Associated user ID |
|
|
458
|
-
| `roleId` | `text` or `integer` | `null` | Associated role ID |
|
|
459
|
-
| `permissionId` | `text` or `integer` | -- | Associated permission ID (not null) |
|
|
460
|
-
|
|
461
|
-
### extraUserRoleColumns
|
|
439
|
+
### extraPolicyDefinitionColumns
|
|
462
440
|
|
|
463
|
-
Returns columns for
|
|
441
|
+
Returns columns for Casbin-style policy definitions that map subjects (users/roles) to targets (resources/permissions).
|
|
464
442
|
|
|
465
443
|
```typescript
|
|
466
|
-
|
|
444
|
+
extraPolicyDefinitionColumns(opts?: { idType: 'string' | 'number' })
|
|
467
445
|
```
|
|
468
446
|
|
|
469
|
-
| Column | Type | Default | Description |
|
|
470
|
-
|
|
471
|
-
|
|
|
472
|
-
| `
|
|
447
|
+
| Column | DB Column | Type | Nullable | Default | Description |
|
|
448
|
+
|--------|-----------|------|----------|---------|-------------|
|
|
449
|
+
| `variant` | `variant` | `text` | No | -- | Policy variant (e.g., `'p'` for policy, `'g'` for grouping) |
|
|
450
|
+
| `subjectType` | `subject_type` | `text` | No | -- | Type of subject (e.g., `'user'`, `'role'`) |
|
|
451
|
+
| `targetType` | `target_type` | `text` | No | -- | Type of target (e.g., `'permission'`, `'role'`) |
|
|
452
|
+
| `action` | `action` | `text` | Yes | `null` | Policy action |
|
|
453
|
+
| `effect` | `effect` | `text` | Yes | `null` | Policy effect (e.g., `'allow'`, `'deny'`) |
|
|
454
|
+
| `domain` | `domain` | `text` | Yes | `null` | Policy domain for multi-tenancy |
|
|
455
|
+
| `subjectId` | `subject_id` | `text` or `integer` | No | -- | Subject ID (type depends on `idType`) |
|
|
456
|
+
| `targetId` | `target_id` | `text` or `integer` | No | -- | Target ID (type depends on `idType`) |
|
|
473
457
|
|
|
474
458
|
### ID Type Polymorphism
|
|
475
459
|
|
|
@@ -701,7 +685,7 @@ flowchart TD
|
|
|
701
685
|
**`all` mode:**
|
|
702
686
|
- Every strategy must pass successfully
|
|
703
687
|
- If any strategy fails, the request is immediately rejected (exception propagates)
|
|
704
|
-
- The
|
|
688
|
+
- The **first** strategy's user payload is used as the identity source
|
|
705
689
|
- **Use case:** Multi-factor authentication (both JWT and Basic required)
|
|
706
690
|
|
|
707
691
|
> [!TIP]
|
|
@@ -51,7 +51,9 @@ flowchart TD
|
|
|
51
51
|
Roles -->|No match| Voters{Has voters?}
|
|
52
52
|
Voters -->|DENY| E403a[/403 Denied by voter/]
|
|
53
53
|
Voters -->|ALLOW| Next4([next - voter allow])
|
|
54
|
-
Voters -->|ABSTAIN / none|
|
|
54
|
+
Voters -->|ABSTAIN / none| HasEnforcers{Enforcers registered?}
|
|
55
|
+
HasEnforcers -->|No| Next6([next - skip, no enforcers])
|
|
56
|
+
HasEnforcers -->|Yes| Resolve[Resolve enforcer by name]
|
|
55
57
|
Resolve --> Cache{Rules cached?}
|
|
56
58
|
Cache -->|Yes| Evaluate
|
|
57
59
|
Cache -->|No| PType{principalType?}
|
|
@@ -91,6 +93,7 @@ classDiagram
|
|
|
91
93
|
-configuredEnforcers: Set
|
|
92
94
|
+getInstance()$ AuthorizationEnforcerRegistry
|
|
93
95
|
+register(opts) this
|
|
96
|
+
+hasEnforcers() boolean
|
|
94
97
|
+resolveEnforcer(opts) Promise
|
|
95
98
|
+resolveOptions() IAuthorizeOptions
|
|
96
99
|
}
|
|
@@ -196,6 +199,7 @@ auth/authorize/
|
|
|
196
199
|
| **Abstract base** | `AbstractAuthRegistry<T>` shared between authentication and authorization registries |
|
|
197
200
|
| **Filtered adapter pattern** | `BaseFilteredAdapter` template method pattern allows custom query backends while sharing formatting logic |
|
|
198
201
|
| **IAuthorizationComparable** | Generic comparison interface for custom action/resource types beyond plain strings |
|
|
202
|
+
| **No-enforcer fallback** | When no enforcers are registered, the middleware skips authorization and calls `next()` instead of throwing -- prevents hard failures during development or gradual rollout |
|
|
199
203
|
|
|
200
204
|
## Component Lifecycle
|
|
201
205
|
|
|
@@ -298,6 +302,7 @@ class AuthorizationEnforcerRegistry extends AbstractAuthRegistry<IAuthorizationE
|
|
|
298
302
|
protected getBindingPrefix(): string; // returns Authorization.ENFORCER
|
|
299
303
|
|
|
300
304
|
register(opts: { ... }): this;
|
|
305
|
+
hasEnforcers(): boolean;
|
|
301
306
|
getDefaultEnforcerName(): string;
|
|
302
307
|
resolveEnforcer(opts: { name: string }): Promise<IAuthorizationEnforcer>;
|
|
303
308
|
resolveOptions(): IAuthorizeOptions | undefined;
|
|
@@ -310,6 +315,7 @@ class AuthorizationEnforcerRegistry extends AbstractAuthRegistry<IAuthorizationE
|
|
|
310
315
|
|--------|---------|-------------|
|
|
311
316
|
| `getInstance()` | `AuthorizationEnforcerRegistry` | Returns the singleton instance (creates on first call) |
|
|
312
317
|
| `register(opts)` | `this` | Registers enforcers with type-safe options. See below. |
|
|
318
|
+
| `hasEnforcers()` | `boolean` | Returns `true` if any enforcers are registered (`descriptors.size > 0`). Used by the middleware to skip authorization when no enforcers exist. |
|
|
313
319
|
| `getDefaultEnforcerName()` | `string` | Delegates to `getDefaultName()` -- returns the first registered enforcer's name |
|
|
314
320
|
| `resolveEnforcer({ name })` | `Promise<IAuthorizationEnforcer>` | Resolves and auto-configures an enforcer (configure-once pattern via `configuredEnforcers` Set) |
|
|
315
321
|
| `resolveOptions()` | `IAuthorizeOptions \| undefined` | Iterates all registered containers looking for `AuthorizeBindingKeys.OPTIONS` |
|
|
@@ -578,12 +584,12 @@ Called once by the registry on first use. Performs:
|
|
|
578
584
|
1. Dynamically imports `casbin` -- throws `"casbin" is not installed` if missing
|
|
579
585
|
2. Validates `options.model` -- throws `options.model is required.` if missing
|
|
580
586
|
3. Resolves model via driver:
|
|
581
|
-
- `'file'`
|
|
582
|
-
- `'text'`
|
|
587
|
+
- `'file'` -> `casbin.newModelFromFile(definition)`
|
|
588
|
+
- `'text'` -> `casbin.newModelFromString(definition)`
|
|
583
589
|
4. Creates enforcer based on cache config:
|
|
584
|
-
- `cached.use: false`
|
|
585
|
-
- `cached.driver: 'in-memory'`
|
|
586
|
-
- `cached.driver: 'redis'`
|
|
590
|
+
- `cached.use: false` -> `casbin.newEnforcer(model, adapter)`
|
|
591
|
+
- `cached.driver: 'in-memory'` -> `casbin.newCachedEnforcer(model, adapter)` + periodic invalidation timer (`setInterval`)
|
|
592
|
+
- `cached.driver: 'redis'` -> `casbin.newEnforcer(model, adapter)` (Redis handles caching externally)
|
|
587
593
|
5. Validates `expiresIn >= MIN_EXPIRES_IN` (10,000 ms) for both in-memory and redis cache drivers
|
|
588
594
|
|
|
589
595
|
### destroy()
|
|
@@ -623,7 +629,7 @@ flowchart TD
|
|
|
623
629
|
|-------------|----------|
|
|
624
630
|
| `use: false` | Load policies from adapter directly via `loadPoliciesFromAdapter()` |
|
|
625
631
|
| `'in-memory'` | Load policies from adapter (periodic invalidation handles cache refresh) |
|
|
626
|
-
| `'redis'` | Check Redis cache
|
|
632
|
+
| `'redis'` | Check Redis cache -> hit: load lines into model via `loadPolicyLinesIntoModel()`; miss: load from adapter, extract lines via `extractPolicyLines()`, cache in Redis with TTL |
|
|
627
633
|
|
|
628
634
|
Returns the `IAuthUser` directly (Casbin evaluates policies from its internal model, not from the returned value).
|
|
629
635
|
|
|
@@ -652,7 +658,7 @@ Returns `AuthorizationDecisions.ALLOW` or `AuthorizationDecisions.DENY`.
|
|
|
652
658
|
| `resolveModel` | `{ casbin, model }` | `Model` | Resolves casbin model from file or text via driver discriminant |
|
|
653
659
|
| `validateExpiresIn` | `{ expiresIn }` | `void` | Throws if `expiresIn < MIN_EXPIRES_IN` |
|
|
654
660
|
| `loadPoliciesFromAdapter` | `{ user }` | `void` | Calls `enforcer.loadFilteredPolicy({ principalType, principalValue })` |
|
|
655
|
-
| `loadPoliciesWithRedisCache` | `{ user, cached }` | `void` | Redis cache flow: check cache
|
|
661
|
+
| `loadPoliciesWithRedisCache` | `{ user, cached }` | `void` | Redis cache flow: check cache -> hit: load lines; miss: load from adapter + cache |
|
|
656
662
|
| `extractPolicyLines` | -- | `string[]` | Extracts `p` and `g` lines from enforcer model via `getPolicy()` and `getGroupingPolicy()` |
|
|
657
663
|
| `loadPolicyLinesIntoModel` | `{ lines }` | `void` | Clears model, loads lines via `Helper.loadPolicyLine()`, rebuilds role links |
|
|
658
664
|
|
|
@@ -982,7 +988,8 @@ for (voter of spec.voters) {
|
|
|
982
988
|
// ABSTAIN → continue to next voter
|
|
983
989
|
}
|
|
984
990
|
|
|
985
|
-
// Step 5: Resolve enforcer
|
|
991
|
+
// Step 5: Resolve enforcer (with no-enforcer fallback)
|
|
992
|
+
if (!registry.hasEnforcers()) → next() // skip if no enforcers registered
|
|
986
993
|
const resolvedName = enforcerName ?? registry.getDefaultEnforcerName();
|
|
987
994
|
const enforcer = await registry.resolveEnforcer({ name: resolvedName });
|
|
988
995
|
|
|
@@ -1114,10 +1121,14 @@ AuthorizationRoles.ADMIN.isEqualTo({ target: AuthorizationRoles.ADMIN });
|
|
|
1114
1121
|
|
|
1115
1122
|
### How Authorization Middleware is Injected
|
|
1116
1123
|
|
|
1117
|
-
|
|
1124
|
+
Authorization is supported in both **REST** and **gRPC** controllers.
|
|
1125
|
+
|
|
1126
|
+
#### REST Controllers
|
|
1127
|
+
|
|
1128
|
+
The `AbstractRestController.buildRouteMiddlewares()` method handles middleware injection order. `getRouteConfigs()` calls `buildRouteMiddlewares()` internally:
|
|
1118
1129
|
|
|
1119
1130
|
```typescript
|
|
1120
|
-
|
|
1131
|
+
buildRouteMiddlewares<RouteConfig extends IAuthRouteConfig>(opts: { configs: RouteConfig }) {
|
|
1121
1132
|
const { authenticate = {}, authorize, ...restConfig } = configs;
|
|
1122
1133
|
const mws = [];
|
|
1123
1134
|
|
|
@@ -1137,13 +1148,38 @@ getRouteConfigs<RouteConfig extends IAuthRouteConfig>(opts: { configs: RouteConf
|
|
|
1137
1148
|
// 3. Custom middleware (last)
|
|
1138
1149
|
if (restConfig.middleware) { ... }
|
|
1139
1150
|
|
|
1140
|
-
return
|
|
1151
|
+
return { restConfig, security, mws };
|
|
1152
|
+
}
|
|
1153
|
+
```
|
|
1154
|
+
|
|
1155
|
+
#### gRPC Controllers
|
|
1156
|
+
|
|
1157
|
+
The `AbstractGrpcController.buildRpcMiddlewares()` method provides symmetric middleware injection for gRPC:
|
|
1158
|
+
|
|
1159
|
+
```typescript
|
|
1160
|
+
buildRpcMiddlewares(opts: { configs: IRpcMetadata }): TRpcMiddleware[] {
|
|
1161
|
+
const { configs } = opts;
|
|
1162
|
+
const mws = [];
|
|
1163
|
+
|
|
1164
|
+
// 1. Authenticate middleware (first)
|
|
1165
|
+
if (configs.authenticate) { ... }
|
|
1166
|
+
|
|
1167
|
+
// 2. Authorize middleware (second) — same pattern as REST
|
|
1168
|
+
if (configs.authorize) {
|
|
1169
|
+
const specs = Array.isArray(configs.authorize) ? configs.authorize : [configs.authorize];
|
|
1170
|
+
for (const spec of specs) {
|
|
1171
|
+
const authzMw = authorizeFn({ spec });
|
|
1172
|
+
mws.push((context, next) => authzMw(context, next));
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
return mws;
|
|
1141
1177
|
}
|
|
1142
1178
|
```
|
|
1143
1179
|
|
|
1144
1180
|
### IAuthRouteConfig
|
|
1145
1181
|
|
|
1146
|
-
Extended route config that supports both authentication and authorization:
|
|
1182
|
+
Extended route config that supports both authentication and authorization (REST):
|
|
1147
1183
|
|
|
1148
1184
|
```typescript
|
|
1149
1185
|
interface IAuthRouteConfig extends HonoRouteConfig {
|
|
@@ -1154,6 +1190,19 @@ interface IAuthRouteConfig extends HonoRouteConfig {
|
|
|
1154
1190
|
|
|
1155
1191
|
When `authorize` is an array, each spec creates a separate middleware. All must pass for the handler to execute.
|
|
1156
1192
|
|
|
1193
|
+
### Per-Route Auth Types (CRUD Factory)
|
|
1194
|
+
|
|
1195
|
+
```typescript
|
|
1196
|
+
/** Per-route authorization config: { skip: true }, single spec, or array of specs. */
|
|
1197
|
+
type TRouteAuthorizeConfig = { skip: true } | IAuthorizationSpec | IAuthorizationSpec[];
|
|
1198
|
+
|
|
1199
|
+
/** Per-route auth config. Endpoint config takes precedence over controller-level config. */
|
|
1200
|
+
type TRouteAuthConfig = {
|
|
1201
|
+
authenticate?: TRouteAuthenticateConfig;
|
|
1202
|
+
authorize?: TRouteAuthorizeConfig;
|
|
1203
|
+
};
|
|
1204
|
+
```
|
|
1205
|
+
|
|
1157
1206
|
## IAuthUser Interface
|
|
1158
1207
|
|
|
1159
1208
|
The user object available during authorization. Defined in `authenticate/common/types.ts`:
|
|
@@ -14,12 +14,13 @@ flowchart TD
|
|
|
14
14
|
S3 -->|Match| OK2([No error - bypass])
|
|
15
15
|
S3 -->|No match| S4{"Step 4: Voters"}
|
|
16
16
|
S4 -->|DENY| E403a[/"403: Authorization denied by voter"/]
|
|
17
|
-
S4 -->|ALLOW/ABSTAIN| S5{"Step 5:
|
|
17
|
+
S4 -->|ALLOW/ABSTAIN| S5{"Step 5: Enforcers registered?"}
|
|
18
18
|
|
|
19
|
-
S5 -->|
|
|
20
|
-
S5 -->|
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
S5 -->|No enforcers| OK3([No error - skip])
|
|
20
|
+
S5 -->|Yes| S5b{"Resolve enforcer"}
|
|
21
|
+
S5b -->|Name not found| E500b[/"500: Descriptor not found"/]
|
|
22
|
+
S5b -->|DI fails| E500c[/"500: Failed to resolve"/]
|
|
23
|
+
S5b -->|OK| S6{"Step 6: Build rules"}
|
|
23
24
|
|
|
24
25
|
S6 -->|No principalType| E400a[/"400: principalType required"/]
|
|
25
26
|
S6 -->|Not initialized| E500d[/"500: Enforcer not initialized"/]
|
|
@@ -29,7 +30,7 @@ flowchart TD
|
|
|
29
30
|
S6 -->|OK| S7{"Step 7: Evaluate"}
|
|
30
31
|
|
|
31
32
|
S7 -->|No action/resource| E500g[/"500: request.action and resource required"/]
|
|
32
|
-
S7 -->|ALLOW|
|
|
33
|
+
S7 -->|ALLOW| OK4([Authorized])
|
|
33
34
|
S7 -->|DENY| E403b[/"403: Authorization denied"/]
|
|
34
35
|
```
|
|
35
36
|
|
|
@@ -63,6 +64,9 @@ All error messages from the authorization module, organized by source:
|
|
|
63
64
|
| <code v-pre>[AuthorizationEnforcerRegistry] Descriptor not found: {{name}}</code> | 500 | `resolveDescriptor` |
|
|
64
65
|
| <code v-pre>[AuthorizationEnforcerRegistry] Failed to resolve: {{name}}</code> | 500 | `resolveDescriptor` |
|
|
65
66
|
|
|
67
|
+
> [!NOTE]
|
|
68
|
+
> The `[AuthorizationEnforcerRegistry] No items registered` error can only occur if `getDefaultEnforcerName()` is called directly. During normal middleware execution, the provider checks `registry.hasEnforcers()` first and skips authorization if no enforcers are registered, so this error is not thrown in the standard pipeline.
|
|
69
|
+
|
|
66
70
|
### Casbin Enforcer Errors (CasbinAuthorizationEnforcer)
|
|
67
71
|
|
|
68
72
|
| Error Message | Status | Method |
|
|
@@ -198,7 +202,7 @@ AuthorizationEnforcerRegistry.getInstance().register({
|
|
|
198
202
|
|
|
199
203
|
### "[AuthorizationEnforcerRegistry] No items registered"
|
|
200
204
|
|
|
201
|
-
**Cause:** Tried to get the default enforcer name but no enforcers are registered. This
|
|
205
|
+
**Cause:** Tried to get the default enforcer name but no enforcers are registered. This error can only occur if `getDefaultEnforcerName()` is called directly. During normal middleware execution, the provider checks `registry.hasEnforcers()` first and skips authorization if no enforcers are registered, so this error is not triggered in the standard pipeline.
|
|
202
206
|
|
|
203
207
|
**Fix:** Register enforcers after registering the component:
|
|
204
208
|
|
|
@@ -358,6 +362,7 @@ Check middleware injection order:
|
|
|
358
362
|
1. Verify `authorize` is set on the route config (not just `authenticate`)
|
|
359
363
|
2. Verify `authenticate` is not set to `{ skip: true }` (which also skips authorization in CRUD factory)
|
|
360
364
|
3. Verify the component is registered and enforcers are registered via the registry
|
|
365
|
+
4. Note: if no enforcers are registered, the middleware skips authorization silently (calls `next()`) rather than throwing an error
|
|
361
366
|
|
|
362
367
|
### Rules Are Built On Every Request
|
|
363
368
|
|
|
@@ -40,10 +40,12 @@ flowchart TD
|
|
|
40
40
|
S3 -->|No match| S4{"4. Voters?"}
|
|
41
41
|
S4 -->|DENY| E403a[/403/]
|
|
42
42
|
S4 -->|ALLOW| Pass4([next])
|
|
43
|
-
S4 -->|ABSTAIN| S5
|
|
44
|
-
S5
|
|
43
|
+
S4 -->|ABSTAIN / none| S5{"5. Enforcers registered?"}
|
|
44
|
+
S5 -->|No enforcers| Pass5([next - skip])
|
|
45
|
+
S5 -->|Yes| S5b["Resolve enforcer"]
|
|
46
|
+
S5b --> S6["6. Build/cache rules"]
|
|
45
47
|
S6 --> S7{"7. Evaluate"}
|
|
46
|
-
S7 -->|ALLOW|
|
|
48
|
+
S7 -->|ALLOW| Pass6([next])
|
|
47
49
|
S7 -->|DENY/ABSTAIN| E403b[/403/]
|
|
48
50
|
```
|
|
49
51
|
|
|
@@ -53,13 +55,16 @@ flowchart TD
|
|
|
53
55
|
| 2 | Get authenticated user from context | Yes -- 401 if missing |
|
|
54
56
|
| 3 | Check role-based shortcuts (`alwaysAllowRoles` + `allowedRoles`) | Yes -- allow if matched |
|
|
55
57
|
| 4 | Execute `voters` (per-route) | Yes -- DENY/ALLOW short-circuits |
|
|
56
|
-
| 5 |
|
|
58
|
+
| 5 | Check if enforcers are registered; resolve enforcer (by name or default) | Yes -- skip all if no enforcers registered |
|
|
57
59
|
| 6 | Build or retrieve cached rules | No |
|
|
58
60
|
| 7 | Evaluate permission via enforcer | Yes -- 403 if denied |
|
|
59
61
|
|
|
60
62
|
> [!NOTE]
|
|
61
63
|
> Step 3 merges the global `alwaysAllowRoles` check and the per-route `allowedRoles` check into a single step. User roles are extracted once and checked against both lists.
|
|
62
64
|
|
|
65
|
+
> [!NOTE]
|
|
66
|
+
> Step 5 has a safety fallback: if no enforcers are registered in the `AuthorizationEnforcerRegistry`, the middleware skips authorization entirely and calls `next()`. This prevents hard failures when authorization is configured on routes but no enforcer has been registered yet.
|
|
67
|
+
|
|
63
68
|
### Authorization Constants
|
|
64
69
|
|
|
65
70
|
| Constant | Value | Description |
|
|
@@ -645,7 +650,7 @@ interface IJWTTokenPayload extends JWTPayload, IAuthUser {
|
|
|
645
650
|
|
|
646
651
|
## Relationship with Authentication
|
|
647
652
|
|
|
648
|
-
Authorization runs **after** authentication in the middleware chain.
|
|
653
|
+
Authorization runs **after** authentication in the middleware chain. Both REST and gRPC controllers ensure the correct ordering:
|
|
649
654
|
|
|
650
655
|
```mermaid
|
|
651
656
|
flowchart LR
|
|
@@ -657,10 +662,79 @@ flowchart LR
|
|
|
657
662
|
|
|
658
663
|
1. Authentication middleware is injected first (from `authenticate` config)
|
|
659
664
|
2. Authorization middleware is injected second (from `authorize` config)
|
|
660
|
-
3. Custom middleware is injected last (from `middleware` config)
|
|
665
|
+
3. Custom middleware is injected last (from `middleware` config -- REST only)
|
|
661
666
|
|
|
662
667
|
This means `Authentication.CURRENT_USER` is always available when the authorization middleware executes.
|
|
663
668
|
|
|
669
|
+
### REST Controllers
|
|
670
|
+
|
|
671
|
+
The `AbstractRestController.buildRouteMiddlewares()` method builds the middleware array from route config. The `getRouteConfigs()` method calls `buildRouteMiddlewares()` internally and wraps it into a Hono route definition:
|
|
672
|
+
|
|
673
|
+
```typescript
|
|
674
|
+
// In AbstractRestController
|
|
675
|
+
buildRouteMiddlewares<RouteConfig extends IAuthRouteConfig>(opts: { configs: RouteConfig }) {
|
|
676
|
+
const { authenticate = {}, authorize, ...restConfig } = opts.configs;
|
|
677
|
+
const mws = [];
|
|
678
|
+
|
|
679
|
+
// 1. Authenticate middleware (first)
|
|
680
|
+
if (strategies.length > 0) {
|
|
681
|
+
mws.push(authenticateFn({ strategies, mode }));
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// 2. Authorize middleware (second) — supports single or array
|
|
685
|
+
if (authorize) {
|
|
686
|
+
const specs = Array.isArray(authorize) ? authorize : [authorize];
|
|
687
|
+
for (const spec of specs) {
|
|
688
|
+
mws.push(authorizeFn({ spec }));
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// 3. Custom middleware (last)
|
|
693
|
+
if (restConfig.middleware) { ... }
|
|
694
|
+
|
|
695
|
+
return { restConfig, security, mws };
|
|
696
|
+
}
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
### gRPC Controllers
|
|
700
|
+
|
|
701
|
+
The `AbstractGrpcController.buildRpcMiddlewares()` method provides symmetric authorization support for gRPC routes. Authorization specs are applied the same way as REST:
|
|
702
|
+
|
|
703
|
+
```typescript
|
|
704
|
+
// In AbstractGrpcController
|
|
705
|
+
buildRpcMiddlewares(opts: { configs: IRpcMetadata }): TRpcMiddleware[] {
|
|
706
|
+
const { configs } = opts;
|
|
707
|
+
const mws = [];
|
|
708
|
+
|
|
709
|
+
// 1. Authenticate middleware
|
|
710
|
+
if (configs.authenticate) { ... }
|
|
711
|
+
|
|
712
|
+
// 2. Authorize middleware — same pattern as REST
|
|
713
|
+
if (configs.authorize) {
|
|
714
|
+
const specs = Array.isArray(configs.authorize) ? configs.authorize : [configs.authorize];
|
|
715
|
+
for (const spec of specs) {
|
|
716
|
+
const authzMw = authorizeFn({ spec });
|
|
717
|
+
mws.push((context, next) => authzMw(context, next));
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
return mws;
|
|
722
|
+
}
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
### IAuthRouteConfig
|
|
726
|
+
|
|
727
|
+
Extended route config that supports both authentication and authorization:
|
|
728
|
+
|
|
729
|
+
```typescript
|
|
730
|
+
interface IAuthRouteConfig extends HonoRouteConfig {
|
|
731
|
+
authenticate?: { strategies?: TAuthStrategy[]; mode?: TAuthMode };
|
|
732
|
+
authorize?: IAuthorizationSpec | IAuthorizationSpec[];
|
|
733
|
+
}
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
When `authorize` is an array, each spec creates a separate middleware. All must pass for the handler to execute.
|
|
737
|
+
|
|
664
738
|
### Per-Route Configuration in CRUD Factory
|
|
665
739
|
|
|
666
740
|
CRUD factory routes support both authentication and authorization configuration:
|
|
@@ -694,6 +768,19 @@ ControllerFactory.defineCrudController({
|
|
|
694
768
|
3. Per-route `authorize` overrides controller-level `authorize`
|
|
695
769
|
4. No per-route config -- inherits controller-level config
|
|
696
770
|
|
|
771
|
+
### Per-Route Auth Types
|
|
772
|
+
|
|
773
|
+
```typescript
|
|
774
|
+
/** Per-route authorization config: { skip: true }, single spec, or array of specs. */
|
|
775
|
+
type TRouteAuthorizeConfig = { skip: true } | IAuthorizationSpec | IAuthorizationSpec[];
|
|
776
|
+
|
|
777
|
+
/** Per-route auth config. Endpoint config takes precedence over controller-level config. */
|
|
778
|
+
type TRouteAuthConfig = {
|
|
779
|
+
authenticate?: TRouteAuthenticateConfig;
|
|
780
|
+
authorize?: TRouteAuthorizeConfig;
|
|
781
|
+
};
|
|
782
|
+
```
|
|
783
|
+
|
|
697
784
|
## See Also
|
|
698
785
|
|
|
699
786
|
- [Usage & Examples](./usage) -- Securing routes, voters, patterns, and CRUD integration
|
|
@@ -10,12 +10,12 @@ Use the `authorize` field in route configs to declare authorization requirements
|
|
|
10
10
|
|
|
11
11
|
```typescript
|
|
12
12
|
import {
|
|
13
|
-
|
|
13
|
+
BaseRestController,
|
|
14
14
|
Authentication,
|
|
15
15
|
AuthorizationActions,
|
|
16
16
|
} from '@venizia/ignis';
|
|
17
17
|
|
|
18
|
-
class ArticleController extends
|
|
18
|
+
class ArticleController extends BaseRestController {
|
|
19
19
|
binding() {
|
|
20
20
|
// Read requires 'read' action on 'Article' resource
|
|
21
21
|
this.defineRoute({
|
|
@@ -102,7 +102,7 @@ Use the `authorize` field alongside `authenticate` in decorator configs:
|
|
|
102
102
|
import { controller, get, post, AuthorizationActions, AuthorizationRoles } from '@venizia/ignis';
|
|
103
103
|
|
|
104
104
|
@controller({ path: '/articles' })
|
|
105
|
-
class ArticleController extends
|
|
105
|
+
class ArticleController extends BaseRestController {
|
|
106
106
|
@get({
|
|
107
107
|
configs: {
|
|
108
108
|
path: '/',
|
|
@@ -134,6 +134,34 @@ class ArticleController extends BaseController {
|
|
|
134
134
|
}
|
|
135
135
|
```
|
|
136
136
|
|
|
137
|
+
### gRPC Route Authorization
|
|
138
|
+
|
|
139
|
+
Authorization works the same way in gRPC controllers. Use the `authorize` field in RPC metadata:
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
import { AuthorizationActions, Authentication } from '@venizia/ignis';
|
|
143
|
+
|
|
144
|
+
class GreeterController extends BaseGrpcController {
|
|
145
|
+
binding() {
|
|
146
|
+
this.defineRoute({
|
|
147
|
+
configs: {
|
|
148
|
+
method: sayHello,
|
|
149
|
+
authenticate: { strategies: [Authentication.STRATEGY_JWT] },
|
|
150
|
+
authorize: {
|
|
151
|
+
action: AuthorizationActions.EXECUTE,
|
|
152
|
+
resource: 'Greeter',
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
handler: async (context) => {
|
|
156
|
+
// Handler runs only if authorized
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
The `AbstractGrpcController.buildRpcMiddlewares()` method injects authorization middleware in the same order as REST controllers: authenticate first, then authorize.
|
|
164
|
+
|
|
137
165
|
## Using the `authorize()` Standalone Function
|
|
138
166
|
|
|
139
167
|
The `authorize()` function is a convenience wrapper around `AuthorizationProvider`. It returns a Hono `MiddlewareHandler`:
|
|
@@ -383,7 +411,7 @@ ControllerFactory.defineCrudController({
|
|
|
383
411
|
|
|
384
412
|
### Priority Resolution (Factory Routes)
|
|
385
413
|
|
|
386
|
-
The `
|
|
414
|
+
The `resolveRouteAuthorize` function in `defineControllerRouteConfigs` resolves authorization with this priority:
|
|
387
415
|
|
|
388
416
|
```mermaid
|
|
389
417
|
flowchart TD
|
|
@@ -403,6 +431,16 @@ flowchart TD
|
|
|
403
431
|
3. **Per-route `authorize` spec** -- overrides controller-level
|
|
404
432
|
4. **Controller-level `authorize`** -- default for all routes
|
|
405
433
|
|
|
434
|
+
### Per-Route Auth Type
|
|
435
|
+
|
|
436
|
+
The per-route authorize config is typed as a discriminated union:
|
|
437
|
+
|
|
438
|
+
```typescript
|
|
439
|
+
type TRouteAuthorizeConfig = { skip: true } | IAuthorizationSpec | IAuthorizationSpec[];
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
This means each route can either skip authorization entirely, provide a single spec, or provide an array of specs that all must pass.
|
|
443
|
+
|
|
406
444
|
## Dynamic Skip Authorization
|
|
407
445
|
|
|
408
446
|
Use `Authorization.SKIP_AUTHORIZATION` to dynamically bypass authorization in middleware (step 1 in the pipeline):
|
|
@@ -213,10 +213,11 @@ The controller uses `this.definitions = RouteConfigs` to store route configurati
|
|
|
213
213
|
#### Controller Source
|
|
214
214
|
```typescript
|
|
215
215
|
import {
|
|
216
|
-
|
|
217
|
-
api, jsonContent, jsonResponse, z,
|
|
216
|
+
BaseRestController, IControllerOptions, TRouteContext,
|
|
218
217
|
} from '@venizia/ignis';
|
|
219
|
-
import {
|
|
218
|
+
import { api, jsonContent, jsonResponse } from '@venizia/ignis';
|
|
219
|
+
import { z } from '@hono/zod-openapi';
|
|
220
|
+
import { HTTP, ValueOrPromise } from '@venizia/ignis-helpers';
|
|
220
221
|
|
|
221
222
|
const RouteConfigs = {
|
|
222
223
|
ROOT: {
|
|
@@ -258,7 +259,7 @@ const RouteConfigs = {
|
|
|
258
259
|
},
|
|
259
260
|
} as const;
|
|
260
261
|
|
|
261
|
-
export class HealthCheckController extends
|
|
262
|
+
export class HealthCheckController extends BaseRestController {
|
|
262
263
|
constructor(opts: IControllerOptions) {
|
|
263
264
|
super({ ...opts, scope: HealthCheckController.name });
|
|
264
265
|
this.definitions = RouteConfigs;
|
|
@@ -15,6 +15,7 @@ Reusable, pluggable modules that group together related features. A component ca
|
|
|
15
15
|
| [WebSocket](./websocket/) | Real-time communication | Bun native WebSocket, Redis Pub/Sub, heartbeat |
|
|
16
16
|
| [Static Asset](./static-asset/) | File management | Upload/download files, MinIO & local filesystem support |
|
|
17
17
|
| [Swagger](./swagger) | API documentation | OpenAPI generation, Swagger UI, Scalar UI |
|
|
18
|
+
| [gRPC](/references/base/grpc-controllers) | gRPC transport | ConnectRPC integration, unary RPC, decorator-based |
|
|
18
19
|
|
|
19
20
|
## Creating a Component
|
|
20
21
|
|
|
@@ -81,6 +82,7 @@ Using components is a great way to organize your application's features into mod
|
|
|
81
82
|
- [WebSocket](./websocket/) - Bun native WebSocket
|
|
82
83
|
- [Static Asset](./static-asset/) - Static file serving
|
|
83
84
|
- [Swagger](./swagger) - API documentation
|
|
85
|
+
- [gRPC](/references/base/grpc-controllers) - gRPC transport (ConnectRPC)
|
|
84
86
|
|
|
85
87
|
- **References:**
|
|
86
88
|
- [BaseComponent API](/references/base/components) - Component base class
|
|
@@ -211,7 +211,7 @@ The `TMailOptions` configuration determines which email transport provider is us
|
|
|
211
211
|
|
|
212
212
|
```typescript
|
|
213
213
|
// Simple SMTP Authentication (e.g., Gmail with app password)
|
|
214
|
-
this.bind<TMailOptions>({ key:
|
|
214
|
+
this.bind<TMailOptions>({ key: MailKeys.MAIL_OPTIONS }).toValue({
|
|
215
215
|
provider: 'nodemailer',
|
|
216
216
|
from: 'noreply@example.com',
|
|
217
217
|
fromName: 'My App',
|
|
@@ -248,7 +248,7 @@ If running without a proxy, ensure the runtime provides connection info (Bun doe
|
|
|
248
248
|
- [All Components](./index) - Built-in components list
|
|
249
249
|
|
|
250
250
|
- **Helpers:**
|
|
251
|
-
- [Logger Helper](/
|
|
251
|
+
- [Logger Helper](/extensions/helpers/logger/) - Logging utilities
|
|
252
252
|
|
|
253
253
|
- **Best Practices:**
|
|
254
254
|
- [Troubleshooting Tips](/best-practices/troubleshooting-tips) - Debugging with request IDs
|
|
@@ -630,7 +630,7 @@ Reads all binding keys from the DI container and validates required ones:
|
|
|
630
630
|
|---------|-----------|------------------|
|
|
631
631
|
| `SERVER_OPTIONS` | Optional, merged with defaults via `Object.assign()` | -- |
|
|
632
632
|
| `REDIS_CONNECTION` | Must be `instanceof DefaultRedisHelper` | `"Invalid instance of redisConnection | Please init connection with RedisHelper for single redis connection or RedisClusterHelper for redis cluster mode!"` |
|
|
633
|
-
| `AUTHENTICATE_HANDLER` | Must be a function (non-null) | `"Invalid authenticateFn to setup io socket server!"` |
|
|
633
|
+
| `AUTHENTICATE_HANDLER` | Must be a function (non-null) | `"[DANGER][SocketIOComponent] Invalid authenticateFn to setup io socket server!"` |
|
|
634
634
|
| `VALIDATE_ROOM_HANDLER` | Optional, resolved from container, `null` coerced to `undefined` | -- |
|
|
635
635
|
| `CLIENT_CONNECTED_HANDLER` | Optional, resolved from container, `null` coerced to `undefined` | -- |
|
|
636
636
|
|
|
@@ -673,7 +673,7 @@ Returns a fetch handler function that routes requests:
|
|
|
673
673
|
Registers a post-start hook that:
|
|
674
674
|
|
|
675
675
|
1. Gets the HTTP server instance via `getServerInstance()`
|
|
676
|
-
2. Validates the server instance exists (throws `"HTTP server not available for Node.js runtime!"` if not)
|
|
676
|
+
2. Validates the server instance exists (throws `"[SocketIOComponent] HTTP server not available for Node.js runtime!"` if not)
|
|
677
677
|
3. Calls `createNodeSocketIOHelper()` which creates `SocketIOServerHelper` with `runtime: RuntimeModules.NODE` and the HTTP server, then awaits `configure()`
|
|
678
678
|
4. Binds the helper to `SOCKET_IO_INSTANCE`
|
|
679
679
|
|
|
@@ -59,6 +59,8 @@
|
|
|
59
59
|
**Fix**: Use `RedisHelper` (single instance) or `RedisClusterHelper` (cluster mode):
|
|
60
60
|
|
|
61
61
|
```typescript
|
|
62
|
+
import { SocketIOBindingKeys } from '@venizia/ignis/socket-io';
|
|
63
|
+
|
|
62
64
|
// Correct -- single instance
|
|
63
65
|
this.bind({ key: SocketIOBindingKeys.REDIS_CONNECTION })
|
|
64
66
|
.toValue(new RedisHelper({ name: 'socket-io', host, port, password }));
|