@venizia/ignis-docs 0.0.8 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/README.md +7 -7
  2. package/content/best-practices/api-usage-examples.md +15 -12
  3. package/content/best-practices/architectural-patterns.md +70 -78
  4. package/content/best-practices/architecture-decisions.md +91 -60
  5. package/content/best-practices/code-style-standards/advanced-patterns.md +56 -44
  6. package/content/best-practices/code-style-standards/constants-configuration.md +11 -11
  7. package/content/best-practices/code-style-standards/control-flow.md +5 -2
  8. package/content/best-practices/code-style-standards/documentation.md +13 -13
  9. package/content/best-practices/code-style-standards/function-patterns.md +9 -10
  10. package/content/best-practices/code-style-standards/index.md +1 -1
  11. package/content/best-practices/code-style-standards/naming-conventions.md +10 -8
  12. package/content/best-practices/code-style-standards/route-definitions.md +30 -12
  13. package/content/best-practices/code-style-standards/tooling.md +8 -5
  14. package/content/best-practices/code-style-standards/type-safety.md +13 -12
  15. package/content/best-practices/common-pitfalls.md +56 -37
  16. package/content/best-practices/contribution-workflow.md +13 -14
  17. package/content/best-practices/data-modeling.md +46 -22
  18. package/content/best-practices/deployment-strategies.md +28 -27
  19. package/content/best-practices/error-handling.md +48 -24
  20. package/content/best-practices/index.md +5 -5
  21. package/content/best-practices/performance-optimization.md +40 -31
  22. package/content/best-practices/security-guidelines.md +52 -23
  23. package/content/best-practices/testing-strategies.md +65 -51
  24. package/content/best-practices/troubleshooting-tips.md +24 -24
  25. package/content/extensions/components/{swagger.md → api-reference.md} +40 -31
  26. package/content/extensions/components/authentication/api.md +19 -19
  27. package/content/extensions/components/authentication/errors.md +7 -7
  28. package/content/extensions/components/authentication/index.md +10 -8
  29. package/content/extensions/components/authentication/usage.md +101 -6
  30. package/content/extensions/components/authorization/api.md +45 -25
  31. package/content/extensions/components/authorization/errors.md +6 -6
  32. package/content/extensions/components/authorization/index.md +11 -10
  33. package/content/extensions/components/authorization/usage.md +21 -21
  34. package/content/extensions/components/health-check.md +1 -1
  35. package/content/extensions/components/index.md +5 -5
  36. package/content/extensions/components/mail/errors.md +15 -15
  37. package/content/extensions/components/mail/index.md +1 -2
  38. package/content/extensions/components/mail/usage.md +1 -1
  39. package/content/extensions/components/request-tracker.md +1 -1
  40. package/content/extensions/components/socket-io/api.md +9 -9
  41. package/content/extensions/components/socket-io/errors.md +5 -5
  42. package/content/extensions/components/socket-io/index.md +8 -8
  43. package/content/extensions/components/socket-io/usage.md +1 -1
  44. package/content/extensions/components/static-asset/api.md +17 -4
  45. package/content/extensions/components/static-asset/errors.md +4 -4
  46. package/content/extensions/components/static-asset/index.md +26 -28
  47. package/content/extensions/components/static-asset/usage.md +13 -12
  48. package/content/extensions/components/template/index.md +2 -2
  49. package/content/extensions/components/template/setup-page.md +1 -1
  50. package/content/extensions/components/websocket/api.md +3 -3
  51. package/content/extensions/components/websocket/errors.md +5 -5
  52. package/content/extensions/components/websocket/index.md +5 -5
  53. package/content/extensions/components/websocket/usage.md +3 -3
  54. package/content/extensions/helpers/cron/index.md +2 -2
  55. package/content/extensions/helpers/crypto/index.md +1 -1
  56. package/content/extensions/helpers/env/index.md +27 -12
  57. package/content/extensions/helpers/error/index.md +81 -25
  58. package/content/extensions/helpers/index.md +2 -3
  59. package/content/extensions/helpers/inversion/index.md +15 -7
  60. package/content/extensions/helpers/kafka/compile-binary.md +92 -0
  61. package/content/extensions/helpers/kafka/examples.md +1 -1
  62. package/content/extensions/helpers/kafka/index.md +3 -0
  63. package/content/extensions/helpers/logger/index.md +32 -2
  64. package/content/extensions/helpers/network/index.md +6 -0
  65. package/content/extensions/helpers/queue/index.md +14 -17
  66. package/content/extensions/helpers/redis/index.md +548 -323
  67. package/content/extensions/helpers/socket-io/index.md +14 -10
  68. package/content/extensions/helpers/storage/api.md +44 -8
  69. package/content/extensions/helpers/storage/index.md +43 -7
  70. package/content/extensions/helpers/template/index.md +6 -3
  71. package/content/extensions/helpers/types/index.md +11 -8
  72. package/content/extensions/helpers/websocket/api.md +9 -9
  73. package/content/extensions/helpers/websocket/index.md +7 -7
  74. package/content/extensions/helpers/worker-thread/index.md +2 -2
  75. package/content/extensions/index.md +3 -4
  76. package/content/extensions/src-details/mcp-server.md +18 -24
  77. package/content/guides/core-concepts/application/bootstrapping.md +11 -14
  78. package/content/guides/core-concepts/application/index.md +3 -3
  79. package/content/guides/core-concepts/components.md +19 -10
  80. package/content/guides/core-concepts/dependency-injection.md +6 -3
  81. package/content/guides/core-concepts/grpc-controllers.md +6 -5
  82. package/content/guides/core-concepts/persistent/datasources.md +42 -43
  83. package/content/guides/core-concepts/persistent/index.md +16 -7
  84. package/content/guides/core-concepts/persistent/models.md +24 -20
  85. package/content/guides/core-concepts/persistent/postgres-drivers.md +201 -0
  86. package/content/guides/core-concepts/persistent/repositories.md +40 -23
  87. package/content/guides/core-concepts/persistent/search-meilisearch.md +185 -0
  88. package/content/guides/core-concepts/persistent/search-typesense.md +431 -0
  89. package/content/guides/core-concepts/persistent/transactions.md +61 -25
  90. package/content/guides/core-concepts/rest-controllers.md +12 -9
  91. package/content/guides/core-concepts/services.md +330 -60
  92. package/content/guides/get-started/5-minute-quickstart.md +15 -15
  93. package/content/guides/get-started/philosophy.md +36 -36
  94. package/content/guides/get-started/setup.md +3 -3
  95. package/content/guides/index.md +3 -3
  96. package/content/guides/migrations/redis-helpers-migration.md +177 -0
  97. package/content/guides/migrations/scoped-rbac-migration.md +17 -17
  98. package/content/guides/migrations/unified-connectors-migration.md +113 -0
  99. package/content/guides/reference/glossary.md +19 -12
  100. package/content/guides/reference/mcp-docs-server.md +22 -18
  101. package/content/guides/tutorials/building-a-crud-api.md +37 -44
  102. package/content/guides/tutorials/complete-installation.md +17 -17
  103. package/content/guides/tutorials/ecommerce-api.md +163 -124
  104. package/content/guides/tutorials/realtime-chat.md +181 -135
  105. package/content/guides/tutorials/testing.md +65 -523
  106. package/content/index.md +2 -180
  107. package/content/public/apple-touch-icon.png +0 -0
  108. package/content/public/og-image.png +0 -0
  109. package/content/public/site.webmanifest +11 -0
  110. package/content/references/base/application.md +4 -5
  111. package/content/references/base/bootstrapping.md +18 -5
  112. package/content/references/base/components.md +149 -120
  113. package/content/references/base/connectors.md +178 -0
  114. package/content/references/base/controllers.md +41 -30
  115. package/content/references/base/datasources.md +163 -92
  116. package/content/references/base/dependency-injection.md +34 -22
  117. package/content/references/base/filter-system/application-usage.md +17 -14
  118. package/content/references/base/filter-system/array-operators.md +7 -2
  119. package/content/references/base/filter-system/comparison-operators.md +3 -0
  120. package/content/references/base/filter-system/default-filter.md +89 -71
  121. package/content/references/base/filter-system/fields-order-pagination.md +22 -22
  122. package/content/references/base/filter-system/index.md +6 -3
  123. package/content/references/base/filter-system/json-filtering.md +20 -1
  124. package/content/references/base/filter-system/list-operators.md +1 -1
  125. package/content/references/base/filter-system/logical-operators.md +33 -1
  126. package/content/references/base/filter-system/null-operators.md +30 -1
  127. package/content/references/base/filter-system/quick-reference.md +23 -4
  128. package/content/references/base/filter-system/tips.md +5 -5
  129. package/content/references/base/filter-system/use-cases.md +12 -12
  130. package/content/references/base/grpc-controllers.md +13 -13
  131. package/content/references/base/index.md +24 -12
  132. package/content/references/base/middlewares.md +265 -327
  133. package/content/references/base/models.md +63 -49
  134. package/content/references/base/providers.md +136 -130
  135. package/content/references/base/repositories/advanced.md +59 -58
  136. package/content/references/base/repositories/index.md +115 -91
  137. package/content/references/base/repositories/mixins.md +55 -291
  138. package/content/references/base/repositories/relations.md +54 -64
  139. package/content/references/base/repositories/soft-deletable.md +31 -30
  140. package/content/references/base/services.md +296 -93
  141. package/content/references/configuration/environment-variables.md +49 -31
  142. package/content/references/configuration/index.md +6 -6
  143. package/content/references/index.md +17 -12
  144. package/content/references/quick-reference.md +65 -106
  145. package/content/references/utilities/crypto.md +65 -23
  146. package/content/references/utilities/index.md +3 -3
  147. package/content/references/utilities/jsx.md +6 -4
  148. package/content/references/utilities/module.md +68 -20
  149. package/content/references/utilities/parse.md +4 -14
  150. package/content/references/utilities/promise.md +9 -7
  151. package/content/references/utilities/schema.md +5 -3
  152. package/dist/mcp-server/common/guards.d.ts +8 -0
  153. package/dist/mcp-server/common/guards.d.ts.map +1 -0
  154. package/dist/mcp-server/common/guards.js +14 -0
  155. package/dist/mcp-server/common/guards.js.map +1 -0
  156. package/dist/mcp-server/common/index.d.ts +1 -0
  157. package/dist/mcp-server/common/index.d.ts.map +1 -1
  158. package/dist/mcp-server/common/index.js +1 -0
  159. package/dist/mcp-server/common/index.js.map +1 -1
  160. package/dist/mcp-server/helpers/docs.helper.d.ts.map +1 -1
  161. package/dist/mcp-server/helpers/docs.helper.js +4 -2
  162. package/dist/mcp-server/helpers/docs.helper.js.map +1 -1
  163. package/dist/mcp-server/helpers/github.helper.js +1 -1
  164. package/dist/mcp-server/index.js +7 -2
  165. package/dist/mcp-server/index.js.map +1 -1
  166. package/dist/mcp-server/tools/base.tool.d.ts +6 -2
  167. package/dist/mcp-server/tools/base.tool.d.ts.map +1 -1
  168. package/dist/mcp-server/tools/base.tool.js.map +1 -1
  169. package/dist/mcp-server/tools/docs/search-documents.tool.d.ts +1 -1
  170. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
  171. package/dist/mcp-server/tools/github/search-code.tool.d.ts +1 -1
  172. package/dist/mcp-server/tools/github/search-code.tool.d.ts.map +1 -1
  173. package/dist/mcp-server/tools/github/search-code.tool.js +4 -1
  174. package/dist/mcp-server/tools/github/search-code.tool.js.map +1 -1
  175. package/dist/mcp-server/tools/github/verify-dependencies.tool.d.ts.map +1 -1
  176. package/dist/mcp-server/tools/github/verify-dependencies.tool.js +3 -1
  177. package/dist/mcp-server/tools/github/verify-dependencies.tool.js.map +1 -1
  178. package/package.json +9 -9
  179. package/content/extensions/helpers/testing/index.md +0 -510
  180. package/content/references/base/middleware.md +0 -347
