@venizia/ignis-docs 0.0.7 → 0.0.8-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 (172) hide show
  1. package/dist/mcp-server/common/paths.d.ts +4 -2
  2. package/dist/mcp-server/common/paths.d.ts.map +1 -1
  3. package/dist/mcp-server/common/paths.js +8 -6
  4. package/dist/mcp-server/common/paths.js.map +1 -1
  5. package/dist/mcp-server/helpers/docs.helper.d.ts.map +1 -1
  6. package/dist/mcp-server/helpers/docs.helper.js +1 -1
  7. package/dist/mcp-server/helpers/docs.helper.js.map +1 -1
  8. package/dist/mcp-server/tools/base.tool.d.ts +1 -1
  9. package/dist/mcp-server/tools/docs/get-document-content.tool.d.ts +1 -1
  10. package/dist/mcp-server/tools/docs/get-document-content.tool.d.ts.map +1 -1
  11. package/dist/mcp-server/tools/docs/get-document-content.tool.js +7 -7
  12. package/dist/mcp-server/tools/docs/get-document-metadata.tool.js +3 -3
  13. package/dist/mcp-server/tools/docs/get-package-overview.tool.d.ts +1 -1
  14. package/dist/mcp-server/tools/docs/get-package-overview.tool.js +1 -1
  15. package/dist/mcp-server/tools/docs/search-documents.tool.d.ts +1 -1
  16. package/dist/mcp-server/tools/docs/search-documents.tool.js +1 -1
  17. package/dist/mcp-server/tools/docs/search-documents.tool.js.map +1 -1
  18. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
  19. package/dist/mcp-server/tools/github/list-project-files.tool.js +1 -1
  20. package/dist/mcp-server/tools/github/list-project-files.tool.js.map +1 -1
  21. package/dist/mcp-server/tools/github/search-code.tool.d.ts +1 -1
  22. package/dist/mcp-server/tools/github/search-code.tool.js +1 -1
  23. package/dist/mcp-server/tools/github/search-code.tool.js.map +1 -1
  24. package/package.json +9 -9
  25. package/wiki/best-practices/api-usage-examples.md +9 -9
  26. package/wiki/best-practices/architectural-patterns.md +19 -3
  27. package/wiki/best-practices/architecture-decisions.md +6 -6
  28. package/wiki/best-practices/code-style-standards/advanced-patterns.md +1 -1
  29. package/wiki/best-practices/code-style-standards/control-flow.md +1 -1
  30. package/wiki/best-practices/code-style-standards/function-patterns.md +2 -2
  31. package/wiki/best-practices/code-style-standards/index.md +2 -2
  32. package/wiki/best-practices/code-style-standards/naming-conventions.md +1 -1
  33. package/wiki/best-practices/code-style-standards/route-definitions.md +4 -4
  34. package/wiki/best-practices/data-modeling.md +1 -1
  35. package/wiki/best-practices/deployment-strategies.md +1 -1
  36. package/wiki/best-practices/error-handling.md +2 -2
  37. package/wiki/best-practices/performance-optimization.md +3 -3
  38. package/wiki/best-practices/security-guidelines.md +2 -2
  39. package/wiki/best-practices/troubleshooting-tips.md +1 -1
  40. package/wiki/{references → extensions}/components/authentication/api.md +12 -20
  41. package/wiki/{references → extensions}/components/authentication/errors.md +1 -1
  42. package/wiki/{references → extensions}/components/authentication/index.md +5 -8
  43. package/wiki/{references → extensions}/components/authentication/usage.md +20 -36
  44. package/wiki/{references → extensions}/components/authorization/api.md +62 -13
  45. package/wiki/{references → extensions}/components/authorization/errors.md +12 -7
  46. package/wiki/{references → extensions}/components/authorization/index.md +93 -6
  47. package/wiki/{references → extensions}/components/authorization/usage.md +42 -4
  48. package/wiki/{references → extensions}/components/health-check.md +5 -4
  49. package/wiki/{references → extensions}/components/index.md +2 -0
  50. package/wiki/{references → extensions}/components/mail/index.md +1 -1
  51. package/wiki/{references → extensions}/components/request-tracker.md +1 -1
  52. package/wiki/{references → extensions}/components/socket-io/api.md +2 -2
  53. package/wiki/{references → extensions}/components/socket-io/errors.md +2 -0
  54. package/wiki/{references → extensions}/components/socket-io/index.md +24 -20
  55. package/wiki/{references → extensions}/components/socket-io/usage.md +2 -2
  56. package/wiki/{references → extensions}/components/static-asset/api.md +14 -15
  57. package/wiki/{references → extensions}/components/static-asset/errors.md +3 -1
  58. package/wiki/{references → extensions}/components/static-asset/index.md +158 -89
  59. package/wiki/{references → extensions}/components/static-asset/usage.md +8 -5
  60. package/wiki/{references → extensions}/components/swagger.md +3 -3
  61. package/wiki/{references → extensions}/components/template/index.md +4 -4
  62. package/wiki/{references → extensions}/components/template/setup-page.md +1 -1
  63. package/wiki/{references → extensions}/components/template/single-page.md +1 -1
  64. package/wiki/{references → extensions}/components/websocket/api.md +7 -6
  65. package/wiki/{references → extensions}/components/websocket/errors.md +17 -3
  66. package/wiki/{references → extensions}/components/websocket/index.md +17 -11
  67. package/wiki/{references → extensions}/components/websocket/usage.md +2 -2
  68. package/wiki/{references → extensions}/helpers/crypto/index.md +1 -1
  69. package/wiki/{references → extensions}/helpers/env/index.md +9 -5
  70. package/wiki/{references → extensions}/helpers/error/index.md +2 -7
  71. package/wiki/{references → extensions}/helpers/index.md +18 -6
  72. package/wiki/{references → extensions}/helpers/kafka/admin.md +13 -1
  73. package/wiki/{references → extensions}/helpers/kafka/consumer.md +32 -31
  74. package/wiki/{references → extensions}/helpers/kafka/examples.md +20 -20
  75. package/wiki/{references → extensions}/helpers/kafka/index.md +61 -54
  76. package/wiki/{references → extensions}/helpers/kafka/producer.md +21 -20
  77. package/wiki/{references → extensions}/helpers/kafka/schema-registry.md +25 -25
  78. package/wiki/{references → extensions}/helpers/logger/index.md +2 -2
  79. package/wiki/{references → extensions}/helpers/queue/index.md +400 -4
  80. package/wiki/{references → extensions}/helpers/storage/api.md +170 -10
  81. package/wiki/{references → extensions}/helpers/storage/index.md +44 -8
  82. package/wiki/{references → extensions}/helpers/template/index.md +1 -1
  83. package/wiki/{references → extensions}/helpers/testing/index.md +4 -4
  84. package/wiki/{references → extensions}/helpers/types/index.md +63 -16
  85. package/wiki/{references → extensions}/helpers/websocket/index.md +1 -1
  86. package/wiki/extensions/index.md +48 -0
  87. package/wiki/guides/core-concepts/application/bootstrapping.md +55 -37
  88. package/wiki/guides/core-concepts/application/index.md +95 -35
  89. package/wiki/guides/core-concepts/components-guide.md +23 -19
  90. package/wiki/guides/core-concepts/components.md +34 -10
  91. package/wiki/guides/core-concepts/dependency-injection.md +99 -34
  92. package/wiki/guides/core-concepts/grpc-controllers.md +295 -0
  93. package/wiki/guides/core-concepts/persistent/datasources.md +37 -19
  94. package/wiki/guides/core-concepts/persistent/index.md +6 -6
  95. package/wiki/guides/core-concepts/persistent/models.md +50 -6
  96. package/wiki/guides/core-concepts/persistent/repositories.md +83 -8
  97. package/wiki/guides/core-concepts/persistent/transactions.md +39 -8
  98. package/wiki/guides/core-concepts/{controllers.md → rest-controllers.md} +32 -35
  99. package/wiki/guides/core-concepts/services.md +19 -6
  100. package/wiki/guides/get-started/5-minute-quickstart.md +17 -17
  101. package/wiki/guides/get-started/philosophy.md +1 -1
  102. package/wiki/guides/index.md +2 -2
  103. package/wiki/guides/reference/glossary.md +7 -7
  104. package/wiki/guides/reference/mcp-docs-server.md +1 -1
  105. package/wiki/guides/tutorials/building-a-crud-api.md +45 -39
  106. package/wiki/guides/tutorials/complete-installation.md +74 -51
  107. package/wiki/guides/tutorials/ecommerce-api.md +39 -30
  108. package/wiki/guides/tutorials/realtime-chat.md +12 -13
  109. package/wiki/guides/tutorials/testing.md +2 -2
  110. package/wiki/index.md +4 -3
  111. package/wiki/references/base/application.md +341 -21
  112. package/wiki/references/base/bootstrapping.md +43 -13
  113. package/wiki/references/base/components.md +259 -8
  114. package/wiki/references/base/controllers.md +556 -253
  115. package/wiki/references/base/datasources.md +159 -79
  116. package/wiki/references/base/dependency-injection.md +299 -48
  117. package/wiki/references/base/filter-system/application-usage.md +18 -2
  118. package/wiki/references/base/filter-system/array-operators.md +14 -6
  119. package/wiki/references/base/filter-system/comparison-operators.md +9 -3
  120. package/wiki/references/base/filter-system/default-filter.md +28 -3
  121. package/wiki/references/base/filter-system/fields-order-pagination.md +17 -13
  122. package/wiki/references/base/filter-system/index.md +169 -11
  123. package/wiki/references/base/filter-system/json-filtering.md +51 -18
  124. package/wiki/references/base/filter-system/list-operators.md +4 -3
  125. package/wiki/references/base/filter-system/logical-operators.md +7 -2
  126. package/wiki/references/base/filter-system/null-operators.md +50 -0
  127. package/wiki/references/base/filter-system/quick-reference.md +82 -243
  128. package/wiki/references/base/filter-system/range-operators.md +7 -1
  129. package/wiki/references/base/filter-system/tips.md +34 -7
  130. package/wiki/references/base/filter-system/use-cases.md +6 -5
  131. package/wiki/references/base/grpc-controllers.md +984 -0
  132. package/wiki/references/base/index.md +32 -24
  133. package/wiki/references/base/middleware.md +347 -0
  134. package/wiki/references/base/models.md +390 -46
  135. package/wiki/references/base/providers.md +14 -14
  136. package/wiki/references/base/repositories/advanced.md +195 -69
  137. package/wiki/references/base/repositories/index.md +447 -12
  138. package/wiki/references/base/repositories/mixins.md +103 -98
  139. package/wiki/references/base/repositories/relations.md +129 -45
  140. package/wiki/references/base/repositories/soft-deletable.md +104 -23
  141. package/wiki/references/base/services.md +94 -14
  142. package/wiki/references/index.md +12 -10
  143. package/wiki/references/quick-reference.md +98 -65
  144. package/wiki/references/utilities/crypto.md +21 -4
  145. package/wiki/references/utilities/date.md +25 -7
  146. package/wiki/references/utilities/index.md +26 -24
  147. package/wiki/references/utilities/jsx.md +54 -54
  148. package/wiki/references/utilities/module.md +8 -6
  149. package/wiki/references/utilities/parse.md +16 -9
  150. package/wiki/references/utilities/performance.md +22 -7
  151. package/wiki/references/utilities/promise.md +19 -16
  152. package/wiki/references/utilities/request.md +48 -26
  153. package/wiki/references/utilities/schema.md +69 -6
  154. package/wiki/references/utilities/statuses.md +131 -140
  155. /package/wiki/{references → extensions}/components/mail/api.md +0 -0
  156. /package/wiki/{references → extensions}/components/mail/errors.md +0 -0
  157. /package/wiki/{references → extensions}/components/mail/usage.md +0 -0
  158. /package/wiki/{references → extensions}/components/template/api-page.md +0 -0
  159. /package/wiki/{references → extensions}/components/template/errors-page.md +0 -0
  160. /package/wiki/{references → extensions}/components/template/usage-page.md +0 -0
  161. /package/wiki/{references → extensions}/helpers/cron/index.md +0 -0
  162. /package/wiki/{references → extensions}/helpers/inversion/index.md +0 -0
  163. /package/wiki/{references → extensions}/helpers/network/api.md +0 -0
  164. /package/wiki/{references → extensions}/helpers/network/index.md +0 -0
  165. /package/wiki/{references → extensions}/helpers/redis/index.md +0 -0
  166. /package/wiki/{references → extensions}/helpers/socket-io/api.md +0 -0
  167. /package/wiki/{references → extensions}/helpers/socket-io/index.md +0 -0
  168. /package/wiki/{references → extensions}/helpers/template/single-page.md +0 -0
  169. /package/wiki/{references → extensions}/helpers/uid/index.md +0 -0
  170. /package/wiki/{references → extensions}/helpers/websocket/api.md +0 -0
  171. /package/wiki/{references → extensions}/helpers/worker-thread/index.md +0 -0
  172. /package/wiki/{references → extensions}/src-details/mcp-server.md +0 -0