@@ -432,8 +432,10 @@ extraPermissionColumns(opts?: { idType: 'string' | 'number' })
432
432
  | `code` | `text` (unique) | -- | Unique permission code |
433
433
  | `name` | `text` | -- | Permission name |
434
434
  | `subject` | `text` | -- | Permission subject (e.g., `'User'`, `'Order'`) |
435
+ | `method` | `text` | -- | HTTP method (e.g., `'GET'`, `'POST'`) |
435
436
  | `action` | `text` | -- | Permitted action (e.g., `'read'`, `'write'`) |
436
437
  | `scope` | `text` | -- | Permission scope |
438
+ | `description` | `text` | `null` | Optional permission description |
437
439
  | `parentId` | `text` or `integer` | `null` | Parent permission ID |
438
440
 
439
441
  ### extraPolicyDefinitionColumns
@@ -695,7 +697,7 @@ flowchart TD
695
697
 
696
698
  ```mermaid
697
699
  flowchart LR
698
- subgraph GENERATE["generate() Token Creation"]
700
+ subgraph GENERATE["generate() - Token Creation"]
699
701
  direction TB
700
702
  P["Payload: { userId, roles, email }"]
701
703
  P --> CHECK1{"applicationSecret?"}
@@ -705,7 +707,7 @@ flowchart LR
705
707
  CHECK1 -->|"No"| PLAIN1["Use payload as-is"]
706
708
  end
707
709
 
708
- subgraph VERIFY["verify() Token Verification"]
710
+ subgraph VERIFY["verify() - Token Verification"]
709
711
  direction TB
710
712
  T["Verified JWT payload"]
711
713
  T --> CHECK2{"applicationSecret?"}
@@ -741,7 +743,7 @@ JWT payloads can optionally be encrypted field-by-field using AES (default `aes-
741
743
 
742
744
  ## Hono Context Extension
743
745
 
744
- The Authentication module extends Hono's `ContextVariableMap` to provide type-safe access to auth data. Note: `ContextVariableMap` does **not** take a generic parameter it is a plain interface augmentation:
746
+ The Authentication module extends Hono's `ContextVariableMap` to provide type-safe access to auth data. Note: `ContextVariableMap` does **not** take a generic parameter - it is a plain interface augmentation:
745
747
 
746
748
  ```typescript
747
749
  declare module 'hono' {
@@ -835,6 +837,8 @@ const JWTTokenPayloadSchema = z.object({
835
837
  });
836
838
  ```
837
839
 
840
+ For the `/who-am-i` response, this schema is extended at runtime with an optional `userInformation` field (typed from `payload.getUserInformation.response.schema`, falling back to `AnyObjectSchema`) so the `withUserInformation` shape is OpenAPI-documented.
841
+
838
842
  ## API Endpoints
839
843
 
840
844
  The built-in auth controller is created by the `defineAuthController()` factory function and is only available when `useAuthController: true` is set in `REST_OPTIONS`.
@@ -844,11 +848,13 @@ The built-in auth controller is created by the `defineAuthController()` factory
844
848
  | `POST` | `/auth/sign-in` | No | Authenticate and receive a JWT token |
845
849
  | `POST` | `/auth/sign-up` | Configurable | Create a new user account |
846
850
  | `POST` | `/auth/change-password` | JWT | Change the authenticated user's password |
847
- | `GET` | `/auth/who-am-i` | JWT | Return the current user's JWT payload |
851
+ | `POST` | `/auth/token/refresh` | JWT | Re-issue an access token using a valid JWT |
852
+ | `GET` | `/auth/who-am-i` | JWT | Return the current user's JWT payload (optionally with attached user information) |
853
+ | `GET` | `/auth/me` | JWT | Return the current user's information from `getUserInformation` |
848
854
  | `GET` | `/certs` | No | JWKS endpoint (JWKS Issuer mode only) |
849
855
 
850
856
  > [!NOTE]
851
- > The base path `/auth` is configurable via `controllerOpts.restPath`. The `/certs` path is configurable via `rest.path` in `IJWKSIssuerOptions`. The `/certs` endpoint is intentionally unauthenticated it serves the public keys needed by external verifiers.
857
+ > The base path `/auth` is configurable via `controllerOpts.restPath`. The `/certs` path is configurable via `rest.path` in `IJWKSIssuerOptions`. The `/certs` endpoint is intentionally unauthenticated - it serves the public keys needed by external verifiers.
852
858
 
853
859
  ### POST /auth/sign-in
854
860
 
@@ -878,6 +884,42 @@ When `requireAuthenticatedSignUp: true`, requires JWT authentication. When `fals
878
884
 
879
885
  **Authentication:** Always requires JWT (`Authentication.STRATEGY_JWT`)
880
886
 
887
+ ### POST /auth/token/refresh
888
+
889
+ **Authentication:** Always requires JWT (`Authentication.STRATEGY_JWT`)
890
+
891
+ Re-issues an access token using the caller's currently valid JWT. There is no separate refresh token - the caller must send a valid Bearer token in the `Authorization` header.
892
+
893
+ **Request Body:** None
894
+
895
+ **Response 200:**
896
+
897
+ Uses `payload.refreshToken.response.schema` if provided, otherwise `AnyObjectSchema`. The response shape is entirely defined by your `IAuthService.refreshToken()` implementation.
898
+
899
+ **Response 501 (Not Implemented):**
900
+
901
+ Returned when the bound `IAuthService` does not implement the optional `refreshToken` method.
902
+
903
+ **Implementing `refreshToken` in your service:**
904
+
905
+ ```typescript
906
+ export class AuthenticationService extends BaseService implements IAuthService {
907
+ async refreshToken(context: TContext<Env>): Promise<{ token: string }> {
908
+ // The current user is already verified by JWT middleware
909
+ const currentUser = context.get(Authentication.CURRENT_USER);
910
+
911
+ // Re-issue a new token with the same payload
912
+ const token = await this._tokenService.generate({ payload: currentUser });
913
+ return { token };
914
+ }
915
+
916
+ // ... signIn, signUp, changePassword ...
917
+ }
918
+ ```
919
+
920
+ > [!NOTE]
921
+ > The framework does not enforce rotation or revocation policy. If you need to invalidate old tokens after refresh, implement that logic (e.g., a token blocklist or short expiry) inside your `refreshToken` implementation.
922
+
881
923
  ### GET /auth/who-am-i
882
924
 
883
925
  **Authentication:** Always requires JWT (`Authentication.STRATEGY_JWT`)
@@ -896,6 +938,57 @@ Returns the current user's decrypted JWT payload directly from context.
896
938
  }
897
939
  ```