@@ -668,7 +668,7 @@ Add Prometheus metrics endpoint:
668
668
  ```typescript
669
669
  // src/controllers/metrics.controller.ts
670
670
  @controller({ path: '/metrics' })
671
- export class MetricsController extends BaseController {
671
+ export class MetricsController extends BaseRestController {
672
672
  @get({ configs: { path: '/' } })
673
673
  getMetrics(c: Context) {
674
674
  return c.text(`
@@ -119,10 +119,10 @@ export class UserService extends BaseService {
119
119
  Controllers should delegate to services and let the global error handler catch exceptions:
120
120
 
121
121
  ```typescript
122
- import { BaseController, controller, get, post } from '@venizia/ignis';
122
+ import { BaseRestController, controller, get, post } from '@venizia/ignis';
123
123
 
124
124
  @controller({ path: '/users' })
125
- export class UserController extends BaseController {
125
+ export class UserController extends BaseRestController {
126
126
 
127
127
  @post({ configs: RouteConfigs.CREATE_USER })
128
128
  async createUser(c: TRouteContext) {
@@ -32,7 +32,7 @@ Prevent blocking the event loop with Worker Threads:
32
32
  - Large file/data processing
33
33
  - Any synchronous task > 5ms
34
34
 
35
- > **Deep Dive:** See [Worker Thread Helper](../references/helpers/worker-thread/) for implementation guide.
35
+ > **Deep Dive:** See [Worker Thread Helper](../extensions/helpers/worker-thread/) for implementation guide.
36
36
 
37
37
  ## 3. Optimize Database Queries
38
38
 
@@ -127,7 +127,7 @@ Reduce database load with caching:
127
127
 
128
128
  | Cache Type | Use Case | Implementation |
129
129
  |-----------|----------|----------------|
130
- | **Redis** | Distributed cache, session storage | [Redis Helper](../references/helpers/redis/) |
130
+ | **Redis** | Distributed cache, session storage | [Redis Helper](../extensions/helpers/redis/) |
131
131
  | **In-Memory** | Single-process cache | `MemoryStorageHelper` |
132
132
 
133
133
  **Example:**
@@ -390,7 +390,7 @@ logger.log('info', MSG_ORDER_FILLED);
390
390
  - Use background flushing to avoid I/O blocking
391
391
  - HfLogger uses a lock-free ring buffer (64K entries, 16MB)
392
392
 
393
- > **Deep Dive:** See [Logger Helper](../references/helpers/logger/) for complete HfLogger API.
393
+ > **Deep Dive:** See [Logger Helper](../extensions/helpers/logger/) for complete HfLogger API.
394
394
 
395
395
  ## Performance Checklist
396
396
 
@@ -68,7 +68,7 @@ const SecureRoute = {
68
68
  };
69
69
  ```
70
70
 
71
- > **Deep Dive:** See [Authentication Component](../references/components/authentication/) for full setup guide.
71
+ > **Deep Dive:** See [Authentication Component](../extensions/components/authentication/) for full setup guide.
72
72
 
73
73
  **Access user in protected routes:**
74
74
  ```typescript
@@ -463,6 +463,6 @@ Before deploying to production, verify:
463
463
 
464
464
  ## See Also
465
465
 
466
- - [Authentication Component](../references/components/authentication/) - JWT setup and configuration
466
+ - [Authentication Component](../extensions/components/authentication/) - JWT setup and configuration
467
467
  - [Common Pitfalls](./common-pitfalls) - Security-related mistakes to avoid
468
468
  - [Deployment Strategies](./deployment-strategies) - Secure deployment practices
@@ -118,7 +118,7 @@ cat .env | grep APP_ENV
118
118
  - Use `try-catch` blocks to catch and log errors
119
119
  - Check database queries with Drizzle's logging: `{ logger: true }`
120
120
 
121
- > **Deep Dive:** See [Logger Helper](../references/helpers/logger/) for advanced logging configuration.
121
+ > **Deep Dive:** See [Logger Helper](../extensions/helpers/logger/) for advanced logging configuration.
122
122
 
123
123
  ## 6. Request ID Tracking
124
124
 
@@ -179,7 +179,7 @@ export const authenticate = (opts: { strategies: string[]; mode?: TAuthMode }) =
179
179
  This is the primary export for creating auth middleware. It creates an `AuthenticationProvider` instance and calls `.value()` to get the middleware factory. The provider uses `AuthenticationStrategyRegistry.getInstance()` internally to resolve strategies.
180
180
 
181
181
  > [!NOTE]
182
- > In `all` mode, if every strategy passes but the final user payload has no `userId`, the middleware throws a `401` with message `"Failed to identify authenticated user!"`. The `any` mode **discards errors** from each failing strategy (logs at debug level) and only throws after all strategies are exhausted.
182
+ > In `all` mode, the **first** strategy's user payload is used as the identity source — all strategies must succeed but the first one wins for identity. If every strategy passes but the first user payload has no `userId`, the middleware throws a `401` with message `"Failed to identify authenticated user!"`. The `any` mode **discards errors** from each failing strategy (logs at debug level) and only throws after all strategies are exhausted.
183
183
 
184
184
  ## Service Class Hierarchy
185
185
 
@@ -531,7 +531,7 @@ Serves the JWKS endpoint (default path `/certs`). This endpoint is **intentional
531
531
  **File:** `packages/core/src/components/auth/authenticate/controllers/jwks/controller.ts`
532
532
 
533
533
  ```typescript
534
- class JWKSController extends BaseController {
534
+ class JWKSController extends BaseRestController {
535
535
  constructor(
536
536
  @inject({
537
537
  key: BindingKeys.build({
@@ -646,43 +646,35 @@ type TPermissionCommonColumns = {
646
646
  code: NotNull<PgTextBuilderInitial<...>>;
647
647
  name: NotNull<PgTextBuilderInitial<...>>;
648
648
  subject: NotNull<PgTextBuilderInitial<...>>;
649
- pType: NotNull<PgTextBuilderInitial<...>>;
650
649
  action: NotNull<PgTextBuilderInitial<...>>;
651
650
  scope: NotNull<PgTextBuilderInitial<...>>;
652
651
  };
653
652
  ```
654
653
 
655
- ### Permission Mapping Types
654
+ ### Policy Definition Types
656
655
 
657
656
  ```typescript
658
- type TPermissionMappingOptions = {
657
+ type TPolicyDefinitionOptions = {
659
658
  idType?: 'string' | 'number';
660
659
  };
661
660
 
662
- type TPermissionMappingCommonColumns = {
663
- effect: PgTextBuilderInitial<...>;
661
+ type TPolicyDefinitionCommonColumns = {
662
+ variant: ReturnType<typeof text>;
663
+ subjectType: ReturnType<typeof text>;
664
+ targetType: ReturnType<typeof text>;
665
+ action: ReturnType<typeof text>;
666
+ effect: ReturnType<typeof text>;
667
+ domain: ReturnType<typeof text>;
664
668
  };
665
669
  ```
666
670
 
667
- ### User Role Types
668
-
669
- ```typescript
670
- type TUserRoleOptions = {
671
- idType?: 'string' | 'number';
672
- };
673
-
674
- type TUserRoleCommonColumns = ReturnType<
675
- typeof generatePrincipalColumnDefs<'principal', 'string' | 'number'>
676
- >;
677
- ```
678
-
679
671
  ## Controller Factory
680
672
 
681
673
  The `defineAuthController()` function dynamically creates a controller class at runtime using decorator composition:
682
674
 
683
675
  **How it works:**
684
676
 
685
- 1. **Class creation:** A new class is created dynamically with `class AuthController extends BaseController {}` inside the factory closure
677
+ 1. **Class creation:** A new class is created dynamically with `class AuthController extends BaseRestController {}` inside the factory closure
686
678
  2. **Decorator application:** The `@controller({ path: restPath })` decorator is applied to set the base path. The controller is created with `isStrict: true`
687
679
  3. **Service injection:** The auth service is injected via `inject({ key: serviceKey })(AuthController, undefined, 0)` after class definition -- this programmatically applies `@inject` to constructor parameter 0
688
680
  - Service key is provided via `controllerOpts.serviceKey` (required)
@@ -209,7 +209,7 @@ The middleware that executes strategies in the configured mode.
209
209
  | Error Message | Status | Method | When |
210
210
  |---------------|--------|--------|------|
211
211
  | <code v-pre>Authentication failed. Tried strategies: {{strategies}}</code> | 401 | `executeAnyMode` | All strategies failed in `'any'` mode — each strategy threw during `authenticate()` |
212
- | `Failed to identify authenticated user!` | 401 | `executeAllMode` | All strategies succeeded in `'all'` mode but the final `authUser.userId` is falsy |
212
+ | `Failed to identify authenticated user!` | 401 | `executeAllMode` | All strategies succeeded in `'all'` mode but the first strategy's `authUser.userId` is falsy |
213
213
  | <code v-pre>Invalid authentication mode &#124; mode: {{mode}}</code> | 500 | `createAuthenticateMiddleware` | `mode` is not `'any'` or `'all'` |
214
214
 
215
215
  ---
@@ -178,17 +178,14 @@ import {
178
178
  extraUserColumns,
179
179
  extraRoleColumns,
180
180
  extraPermissionColumns,
181
- extraPermissionMappingColumns,
182
- extraUserRoleColumns,
181
+ extraPolicyDefinitionColumns,
183
182
  } from '@venizia/ignis';
184
183
 
185
184
  import type {
186
185
  TPermissionOptions,
187
186
  TPermissionCommonColumns,
188
- TPermissionMappingOptions,
189
- TPermissionMappingCommonColumns,
190
- TUserRoleOptions,
191
- TUserRoleCommonColumns,
187
+ TPolicyDefinitionOptions,
188
+ TPolicyDefinitionCommonColumns,
192
189
  } from '@venizia/ignis';
193
190
  ```
194
191
 
@@ -866,13 +863,13 @@ type TAuthMode = TConstValue<typeof AuthenticationModes>;
866
863
 
867
864
  - **Guides:**
868
865
  - [Components Overview](/guides/core-concepts/components) -- Component system basics
869
- - [Controllers](/guides/core-concepts/controllers) -- Protecting routes with auth
866
+ - [REST Controllers](/guides/core-concepts/rest-controllers) | [gRPC Controllers](/guides/core-concepts/grpc-controllers) -- Protecting routes with auth
870
867
 
871
868
  - **Components:**
872
869
  - [All Components](../index) -- Built-in components list
873
870
 
874
871
  - **Helpers:**
875
- - [Crypto Helper](/references/helpers/crypto/) -- Password hashing utilities
872
+ - [Crypto Helper](/extensions/helpers/crypto/) -- Password hashing utilities
876
873
 
877
874
  - **References:**
878
875
  - [Middlewares](/references/base/middlewares) -- Custom authentication middleware
@@ -350,8 +350,7 @@ import {
350
350
  extraUserColumns,
351
351
  extraRoleColumns,
352
352
  extraPermissionColumns,
353
- extraPermissionMappingColumns,
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
- // Permission mapping (role-to-permission or user-to-permission)
383
- export const permissionMappings = pgTable('permission_mappings', {
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
- ...extraUserRoleColumns(),
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
- ### extraPermissionMappingColumns
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 the user-role junction table. Includes principal columns (polymorphic type/ID fields) via `generatePrincipalColumnDefs` with a default polymorphic value of `'Role'`.
441
+ Returns columns for Casbin-style policy definitions that map subjects (users/roles) to targets (resources/permissions).
464
442
 
465
443
  ```typescript
466
- extraUserRoleColumns(opts?: { idType: 'string' | 'number' })
444
+ extraPolicyDefinitionColumns(opts?: { idType: 'string' | 'number' })
467
445
  ```
468
446
 
469
- | Column | Type | Default | Description |
470
- |--------|------|---------|-------------|
471
- | _(principal columns)_ | _various_ | -- | Polymorphic columns from `generatePrincipalColumnDefs` |
472
- | `userId` | `text` or `integer` | -- | Associated user ID (not null) |
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 last strategy's user payload is used
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| Resolve[Resolve enforcer by name]
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'` `casbin.newModelFromFile(definition)`
582
- - `'text'` `casbin.newModelFromString(definition)`
587
+ - `'file'` -> `casbin.newModelFromFile(definition)`
588
+ - `'text'` -> `casbin.newModelFromString(definition)`
583
589
  4. Creates enforcer based on cache config:
584
- - `cached.use: false` `casbin.newEnforcer(model, adapter)`
585
- - `cached.driver: 'in-memory'` `casbin.newCachedEnforcer(model, adapter)` + periodic invalidation timer (`setInterval`)
586
- - `cached.driver: 'redis'` `casbin.newEnforcer(model, adapter)` (Redis handles caching externally)
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 hit: load lines into model via `loadPolicyLinesIntoModel()`; miss: load from adapter, extract lines via `extractPolicyLines()`, cache in Redis with TTL |
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 hit: load lines; miss: load from adapter + 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
- The `AbstractController.getRouteConfigs()` method handles middleware injection order:
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
- getRouteConfigs<RouteConfig extends IAuthRouteConfig>(opts: { configs: RouteConfig }) {
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 createRoute({ ...restConfig, middleware: mws, ... });
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: Resolve enforcer"}
17
+ S4 -->|ALLOW/ABSTAIN| S5{"Step 5: Enforcers registered?"}
18
18
 
19
- S5 -->|Registry empty| E500a[/"500: No items registered"/]
20
- S5 -->|Name not found| E500b[/"500: Descriptor not found"/]
21
- S5 -->|DI fails| E500c[/"500: Failed to resolve"/]
22
- S5 -->|OK| S6{"Step 6: Build rules"}
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| OK3([Authorized])
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 usually means `AuthorizationEnforcerRegistry.register()` was never called.
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["5. Resolve enforcer"]
44
- S5 --> S6["6. Build/cache rules"]
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| Pass5([next])
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 | Resolve enforcer (by name or default) | No |
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. The `AbstractController.getRouteConfigs()` method ensures the correct ordering:
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