898
940
 
941
+ **Query Parameters:**
942
+
943
+ | Name | Type | Description |
944
+ |------|------|-------------|
945
+ | `withUserInformation` | `true` \| `false` \| `1` \| `0` | When truthy, attaches a `userInformation` field built from `IAuthService.getUserInformation`. Defaults to `false`. |
946
+
947
+ When `withUserInformation` is truthy, the response merges the `getUserInformation` result into the payload:
948
+
949
+ ```json
950
+ {
951
+ "userId": "123",
952
+ "roles": [{ "id": "1", "identifier": "admin", "priority": 0 }],
953
+ "userInformation": { "fullName": "Ada Lovelace", "department": "R&D" }
954
+ }
955
+ ```
956
+
957
+ **Response 501 (Not Implemented):**
958
+
959
+ Returned when `withUserInformation` is truthy but the bound `IAuthService` does not implement the optional `getUserInformation` method.
960
+
961
+ ### GET /auth/me
962
+
963
+ **Authentication:** Always requires JWT (`Authentication.STRATEGY_JWT`)
964
+
965
+ Returns the current user's information by delegating to `IAuthService.getUserInformation(context, {})`. Unlike `who-am-i`, the response is entirely the service result -- it is not merged with the JWT payload.
966
+
967
+ **Response 200:**
968
+
969
+ Uses `payload.getUserInformation.response.schema` if provided, otherwise `AnyObjectSchema`. The shape is defined by your `getUserInformation` implementation.
970
+
971
+ **Response 501 (Not Implemented):**
972
+
973
+ Returned when the bound `IAuthService` does not implement the optional `getUserInformation` method.
974
+
975
+ **Implementing `getUserInformation` in your service:**
976
+
977
+ ```typescript
978
+ export class AuthenticationService extends BaseService implements IAuthService {
979
+ async getUserInformation(context: TContext<Env>, _opts: AnyObject): Promise<AnyObject> {
980
+ // The current user is already verified by JWT middleware
981
+ const currentUser = context.get(Authentication.CURRENT_USER);
982
+ return this.userRepository.findById({ id: currentUser.userId });
983
+ }
984
+
985
+ // ... signIn, signUp, changePassword ...
986
+ }
987
+ ```
988
+
989
+ > [!TIP]
990
+ > One `getUserInformation` implementation backs both routes. Use `GET /me` when you want the raw profile, or `GET /who-am-i?withUserInformation=true` when you want it merged into the principal in a single round-trip.
991
+
899
992
  ### GET /certs (JWKS Issuer Only)
900
993
 
901
994
  **Authentication:** None (intentionally public)
@@ -922,7 +1015,7 @@ Returns the JSON Web Key Set for external verifiers.
922
1015
 
923
1016
  ## Auth Entity Column Helpers
924
1017
 
925
- Ignis provides column helper functions that return pre-configured Drizzle column objects for common auth-related database tables. These functions are designed to be spread into `pgTable()` definitions, giving you standardized columns for User, Role, Permission, and PolicyDefinition entities without manually defining each column.
1018
+ IGNIS provides column helper functions that return pre-configured Drizzle column objects for common auth-related database tables. These functions are designed to be spread into `pgTable()` definitions, giving you standardized columns for User, Role, Permission, and PolicyDefinition entities without manually defining each column.
926
1019
 
927
1020
  All helpers that accept an `opts` parameter support `{ idType: 'string' | 'number' }` to control whether foreign key columns use `text` (for UUIDs) or `integer` (for serial IDs). The default is `'number'`.
928
1021
 
@@ -966,8 +1059,10 @@ All helpers that accept an `opts` parameter support `{ idType: 'string' | 'numbe
966
1059
  | `code` | `code` | `text` (unique) | No | -- | Unique permission code |
967
1060
  | `name` | `name` | `text` | No | -- | Permission display name |
968
1061
  | `subject` | `subject` | `text` | No | -- | Permission subject (e.g., `'User'`, `'Order'`) |
1062
+ | `method` | `method` | `text` | No | -- | HTTP method (e.g., `'GET'`, `'POST'`) |
969
1063
  | `action` | `action` | `text` | No | -- | Permitted action (e.g., `'read'`, `'write'`) |
970
1064
  | `scope` | `scope` | `text` | No | -- | Permission scope |
1065
+ | `description` | `description` | `text` | Yes | `null` | Optional permission description |
971
1066
  | `parentId` | `parent_id` | `text` or `integer` | Yes | `null` | Parent permission ID (type depends on `idType`) |
972
1067
 
973
1068
  ### extraPolicyDefinitionColumns
@@ -119,8 +119,8 @@ classDiagram
119
119
 
120
120
  class BaseFilteredAdapter~TFilter~ {
121
121
  <<abstract>>
122
- #dataSource: IDataSource
123
- #connector: TAnyConnector
122
+ #dataSource: ICasbinPolicySource
123
+ #connector: TCasbinPolicyConnector
124
124
  +loadFilteredPolicy(model, filter)* void
125
125
  +isFiltered() boolean
126
126
  #loadLines(opts) void
@@ -421,7 +421,7 @@ interface IAuthorizationEnforcer<
421
421
 
422
422
  | Enforcer | TRules | Description |
423
423
  |----------|--------|-------------|
424
- | `CasbinAuthorizationEnforcer` | `ICasbinRules` | `{ user, lines }` the user plus their resolved Casbin policy lines (loaded into a pooled enforcer at evaluate time) |
424
+ | `CasbinAuthorizationEnforcer` | `ICasbinRules` | `{ user, lines }` - the user plus their resolved Casbin policy lines (loaded into a pooled enforcer at evaluate time) |
425
425
  | Custom | Any type | Your custom rules structure |
426
426
 
427
427
  ### Method Contracts
@@ -517,20 +517,20 @@ Injects `ICasbinEnforcerOptions` from the DI container using the binding key `Au
517
517
 
518
518
  Called once by the registry on first use. Performs:
519
519
 
520
- 1. Dynamically imports `casbin` throws `"casbin" is not installed` if missing.
521
- 2. Validates `options.model` throws `options.model is required.` if missing.
520
+ 1. Dynamically imports `casbin` - throws `"casbin" is not installed` if missing.
521
+ 2. Validates `options.model` - throws `options.model is required.` if missing.
522
522
  3. Memoizes the payload normalizer (`options.normalizePayloadFn ?? defaultScopedPayloadFn()`).
523
523
  4. If `cached.use`, validates `expiresIn >= MIN_EXPIRES_IN` (10,000 ms).
524
- 5. Builds a **`BasePoolHelper<Enforcer>`** (`size = poolSize ?? 16`, `acquireTimeoutMs = poolAcquireTimeoutMs ?? 5000`). Each pooled enforcer is created **without an adapter** (`newEnforcer(model)` no DB load at warmup), then `registerMatchers()` and `assertMatcherCompilesSync()` run on it.
525
- 6. `await pool.warmup()` pre-creates the enforcers.
524
+ 5. Builds a **`BasePoolHelper<Enforcer>`** (`size = poolSize ?? 16`, `acquireTimeoutMs = poolAcquireTimeoutMs ?? 5000`). Each pooled enforcer is created **without an adapter** (`newEnforcer(model)` - no DB load at warmup), then `registerMatchers()` and `assertMatcherCompilesSync()` run on it.
525
+ 6. `await pool.warmup()` - pre-creates the enforcers.
526
526
 
527
- `registerMatchers()` when `isScoped`, registers `keyMatch` as the domain matching func on `g`, adds `objectMatch` as a function, and registers it as the matching func on the resource relation (`g4`). When `domainMatching` is set (non-scoped), registers the chosen `Util.*Func` on the named role definition. Always finishes with `buildRoleLinks()`.
527
+ `registerMatchers()` - when `isScoped`, registers `keyMatch` as the domain matching func on `g`, adds `objectMatch` as a function, and registers it as the matching func on the resource relation (`g4`). When `domainMatching` is set (non-scoped), registers the chosen `Util.*Func` on the named role definition. Always finishes with `buildRoleLinks()`.
528
528
 
529
- `assertMatcherCompilesSync()` a boot-time smoke test: forces casbin's lazy matcher compile by running one dummy `enforceSync` (4 args when scoped/`normalizePayloadFn`, else 3), so a malformed matcher, an unregistered function, or an arity mismatch fails at warmup instead of on the first real request.
529
+ `assertMatcherCompilesSync()` - a boot-time smoke test: forces casbin's lazy matcher compile by running one dummy `enforceSync` (4 args when scoped/`normalizePayloadFn`, else 3), so a malformed matcher, an unregistered function, or an arity mismatch fails at warmup instead of on the first real request.
530
530
 
531
531
  ### destroy()
532
532
 
533
- `this.pool?.destroy()` drains and disposes the pooled enforcers.
533
+ `this.pool?.destroy()` - drains and disposes the pooled enforcers.
534
534
 
535
535
  ### buildRules()
536
536
 
@@ -551,7 +551,7 @@ flowchart TD
551
551
 
552
552
  - **`extractUserLines(user)`** builds a fresh, **isolated** enforcer *with the adapter*, calls
553
553
  `adapter.loadFilteredPolicy({ principal: { type, id } })`, then `extractLinesFrom()` serializes every
554
- p-type and g-type rule back into lines. This throwaway enforcer never serves a request that is the
554
+ p-type and g-type rule back into lines. This throwaway enforcer never serves a request - that is the
555
555
  anti-poisoning guarantee.
556
556
  - **`fetchLinesWithRedisCache`** returns cached lines on hit (Redis owns expiry via `PX`). On miss it
557
557
  dedups concurrent misses through `pendingLineFetches` (single-flight), extracts once, and writes the
@@ -631,7 +631,7 @@ await opts.enforcer.buildRoleLinks();
631
631
  ## BaseFilteredAdapter
632
632
 
633
633
  Thin read-only base for casbin `FilteredAdapter`s backed by a datasource. It owns the boilerplate
634
- every filtered adapter repeats datasource/connector plumbing, the `isFiltered() === true` flag, the
634
+ every filtered adapter repeats - datasource/connector plumbing, the `isFiltered() === true` flag, the
635
635
  no-op write methods, and a `loadLines` helper. A subclass implements only `loadFilteredPolicy`: query
636
636
  the store for ONE principal's policies and turn them into casbin lines.
637
637
 
@@ -642,10 +642,10 @@ abstract class BaseFilteredAdapter<TFilter = ICasbinPolicyFilter>
642
642
  extends BaseHelper
643
643
  implements FilteredAdapter
644
644
  {
645
- protected readonly dataSource: IDataSource;
646
- protected get connector(): TAnyConnector;
645
+ protected readonly dataSource: ICasbinPolicySource;
646
+ protected get connector(): TCasbinPolicyConnector;
647
647
 
648
- constructor(opts: { scope: string; dataSource: IDataSource });
648
+ constructor(opts: { scope: string; dataSource: ICasbinPolicySource });
649
649
 
650
650
  // Subclasses implement ONLY this:
651
651
  abstract loadFilteredPolicy(model: Model, filter: TFilter): Promise<void>;
@@ -680,9 +680,29 @@ interface ICasbinPolicyFilter {
680
680
  }
681
681
  ```
682
682
 
683
+ ### ICasbinPolicySource
684
+
685
+ The minimal contract `BaseFilteredAdapter` depends on for its `dataSource` -- **not** the framework's
686
+ general `IDataSource` interface. Any drizzle-backed datasource (e.g. `BasePostgresDataSource`) satisfies
687
+ it structurally; the adapter only ever needs the connector to run policy queries.
688
+
689
+ ```typescript
690
+ interface ICasbinPolicySource {
691
+ connector: TCasbinPolicyConnector;
692
+ }
693
+
694
+ type TCasbinPolicyConnector = ReturnType<
695
+ typeof drizzle<Record<string, AnyType>, NodePgClient>
696
+ >;
697
+ ```
698
+
699
+ > [!NOTE]
700
+ > Components that only need query execution depend on this minimal local contract rather than a
701
+ > connector class -- keeps the casbin adapters decoupled from the full datasource surface.
702
+
683
703
  ### loadLines()
684
704
 
685
- The base's only orchestration helper subclasses call it from `loadFilteredPolicy` after assembling
705
+ The base's only orchestration helper - subclasses call it from `loadFilteredPolicy` after assembling
686
706
  their casbin lines:
687
707
 
688
708
  ```typescript
@@ -694,14 +714,14 @@ protected async loadLines(opts: { model: Model; lines: string[] }): Promise<void
694
714
  }
695
715
  ```
696
716
 
697
- There are no template-method query hooks or shared line formatters on the base a subclass owns its
717
+ There are no template-method query hooks or shared line formatters on the base - a subclass owns its
698
718
  own queries and line construction (see `ScopedCasbinAdapter` below for the reference implementation).
699
719
 
700
720
  ## ScopedCasbinAdapter
701
721
 
702
722
  The generic, read-only `FilteredAdapter` for the scoped RBAC model. It reads **one principal's edges**
703
723
  plus the **shared structural hierarchy** from a single `PolicyDefinition` edge table (joined to
704
- `Permission` for codes) and emits casbin lines. No subclassing configure it with `IScopedCasbinEntities`.
724
+ `Permission` for codes) and emits casbin lines. No subclassing - configure it with `IScopedCasbinEntities`.
705
725
 
706
726
  ### Class
707
727
 
@@ -709,7 +729,7 @@ plus the **shared structural hierarchy** from a single `PolicyDefinition` edge t
709
729
  class ScopedCasbinAdapter extends BaseFilteredAdapter<IScopedCasbinPolicyFilter> {
710
730
  protected readonly entities: IScopedCasbinEntities;
711
731
 
712
- constructor(opts: { dataSource: IDataSource; entities: IScopedCasbinEntities });
732
+ constructor(opts: { dataSource: ICasbinPolicySource; entities: IScopedCasbinEntities });
713
733
 
714
734
  async loadFilteredPolicy(model: Model, filter: IScopedCasbinPolicyFilter): Promise<void>;
715
735
 
@@ -750,7 +770,7 @@ interface IScopedCasbinPolicyFilter {
750
770
  }
751
771
  ```
752
772
 
753
- ### loadFilteredPolicy() two waves
773
+ ### loadFilteredPolicy() - two waves
754
774
 
755
775
  ```mermaid
756
776
  flowchart TD
@@ -761,8 +781,8 @@ flowchart TD
761
781
  W2 --> Load["loadLines(model, all lines)"]
762
782
  ```
763
783
 
764
- 1. **Wave 1 (parallel):** the principal's own edges role assignments (`g`), domain memberships
765
- (`g2`), direct grants (`p`) plus the shared structural trees (`role_inherits` → `g`,
784
+ 1. **Wave 1 (parallel):** the principal's own edges - role assignments (`g`), domain memberships
785
+ (`g2`), direct grants (`p`) - plus the shared structural trees (`role_inherits` → `g`,
766
786
  `domain_inherits` → `g3`, `resource_inherits` → `g4`, `action_inherits` → `g5`).
767
787
  2. **Role closure:** `expandRoleClosure` does a cycle-safe BFS over the `role_inherits` (`g`) edges to
768
788
  collect the assigned roles + all transitive parents.
@@ -898,7 +918,7 @@ Supports these formats:
898
918
  // String array
899
919
  roles: ['admin', 'user']
900
920
 
901
- // Object array with identifier (preferred matches AuthorizationRole.identifier)
921
+ // Object array with identifier (preferred - matches AuthorizationRole.identifier)
902
922
  roles: [{ id: 1, identifier: '900_admin', priority: 900 }]
903
923
 
904
924
  // Object array with name fallback
@@ -999,7 +1019,7 @@ buildRouteMiddlewares<RouteConfig extends IAuthRouteConfig>(opts: { configs: Rou
999
1019
  mws.push(authenticateFn({ strategies, mode }));
1000
1020
  }
1001
1021
 
1002
- // 2. Authorize middleware (second) supports single or array
1022
+ // 2. Authorize middleware (second) - supports single or array
1003
1023
  if (authorize) {
1004
1024
  const specs = Array.isArray(authorize) ? authorize : [authorize];
1005
1025
  for (const spec of specs) {
@@ -1026,7 +1046,7 @@ buildRpcMiddlewares(opts: { configs: IRpcMetadata }): TRpcMiddleware[] {
1026
1046
  // 1. Authenticate middleware (first)
1027
1047
  if (configs.authenticate) { ... }
1028
1048
 
1029
- // 2. Authorize middleware (second) same pattern as REST
1049
+ // 2. Authorize middleware (second) - same pattern as REST
1030
1050
  if (configs.authorize) {
1031
1051
  const specs = Array.isArray(configs.authorize) ? configs.authorize : [configs.authorize];
1032
1052
  for (const spec of specs) {
@@ -76,13 +76,13 @@ All error messages from the authorization module, organized by source:
76
76
  | `[CasbinAuthorizationEnforcer] request.action and request.resource are required.` | 500 | `evaluate` |
77
77
  | `[CasbinAuthorizationEnforcer] keyFn returned an empty cache key.` | 400 | `buildRules`/cache management (via `resolveCacheKey`) |
78
78
  | <code v-pre>[CasbinAuthorizationEnforcer] cached.options.expiresIn must be >= 10000 (ms) &#124; Received: {{value}}</code> | 500 | `configure` (via `validateExpiresIn`) |
79
- | <code v-pre>[CasbinAuthorizationEnforcer] Matcher smoke test failed at warmup ...</code> | 500 | `configure` (via `assertMatcherCompilesSync`) |
79
+ | <code v-pre>[CasbinAuthorizationEnforcer] Matcher smoke test failed at warmup - ...</code> | 500 | `configure` (via `assertMatcherCompilesSync`) |
80
80
  | <code v-pre>[resolveDomainMatchingFn] Unsupported func: {{name}} &#124; Valids: [...]</code> | 500 | `configure` (via `registerMatchers`) |
81
81
  | <code v-pre>[registerMatchers] Role definition "{{name}}" is not declared in the Casbin model. ...</code> | 500 | `configure` (via `registerMatchers`, only when `domainMatching` is set) |
82
82
  | <code v-pre>[resolveModel] Invalid model.driver &#124; Valids: [file, text]</code> | 500 | `configure` (via `resolveModel`) |
83
83
  | `[CasbinAuthorizationEnforcer] Cache management requires the redis cache driver, but caching is disabled.` | 500 | `invalidateUserCache`/`rebuildUserCache` (via `requireRedisCache`) |
84
84
 
85
- > The `Invalid cached.driver` errors were removed `cached` is now a typed union
85
+ > The `Invalid cached.driver` errors were removed - `cached` is now a typed union
86
86
  > (`{ use: false } | { use: true, driver: 'redis', ... }`), so an invalid driver is a compile-time
87
87
  > error, not a runtime one.
88
88
 
@@ -93,9 +93,9 @@ All error messages from the authorization module, organized by source:
93
93
  | `[CasbinAuthorizationEnforcer] keyFn returned an empty cache key.` | 400 | `resolveCacheKey` (read + cache-management paths) |
94
94
  | `[extractUserLines] Adapter does not support loadFilteredPolicy.` | 500 | `extractUserLines` |
95
95
  | `[loadPolicyLinesIntoModel] Not configured. Call configure() first.` | 500 | `loadPolicyLinesIntoModel` |
96
- | `[CasbinAuthorizationEnforcer] Cached payload is not an array of policy lines.` | (logged, not thrown) | `parseCachedPolicyLines` corrupt entry is discarded + refetched |
96
+ | `[CasbinAuthorizationEnforcer] Cached payload is not an array of policy lines.` | - (logged, not thrown) | `parseCachedPolicyLines` - corrupt entry is discarded + refetched |
97
97
 
98
- > A corrupted Redis cache entry does **not** raise an error it is logged and discarded, and the
98
+ > A corrupted Redis cache entry does **not** raise an error - it is logged and discarded, and the
99
99
  > lines are refetched from the adapter (the request never 500s on cache corruption).
100
100
 
101
101
  ### Registry Errors (AuthorizationEnforcerRegistry)
@@ -360,7 +360,7 @@ keyFn: ({ user }) => `authz:policies:${user.principalType}:${user.userId}`,
360
360
 
361
361
  **Fix:** Use `CasbinEnforcerModelDrivers.FILE` (`'file'`) or `CasbinEnforcerModelDrivers.TEXT` (`'text'`).
362
362
 
363
- > There is no longer an `Invalid cached.driver` runtime error `cached` is a typed union, so an
363
+ > There is no longer an `Invalid cached.driver` runtime error - `cached` is a typed union, so an
364
364
  > unsupported cache driver is caught at compile time. Caching is **Redis-only**.
365
365
 
366
366
  ## Common Patterns
@@ -389,7 +389,7 @@ Check that rules are being cached correctly. The middleware caches on `Authoriza
389
389
 
390
390
  ### Redis Cache Not Working
391
391
 
392
- 1. **Check Redis connection** -- verify `DefaultRedisHelper` is properly connected
392
+ 1. **Check Redis connection** -- verify the `AbstractRedisHelper` subclass (`RedisSingleHelper`, `RedisClusterHelper`, or `RedisSentinelHelper`) is properly connected
393
393
  2. **Check keyFn** -- ensure it returns a unique, non-empty key per user
394
394
  3. **Check expiresIn** -- must be >= 10,000 ms (`MIN_EXPIRES_IN`)
395
395
  4. **Verify cache hit** -- check logs for `"Loaded CACHED Policies"` vs `"Loaded ADAPTER + CACHED Policies"`
@@ -21,7 +21,7 @@
21
21
  | **authorize** | Standalone function wrapping `AuthorizationProvider.value()` |
22
22
  | **AuthorizationRole** | Value object for role identity with priority-based comparison |
23
23
  | **BaseFilteredAdapter** | Thin abstract casbin `FilteredAdapter` (datasource plumbing + `loadLines`); subclasses implement only `loadFilteredPolicy` |
24
- | **ScopedCasbinAdapter** | Generic read-only `FilteredAdapter` for the scoped RBAC model reads one principal's edges + the shared hierarchy from a single `PolicyDefinition` table |
24
+ | **ScopedCasbinAdapter** | Generic read-only `FilteredAdapter` for the scoped RBAC model - reads one principal's edges + the shared hierarchy from a single `PolicyDefinition` table |
25
25
  | **AbstractAuthRegistry** | Shared base class for authentication strategy registry and authorization enforcer registry |
26
26
 
27
27
  ### Authorization Flow (7 Steps)
@@ -147,7 +147,7 @@ Built-in Casbin matching functions selectable for `ICasbinEnforcerOptions.domain
147
147
  `CasbinDomainMatchingFunctions.SCHEME_SET` contains all valid values. `CasbinDomainMatchingFunctions.isValid(input)` checks membership. Companion type: `TCasbinDomainMatchingFunction`.
148
148
 
149
149
  > [!IMPORTANT]
150
- > The function is applied as `fn(requestDomain, policyDomain)` the wildcard must live on the **stored/policy** side. With `keyMatch`: `keyMatch("Merchant_X", "*") === true`, `keyMatch("Merchant_X", "Merchant_X") === true`, `keyMatch("Merchant_X", "Merchant_Y") === false`. Store only `*` or exact domain values (never partial patterns like `Merchant_*`) to keep tenant isolation guaranteed.
150
+ > The function is applied as `fn(requestDomain, policyDomain)` - the wildcard must live on the **stored/policy** side. With `keyMatch`: `keyMatch("Merchant_X", "*") === true`, `keyMatch("Merchant_X", "Merchant_X") === true`, `keyMatch("Merchant_X", "Merchant_Y") === false`. Store only `*` or exact domain values (never partial patterns like `Merchant_*`) to keep tenant isolation guaranteed.
151
151
 
152
152
  ### Casbin Rule Variants
153
153
 
@@ -264,6 +264,7 @@ import type {
264
264
 
265
265
  // Adapter types
266
266
  ICasbinPolicyFilter,
267
+ ICasbinPolicySource,
267
268
  IScopedCasbinEntities,
268
269
  IScopedCasbinPolicyFilter,
269
270
 
@@ -345,7 +346,7 @@ import {
345
346
  CASBIN_RBAC_DOMAIN_SCOPED_MODEL,
346
347
  } from '@venizia/ignis';
347
348
 
348
- // The generic scoped adapter reads one principal's edges + the shared hierarchy from a single
349
+ // The generic scoped adapter - reads one principal's edges + the shared hierarchy from a single
349
350
  // PolicyDefinition edge table. No subclassing; configure it with IScopedCasbinEntities.
350
351
  const adapter = new ScopedCasbinAdapter({
351
352
  dataSource,
@@ -381,7 +382,7 @@ AuthorizationEnforcerRegistry.getInstance().register({
381
382
  },
382
383
  },
383
384
  // poolSize / poolAcquireTimeoutMs are optional (defaults 16 / 5000ms).
384
- // In scoped mode you do NOT pass domainMatching or normalizePayloadFn the request domain
385
+ // In scoped mode you do NOT pass domainMatching or normalizePayloadFn - the request domain
385
386
  // is supplied by the provider's domain resolver (see "Domain scoping" in usage.md).
386
387
  },
387
388
  }],
@@ -493,7 +494,7 @@ Casbin-specific options, provided per-enforcer via `AuthorizationEnforcerRegistr
493
494
  | `poolSize` | `number` | `16` | Number of pooled enforcers (each request enforces on its own) |
494
495
  | `poolAcquireTimeoutMs` | `number` | `5000` | Max ms to wait for a free pooled enforcer before failing closed |
495
496
  | `normalizePayloadFn` | `(opts) => { subject, resource, action, domain? }` | -- | (Non-scoped/custom) normalize subject/resource/action before evaluation |
496
- | `domainMatching` | `{ roleDefinition: string; fn: TCasbinDomainMatchingFunction }` | -- | (Non-scoped) opt-in domain matching function on a role definition. **Not needed when `isScoped: true`** the scoped model registers its matchers automatically |
497
+ | `domainMatching` | `{ roleDefinition: string; fn: TCasbinDomainMatchingFunction }` | -- | (Non-scoped) opt-in domain matching function on a role definition. **Not needed when `isScoped: true`** - the scoped model registers its matchers automatically |
497
498
 
498
499
  ```typescript
499
500
  interface ICasbinEnforcerOptions<
@@ -532,7 +533,7 @@ interface ICasbinEnforcerOptions<
532
533
  };
533
534
 
534
535
  // Non-scoped only. Registers a Casbin domain matching function on the named role definition.
535
- // When isScoped is true, the scoped model registers its own matchers do not set this.
536
+ // When isScoped is true, the scoped model registers its own matchers - do not set this.
536
537
  domainMatching?: {
537
538
  roleDefinition: string; // e.g. 'g'
538
539
  fn: TCasbinDomainMatchingFunction;
@@ -548,14 +549,14 @@ interface ICasbinEnforcerOptions<
548
549
  The `cached` field is a discriminated union. **Caching is Redis-only** (the in-memory driver was removed):
549
550
 
550
551
  ```typescript
551
- // No caching every request rebuilds the user's policy from the datasource.
552
+ // No caching - every request rebuilds the user's policy from the datasource.
552
553
  interface { use: false }
553
554
 
554
555
  // Redis cache (store/retrieve the user's policy lines from Redis, TTL via PX).
555
556
  interface ICasbinEnforcerCachedRedis {
556
557
  driver: 'redis';
557
558
  options: {
558
- connection: DefaultRedisHelper;
559
+ connection: IRedisHelper;
559
560
  expiresIn: number;
560
561
  keyFn: (opts: { user: IAuthorizationUser }) => ValueOrPromise<string>;
561
562
  };
@@ -743,7 +744,7 @@ buildRouteMiddlewares<RouteConfig extends IAuthRouteConfig>(opts: { configs: Rou
743
744
  mws.push(authenticateFn({ strategies, mode }));
744
745
  }
745
746
 
746
- // 2. Authorize middleware (second) supports single or array
747
+ // 2. Authorize middleware (second) - supports single or array
747
748
  if (authorize) {
748
749
  const specs = Array.isArray(authorize) ? authorize : [authorize];
749
750
  for (const spec of specs) {
@@ -771,7 +772,7 @@ buildRpcMiddlewares(opts: { configs: IRpcMetadata }): TRpcMiddleware[] {
771
772
  // 1. Authenticate middleware
772
773
  if (configs.authenticate) { ... }
773
774
 
774
- // 2. Authorize middleware same pattern as REST
775
+ // 2. Authorize middleware - same pattern as REST
775
776
  if (configs.authorize) {
776
777
  const specs = Array.isArray(configs.authorize) ? configs.authorize : [configs.authorize];
777
778
  for (const spec of specs) {