@stratal/framework 0.0.0-canary-1658945 → 0.0.0-canary-13b0e8d

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 (38) hide show
  1. package/dist/access-control/index.d.mts +3 -3
  2. package/dist/access-control/index.mjs +2 -2
  3. package/dist/{access.service-Cb99esfz.mjs → access.service-DWRsDLJq.mjs} +5 -11
  4. package/dist/access.service-DWRsDLJq.mjs.map +1 -0
  5. package/dist/auth/index.d.mts +22 -20
  6. package/dist/auth/index.d.mts.map +1 -1
  7. package/dist/auth/index.mjs +27 -32
  8. package/dist/auth/index.mjs.map +1 -1
  9. package/dist/{auth-context-MWdQipaK.mjs → auth-context-CObJcW0t.mjs} +5 -5
  10. package/dist/auth-context-CObJcW0t.mjs.map +1 -0
  11. package/dist/{auth-context-DXSTlnQH.d.mts → auth-context-D70ktWUf.d.mts} +1 -1
  12. package/dist/{auth-context-DXSTlnQH.d.mts.map → auth-context-D70ktWUf.d.mts.map} +1 -1
  13. package/dist/context/index.d.mts +1 -1
  14. package/dist/context/index.mjs +2 -2
  15. package/dist/database/index.d.mts +2 -2
  16. package/dist/database/index.mjs +39 -10
  17. package/dist/database/index.mjs.map +1 -1
  18. package/dist/{errors-opqKWAoa.mjs → errors-MCyrn_V2.mjs} +1 -1
  19. package/dist/{errors-opqKWAoa.mjs.map → errors-MCyrn_V2.mjs.map} +1 -1
  20. package/dist/factory/index.d.mts +1 -1
  21. package/dist/guards/index.d.mts.map +1 -1
  22. package/dist/guards/index.mjs +7 -14
  23. package/dist/guards/index.mjs.map +1 -1
  24. package/dist/{index-Cnx6eRgJ.d.mts → index-NV8eVa_e.d.mts} +3 -3
  25. package/dist/index-NV8eVa_e.d.mts.map +1 -0
  26. package/dist/index.d.mts +1 -1
  27. package/dist/{insufficient-permissions.error-CnzLRFdB.mjs → insufficient-permissions.error-GwrkWnEM.mjs} +1 -1
  28. package/dist/{insufficient-permissions.error-CnzLRFdB.mjs.map → insufficient-permissions.error-GwrkWnEM.mjs.map} +1 -1
  29. package/dist/{types-BZlcRR2M.d.mts → types-4uX3XKRM.d.mts} +1 -1
  30. package/dist/{types-BZlcRR2M.d.mts.map → types-4uX3XKRM.d.mts.map} +1 -1
  31. package/dist/{types-BLyu9dAd.d.mts → types-tu9pTehB.d.mts} +1 -1
  32. package/dist/{types-BLyu9dAd.d.mts.map → types-tu9pTehB.d.mts.map} +1 -1
  33. package/package.json +9 -11
  34. package/dist/access.service-Cb99esfz.mjs.map +0 -1
  35. package/dist/auth-context-MWdQipaK.mjs.map +0 -1
  36. package/dist/decorateMetadata-D5WUsc6Y.mjs +0 -6
  37. package/dist/index-Cnx6eRgJ.d.mts.map +0 -1
  38. /package/dist/{decorate-DViXs-0l.mjs → decorate-7CAoTBu4.mjs} +0 -0
@@ -1,6 +1,6 @@
1
- import { n as RolePermissions, t as AccessControlOptions } from "../types-BLyu9dAd.mjs";
2
- import { O as DatabaseService } from "../index-Cnx6eRgJ.mjs";
3
- import { t as AuthContext } from "../auth-context-DXSTlnQH.mjs";
1
+ import { n as RolePermissions, t as AccessControlOptions } from "../types-tu9pTehB.mjs";
2
+ import { O as DatabaseService } from "../index-NV8eVa_e.mjs";
3
+ import { t as AuthContext } from "../auth-context-D70ktWUf.mjs";
4
4
  import { HttpException } from "stratal/errors";
5
5
  import { AccessControl, Role, Statements } from "better-auth/plugins/access";
6
6
  import { BetterAuthPlugin } from "better-auth";
@@ -1,6 +1,6 @@
1
- import { n as createStratalAcPlugin, t as AccessService } from "../access.service-Cb99esfz.mjs";
1
+ import { n as createStratalAcPlugin, t as AccessService } from "../access.service-DWRsDLJq.mjs";
2
2
  import { n as AC_TOKENS } from "../decorateParam-C_dJ_dIO.mjs";
3
- import { t as InsufficientPermissionsError } from "../insufficient-permissions.error-CnzLRFdB.mjs";
3
+ import { t as InsufficientPermissionsError } from "../insufficient-permissions.error-GwrkWnEM.mjs";
4
4
  import { createAccessControl as createAccessControl$1 } from "better-auth/plugins/access";
5
5
  //#region src/access-control/create-access-control.ts
6
6
  /**
@@ -1,7 +1,6 @@
1
1
  import { n as AC_TOKENS, t as __decorateParam } from "./decorateParam-C_dJ_dIO.mjs";
2
- import { t as __decorateMetadata } from "./decorateMetadata-D5WUsc6Y.mjs";
3
- import { t as __decorate } from "./decorate-DViXs-0l.mjs";
4
- import { DI_TOKENS, Transient, inject } from "stratal/di";
2
+ import { t as __decorate } from "./decorate-7CAoTBu4.mjs";
3
+ import { DI_TOKENS, Request, inject } from "stratal/di";
5
4
  //#region src/access-control/plugin.ts
6
5
  /**
7
6
  * Creates the Stratal access control Better Auth plugin.
@@ -132,17 +131,12 @@ let AccessService = class AccessService {
132
131
  }
133
132
  };
134
133
  AccessService = __decorate([
135
- Transient(AC_TOKENS.AccessService),
134
+ Request(AC_TOKENS.AccessService),
136
135
  __decorateParam(0, inject(DI_TOKENS.AuthContext)),
137
136
  __decorateParam(1, inject(DI_TOKENS.Database)),
138
- __decorateParam(2, inject(AC_TOKENS.Options)),
139
- __decorateMetadata("design:paramtypes", [
140
- Object,
141
- Object,
142
- Object
143
- ])
137
+ __decorateParam(2, inject(AC_TOKENS.Options))
144
138
  ], AccessService);
145
139
  //#endregion
146
140
  export { createStratalAcPlugin as n, AccessService as t };
147
141
 
148
- //# sourceMappingURL=access.service-Cb99esfz.mjs.map
142
+ //# sourceMappingURL=access.service-DWRsDLJq.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"access.service-DWRsDLJq.mjs","names":[],"sources":["../src/access-control/plugin.ts","../src/access-control/services/access.service.ts"],"sourcesContent":["import type { BetterAuthPlugin } from 'better-auth'\nimport type { AccessControlOptions } from './types'\n\n/**\n * Creates the Stratal access control Better Auth plugin.\n *\n * Ensures the `user.role` schema field exists.\n * No endpoints are added — all permission logic lives in AccessService.\n *\n * Auto-added to Better Auth options when `accessControl` is provided to\n * `AuthModule.forRootAsync()`. Users never call this directly.\n */\nexport function createStratalAcPlugin(_options: AccessControlOptions): BetterAuthPlugin {\n return {\n id: 'stratal-ac',\n schema: {\n user: {\n fields: {\n role: {\n type: 'string',\n required: false,\n input: false,\n defaultValue: 'user',\n },\n },\n },\n },\n }\n}\n","import type { DatabaseService } from '@stratal/framework/database'\nimport { DI_TOKENS, inject, Request } from 'stratal/di'\nimport type { AuthContext } from '../../context/auth-context'\nimport { AC_TOKENS } from '../tokens'\nimport type { AccessControlOptions } from '../types'\n\nfunction parseRoles(role: string | null | undefined): string[] {\n if (!role) return []\n return role.split(',').map(r => r.trim()).filter(Boolean)\n}\n\n/**\n * AccessService\n *\n * Request-scoped service for role and permission management.\n *\n * Roles for the current user are read from AuthContext (populated by\n * SessionVerificationMiddleware — no DB hit). Other users are resolved\n * from the database.\n *\n * Permission checks use Better Auth's `role.authorize()` locally with\n * OR logic — access is granted if any of the user's roles allows it.\n *\n * @example\n * ```typescript\n * // Check current user\n * await accessService.currentUserHasPermission({ posts: ['update'] })\n *\n * // Check arbitrary user (e.g. from an admin action)\n * await accessService.hasPermission(userId, { admin: ['access'] })\n *\n * // Assign a role\n * await accessService.setUserRole(userId, 'admin')\n *\n * // Assign multiple roles\n * await accessService.setUserRole(userId, ['editor', 'reviewer'])\n * ```\n */\n@Request(AC_TOKENS.AccessService)\nexport class AccessService {\n constructor(\n @inject(DI_TOKENS.AuthContext)\n private readonly authContext: AuthContext,\n @inject(DI_TOKENS.Database)\n private readonly db: DatabaseService,\n @inject(AC_TOKENS.Options)\n private readonly options: AccessControlOptions\n ) { }\n\n /**\n * Get all roles for a user.\n *\n * Uses AuthContext for the current user (no DB hit).\n * Falls back to DB for other users.\n */\n async getUserRoles(userId: string): Promise<string[]> {\n if (userId === this.authContext.getUserId()) {\n const roles = this.authContext.getRoles()\n if (roles.length > 0) return roles\n }\n const user = await (this.db).user.findUnique({\n where: { id: userId },\n select: { role: true },\n })\n return parseRoles(user?.role)\n }\n\n /**\n * Assign one or more roles to a user.\n *\n * Multiple roles are stored as a comma-separated string in `user.role`.\n */\n async setUserRole(userId: string, role: string | string[]): Promise<void> {\n const roleStr = Array.isArray(role) ? role.join(',') : role\n await this.db.user.update({\n where: { id: userId },\n data: { role: roleStr },\n })\n }\n\n /**\n * Check if a user has the required permissions.\n *\n * Returns true if any of the user's roles grants all of the requested permissions.\n */\n async hasPermission(userId: string, permissions: Record<string, string[]>): Promise<boolean> {\n const roles = await this.getUserRoles(userId)\n return this.checkPermissions(roles, permissions)\n }\n\n /**\n * Get the merged permission set for a user across all their roles.\n * Useful for sending to the frontend.\n */\n async getPermissionsForUser(userId: string): Promise<Record<string, string[]>> {\n const roles = await this.getUserRoles(userId)\n return this.mergePermissions(roles)\n }\n\n /**\n * Get all roles for the currently authenticated user.\n * Reads from AuthContext — no DB hit.\n */\n getCurrentUserRoles(): string[] {\n return this.authContext.getRoles()\n }\n\n /**\n * Check if the currently authenticated user has the required permissions.\n * Reads roles from AuthContext — no DB hit.\n */\n currentUserHasPermission(permissions: Record<string, string[]>): boolean {\n const roles = this.authContext.getRoles()\n if (roles.length === 0) return false\n return this.checkPermissions(roles, permissions)\n }\n\n /**\n * Get merged permissions for the currently authenticated user.\n * Reads roles from AuthContext — no DB hit.\n */\n getCurrentUserPermissions(): Record<string, string[]> {\n const roles = this.authContext.getRoles()\n return this.mergePermissions(roles)\n }\n\n private checkPermissions(roles: string[], permissions: Record<string, string[]>): boolean {\n return roles.some(roleName => {\n const roleObj = this.options.roles[roleName]\n if (!roleObj) return false\n\n const specific: Record<string, string[]> = {}\n\n for (const [resource, actions] of Object.entries(permissions)) {\n if (actions.includes('*')) {\n // Wildcard: role must have at least one action defined for this resource\n const roleActions = (roleObj.statements as Record<string, readonly string[]>)[resource]\n if (!roleActions?.length) return false\n } else {\n specific[resource] = actions\n }\n }\n\n return Object.keys(specific).length === 0 || roleObj.authorize(specific).success\n })\n }\n\n private mergePermissions(roles: string[]): Record<string, string[]> {\n const result: Record<string, string[]> = {}\n for (const roleName of roles) {\n const roleObj = this.options.roles[roleName]\n if (!roleObj) continue\n for (const [resource, actions] of Object.entries(roleObj.statements)) {\n result[resource] ??= []\n for (const action of actions as string[]) {\n if (!result[resource].includes(action)) {\n result[resource].push(action)\n }\n }\n }\n }\n return result\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAYA,SAAgB,sBAAsB,UAAkD;CACtF,OAAO;EACL,IAAI;EACJ,QAAQ,EACN,MAAM,EACJ,QAAQ,EACN,MAAM;GACJ,MAAM;GACN,UAAU;GACV,OAAO;GACP,cAAc;GACf,EACF,EACF,EACF;EACF;;;;ACrBH,SAAS,WAAW,MAA2C;CAC7D,IAAI,CAAC,MAAM,OAAO,EAAE;CACpB,OAAO,KAAK,MAAM,IAAI,CAAC,KAAI,MAAK,EAAE,MAAM,CAAC,CAAC,OAAO,QAAQ;;AA+BpD,IAAA,gBAAA,MAAM,cAAc;CAGN;CAEA;CAEA;CANnB,YACE,aAEA,IAEA,SAEA;EALiB,KAAA,cAAA;EAEA,KAAA,KAAA;EAEA,KAAA,UAAA;;;;;;;;CASnB,MAAM,aAAa,QAAmC;EACpD,IAAI,WAAW,KAAK,YAAY,WAAW,EAAE;GAC3C,MAAM,QAAQ,KAAK,YAAY,UAAU;GACzC,IAAI,MAAM,SAAS,GAAG,OAAO;;EAM/B,OAAO,YAAW,MAJE,KAAK,GAAI,KAAK,WAAW;GAC3C,OAAO,EAAE,IAAI,QAAQ;GACrB,QAAQ,EAAE,MAAM,MAAM;GACvB,CAAC,GACsB,KAAK;;;;;;;CAQ/B,MAAM,YAAY,QAAgB,MAAwC;EACxE,MAAM,UAAU,MAAM,QAAQ,KAAK,GAAG,KAAK,KAAK,IAAI,GAAG;EACvD,MAAM,KAAK,GAAG,KAAK,OAAO;GACxB,OAAO,EAAE,IAAI,QAAQ;GACrB,MAAM,EAAE,MAAM,SAAS;GACxB,CAAC;;;;;;;CAQJ,MAAM,cAAc,QAAgB,aAAyD;EAC3F,MAAM,QAAQ,MAAM,KAAK,aAAa,OAAO;EAC7C,OAAO,KAAK,iBAAiB,OAAO,YAAY;;;;;;CAOlD,MAAM,sBAAsB,QAAmD;EAC7E,MAAM,QAAQ,MAAM,KAAK,aAAa,OAAO;EAC7C,OAAO,KAAK,iBAAiB,MAAM;;;;;;CAOrC,sBAAgC;EAC9B,OAAO,KAAK,YAAY,UAAU;;;;;;CAOpC,yBAAyB,aAAgD;EACvE,MAAM,QAAQ,KAAK,YAAY,UAAU;EACzC,IAAI,MAAM,WAAW,GAAG,OAAO;EAC/B,OAAO,KAAK,iBAAiB,OAAO,YAAY;;;;;;CAOlD,4BAAsD;EACpD,MAAM,QAAQ,KAAK,YAAY,UAAU;EACzC,OAAO,KAAK,iBAAiB,MAAM;;CAGrC,iBAAyB,OAAiB,aAAgD;EACxF,OAAO,MAAM,MAAK,aAAY;GAC5B,MAAM,UAAU,KAAK,QAAQ,MAAM;GACnC,IAAI,CAAC,SAAS,OAAO;GAErB,MAAM,WAAqC,EAAE;GAE7C,KAAK,MAAM,CAAC,UAAU,YAAY,OAAO,QAAQ,YAAY,EAC3D,IAAI,QAAQ,SAAS,IAAI;QAGnB,CADiB,QAAQ,WAAiD,WAC5D,QAAQ,OAAO;UAEjC,SAAS,YAAY;GAIzB,OAAO,OAAO,KAAK,SAAS,CAAC,WAAW,KAAK,QAAQ,UAAU,SAAS,CAAC;IACzE;;CAGJ,iBAAyB,OAA2C;EAClE,MAAM,SAAmC,EAAE;EAC3C,KAAK,MAAM,YAAY,OAAO;GAC5B,MAAM,UAAU,KAAK,QAAQ,MAAM;GACnC,IAAI,CAAC,SAAS;GACd,KAAK,MAAM,CAAC,UAAU,YAAY,OAAO,QAAQ,QAAQ,WAAW,EAAE;IACpE,OAAO,cAAc,EAAE;IACvB,KAAK,MAAM,UAAU,SACnB,IAAI,CAAC,OAAO,UAAU,SAAS,OAAO,EACpC,OAAO,UAAU,KAAK,OAAO;;;EAKrC,OAAO;;;;CA3HV,QAAQ,UAAU,cAAc;oBAG5B,OAAO,UAAU,YAAY,CAAA;oBAE7B,OAAO,UAAU,SAAS,CAAA;oBAE1B,OAAO,UAAU,QAAQ,CAAA"}
@@ -1,11 +1,25 @@
1
- import { t as AccessControlOptions } from "../types-BLyu9dAd.mjs";
1
+ import { t as AccessControlOptions } from "../types-tu9pTehB.mjs";
2
+ import { r as AuthUser } from "../auth-context-D70ktWUf.mjs";
2
3
  import { AsyncModuleOptions, DynamicModule } from "stratal/module";
3
4
  import { ApplicationError, HttpException } from "stratal/errors";
5
+ import { Middleware, Next, RouteConfigurable, Router, RouterContext } from "stratal/router";
4
6
  import { LoggerService } from "stratal/logger";
5
7
  import { APIError } from "better-auth/api";
6
8
  import { Auth, BetterAuthOptions } from "better-auth";
7
- import { Middleware, Next, RouteConfigurable, Router, RouterContext } from "stratal/router";
8
9
 
10
+ //#region src/context/router-context.augment.d.ts
11
+ declare module 'stratal/router' {
12
+ interface RouterContext {
13
+ /**
14
+ * The authenticated user for the current request.
15
+ *
16
+ * Throws `UserNotAuthenticatedError` if the request is unauthenticated.
17
+ * Provided by `@stratal/framework`'s `AuthModule` via {@link AuthContext}.
18
+ */
19
+ user(): AuthUser;
20
+ }
21
+ } //# sourceMappingURL=router-context.augment.d.ts.map
22
+ //#endregion
9
23
  //#region src/auth/auth.module.d.ts
10
24
  interface AuthModuleAsyncOptions<TOptions extends BetterAuthOptions = BetterAuthOptions> extends AsyncModuleOptions<TOptions> {
11
25
  /**
@@ -18,9 +32,8 @@ declare class AuthModule implements RouteConfigurable {
18
32
  /**
19
33
  * Configure auth middleware globally.
20
34
  *
21
- * Registers middlewares in order:
22
- * 1. AuthContextMiddleware - Creates and registers AuthContext in request container
23
- * 2. SessionVerificationMiddleware - Verifies session and populates AuthContext with userId + role
35
+ * SessionVerificationMiddleware verifies the session and populates the
36
+ * request-scoped AuthContext with the authenticated user.
24
37
  */
25
38
  configureRoutes(router: Router): void;
26
39
  /**
@@ -167,18 +180,6 @@ declare class TokenRequiredError extends HttpException {
167
180
  constructor();
168
181
  }
169
182
  //#endregion
170
- //#region src/auth/middleware/auth-context.middleware.d.ts
171
- /**
172
- * Auth Context Middleware
173
- *
174
- * Registers AuthContext in the request container at the start of each request.
175
- * This MUST run before SessionVerificationMiddleware and any other middleware
176
- * that depends on AuthContext.
177
- */
178
- declare class AuthContextMiddleware implements Middleware {
179
- handle(ctx: RouterContext, next: Next): Promise<void>;
180
- }
181
- //#endregion
182
183
  //#region src/auth/services/auth.service.d.ts
183
184
  /**
184
185
  * AuthService
@@ -187,11 +188,12 @@ declare class AuthContextMiddleware implements Middleware {
187
188
  * Configured via AuthModule.forRootAsync() from the application layer.
188
189
  *
189
190
  * **Extensibility:**
190
- * Extend this class in application layer to add custom methods.
191
+ * Extend this class to add custom methods. Subclasses inherit
192
+ * `@Request(AUTH_SERVICE)` scope automatically — no decorator needed.
191
193
  *
192
194
  * @example
193
195
  * ```typescript
194
- * @Transient(AUTH_SERVICE)
196
+ * @Request(AUTH_SERVICE)
195
197
  * export class AppAuthService extends AuthService<AuthOptions> {
196
198
  * async signInMagicLink(email: string) {
197
199
  * return wrapBetterAuth(async () => {
@@ -253,5 +255,5 @@ declare function mapBetterAuthError(error: APIError): ApplicationError;
253
255
  */
254
256
  declare function isAPIError(error: unknown): error is APIError;
255
257
  //#endregion
256
- export { AUTH_OPTIONS, AUTH_SERVICE, AccountAlreadyExistsError, AccountNotFoundError, AuthContextMiddleware, AuthModule, AuthModuleAsyncOptions, AuthService, AuthValidationFailedError, CannotUnlinkLastAccountError, CredentialAccountNotFoundError, EmailAlreadyVerifiedError, EmailCannotBeUpdatedError, EmailMismatchError, EmailNotVerifiedError, IdTokenNotSupportedError, InvalidCallbackUrlError, InvalidCredentialsError, InvalidEmailError, InvalidOriginError, InvalidPasswordError, InvalidTokenError, OrganizationConflictError, OrganizationInvitationNotFoundError, OrganizationInvitationRecipientMismatchError, OrganizationLimitReachedError, OrganizationMemberNotFoundError, OrganizationMembershipError, OrganizationNotFoundError, OrganizationPermissionDeniedError, OrganizationRoleNotFoundError, OrganizationTeamNotFoundError, PasswordTooLongError, PasswordTooShortError, ProviderNotFoundError, SessionExpiredError, SessionVerificationMiddleware, SocialAccountLinkedError, TokenExpiredError, TokenRequiredError, UserAlreadyHasPasswordError, UserEmailNotFoundError, UserNotFoundError, getErrorHandlerConfig, isAPIError, mapBetterAuthError, wrapBetterAuth };
258
+ export { AUTH_OPTIONS, AUTH_SERVICE, AccountAlreadyExistsError, AccountNotFoundError, AuthModule, AuthModuleAsyncOptions, AuthService, AuthValidationFailedError, CannotUnlinkLastAccountError, CredentialAccountNotFoundError, EmailAlreadyVerifiedError, EmailCannotBeUpdatedError, EmailMismatchError, EmailNotVerifiedError, IdTokenNotSupportedError, InvalidCallbackUrlError, InvalidCredentialsError, InvalidEmailError, InvalidOriginError, InvalidPasswordError, InvalidTokenError, OrganizationConflictError, OrganizationInvitationNotFoundError, OrganizationInvitationRecipientMismatchError, OrganizationLimitReachedError, OrganizationMemberNotFoundError, OrganizationMembershipError, OrganizationNotFoundError, OrganizationPermissionDeniedError, OrganizationRoleNotFoundError, OrganizationTeamNotFoundError, PasswordTooLongError, PasswordTooShortError, ProviderNotFoundError, SessionExpiredError, SessionVerificationMiddleware, SocialAccountLinkedError, TokenExpiredError, TokenRequiredError, UserAlreadyHasPasswordError, UserEmailNotFoundError, UserNotFoundError, getErrorHandlerConfig, isAPIError, mapBetterAuthError, wrapBetterAuth };
257
259
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/auth/auth.module.ts","../../src/auth/auth.tokens.ts","../../src/auth/errors/auth-errors.ts","../../src/auth/errors/invalid-token.error.ts","../../src/auth/errors/organization-errors.ts","../../src/auth/errors/token-required.error.ts","../../src/auth/middleware/auth-context.middleware.ts","../../src/auth/services/auth.service.ts","../../src/auth/middleware/session-verification.middleware.ts","../../src/auth/utils/auth-helpers.ts","../../src/auth/utils/better-auth-error-handler.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/context/router-context.augment.ts","../../src/auth/auth.module.ts","../../src/auth/auth.tokens.ts","../../src/auth/errors/auth-errors.ts","../../src/auth/errors/invalid-token.error.ts","../../src/auth/errors/organization-errors.ts","../../src/auth/errors/token-required.error.ts","../../src/auth/services/auth.service.ts","../../src/auth/middleware/session-verification.middleware.ts","../../src/auth/utils/auth-helpers.ts","../../src/auth/utils/better-auth-error-handler.ts"],"mappings":"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"}
@@ -1,33 +1,34 @@
1
- import { n as createStratalAcPlugin, t as AccessService } from "../access.service-Cb99esfz.mjs";
1
+ import { n as createStratalAcPlugin, t as AccessService } from "../access.service-DWRsDLJq.mjs";
2
2
  import { n as AC_TOKENS, t as __decorateParam } from "../decorateParam-C_dJ_dIO.mjs";
3
- import { t as __decorateMetadata } from "../decorateMetadata-D5WUsc6Y.mjs";
4
- import { t as __decorate } from "../decorate-DViXs-0l.mjs";
5
- import { t as AuthContext } from "../auth-context-MWdQipaK.mjs";
6
- import { CONTAINER_TOKEN, DI_TOKENS, Transient } from "stratal/di";
3
+ import { t as __decorate } from "../decorate-7CAoTBu4.mjs";
4
+ import { t as AuthContext } from "../auth-context-CObJcW0t.mjs";
5
+ import { CONTAINER_TOKEN, DI_TOKENS, Request, Transient, inject } from "stratal/di";
7
6
  import { Module } from "stratal/module";
8
7
  import { RATE_LIMITER_TOKENS, RateLimiterRegistry } from "stratal/rate-limiter";
9
8
  import { AuthError, HttpException } from "stratal/errors";
9
+ import { RouterContext } from "stratal/router";
10
10
  import { LOGGER_TOKENS } from "stratal/logger";
11
- import { inject as inject$1 } from "tsyringe";
12
11
  import { betterAuth } from "better-auth/minimal";
13
12
  import { APIError } from "better-auth/api";
13
+ //#region src/context/router-context.augment.ts
14
+ /**
15
+ * Augments Stratal's `RouterContext` with a `user()` accessor backed by the
16
+ * request-scoped {@link AuthContext}.
17
+ *
18
+ * Side-effect import: registers the `user` macro on `RouterContext` and the
19
+ * `declare module` augmentation that exposes it at the type level. Imported by
20
+ * {@link AuthModule} so it runs whenever auth is configured.
21
+ */
22
+ RouterContext.macro("user", function() {
23
+ return this.getContainer().resolve(DI_TOKENS.AuthContext).requireUser();
24
+ });
25
+ //#endregion
14
26
  //#region src/auth/auth.tokens.ts
15
27
  /** Token for AuthService - core authentication service */
16
28
  const AUTH_SERVICE = Symbol.for("stratal:auth:service");
17
29
  /** Token for Better Auth options configuration */
18
30
  const AUTH_OPTIONS = Symbol.for("stratal:auth:options");
19
31
  //#endregion
20
- //#region src/auth/middleware/auth-context.middleware.ts
21
- let AuthContextMiddleware = class AuthContextMiddleware {
22
- async handle(ctx, next) {
23
- const requestContainer = ctx.getContainer();
24
- const authContext = new AuthContext();
25
- requestContainer.registerValue(DI_TOKENS.AuthContext, authContext);
26
- return next();
27
- }
28
- };
29
- AuthContextMiddleware = __decorate([Transient()], AuthContextMiddleware);
30
- //#endregion
31
32
  //#region src/auth/middleware/session-verification.middleware.ts
32
33
  let SessionVerificationMiddleware = class SessionVerificationMiddleware {
33
34
  authService;
@@ -48,9 +49,8 @@ let SessionVerificationMiddleware = class SessionVerificationMiddleware {
48
49
  };
49
50
  SessionVerificationMiddleware = __decorate([
50
51
  Transient(),
51
- __decorateParam(0, inject$1(AUTH_SERVICE)),
52
- __decorateParam(1, inject$1(LOGGER_TOKENS.LoggerService)),
53
- __decorateMetadata("design:paramtypes", [Object, Object])
52
+ __decorateParam(0, inject(AUTH_SERVICE)),
53
+ __decorateParam(1, inject(LOGGER_TOKENS.LoggerService))
54
54
  ], SessionVerificationMiddleware);
55
55
  //#endregion
56
56
  //#region src/auth/rate-limit-bridge.ts
@@ -455,11 +455,7 @@ let AuthService = class AuthService {
455
455
  return this._authInstance;
456
456
  }
457
457
  };
458
- AuthService = __decorate([
459
- Transient(AUTH_SERVICE),
460
- __decorateParam(0, inject$1(AUTH_OPTIONS)),
461
- __decorateMetadata("design:paramtypes", [Object])
462
- ], AuthService);
458
+ AuthService = __decorate([Request(AUTH_SERVICE), __decorateParam(0, inject(AUTH_OPTIONS))], AuthService);
463
459
  //#endregion
464
460
  //#region src/auth/auth.module.ts
465
461
  var _AuthModule;
@@ -467,12 +463,11 @@ let AuthModule = _AuthModule = class AuthModule {
467
463
  /**
468
464
  * Configure auth middleware globally.
469
465
  *
470
- * Registers middlewares in order:
471
- * 1. AuthContextMiddleware - Creates and registers AuthContext in request container
472
- * 2. SessionVerificationMiddleware - Verifies session and populates AuthContext with userId + role
466
+ * SessionVerificationMiddleware verifies the session and populates the
467
+ * request-scoped AuthContext with the authenticated user.
473
468
  */
474
469
  configureRoutes(router) {
475
- router.use(AuthContextMiddleware, SessionVerificationMiddleware);
470
+ router.use(SessionVerificationMiddleware);
476
471
  }
477
472
  /**
478
473
  * Configure AuthModule with async options factory.
@@ -496,7 +491,7 @@ let AuthModule = _AuthModule = class AuthModule {
496
491
  ...raw,
497
492
  plugins: [createStratalAcPlugin(accessControl), ...raw.plugins ?? []]
498
493
  };
499
- if (container.getTsyringeContainer().isRegistered(RATE_LIMITER_TOKENS.ModuleMarker, true)) {
494
+ if (container.isRegistered(RATE_LIMITER_TOKENS.ModuleMarker)) {
500
495
  const store = container.resolve(RATE_LIMITER_TOKENS.Store);
501
496
  const registry = container.resolve(RATE_LIMITER_TOKENS.Registry);
502
497
  raw = {
@@ -535,8 +530,8 @@ let AuthModule = _AuthModule = class AuthModule {
535
530
  };
536
531
  }
537
532
  };
538
- AuthModule = _AuthModule = __decorate([Module({ providers: [] })], AuthModule);
533
+ AuthModule = _AuthModule = __decorate([Module({ providers: [AuthContext] })], AuthModule);
539
534
  //#endregion
540
- export { AUTH_OPTIONS, AUTH_SERVICE, AccountAlreadyExistsError, AccountNotFoundError, AuthContextMiddleware, AuthModule, AuthService, AuthValidationFailedError, CannotUnlinkLastAccountError, CredentialAccountNotFoundError, EmailAlreadyVerifiedError, EmailCannotBeUpdatedError, EmailMismatchError, EmailNotVerifiedError, IdTokenNotSupportedError, InvalidCallbackUrlError, InvalidCredentialsError, InvalidEmailError, InvalidOriginError, InvalidPasswordError, InvalidTokenError, OrganizationConflictError, OrganizationInvitationNotFoundError, OrganizationInvitationRecipientMismatchError, OrganizationLimitReachedError, OrganizationMemberNotFoundError, OrganizationMembershipError, OrganizationNotFoundError, OrganizationPermissionDeniedError, OrganizationRoleNotFoundError, OrganizationTeamNotFoundError, PasswordTooLongError, PasswordTooShortError, ProviderNotFoundError, SessionExpiredError, SessionVerificationMiddleware, SocialAccountLinkedError, TokenExpiredError, TokenRequiredError, UserAlreadyHasPasswordError, UserEmailNotFoundError, UserNotFoundError, getErrorHandlerConfig, isAPIError, mapBetterAuthError, wrapBetterAuth };
535
+ export { AUTH_OPTIONS, AUTH_SERVICE, AccountAlreadyExistsError, AccountNotFoundError, AuthModule, AuthService, AuthValidationFailedError, CannotUnlinkLastAccountError, CredentialAccountNotFoundError, EmailAlreadyVerifiedError, EmailCannotBeUpdatedError, EmailMismatchError, EmailNotVerifiedError, IdTokenNotSupportedError, InvalidCallbackUrlError, InvalidCredentialsError, InvalidEmailError, InvalidOriginError, InvalidPasswordError, InvalidTokenError, OrganizationConflictError, OrganizationInvitationNotFoundError, OrganizationInvitationRecipientMismatchError, OrganizationLimitReachedError, OrganizationMemberNotFoundError, OrganizationMembershipError, OrganizationNotFoundError, OrganizationPermissionDeniedError, OrganizationRoleNotFoundError, OrganizationTeamNotFoundError, PasswordTooLongError, PasswordTooShortError, ProviderNotFoundError, SessionExpiredError, SessionVerificationMiddleware, SocialAccountLinkedError, TokenExpiredError, TokenRequiredError, UserAlreadyHasPasswordError, UserEmailNotFoundError, UserNotFoundError, getErrorHandlerConfig, isAPIError, mapBetterAuthError, wrapBetterAuth };
541
536
 
542
537
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["inject","inject"],"sources":["../../src/auth/auth.tokens.ts","../../src/auth/middleware/auth-context.middleware.ts","../../src/auth/middleware/session-verification.middleware.ts","../../src/auth/rate-limit-bridge.ts","../../src/auth/errors/auth-errors.ts","../../src/auth/errors/invalid-token.error.ts","../../src/auth/errors/organization-errors.ts","../../src/auth/errors/token-required.error.ts","../../src/auth/utils/better-auth-error-handler.ts","../../src/auth/utils/auth-helpers.ts","../../src/auth/services/auth.service.ts","../../src/auth/auth.module.ts"],"sourcesContent":["/** Token for AuthService - core authentication service */\nexport const AUTH_SERVICE = Symbol.for('stratal:auth:service')\n\n/** Token for Better Auth options configuration */\nexport const AUTH_OPTIONS = Symbol.for('stratal:auth:options')\n","import { DI_TOKENS, Transient } from 'stratal/di'\nimport type { Middleware, Next, RouterContext } from 'stratal/router'\nimport { AuthContext } from '../../context/auth-context'\n\n/**\n * Auth Context Middleware\n *\n * Registers AuthContext in the request container at the start of each request.\n * This MUST run before SessionVerificationMiddleware and any other middleware\n * that depends on AuthContext.\n */\n@Transient()\nexport class AuthContextMiddleware implements Middleware {\n async handle(ctx: RouterContext, next: Next): Promise<void> {\n const requestContainer = ctx.getContainer()\n\n const authContext = new AuthContext()\n requestContainer.registerValue(DI_TOKENS.AuthContext, authContext)\n\n return next()\n }\n}\n","import { DI_TOKENS, Transient } from 'stratal/di'\nimport { LOGGER_TOKENS, type LoggerService } from 'stratal/logger'\nimport type { Middleware, Next, RouterContext } from 'stratal/router'\nimport { inject } from 'tsyringe'\nimport { type AuthContext } from '../../context/auth-context'\nimport { AUTH_SERVICE } from '../auth.tokens'\nimport type { AuthService } from '../services/auth.service'\n\n/**\n * Session Verification Middleware\n *\n * Verifies user session via Better Auth and populates AuthContext with\n * the authenticated user.\n *\n * **Responsibilities:**\n * - Calls Better Auth's getSession() API\n * - Populates AuthContext with the user record if the session is valid\n * - Continues request chain regardless of session status\n */\n@Transient()\nexport class SessionVerificationMiddleware implements Middleware {\n constructor(\n @inject(AUTH_SERVICE)\n private readonly authService: AuthService,\n @inject(LOGGER_TOKENS.LoggerService) private logger: LoggerService\n ) { }\n\n async handle(ctx: RouterContext, next: Next): Promise<void> {\n try {\n const result = await this.authService.auth.api.getSession({\n headers: ctx.c.req.raw.headers\n })\n\n if (result) {\n const authContext = ctx.getContainer().resolve<AuthContext>(DI_TOKENS.AuthContext)\n authContext.setAuthContext({\n user: result.user,\n })\n }\n } catch (error: unknown) {\n this.logger.debug('Session validation failed (e.g., invalidated in DB)', { error })\n }\n\n return next()\n }\n}\n","/**\n * Rate-limit bridge between Stratal's `RateLimiterModule` and better-auth.\n *\n * Importing this file (transitively, via `auth.module.ts`) does two things:\n *\n * 1. Augments `RateLimiterRegistry` with `forPath()` + `pathEntries()` via\n * Stratal's `Macroable`. Path-keyed rules registered on the same registry\n * used for Stratal's own throttling are projected into better-auth's\n * `customRules` by {@link projectCustomRules}.\n * 2. Exports {@link createBetterAuthRateLimitStorage} — adapts Stratal's\n * {@link IRateLimiterStore} into better-auth's `customStorage`, so both\n * systems share one backing store.\n *\n * `AuthModule.forRootAsync` wires both automatically when `RateLimiterModule`\n * is imported. Users with explicit `rateLimit.customStorage` /\n * `rateLimit.customRules` keys in their auth factory keep precedence.\n *\n * Frictions, documented for path-keyed entries:\n *\n * - `Limit.by(...)` is meaningless. Better-auth scopes per-IP+path.\n * - Multiple `Limit`s reduce to the most restrictive (smallest max-per-second).\n * - `Limit.none()` projects to `false` (better-auth's \"disable\" sentinel).\n * - `Limit.response(...)` is a no-op. Better-auth renders its own 429.\n * - Snapshot caveat: `customRules` is built once at AuthService construction,\n * so register all `forPath()` entries inside `OnInitialize` hooks.\n */\nimport { type IRateLimiterStore, type Limit, RateLimiterRegistry } from 'stratal/rate-limiter'\n\n/**\n * Resolver attached to a path-keyed limiter entry. Receives the native\n * `Request` (better-auth's customRules invokes us with the live Request)\n * and returns one or more `Limit`s. Async is supported.\n */\nexport type PathLimitResolver = (\n req: Request,\n) => Limit | Limit[] | Promise<Limit | Limit[]>\n\ninterface BetterAuthRateLimit {\n key: string\n count: number\n lastRequest: number\n}\n\ninterface BetterAuthRateLimitRule {\n window: number\n max: number\n}\n\ntype BetterAuthCustomRule =\n | BetterAuthRateLimitRule\n | false\n | ((req: Request) => Promise<BetterAuthRateLimitRule | false>)\n\n// Per-instance path map — keyed by registry so we don't pin GC roots.\nconst pathResolvers = new WeakMap<RateLimiterRegistry, Map<string, PathLimitResolver>>()\n\nfunction getOrCreatePathMap(registry: RateLimiterRegistry): Map<string, PathLimitResolver> {\n let map = pathResolvers.get(registry)\n if (!map) {\n map = new Map()\n pathResolvers.set(registry, map)\n }\n return map\n}\n\nRateLimiterRegistry.macro('forPath', function (\n this: RateLimiterRegistry,\n path: string,\n resolver: PathLimitResolver,\n): void {\n getOrCreatePathMap(this).set(path, resolver)\n})\n\nRateLimiterRegistry.macro('pathEntries', function (\n this: RateLimiterRegistry,\n): IterableIterator<[string, PathLimitResolver]> {\n return (pathResolvers.get(this) ?? new Map<string, PathLimitResolver>()).entries()\n})\n\ndeclare module 'stratal/rate-limiter' {\n interface RateLimiterRegistry {\n /**\n * Register a rate-limit rule for a better-auth path pattern. The rule\n * is projected into better-auth's `rateLimit.customRules` automatically\n * when both modules are imported.\n *\n * @example\n * limiter.forPath('/sign-in/email', () => Limit.perSeconds(10, 3))\n * limiter.forPath('/two-factor/*', async (req) => { ... })\n * limiter.forPath('/forget-password', () => Limit.none())\n */\n forPath(path: string, resolver: PathLimitResolver): void\n\n /**\n * Iterate every path-keyed entry registered via `forPath`. Used by the\n * auth bridge to project entries into better-auth's `customRules`.\n */\n pathEntries(): IterableIterator<[string, PathLimitResolver]>\n }\n}\n\n// Better-auth manages window expiry itself by reading `lastRequest`. We still\n// need a TTL on the underlying KV so dead records don't accumulate. 1 day\n// covers any reasonable better-auth window without colliding with the next.\nconst BETTER_AUTH_TTL_SECONDS = 86_400\nconst BETTER_AUTH_KEY_PREFIX = 'ba-rl:'\n\n/**\n * Adapt Stratal's `IRateLimiterStore` into better-auth's `customStorage` shape.\n * Better-auth supplies its own `RateLimit` records (`{ key, count, lastRequest }`);\n * the adapter just persists them under a separate key namespace.\n */\nexport function createBetterAuthRateLimitStorage(store: IRateLimiterStore): {\n get: (key: string) => Promise<BetterAuthRateLimit | null>\n set: (key: string, value: BetterAuthRateLimit, update?: boolean) => Promise<void>\n} {\n return {\n async get(key) {\n return await store.get<BetterAuthRateLimit>(`${BETTER_AUTH_KEY_PREFIX}${key}`)\n },\n async set(key, value, _update) {\n await store.set(`${BETTER_AUTH_KEY_PREFIX}${key}`, value, BETTER_AUTH_TTL_SECONDS)\n },\n }\n}\n\n/**\n * Project every `forPath` entry on the registry into better-auth's\n * `customRules` shape. Each entry becomes an async function that resolves\n * the user's `Limit`(s) and reduces them to a single `{ window, max }` pair\n * (or `false` for `Limit.none()`).\n *\n * Multi-`Limit` reduction picks the most restrictive — smallest\n * `max / windowSeconds` ratio; ties favour the first.\n */\nexport function projectCustomRules(\n registry: RateLimiterRegistry,\n): Record<string, BetterAuthCustomRule> {\n const rules: Record<string, BetterAuthCustomRule> = {}\n\n for (const [path, resolver] of registry.pathEntries()) {\n rules[path] = async (req: Request): Promise<BetterAuthRateLimitRule | false> => {\n const resolved = await resolver(req)\n const candidates = (Array.isArray(resolved) ? resolved : [resolved]).filter((l) => !l.disabled)\n if (candidates.length === 0) return false\n\n const chosen = candidates.reduce((a, b) =>\n a.max / a.windowSeconds <= b.max / b.windowSeconds ? a : b,\n )\n\n return { window: chosen.windowSeconds, max: chosen.max }\n }\n }\n\n return rules\n}\n","import { HttpException } from 'stratal/errors'\n\nexport class UserNotFoundError extends HttpException {\n constructor(public readonly email?: string) {\n super(404, 'User not found')\n }\n}\n\nexport class InvalidCredentialsError extends HttpException {\n constructor() { super(401, 'Invalid email or password') }\n}\n\nexport class InvalidPasswordError extends HttpException {\n constructor() { super(401, 'Invalid password') }\n}\n\nexport class InvalidEmailError extends HttpException {\n constructor(public readonly email?: string) {\n super(422, 'Invalid email address')\n }\n}\n\nexport class SessionExpiredError extends HttpException {\n constructor() { super(401, 'Session expired') }\n}\n\nexport class EmailNotVerifiedError extends HttpException {\n constructor(public readonly email?: string) {\n super(403, 'Email not verified')\n }\n}\n\nexport class PasswordTooShortError extends HttpException {\n constructor(public readonly minLength?: number) {\n super(422, 'Password too short')\n }\n}\n\nexport class PasswordTooLongError extends HttpException {\n constructor(public readonly maxLength?: number) {\n super(422, 'Password too long')\n }\n}\n\nexport class AccountAlreadyExistsError extends HttpException {\n constructor(public readonly email?: string) {\n super(409, 'Account already exists')\n }\n}\n\nexport class SocialAccountLinkedError extends HttpException {\n constructor(public readonly provider?: string) {\n super(409, 'Social account already linked')\n }\n}\n\nexport class CannotUnlinkLastAccountError extends HttpException {\n constructor() { super(409, 'Cannot unlink last account') }\n}\n\nexport class ProviderNotFoundError extends HttpException {\n constructor(public readonly provider?: string) {\n super(404, 'Authentication provider not found')\n }\n}\n\nexport class UserEmailNotFoundError extends HttpException {\n constructor() { super(404, 'User email not found') }\n}\n\nexport class AccountNotFoundError extends HttpException {\n constructor() { super(404, 'Account not found') }\n}\n\nexport class CredentialAccountNotFoundError extends HttpException {\n constructor() { super(404, 'Credential account not found') }\n}\n\nexport class UserAlreadyHasPasswordError extends HttpException {\n constructor() { super(409, 'User already has a password') }\n}\n\nexport class EmailCannotBeUpdatedError extends HttpException {\n constructor(public readonly reason?: string) {\n super(422, 'Email cannot be updated')\n }\n}\n\nexport class IdTokenNotSupportedError extends HttpException {\n constructor() { super(422, 'ID token not supported') }\n}\n\nexport class TokenExpiredError extends HttpException {\n constructor() { super(401, 'Token expired') }\n}\n\nexport class InvalidCallbackUrlError extends HttpException {\n constructor() { super(422, 'Invalid callback URL') }\n}\n\nexport class InvalidOriginError extends HttpException {\n constructor() { super(403, 'Invalid request origin') }\n}\n\nexport class AuthValidationFailedError extends HttpException {\n constructor() { super(422, 'Authentication validation failed') }\n}\n\nexport class EmailAlreadyVerifiedError extends HttpException {\n constructor() { super(409, 'Email already verified') }\n}\n\nexport class EmailMismatchError extends HttpException {\n constructor() { super(422, 'Email mismatch') }\n}\n","import { HttpException } from 'stratal/errors'\n\nexport class InvalidTokenError extends HttpException {\n constructor() { super(401, 'Invalid or expired token') }\n}\n","import { HttpException } from 'stratal/errors'\n\nexport class OrganizationNotFoundError extends HttpException {\n constructor() { super(404, 'Organization not found') }\n}\nexport class OrganizationMemberNotFoundError extends HttpException {\n constructor() { super(404, 'Organization member not found') }\n}\nexport class OrganizationInvitationNotFoundError extends HttpException {\n constructor() { super(404, 'Invitation not found') }\n}\nexport class OrganizationPermissionDeniedError extends HttpException {\n constructor() { super(403, 'Organization permission denied') }\n}\nexport class OrganizationInvitationRecipientMismatchError extends HttpException {\n constructor() { super(403, 'Invitation recipient mismatch') }\n}\nexport class OrganizationConflictError extends HttpException {\n constructor() { super(409, 'Organization resource conflict') }\n}\nexport class OrganizationLimitReachedError extends HttpException {\n constructor() { super(422, 'Organization limit reached') }\n}\nexport class OrganizationMembershipError extends HttpException {\n constructor() { super(422, 'Organization membership constraint violated') }\n}\nexport class OrganizationTeamNotFoundError extends HttpException {\n constructor() { super(404, 'Team not found') }\n}\nexport class OrganizationRoleNotFoundError extends HttpException {\n constructor() { super(404, 'Role not found') }\n}\n","import { HttpException } from 'stratal/errors'\n\nexport class TokenRequiredError extends HttpException {\n constructor() { super(401, 'Verification token is required') }\n}\n","import { APIError } from 'better-auth/api'\nimport { AuthError } from 'stratal/errors'\nimport type { ApplicationError } from 'stratal/errors'\nimport {\n AccountAlreadyExistsError,\n AccountNotFoundError,\n AuthValidationFailedError,\n CannotUnlinkLastAccountError,\n CredentialAccountNotFoundError,\n EmailAlreadyVerifiedError,\n EmailCannotBeUpdatedError,\n EmailMismatchError,\n EmailNotVerifiedError,\n IdTokenNotSupportedError,\n InvalidCallbackUrlError,\n InvalidCredentialsError,\n InvalidEmailError,\n InvalidOriginError,\n InvalidPasswordError,\n InvalidTokenError,\n OrganizationConflictError,\n OrganizationInvitationNotFoundError,\n OrganizationInvitationRecipientMismatchError,\n OrganizationLimitReachedError,\n OrganizationMemberNotFoundError,\n OrganizationMembershipError,\n OrganizationNotFoundError,\n OrganizationPermissionDeniedError,\n OrganizationRoleNotFoundError,\n OrganizationTeamNotFoundError,\n PasswordTooLongError,\n PasswordTooShortError,\n ProviderNotFoundError,\n SessionExpiredError,\n SocialAccountLinkedError,\n TokenExpiredError,\n UserAlreadyHasPasswordError,\n UserEmailNotFoundError,\n UserNotFoundError,\n} from '../errors'\n\n/**\n * Maps Better Auth API error codes to ApplicationError instances.\n */\nexport function mapBetterAuthError(error: APIError): ApplicationError {\n const errorCode = error.body?.code\n\n if (error.status === 'FOUND') {\n const headers = error.headers as Headers\n const location = headers.get('location') ?? ''\n\n if (location.includes('INVALID_TOKEN')) return new InvalidTokenError()\n if (location.includes('EXPIRED_TOKEN')) return new TokenExpiredError()\n if (location.includes('ATTEMPTS_EXCEEDED')) return new InvalidTokenError()\n if (location.includes('new_user_signup_disabled')) return new UserNotFoundError()\n if (location.includes('failed_to_create_user')) return new AuthError('Failed to create user')\n if (location.includes('failed_to_create_session')) return new AuthError('Failed to create session')\n }\n\n if (!errorCode) {\n return new AuthError('An authentication error occurred')\n }\n\n // ── Base Error Codes ──────────────────────────────────────────────────\n\n // User errors\n if (errorCode === 'USER_NOT_FOUND' || errorCode === 'INVALID_USER') return new UserNotFoundError()\n if (errorCode === 'USER_EMAIL_NOT_FOUND') return new UserEmailNotFoundError()\n\n // Credential errors\n if (errorCode === 'INVALID_EMAIL_OR_PASSWORD') return new InvalidCredentialsError()\n if (errorCode === 'INVALID_PASSWORD') return new InvalidPasswordError()\n if (errorCode === 'INVALID_EMAIL') return new InvalidEmailError()\n\n // Session errors\n if (errorCode === 'SESSION_EXPIRED' || errorCode === 'SESSION_NOT_FRESH') return new SessionExpiredError()\n if (errorCode === 'FAILED_TO_CREATE_SESSION') return new AuthError('Failed to create session')\n if (errorCode === 'FAILED_TO_GET_SESSION') return new AuthError('Failed to retrieve session')\n\n // Email verification\n if (errorCode === 'EMAIL_NOT_VERIFIED') return new EmailNotVerifiedError()\n if (errorCode === 'EMAIL_CAN_NOT_BE_UPDATED') return new EmailCannotBeUpdatedError()\n if (errorCode === 'EMAIL_ALREADY_VERIFIED') return new EmailAlreadyVerifiedError()\n if (errorCode === 'EMAIL_MISMATCH') return new EmailMismatchError()\n\n // Password validation\n if (errorCode === 'PASSWORD_TOO_SHORT') return new PasswordTooShortError(8)\n if (errorCode === 'PASSWORD_TOO_LONG') return new PasswordTooLongError(128)\n\n // Account errors\n if (errorCode === 'USER_ALREADY_EXISTS' || errorCode === 'USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL') {\n return new AccountAlreadyExistsError()\n }\n if (errorCode === 'ACCOUNT_NOT_FOUND') return new AccountNotFoundError()\n if (errorCode === 'CREDENTIAL_ACCOUNT_NOT_FOUND') return new CredentialAccountNotFoundError()\n if (errorCode === 'FAILED_TO_UNLINK_LAST_ACCOUNT') return new CannotUnlinkLastAccountError()\n\n // User creation/update errors\n if (errorCode === 'FAILED_TO_CREATE_USER') return new AuthError('Failed to create user')\n if (errorCode === 'FAILED_TO_UPDATE_USER') return new AuthError('Failed to update user')\n if (errorCode === 'FAILED_TO_GET_USER_INFO') return new AuthError('Failed to retrieve user info')\n\n // Social account errors\n if (errorCode === 'SOCIAL_ACCOUNT_ALREADY_LINKED' || errorCode === 'LINKED_ACCOUNT_ALREADY_EXISTS') {\n return new SocialAccountLinkedError()\n }\n if (errorCode === 'PROVIDER_NOT_FOUND') return new ProviderNotFoundError()\n\n // Token errors\n if (errorCode === 'ID_TOKEN_NOT_SUPPORTED') return new IdTokenNotSupportedError()\n if (errorCode === 'INVALID_TOKEN') return new InvalidTokenError()\n if (errorCode === 'TOKEN_EXPIRED') return new TokenExpiredError()\n\n // Password management\n if (errorCode === 'USER_ALREADY_HAS_PASSWORD' || errorCode === 'PASSWORD_ALREADY_SET') {\n return new UserAlreadyHasPasswordError()\n }\n\n // Callback/redirect URL errors\n if (\n errorCode === 'INVALID_CALLBACK_URL'\n || errorCode === 'INVALID_REDIRECT_URL'\n || errorCode === 'INVALID_NEW_USER_CALLBACK_URL'\n || errorCode === 'INVALID_ERROR_CALLBACK_URL'\n || errorCode === 'CALLBACK_URL_REQUIRED'\n ) {\n return new InvalidCallbackUrlError()\n }\n\n // Origin/CORS errors\n if (\n errorCode === 'INVALID_ORIGIN'\n || errorCode === 'MISSING_OR_NULL_ORIGIN'\n || errorCode === 'CROSS_SITE_NAVIGATION_LOGIN_BLOCKED'\n ) {\n return new InvalidOriginError()\n }\n\n // Validation errors\n if (\n errorCode === 'VALIDATION_ERROR'\n || errorCode === 'MISSING_FIELD'\n || errorCode === 'FIELD_NOT_ALLOWED'\n || errorCode === 'BODY_MUST_BE_AN_OBJECT'\n || errorCode === 'ASYNC_VALIDATION_NOT_SUPPORTED'\n || errorCode === 'METHOD_NOT_ALLOWED_DEFER_SESSION_REQUIRED'\n ) {\n return new AuthValidationFailedError()\n }\n\n // Verification errors\n if (errorCode === 'FAILED_TO_CREATE_VERIFICATION' || errorCode === 'VERIFICATION_EMAIL_NOT_ENABLED') {\n return new AuthError('Failed to create session')\n }\n\n // ── Organization Plugin Error Codes ───────────────────────────────────\n\n // Organization not found\n if (errorCode === 'ORGANIZATION_NOT_FOUND' || errorCode === 'NO_ACTIVE_ORGANIZATION') {\n return new OrganizationNotFoundError()\n }\n\n // Member not found\n if (\n errorCode === 'MEMBER_NOT_FOUND'\n || errorCode === 'USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION'\n || errorCode === 'USER_IS_NOT_A_MEMBER_OF_THE_TEAM'\n ) {\n return new OrganizationMemberNotFoundError()\n }\n\n // Invitation not found\n if (errorCode === 'INVITATION_NOT_FOUND' || errorCode === 'FAILED_TO_RETRIEVE_INVITATION') {\n return new OrganizationInvitationNotFoundError()\n }\n\n // Invitation recipient mismatch\n if (\n errorCode === 'YOU_ARE_NOT_THE_RECIPIENT_OF_THE_INVITATION'\n || errorCode === 'EMAIL_VERIFICATION_REQUIRED_BEFORE_ACCEPTING_OR_REJECTING_INVITATION'\n ) {\n return new OrganizationInvitationRecipientMismatchError()\n }\n\n // Team not found\n if (errorCode === 'TEAM_NOT_FOUND' || errorCode === 'YOU_DO_NOT_HAVE_AN_ACTIVE_TEAM') {\n return new OrganizationTeamNotFoundError()\n }\n\n // Role not found\n if (errorCode === 'ROLE_NOT_FOUND' || errorCode === 'INVALID_RESOURCE') {\n return new OrganizationRoleNotFoundError()\n }\n\n // Organization conflict/already exists\n if (\n errorCode === 'ORGANIZATION_ALREADY_EXISTS'\n || errorCode === 'ORGANIZATION_SLUG_ALREADY_TAKEN'\n || errorCode === 'USER_IS_ALREADY_A_MEMBER_OF_THIS_ORGANIZATION'\n || errorCode === 'USER_IS_ALREADY_INVITED_TO_THIS_ORGANIZATION'\n || errorCode === 'TEAM_ALREADY_EXISTS'\n || errorCode === 'ROLE_NAME_IS_ALREADY_TAKEN'\n ) {\n return new OrganizationConflictError()\n }\n\n // Organization limit reached\n if (\n errorCode === 'YOU_HAVE_REACHED_THE_MAXIMUM_NUMBER_OF_ORGANIZATIONS'\n || errorCode === 'YOU_HAVE_REACHED_THE_MAXIMUM_NUMBER_OF_TEAMS'\n || errorCode === 'ORGANIZATION_MEMBERSHIP_LIMIT_REACHED'\n || errorCode === 'INVITATION_LIMIT_REACHED'\n || errorCode === 'TEAM_MEMBER_LIMIT_REACHED'\n || errorCode === 'TOO_MANY_ROLES'\n ) {\n return new OrganizationLimitReachedError()\n }\n\n // Organization membership constraints\n if (\n errorCode === 'YOU_CANNOT_LEAVE_THE_ORGANIZATION_AS_THE_ONLY_OWNER'\n || errorCode === 'YOU_CANNOT_LEAVE_THE_ORGANIZATION_WITHOUT_AN_OWNER'\n || errorCode === 'UNABLE_TO_REMOVE_LAST_TEAM'\n || errorCode === 'CANNOT_DELETE_A_PRE_DEFINED_ROLE'\n || errorCode === 'ROLE_IS_ASSIGNED_TO_MEMBERS'\n || errorCode === 'YOU_CANNOT_IMPERSONATE_ADMINS'\n || errorCode === 'YOU_CANNOT_BAN_YOURSELF'\n || errorCode === 'YOU_CANNOT_REMOVE_YOURSELF'\n || errorCode === 'INVITER_IS_NO_LONGER_A_MEMBER_OF_THE_ORGANIZATION'\n ) {\n return new OrganizationMembershipError()\n }\n\n // Organization permission denied (catch-all for YOU_ARE_NOT_ALLOWED_TO_* patterns)\n if (\n errorCode.startsWith('YOU_ARE_NOT_ALLOWED_TO_')\n || errorCode === 'YOU_ARE_NOT_A_MEMBER_OF_THIS_ORGANIZATION'\n || errorCode === 'YOU_CAN_NOT_ACCESS_THE_MEMBERS_OF_THIS_TEAM'\n || errorCode === 'YOU_MUST_BE_IN_AN_ORGANIZATION_TO_CREATE_A_ROLE'\n || errorCode === 'MISSING_AC_INSTANCE'\n ) {\n return new OrganizationPermissionDeniedError()\n }\n\n // Unknown error code\n return new AuthError('An authentication error occurred')\n}\n\n/**\n * Type guard to check if an error is a Better Auth APIError.\n * Uses duck typing to handle bundler environments (e.g. Vite)\n * where instanceof may fail across module boundaries.\n */\nexport function isAPIError(error: unknown): error is APIError {\n if (error instanceof APIError) return true\n\n return (\n error instanceof Error\n && error.name === 'APIError'\n && 'status' in error\n && 'statusCode' in error\n )\n}\n","import type { BetterAuthOptions } from 'better-auth'\nimport { isAPIError, mapBetterAuthError } from './better-auth-error-handler'\n\n/**\n * Get shared Better Auth error handler configuration.\n * Use this in Better Auth config's onAPIError option.\n */\nexport function getErrorHandlerConfig(): BetterAuthOptions['onAPIError'] {\n return {\n throw: false,\n onError: (error) => {\n if (isAPIError(error)) {\n throw mapBetterAuthError(error)\n }\n throw error\n },\n }\n}\n\n/**\n * Wrap a Better Auth function in a try/catch block and map errors to ApplicationError.\n */\nexport const wrapBetterAuth = async <T>(fn: () => Promise<T>): Promise<T> => {\n try {\n return await fn()\n } catch (error) {\n if (isAPIError(error)) {\n throw mapBetterAuthError(error)\n }\n throw error\n }\n}\n","import type { Auth, BetterAuthOptions } from 'better-auth';\nimport { betterAuth } from 'better-auth/minimal';\nimport { Transient } from 'stratal/di';\nimport { inject } from 'tsyringe';\nimport { AUTH_OPTIONS, AUTH_SERVICE } from '../auth.tokens';\nimport { getErrorHandlerConfig } from '../utils';\n\n/**\n * AuthService\n *\n * Base authentication service using Better Auth.\n * Configured via AuthModule.forRootAsync() from the application layer.\n *\n * **Extensibility:**\n * Extend this class in application layer to add custom methods.\n *\n * @example\n * ```typescript\n * @Transient(AUTH_SERVICE)\n * export class AppAuthService extends AuthService<AuthOptions> {\n * async signInMagicLink(email: string) {\n * return wrapBetterAuth(async () => {\n * return this.auth.api.signInMagicLink({ body: { email }, headers: new Headers() })\n * })\n * }\n * }\n * ```\n */\n@Transient(AUTH_SERVICE)\nexport class AuthService<TOptions extends BetterAuthOptions = BetterAuthOptions> {\n private _authInstance?: Auth<TOptions>\n\n constructor(\n @inject(AUTH_OPTIONS) protected readonly options: TOptions\n ) {}\n\n /**\n * Get the Better Auth instance.\n */\n get auth(): Auth<TOptions> {\n this._authInstance ??= betterAuth({\n ...this.options,\n onAPIError: getErrorHandlerConfig()\n }) as Auth<TOptions>;\n \n return this._authInstance\n }\n}\n","/**\n * Auth Module\n *\n * Provides configurable authentication using Better Auth.\n * Use `forRootAsync` to configure Better Auth options from the application layer.\n *\n * Optionally pass `accessControl` to enable permission-based authorization.\n * This auto-adds the Stratal AC plugin to Better Auth and registers `AccessService`.\n *\n * @example Without access control\n * ```typescript\n * @Module({\n * imports: [\n * AuthModule.forRootAsync({\n * inject: [DI_TOKENS.Database, CONFIG_TOKENS.ConfigService],\n * useFactory: (db, config) => createAuthOptions(db, config)\n * })\n * ]\n * })\n * export class AppModule {}\n * ```\n *\n * @example With access control\n * ```typescript\n * import { createAccessControl } from '@stratal/framework/access-control'\n * import { admin } from 'better-auth/plugins'\n *\n * const permissions = createAccessControl({\n * resources: { posts: ['create', 'read', 'update', 'delete'] } as const,\n * roles: { admin: { posts: ['create', 'read', 'update', 'delete'] }, user: { posts: ['read'] } },\n * })\n *\n * @Module({\n * imports: [\n * AuthModule.forRootAsync({\n * inject: [DI_TOKENS.Database],\n * useFactory: (db) => ({\n * database: ...,\n * plugins: [admin({ ...permissions })],\n * }),\n * accessControl: permissions,\n * })\n * ]\n * })\n * ```\n */\n\nimport type { BetterAuthOptions } from 'better-auth'\nimport { CONTAINER_TOKEN, type Container } from 'stratal/di'\nimport type { AsyncModuleOptions, DynamicModule } from 'stratal/module'\nimport { Module } from 'stratal/module'\nimport type { IRateLimiterStore, RateLimiterRegistry } from 'stratal/rate-limiter'\nimport { RATE_LIMITER_TOKENS } from 'stratal/rate-limiter'\nimport type { RouteConfigurable, Router } from 'stratal/router'\nimport { createStratalAcPlugin } from '../access-control/plugin'\nimport { AccessService } from '../access-control/services/access.service'\nimport { AC_TOKENS } from '../access-control/tokens'\nimport type { AccessControlOptions } from '../access-control/types'\nimport { AUTH_OPTIONS, AUTH_SERVICE } from './auth.tokens'\nimport { AuthContextMiddleware } from './middleware/auth-context.middleware'\nimport { SessionVerificationMiddleware } from './middleware/session-verification.middleware'\n// Side-effect import: registers `forPath`/`pathEntries` macros on\n// `RateLimiterRegistry` and the `declare module` augmentation that exposes\n// them at the type level. Must run before any consumer calls `forPath()`.\nimport {\n createBetterAuthRateLimitStorage,\n projectCustomRules,\n} from './rate-limit-bridge'\nimport { AuthService } from './services/auth.service'\n\nexport interface AuthModuleAsyncOptions<TOptions extends BetterAuthOptions = BetterAuthOptions>\n extends AsyncModuleOptions<TOptions> {\n /**\n * Optional access control configuration.\n * When provided, registers AccessService and auto-adds the Stratal AC plugin to Better Auth.\n */\n accessControl?: AccessControlOptions\n}\n\n@Module({\n providers: []\n})\nexport class AuthModule implements RouteConfigurable {\n /**\n * Configure auth middleware globally.\n *\n * Registers middlewares in order:\n * 1. AuthContextMiddleware - Creates and registers AuthContext in request container\n * 2. SessionVerificationMiddleware - Verifies session and populates AuthContext with userId + role\n */\n configureRoutes(router: Router): void {\n router.use(AuthContextMiddleware, SessionVerificationMiddleware)\n }\n\n /**\n * Configure AuthModule with async options factory.\n * Optionally provide `accessControl` to enable permission-based authorization.\n *\n * When `RateLimiterModule` is also imported, better-auth's `rateLimit`\n * block is auto-wired: `customStorage` shares Stratal's backing store, and\n * any `RateLimiterRegistry.forPath(...)` entries are projected into\n * `customRules`. User-supplied `rateLimit.{customStorage, customRules}` keys\n * take precedence on a per-key basis.\n */\n static forRootAsync<TOptions extends BetterAuthOptions>(\n options: AuthModuleAsyncOptions<TOptions>\n ): DynamicModule {\n const { accessControl } = options\n const userInject = options.inject ?? []\n const userFactory = options.useFactory as (...args: unknown[]) => TOptions\n\n const authOptionsProvider = {\n provide: AUTH_OPTIONS,\n useFactory: (container: Container, ...userDeps: unknown[]): BetterAuthOptions => {\n let raw = userFactory(...userDeps) as BetterAuthOptions\n\n if (accessControl) {\n raw = {\n ...raw,\n plugins: [createStratalAcPlugin(accessControl), ...(raw.plugins ?? [])],\n }\n }\n\n const tsyringe = container.getTsyringeContainer()\n const rateLimiterPresent = tsyringe.isRegistered(\n RATE_LIMITER_TOKENS.ModuleMarker,\n true,\n )\n\n if (rateLimiterPresent) {\n const store = container.resolve<IRateLimiterStore>(RATE_LIMITER_TOKENS.Store)\n const registry = container.resolve<RateLimiterRegistry>(RATE_LIMITER_TOKENS.Registry)\n\n raw = {\n ...raw,\n rateLimit: {\n enabled: true,\n ...raw.rateLimit,\n customStorage: raw.rateLimit?.customStorage ?? createBetterAuthRateLimitStorage(store),\n customRules: {\n ...projectCustomRules(registry),\n ...(raw.rateLimit?.customRules ?? {}),\n },\n },\n }\n }\n\n return raw\n },\n inject: [CONTAINER_TOKEN, ...userInject],\n }\n\n return {\n module: AuthModule,\n providers: [\n authOptionsProvider,\n {\n provide: AUTH_SERVICE,\n useClass: AuthService,\n },\n ...(accessControl\n ? [\n { provide: AC_TOKENS.Options, useValue: accessControl as unknown as object },\n { provide: AC_TOKENS.AccessService, useClass: AccessService },\n ]\n : []),\n ],\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/context/router-context.augment.ts","../../src/auth/auth.tokens.ts","../../src/auth/middleware/session-verification.middleware.ts","../../src/auth/rate-limit-bridge.ts","../../src/auth/errors/auth-errors.ts","../../src/auth/errors/invalid-token.error.ts","../../src/auth/errors/organization-errors.ts","../../src/auth/errors/token-required.error.ts","../../src/auth/utils/better-auth-error-handler.ts","../../src/auth/utils/auth-helpers.ts","../../src/auth/services/auth.service.ts","../../src/auth/auth.module.ts"],"sourcesContent":["/**\n * Augments Stratal's `RouterContext` with a `user()` accessor backed by the\n * request-scoped {@link AuthContext}.\n *\n * Side-effect import: registers the `user` macro on `RouterContext` and the\n * `declare module` augmentation that exposes it at the type level. Imported by\n * {@link AuthModule} so it runs whenever auth is configured.\n */\nimport { DI_TOKENS } from 'stratal/di'\nimport { RouterContext } from 'stratal/router'\nimport type { AuthContext, AuthUser } from './auth-context'\n\ndeclare module 'stratal/router' {\n interface RouterContext {\n /**\n * The authenticated user for the current request.\n *\n * Throws `UserNotAuthenticatedError` if the request is unauthenticated.\n * Provided by `@stratal/framework`'s `AuthModule` via {@link AuthContext}.\n */\n user(): AuthUser\n }\n}\n\nRouterContext.macro('user', function (this: RouterContext): AuthUser {\n return this.getContainer().resolve<AuthContext>(DI_TOKENS.AuthContext).requireUser()\n})\n","/** Token for AuthService - core authentication service */\nexport const AUTH_SERVICE = Symbol.for('stratal:auth:service')\n\n/** Token for Better Auth options configuration */\nexport const AUTH_OPTIONS = Symbol.for('stratal:auth:options')\n","import { DI_TOKENS, inject, Transient } from 'stratal/di'\nimport { LOGGER_TOKENS, type LoggerService } from 'stratal/logger'\nimport type { Middleware, Next, RouterContext } from 'stratal/router'\nimport { type AuthContext } from '../../context/auth-context'\nimport { AUTH_SERVICE } from '../auth.tokens'\nimport type { AuthService } from '../services/auth.service'\n\n/**\n * Session Verification Middleware\n *\n * Verifies user session via Better Auth and populates AuthContext with\n * the authenticated user.\n *\n * **Responsibilities:**\n * - Calls Better Auth's getSession() API\n * - Populates AuthContext with the user record if the session is valid\n * - Continues request chain regardless of session status\n */\n@Transient()\nexport class SessionVerificationMiddleware implements Middleware {\n constructor(\n @inject(AUTH_SERVICE)\n private readonly authService: AuthService,\n @inject(LOGGER_TOKENS.LoggerService) private logger: LoggerService\n ) { }\n\n async handle(ctx: RouterContext, next: Next): Promise<void> {\n try {\n const result = await this.authService.auth.api.getSession({\n headers: ctx.c.req.raw.headers\n })\n\n if (result) {\n const authContext = ctx.getContainer().resolve<AuthContext>(DI_TOKENS.AuthContext)\n authContext.setAuthContext({\n user: result.user,\n })\n }\n } catch (error: unknown) {\n this.logger.debug('Session validation failed (e.g., invalidated in DB)', { error })\n }\n\n return next()\n }\n}\n","/**\n * Rate-limit bridge between Stratal's `RateLimiterModule` and better-auth.\n *\n * Importing this file (transitively, via `auth.module.ts`) does two things:\n *\n * 1. Augments `RateLimiterRegistry` with `forPath()` + `pathEntries()` via\n * Stratal's `Macroable`. Path-keyed rules registered on the same registry\n * used for Stratal's own throttling are projected into better-auth's\n * `customRules` by {@link projectCustomRules}.\n * 2. Exports {@link createBetterAuthRateLimitStorage} — adapts Stratal's\n * {@link IRateLimiterStore} into better-auth's `customStorage`, so both\n * systems share one backing store.\n *\n * `AuthModule.forRootAsync` wires both automatically when `RateLimiterModule`\n * is imported. Users with explicit `rateLimit.customStorage` /\n * `rateLimit.customRules` keys in their auth factory keep precedence.\n *\n * Frictions, documented for path-keyed entries:\n *\n * - `Limit.by(...)` is meaningless. Better-auth scopes per-IP+path.\n * - Multiple `Limit`s reduce to the most restrictive (smallest max-per-second).\n * - `Limit.none()` projects to `false` (better-auth's \"disable\" sentinel).\n * - `Limit.response(...)` is a no-op. Better-auth renders its own 429.\n * - Snapshot caveat: `customRules` is built once at AuthService construction,\n * so register all `forPath()` entries inside `OnInitialize` hooks.\n */\nimport { type IRateLimiterStore, type Limit, RateLimiterRegistry } from 'stratal/rate-limiter'\n\n/**\n * Resolver attached to a path-keyed limiter entry. Receives the native\n * `Request` (better-auth's customRules invokes us with the live Request)\n * and returns one or more `Limit`s. Async is supported.\n */\nexport type PathLimitResolver = (\n req: Request,\n) => Limit | Limit[] | Promise<Limit | Limit[]>\n\ninterface BetterAuthRateLimit {\n key: string\n count: number\n lastRequest: number\n}\n\ninterface BetterAuthRateLimitRule {\n window: number\n max: number\n}\n\ntype BetterAuthCustomRule =\n | BetterAuthRateLimitRule\n | false\n | ((req: Request) => Promise<BetterAuthRateLimitRule | false>)\n\n// Per-instance path map — keyed by registry so we don't pin GC roots.\nconst pathResolvers = new WeakMap<RateLimiterRegistry, Map<string, PathLimitResolver>>()\n\nfunction getOrCreatePathMap(registry: RateLimiterRegistry): Map<string, PathLimitResolver> {\n let map = pathResolvers.get(registry)\n if (!map) {\n map = new Map()\n pathResolvers.set(registry, map)\n }\n return map\n}\n\nRateLimiterRegistry.macro('forPath', function (\n this: RateLimiterRegistry,\n path: string,\n resolver: PathLimitResolver,\n): void {\n getOrCreatePathMap(this).set(path, resolver)\n})\n\nRateLimiterRegistry.macro('pathEntries', function (\n this: RateLimiterRegistry,\n): IterableIterator<[string, PathLimitResolver]> {\n return (pathResolvers.get(this) ?? new Map<string, PathLimitResolver>()).entries()\n})\n\ndeclare module 'stratal/rate-limiter' {\n interface RateLimiterRegistry {\n /**\n * Register a rate-limit rule for a better-auth path pattern. The rule\n * is projected into better-auth's `rateLimit.customRules` automatically\n * when both modules are imported.\n *\n * @example\n * limiter.forPath('/sign-in/email', () => Limit.perSeconds(10, 3))\n * limiter.forPath('/two-factor/*', async (req) => { ... })\n * limiter.forPath('/forget-password', () => Limit.none())\n */\n forPath(path: string, resolver: PathLimitResolver): void\n\n /**\n * Iterate every path-keyed entry registered via `forPath`. Used by the\n * auth bridge to project entries into better-auth's `customRules`.\n */\n pathEntries(): IterableIterator<[string, PathLimitResolver]>\n }\n}\n\n// Better-auth manages window expiry itself by reading `lastRequest`. We still\n// need a TTL on the underlying KV so dead records don't accumulate. 1 day\n// covers any reasonable better-auth window without colliding with the next.\nconst BETTER_AUTH_TTL_SECONDS = 86_400\nconst BETTER_AUTH_KEY_PREFIX = 'ba-rl:'\n\n/**\n * Adapt Stratal's `IRateLimiterStore` into better-auth's `customStorage` shape.\n * Better-auth supplies its own `RateLimit` records (`{ key, count, lastRequest }`);\n * the adapter just persists them under a separate key namespace.\n */\nexport function createBetterAuthRateLimitStorage(store: IRateLimiterStore): {\n get: (key: string) => Promise<BetterAuthRateLimit | null>\n set: (key: string, value: BetterAuthRateLimit, update?: boolean) => Promise<void>\n} {\n return {\n async get(key) {\n return await store.get<BetterAuthRateLimit>(`${BETTER_AUTH_KEY_PREFIX}${key}`)\n },\n async set(key, value, _update) {\n await store.set(`${BETTER_AUTH_KEY_PREFIX}${key}`, value, BETTER_AUTH_TTL_SECONDS)\n },\n }\n}\n\n/**\n * Project every `forPath` entry on the registry into better-auth's\n * `customRules` shape. Each entry becomes an async function that resolves\n * the user's `Limit`(s) and reduces them to a single `{ window, max }` pair\n * (or `false` for `Limit.none()`).\n *\n * Multi-`Limit` reduction picks the most restrictive — smallest\n * `max / windowSeconds` ratio; ties favour the first.\n */\nexport function projectCustomRules(\n registry: RateLimiterRegistry,\n): Record<string, BetterAuthCustomRule> {\n const rules: Record<string, BetterAuthCustomRule> = {}\n\n for (const [path, resolver] of registry.pathEntries()) {\n rules[path] = async (req: Request): Promise<BetterAuthRateLimitRule | false> => {\n const resolved = await resolver(req)\n const candidates = (Array.isArray(resolved) ? resolved : [resolved]).filter((l) => !l.disabled)\n if (candidates.length === 0) return false\n\n const chosen = candidates.reduce((a, b) =>\n a.max / a.windowSeconds <= b.max / b.windowSeconds ? a : b,\n )\n\n return { window: chosen.windowSeconds, max: chosen.max }\n }\n }\n\n return rules\n}\n","import { HttpException } from 'stratal/errors'\n\nexport class UserNotFoundError extends HttpException {\n constructor(public readonly email?: string) {\n super(404, 'User not found')\n }\n}\n\nexport class InvalidCredentialsError extends HttpException {\n constructor() { super(401, 'Invalid email or password') }\n}\n\nexport class InvalidPasswordError extends HttpException {\n constructor() { super(401, 'Invalid password') }\n}\n\nexport class InvalidEmailError extends HttpException {\n constructor(public readonly email?: string) {\n super(422, 'Invalid email address')\n }\n}\n\nexport class SessionExpiredError extends HttpException {\n constructor() { super(401, 'Session expired') }\n}\n\nexport class EmailNotVerifiedError extends HttpException {\n constructor(public readonly email?: string) {\n super(403, 'Email not verified')\n }\n}\n\nexport class PasswordTooShortError extends HttpException {\n constructor(public readonly minLength?: number) {\n super(422, 'Password too short')\n }\n}\n\nexport class PasswordTooLongError extends HttpException {\n constructor(public readonly maxLength?: number) {\n super(422, 'Password too long')\n }\n}\n\nexport class AccountAlreadyExistsError extends HttpException {\n constructor(public readonly email?: string) {\n super(409, 'Account already exists')\n }\n}\n\nexport class SocialAccountLinkedError extends HttpException {\n constructor(public readonly provider?: string) {\n super(409, 'Social account already linked')\n }\n}\n\nexport class CannotUnlinkLastAccountError extends HttpException {\n constructor() { super(409, 'Cannot unlink last account') }\n}\n\nexport class ProviderNotFoundError extends HttpException {\n constructor(public readonly provider?: string) {\n super(404, 'Authentication provider not found')\n }\n}\n\nexport class UserEmailNotFoundError extends HttpException {\n constructor() { super(404, 'User email not found') }\n}\n\nexport class AccountNotFoundError extends HttpException {\n constructor() { super(404, 'Account not found') }\n}\n\nexport class CredentialAccountNotFoundError extends HttpException {\n constructor() { super(404, 'Credential account not found') }\n}\n\nexport class UserAlreadyHasPasswordError extends HttpException {\n constructor() { super(409, 'User already has a password') }\n}\n\nexport class EmailCannotBeUpdatedError extends HttpException {\n constructor(public readonly reason?: string) {\n super(422, 'Email cannot be updated')\n }\n}\n\nexport class IdTokenNotSupportedError extends HttpException {\n constructor() { super(422, 'ID token not supported') }\n}\n\nexport class TokenExpiredError extends HttpException {\n constructor() { super(401, 'Token expired') }\n}\n\nexport class InvalidCallbackUrlError extends HttpException {\n constructor() { super(422, 'Invalid callback URL') }\n}\n\nexport class InvalidOriginError extends HttpException {\n constructor() { super(403, 'Invalid request origin') }\n}\n\nexport class AuthValidationFailedError extends HttpException {\n constructor() { super(422, 'Authentication validation failed') }\n}\n\nexport class EmailAlreadyVerifiedError extends HttpException {\n constructor() { super(409, 'Email already verified') }\n}\n\nexport class EmailMismatchError extends HttpException {\n constructor() { super(422, 'Email mismatch') }\n}\n","import { HttpException } from 'stratal/errors'\n\nexport class InvalidTokenError extends HttpException {\n constructor() { super(401, 'Invalid or expired token') }\n}\n","import { HttpException } from 'stratal/errors'\n\nexport class OrganizationNotFoundError extends HttpException {\n constructor() { super(404, 'Organization not found') }\n}\nexport class OrganizationMemberNotFoundError extends HttpException {\n constructor() { super(404, 'Organization member not found') }\n}\nexport class OrganizationInvitationNotFoundError extends HttpException {\n constructor() { super(404, 'Invitation not found') }\n}\nexport class OrganizationPermissionDeniedError extends HttpException {\n constructor() { super(403, 'Organization permission denied') }\n}\nexport class OrganizationInvitationRecipientMismatchError extends HttpException {\n constructor() { super(403, 'Invitation recipient mismatch') }\n}\nexport class OrganizationConflictError extends HttpException {\n constructor() { super(409, 'Organization resource conflict') }\n}\nexport class OrganizationLimitReachedError extends HttpException {\n constructor() { super(422, 'Organization limit reached') }\n}\nexport class OrganizationMembershipError extends HttpException {\n constructor() { super(422, 'Organization membership constraint violated') }\n}\nexport class OrganizationTeamNotFoundError extends HttpException {\n constructor() { super(404, 'Team not found') }\n}\nexport class OrganizationRoleNotFoundError extends HttpException {\n constructor() { super(404, 'Role not found') }\n}\n","import { HttpException } from 'stratal/errors'\n\nexport class TokenRequiredError extends HttpException {\n constructor() { super(401, 'Verification token is required') }\n}\n","import { APIError } from 'better-auth/api'\nimport { AuthError } from 'stratal/errors'\nimport type { ApplicationError } from 'stratal/errors'\nimport {\n AccountAlreadyExistsError,\n AccountNotFoundError,\n AuthValidationFailedError,\n CannotUnlinkLastAccountError,\n CredentialAccountNotFoundError,\n EmailAlreadyVerifiedError,\n EmailCannotBeUpdatedError,\n EmailMismatchError,\n EmailNotVerifiedError,\n IdTokenNotSupportedError,\n InvalidCallbackUrlError,\n InvalidCredentialsError,\n InvalidEmailError,\n InvalidOriginError,\n InvalidPasswordError,\n InvalidTokenError,\n OrganizationConflictError,\n OrganizationInvitationNotFoundError,\n OrganizationInvitationRecipientMismatchError,\n OrganizationLimitReachedError,\n OrganizationMemberNotFoundError,\n OrganizationMembershipError,\n OrganizationNotFoundError,\n OrganizationPermissionDeniedError,\n OrganizationRoleNotFoundError,\n OrganizationTeamNotFoundError,\n PasswordTooLongError,\n PasswordTooShortError,\n ProviderNotFoundError,\n SessionExpiredError,\n SocialAccountLinkedError,\n TokenExpiredError,\n UserAlreadyHasPasswordError,\n UserEmailNotFoundError,\n UserNotFoundError,\n} from '../errors'\n\n/**\n * Maps Better Auth API error codes to ApplicationError instances.\n */\nexport function mapBetterAuthError(error: APIError): ApplicationError {\n const errorCode = error.body?.code\n\n if (error.status === 'FOUND') {\n const headers = error.headers as Headers\n const location = headers.get('location') ?? ''\n\n if (location.includes('INVALID_TOKEN')) return new InvalidTokenError()\n if (location.includes('EXPIRED_TOKEN')) return new TokenExpiredError()\n if (location.includes('ATTEMPTS_EXCEEDED')) return new InvalidTokenError()\n if (location.includes('new_user_signup_disabled')) return new UserNotFoundError()\n if (location.includes('failed_to_create_user')) return new AuthError('Failed to create user')\n if (location.includes('failed_to_create_session')) return new AuthError('Failed to create session')\n }\n\n if (!errorCode) {\n return new AuthError('An authentication error occurred')\n }\n\n // ── Base Error Codes ──────────────────────────────────────────────────\n\n // User errors\n if (errorCode === 'USER_NOT_FOUND' || errorCode === 'INVALID_USER') return new UserNotFoundError()\n if (errorCode === 'USER_EMAIL_NOT_FOUND') return new UserEmailNotFoundError()\n\n // Credential errors\n if (errorCode === 'INVALID_EMAIL_OR_PASSWORD') return new InvalidCredentialsError()\n if (errorCode === 'INVALID_PASSWORD') return new InvalidPasswordError()\n if (errorCode === 'INVALID_EMAIL') return new InvalidEmailError()\n\n // Session errors\n if (errorCode === 'SESSION_EXPIRED' || errorCode === 'SESSION_NOT_FRESH') return new SessionExpiredError()\n if (errorCode === 'FAILED_TO_CREATE_SESSION') return new AuthError('Failed to create session')\n if (errorCode === 'FAILED_TO_GET_SESSION') return new AuthError('Failed to retrieve session')\n\n // Email verification\n if (errorCode === 'EMAIL_NOT_VERIFIED') return new EmailNotVerifiedError()\n if (errorCode === 'EMAIL_CAN_NOT_BE_UPDATED') return new EmailCannotBeUpdatedError()\n if (errorCode === 'EMAIL_ALREADY_VERIFIED') return new EmailAlreadyVerifiedError()\n if (errorCode === 'EMAIL_MISMATCH') return new EmailMismatchError()\n\n // Password validation\n if (errorCode === 'PASSWORD_TOO_SHORT') return new PasswordTooShortError(8)\n if (errorCode === 'PASSWORD_TOO_LONG') return new PasswordTooLongError(128)\n\n // Account errors\n if (errorCode === 'USER_ALREADY_EXISTS' || errorCode === 'USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL') {\n return new AccountAlreadyExistsError()\n }\n if (errorCode === 'ACCOUNT_NOT_FOUND') return new AccountNotFoundError()\n if (errorCode === 'CREDENTIAL_ACCOUNT_NOT_FOUND') return new CredentialAccountNotFoundError()\n if (errorCode === 'FAILED_TO_UNLINK_LAST_ACCOUNT') return new CannotUnlinkLastAccountError()\n\n // User creation/update errors\n if (errorCode === 'FAILED_TO_CREATE_USER') return new AuthError('Failed to create user')\n if (errorCode === 'FAILED_TO_UPDATE_USER') return new AuthError('Failed to update user')\n if (errorCode === 'FAILED_TO_GET_USER_INFO') return new AuthError('Failed to retrieve user info')\n\n // Social account errors\n if (errorCode === 'SOCIAL_ACCOUNT_ALREADY_LINKED' || errorCode === 'LINKED_ACCOUNT_ALREADY_EXISTS') {\n return new SocialAccountLinkedError()\n }\n if (errorCode === 'PROVIDER_NOT_FOUND') return new ProviderNotFoundError()\n\n // Token errors\n if (errorCode === 'ID_TOKEN_NOT_SUPPORTED') return new IdTokenNotSupportedError()\n if (errorCode === 'INVALID_TOKEN') return new InvalidTokenError()\n if (errorCode === 'TOKEN_EXPIRED') return new TokenExpiredError()\n\n // Password management\n if (errorCode === 'USER_ALREADY_HAS_PASSWORD' || errorCode === 'PASSWORD_ALREADY_SET') {\n return new UserAlreadyHasPasswordError()\n }\n\n // Callback/redirect URL errors\n if (\n errorCode === 'INVALID_CALLBACK_URL'\n || errorCode === 'INVALID_REDIRECT_URL'\n || errorCode === 'INVALID_NEW_USER_CALLBACK_URL'\n || errorCode === 'INVALID_ERROR_CALLBACK_URL'\n || errorCode === 'CALLBACK_URL_REQUIRED'\n ) {\n return new InvalidCallbackUrlError()\n }\n\n // Origin/CORS errors\n if (\n errorCode === 'INVALID_ORIGIN'\n || errorCode === 'MISSING_OR_NULL_ORIGIN'\n || errorCode === 'CROSS_SITE_NAVIGATION_LOGIN_BLOCKED'\n ) {\n return new InvalidOriginError()\n }\n\n // Validation errors\n if (\n errorCode === 'VALIDATION_ERROR'\n || errorCode === 'MISSING_FIELD'\n || errorCode === 'FIELD_NOT_ALLOWED'\n || errorCode === 'BODY_MUST_BE_AN_OBJECT'\n || errorCode === 'ASYNC_VALIDATION_NOT_SUPPORTED'\n || errorCode === 'METHOD_NOT_ALLOWED_DEFER_SESSION_REQUIRED'\n ) {\n return new AuthValidationFailedError()\n }\n\n // Verification errors\n if (errorCode === 'FAILED_TO_CREATE_VERIFICATION' || errorCode === 'VERIFICATION_EMAIL_NOT_ENABLED') {\n return new AuthError('Failed to create session')\n }\n\n // ── Organization Plugin Error Codes ───────────────────────────────────\n\n // Organization not found\n if (errorCode === 'ORGANIZATION_NOT_FOUND' || errorCode === 'NO_ACTIVE_ORGANIZATION') {\n return new OrganizationNotFoundError()\n }\n\n // Member not found\n if (\n errorCode === 'MEMBER_NOT_FOUND'\n || errorCode === 'USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION'\n || errorCode === 'USER_IS_NOT_A_MEMBER_OF_THE_TEAM'\n ) {\n return new OrganizationMemberNotFoundError()\n }\n\n // Invitation not found\n if (errorCode === 'INVITATION_NOT_FOUND' || errorCode === 'FAILED_TO_RETRIEVE_INVITATION') {\n return new OrganizationInvitationNotFoundError()\n }\n\n // Invitation recipient mismatch\n if (\n errorCode === 'YOU_ARE_NOT_THE_RECIPIENT_OF_THE_INVITATION'\n || errorCode === 'EMAIL_VERIFICATION_REQUIRED_BEFORE_ACCEPTING_OR_REJECTING_INVITATION'\n ) {\n return new OrganizationInvitationRecipientMismatchError()\n }\n\n // Team not found\n if (errorCode === 'TEAM_NOT_FOUND' || errorCode === 'YOU_DO_NOT_HAVE_AN_ACTIVE_TEAM') {\n return new OrganizationTeamNotFoundError()\n }\n\n // Role not found\n if (errorCode === 'ROLE_NOT_FOUND' || errorCode === 'INVALID_RESOURCE') {\n return new OrganizationRoleNotFoundError()\n }\n\n // Organization conflict/already exists\n if (\n errorCode === 'ORGANIZATION_ALREADY_EXISTS'\n || errorCode === 'ORGANIZATION_SLUG_ALREADY_TAKEN'\n || errorCode === 'USER_IS_ALREADY_A_MEMBER_OF_THIS_ORGANIZATION'\n || errorCode === 'USER_IS_ALREADY_INVITED_TO_THIS_ORGANIZATION'\n || errorCode === 'TEAM_ALREADY_EXISTS'\n || errorCode === 'ROLE_NAME_IS_ALREADY_TAKEN'\n ) {\n return new OrganizationConflictError()\n }\n\n // Organization limit reached\n if (\n errorCode === 'YOU_HAVE_REACHED_THE_MAXIMUM_NUMBER_OF_ORGANIZATIONS'\n || errorCode === 'YOU_HAVE_REACHED_THE_MAXIMUM_NUMBER_OF_TEAMS'\n || errorCode === 'ORGANIZATION_MEMBERSHIP_LIMIT_REACHED'\n || errorCode === 'INVITATION_LIMIT_REACHED'\n || errorCode === 'TEAM_MEMBER_LIMIT_REACHED'\n || errorCode === 'TOO_MANY_ROLES'\n ) {\n return new OrganizationLimitReachedError()\n }\n\n // Organization membership constraints\n if (\n errorCode === 'YOU_CANNOT_LEAVE_THE_ORGANIZATION_AS_THE_ONLY_OWNER'\n || errorCode === 'YOU_CANNOT_LEAVE_THE_ORGANIZATION_WITHOUT_AN_OWNER'\n || errorCode === 'UNABLE_TO_REMOVE_LAST_TEAM'\n || errorCode === 'CANNOT_DELETE_A_PRE_DEFINED_ROLE'\n || errorCode === 'ROLE_IS_ASSIGNED_TO_MEMBERS'\n || errorCode === 'YOU_CANNOT_IMPERSONATE_ADMINS'\n || errorCode === 'YOU_CANNOT_BAN_YOURSELF'\n || errorCode === 'YOU_CANNOT_REMOVE_YOURSELF'\n || errorCode === 'INVITER_IS_NO_LONGER_A_MEMBER_OF_THE_ORGANIZATION'\n ) {\n return new OrganizationMembershipError()\n }\n\n // Organization permission denied (catch-all for YOU_ARE_NOT_ALLOWED_TO_* patterns)\n if (\n errorCode.startsWith('YOU_ARE_NOT_ALLOWED_TO_')\n || errorCode === 'YOU_ARE_NOT_A_MEMBER_OF_THIS_ORGANIZATION'\n || errorCode === 'YOU_CAN_NOT_ACCESS_THE_MEMBERS_OF_THIS_TEAM'\n || errorCode === 'YOU_MUST_BE_IN_AN_ORGANIZATION_TO_CREATE_A_ROLE'\n || errorCode === 'MISSING_AC_INSTANCE'\n ) {\n return new OrganizationPermissionDeniedError()\n }\n\n // Unknown error code\n return new AuthError('An authentication error occurred')\n}\n\n/**\n * Type guard to check if an error is a Better Auth APIError.\n * Uses duck typing to handle bundler environments (e.g. Vite)\n * where instanceof may fail across module boundaries.\n */\nexport function isAPIError(error: unknown): error is APIError {\n if (error instanceof APIError) return true\n\n return (\n error instanceof Error\n && error.name === 'APIError'\n && 'status' in error\n && 'statusCode' in error\n )\n}\n","import type { BetterAuthOptions } from 'better-auth'\nimport { isAPIError, mapBetterAuthError } from './better-auth-error-handler'\n\n/**\n * Get shared Better Auth error handler configuration.\n * Use this in Better Auth config's onAPIError option.\n */\nexport function getErrorHandlerConfig(): BetterAuthOptions['onAPIError'] {\n return {\n throw: false,\n onError: (error) => {\n if (isAPIError(error)) {\n throw mapBetterAuthError(error)\n }\n throw error\n },\n }\n}\n\n/**\n * Wrap a Better Auth function in a try/catch block and map errors to ApplicationError.\n */\nexport const wrapBetterAuth = async <T>(fn: () => Promise<T>): Promise<T> => {\n try {\n return await fn()\n } catch (error) {\n if (isAPIError(error)) {\n throw mapBetterAuthError(error)\n }\n throw error\n }\n}\n","import type { Auth, BetterAuthOptions } from 'better-auth';\nimport { betterAuth } from 'better-auth/minimal';\nimport { inject, Request } from 'stratal/di';\nimport { AUTH_OPTIONS, AUTH_SERVICE } from '../auth.tokens';\nimport { getErrorHandlerConfig } from '../utils';\n\n/**\n * AuthService\n *\n * Base authentication service using Better Auth.\n * Configured via AuthModule.forRootAsync() from the application layer.\n *\n * **Extensibility:**\n * Extend this class to add custom methods. Subclasses inherit\n * `@Request(AUTH_SERVICE)` scope automatically — no decorator needed.\n *\n * @example\n * ```typescript\n * @Request(AUTH_SERVICE)\n * export class AppAuthService extends AuthService<AuthOptions> {\n * async signInMagicLink(email: string) {\n * return wrapBetterAuth(async () => {\n * return this.auth.api.signInMagicLink({ body: { email }, headers: new Headers() })\n * })\n * }\n * }\n * ```\n */\n@Request(AUTH_SERVICE)\nexport class AuthService<TOptions extends BetterAuthOptions = BetterAuthOptions> {\n private _authInstance?: Auth<TOptions>\n\n constructor(\n @inject(AUTH_OPTIONS) protected readonly options: TOptions\n ) {}\n\n /**\n * Get the Better Auth instance.\n */\n get auth(): Auth<TOptions> {\n this._authInstance ??= betterAuth({\n ...this.options,\n onAPIError: getErrorHandlerConfig()\n }) as Auth<TOptions>;\n\n return this._authInstance\n }\n}\n","/**\n * Auth Module\n *\n * Provides configurable authentication using Better Auth.\n * Use `forRootAsync` to configure Better Auth options from the application layer.\n *\n * Optionally pass `accessControl` to enable permission-based authorization.\n * This auto-adds the Stratal AC plugin to Better Auth and registers `AccessService`.\n *\n * @example Without access control\n * ```typescript\n * @Module({\n * imports: [\n * AuthModule.forRootAsync({\n * inject: [DI_TOKENS.Database, CONFIG_TOKENS.ConfigService],\n * useFactory: (db, config) => createAuthOptions(db, config)\n * })\n * ]\n * })\n * export class AppModule {}\n * ```\n *\n * @example With access control\n * ```typescript\n * import { createAccessControl } from '@stratal/framework/access-control'\n * import { admin } from 'better-auth/plugins'\n *\n * const permissions = createAccessControl({\n * resources: { posts: ['create', 'read', 'update', 'delete'] } as const,\n * roles: { admin: { posts: ['create', 'read', 'update', 'delete'] }, user: { posts: ['read'] } },\n * })\n *\n * @Module({\n * imports: [\n * AuthModule.forRootAsync({\n * inject: [DI_TOKENS.Database],\n * useFactory: (db) => ({\n * database: ...,\n * plugins: [admin({ ...permissions })],\n * }),\n * accessControl: permissions,\n * })\n * ]\n * })\n * ```\n */\n\nimport type { BetterAuthOptions } from 'better-auth'\nimport { CONTAINER_TOKEN, type Container } from 'stratal/di'\nimport type { AsyncModuleOptions, DynamicModule } from 'stratal/module'\nimport { Module } from 'stratal/module'\nimport type { IRateLimiterStore, RateLimiterRegistry } from 'stratal/rate-limiter'\nimport { RATE_LIMITER_TOKENS } from 'stratal/rate-limiter'\nimport type { RouteConfigurable, Router } from 'stratal/router'\nimport { createStratalAcPlugin } from '../access-control/plugin'\nimport { AccessService } from '../access-control/services/access.service'\nimport { AC_TOKENS } from '../access-control/tokens'\nimport type { AccessControlOptions } from '../access-control/types'\nimport { AuthContext } from '../context/auth-context'\n// Side-effect import: registers the `user()` macro on `RouterContext` and its\n// type augmentation, backed by the request-scoped `AuthContext`.\nimport '../context/router-context.augment'\nimport { AUTH_OPTIONS, AUTH_SERVICE } from './auth.tokens'\nimport { SessionVerificationMiddleware } from './middleware/session-verification.middleware'\n// Side-effect import: registers `forPath`/`pathEntries` macros on\n// `RateLimiterRegistry` and the `declare module` augmentation that exposes\n// them at the type level. Must run before any consumer calls `forPath()`.\nimport {\n createBetterAuthRateLimitStorage,\n projectCustomRules,\n} from './rate-limit-bridge'\nimport { AuthService } from './services/auth.service'\n\nexport interface AuthModuleAsyncOptions<TOptions extends BetterAuthOptions = BetterAuthOptions>\n extends AsyncModuleOptions<TOptions> {\n /**\n * Optional access control configuration.\n * When provided, registers AccessService and auto-adds the Stratal AC plugin to Better Auth.\n */\n accessControl?: AccessControlOptions\n}\n\n@Module({\n providers: [AuthContext]\n})\nexport class AuthModule implements RouteConfigurable {\n /**\n * Configure auth middleware globally.\n *\n * SessionVerificationMiddleware verifies the session and populates the\n * request-scoped AuthContext with the authenticated user.\n */\n configureRoutes(router: Router): void {\n router.use(SessionVerificationMiddleware)\n }\n\n /**\n * Configure AuthModule with async options factory.\n * Optionally provide `accessControl` to enable permission-based authorization.\n *\n * When `RateLimiterModule` is also imported, better-auth's `rateLimit`\n * block is auto-wired: `customStorage` shares Stratal's backing store, and\n * any `RateLimiterRegistry.forPath(...)` entries are projected into\n * `customRules`. User-supplied `rateLimit.{customStorage, customRules}` keys\n * take precedence on a per-key basis.\n */\n static forRootAsync<TOptions extends BetterAuthOptions>(\n options: AuthModuleAsyncOptions<TOptions>\n ): DynamicModule {\n const { accessControl } = options\n const userInject = options.inject ?? []\n const userFactory = options.useFactory as (...args: unknown[]) => TOptions\n\n const authOptionsProvider = {\n provide: AUTH_OPTIONS,\n useFactory: (container: Container, ...userDeps: unknown[]): BetterAuthOptions => {\n let raw = userFactory(...userDeps) as BetterAuthOptions\n\n if (accessControl) {\n raw = {\n ...raw,\n plugins: [createStratalAcPlugin(accessControl), ...(raw.plugins ?? [])],\n }\n }\n\n const rateLimiterPresent = container.isRegistered(\n RATE_LIMITER_TOKENS.ModuleMarker,\n )\n\n if (rateLimiterPresent) {\n const store = container.resolve<IRateLimiterStore>(RATE_LIMITER_TOKENS.Store)\n const registry = container.resolve<RateLimiterRegistry>(RATE_LIMITER_TOKENS.Registry)\n\n raw = {\n ...raw,\n rateLimit: {\n enabled: true,\n ...raw.rateLimit,\n customStorage: raw.rateLimit?.customStorage ?? createBetterAuthRateLimitStorage(store),\n customRules: {\n ...projectCustomRules(registry),\n ...(raw.rateLimit?.customRules ?? {}),\n },\n },\n }\n }\n\n return raw\n },\n inject: [CONTAINER_TOKEN, ...userInject],\n }\n\n return {\n module: AuthModule,\n providers: [\n authOptionsProvider,\n {\n provide: AUTH_SERVICE,\n useClass: AuthService,\n },\n ...(accessControl\n ? [\n { provide: AC_TOKENS.Options, useValue: accessControl as unknown as object },\n { provide: AC_TOKENS.AccessService, useClass: AccessService },\n ]\n : []),\n ],\n }\n }\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
- import { t as __decorate } from "./decorate-DViXs-0l.mjs";
2
- import { n as UserNotAuthenticatedError } from "./errors-opqKWAoa.mjs";
3
- import { DI_TOKENS, Transient } from "stratal/di";
1
+ import { t as __decorate } from "./decorate-7CAoTBu4.mjs";
2
+ import { n as UserNotAuthenticatedError } from "./errors-MCyrn_V2.mjs";
3
+ import { DI_TOKENS, Request } from "stratal/di";
4
4
  import { AuthError } from "stratal/errors";
5
5
  //#region src/context/auth-context.ts
6
6
  let AuthContext = class AuthContext {
@@ -79,8 +79,8 @@ let AuthContext = class AuthContext {
79
79
  this.user = void 0;
80
80
  }
81
81
  };
82
- AuthContext = __decorate([Transient(DI_TOKENS.AuthContext)], AuthContext);
82
+ AuthContext = __decorate([Request(DI_TOKENS.AuthContext)], AuthContext);
83
83
  //#endregion
84
84
  export { AuthContext as t };
85
85
 
86
- //# sourceMappingURL=auth-context-MWdQipaK.mjs.map
86
+ //# sourceMappingURL=auth-context-CObJcW0t.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-context-CObJcW0t.mjs","names":[],"sources":["../src/context/auth-context.ts"],"sourcesContent":["import type { BaseUser } from '@better-auth/core/db'\nimport { Request, DI_TOKENS } from 'stratal/di'\nimport { AuthError } from 'stratal/errors'\nimport {\n UserNotAuthenticatedError\n} from './errors'\n\n/**\n * Authenticated user shape stored in {@link AuthContext}.\n *\n * Inherits Better Auth's base user fields. Apps whose schema stores\n * `firstName`/`lastName` instead of a `name` column should expose a `name`\n * via a ZenStack result extension (see\n * https://zenstack.dev/docs/orm/plugins/extending-orm-client#adding-fields-to-query-results)\n * so reads return a populated `name` for free.\n *\n * Augment via TypeScript module declaration to add app-specific fields. Match\n * the augmentation to whatever your Better Auth `user.additionalFields` /\n * plugins are configured to return:\n *\n * @example\n * ```ts\n * declare module '@stratal/framework/context' {\n * interface AuthUser {\n * role: string\n * locale: string\n * }\n * }\n * ```\n */\nexport interface AuthUser extends BaseUser {}\n\nexport interface AuthInfo {\n user: AuthUser\n}\n\n@Request(DI_TOKENS.AuthContext)\nexport class AuthContext {\n protected user?: AuthUser\n\n /**\n * Set authentication context.\n * This should be called once per request with the authenticated user.\n */\n setAuthContext(info: AuthInfo): void {\n this.user = info.user\n }\n\n /**\n * Get the authenticated user if available.\n * Returns undefined if no user is authenticated.\n */\n getUser(): AuthUser | undefined {\n return this.user\n }\n\n /**\n * Get the authenticated user or throw if not authenticated.\n */\n requireUser(): AuthUser {\n if (!this.user) {\n throw new UserNotAuthenticatedError()\n }\n return this.user\n }\n\n /**\n * Get user ID if available.\n * Returns undefined if no user is authenticated.\n */\n getUserId(): string | undefined {\n return this.user?.id\n }\n\n /**\n * Get user ID or throw if not authenticated.\n * Use this when authentication is required.\n */\n requireUserId(): string {\n return this.requireUser().id\n }\n\n /**\n * Get full authentication context or throw if not initialized.\n */\n getAuthInfo(): AuthInfo {\n if (!this.user) {\n throw new AuthError('Auth context has not been initialized')\n }\n return { user: this.user }\n }\n\n /**\n * Get the raw role string from the authenticated user.\n *\n * Reads from `user.role` — apps that use roles should augment {@link AuthUser}\n * with `role: string` (or similar) so this returns a typed value.\n */\n getRole(): string | undefined {\n return (this.user as { role?: string } | undefined)?.role\n }\n\n /**\n * Get the user's roles as an array.\n * Returns an empty array if no role is set or user is not authenticated.\n */\n getRoles(): string[] {\n const role = this.getRole()\n if (!role) return []\n return role.split(',').map(r => r.trim()).filter(Boolean)\n }\n\n /**\n * Check if user is authenticated.\n */\n isAuthenticated(): boolean {\n return !!this.user\n }\n\n /**\n * Clear authentication context.\n * Useful for testing or cleanup.\n */\n clearAuthContext(): void {\n this.user = undefined\n }\n}\n"],"mappings":";;;;;AAqCO,IAAA,cAAA,MAAM,YAAY;CACvB;;;;;CAMA,eAAe,MAAsB;EACnC,KAAK,OAAO,KAAK;;;;;;CAOnB,UAAgC;EAC9B,OAAO,KAAK;;;;;CAMd,cAAwB;EACtB,IAAI,CAAC,KAAK,MACR,MAAM,IAAI,2BAA2B;EAEvC,OAAO,KAAK;;;;;;CAOd,YAAgC;EAC9B,OAAO,KAAK,MAAM;;;;;;CAOpB,gBAAwB;EACtB,OAAO,KAAK,aAAa,CAAC;;;;;CAM5B,cAAwB;EACtB,IAAI,CAAC,KAAK,MACR,MAAM,IAAI,UAAU,wCAAwC;EAE9D,OAAO,EAAE,MAAM,KAAK,MAAM;;;;;;;;CAS5B,UAA8B;EAC5B,OAAQ,KAAK,MAAwC;;;;;;CAOvD,WAAqB;EACnB,MAAM,OAAO,KAAK,SAAS;EAC3B,IAAI,CAAC,MAAM,OAAO,EAAE;EACpB,OAAO,KAAK,MAAM,IAAI,CAAC,KAAI,MAAK,EAAE,MAAM,CAAC,CAAC,OAAO,QAAQ;;;;;CAM3D,kBAA2B;EACzB,OAAO,CAAC,CAAC,KAAK;;;;;;CAOhB,mBAAyB;EACvB,KAAK,OAAO,KAAA;;;0BAxFf,QAAQ,UAAU,YAAY,CAAA,EAAA,YAAA"}
@@ -82,4 +82,4 @@ declare class AuthContext {
82
82
  }
83
83
  //#endregion
84
84
  export { AuthInfo as n, AuthUser as r, AuthContext as t };
85
- //# sourceMappingURL=auth-context-DXSTlnQH.d.mts.map
85
+ //# sourceMappingURL=auth-context-D70ktWUf.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth-context-DXSTlnQH.d.mts","names":[],"sources":["../src/context/auth-context.ts"],"mappings":";;;;;AA8BA;;;;;AAEA;;;;;AAIA;;;;;;;;;;;UANiB,QAAA,SAAiB,QAAA;AAAA,UAEjB,QAAA;EACf,IAAA,EAAM,QAAA;AAAA;AAAA,cAIK,WAAA;EAAA,UACD,IAAA,GAAO,QAAA;EAqBF;;;;EAff,cAAA,CAAe,IAAA,EAAM,QAAA;EAsDrB;;;;EA9CA,OAAA,CAAA,GAAW,QAAA;EAuEK;;;EAhEhB,WAAA,CAAA,GAAe,QAAA;;;;;EAWf,SAAA,CAAA;;;;;EAQA,aAAA,CAAA;;;;EAOA,WAAA,CAAA,GAAe,QAAA;;;;;;;EAaf,OAAA,CAAA;;;;;EAQA,QAAA,CAAA;;;;EASA,eAAA,CAAA;;;;;EAQA,gBAAA,CAAA;AAAA"}
1
+ {"version":3,"file":"auth-context-D70ktWUf.d.mts","names":[],"sources":["../src/context/auth-context.ts"],"mappings":";;;;;AA8BA;;;;;AAEA;;;;;AAIA;;;;;;;;;;;UANiB,QAAA,SAAiB,QAAA;AAAA,UAEjB,QAAA;EACf,IAAA,EAAM,QAAA;AAAA;AAAA,cAIK,WAAA;EAAA,UACD,IAAA,GAAO,QAAA;EAqBF;;;;EAff,cAAA,CAAe,IAAA,EAAM,QAAA;EAsDrB;;;;EA9CA,OAAA,CAAA,GAAW,QAAA;EAuEK;;;EAhEhB,WAAA,CAAA,GAAe,QAAA;;;;;EAWf,SAAA,CAAA;;;;;EAQA,aAAA,CAAA;;;;EAOA,WAAA,CAAA,GAAe,QAAA;;;;;;;EAaf,OAAA,CAAA;;;;;EAQA,QAAA,CAAA;;;;EASA,eAAA,CAAA;;;;;EAQA,gBAAA,CAAA;AAAA"}
@@ -1,4 +1,4 @@
1
- import { n as AuthInfo, r as AuthUser, t as AuthContext } from "../auth-context-DXSTlnQH.mjs";
1
+ import { n as AuthInfo, r as AuthUser, t as AuthContext } from "../auth-context-D70ktWUf.mjs";
2
2
  import { HttpException } from "stratal/errors";
3
3
 
4
4
  //#region src/context/errors/user-not-authenticated.error.d.ts
@@ -1,3 +1,3 @@
1
- import { n as UserNotAuthenticatedError, t as UserNotAuthorizedError } from "../errors-opqKWAoa.mjs";
2
- import { t as AuthContext } from "../auth-context-MWdQipaK.mjs";
1
+ import { n as UserNotAuthenticatedError, t as UserNotAuthorizedError } from "../errors-MCyrn_V2.mjs";
2
+ import { t as AuthContext } from "../auth-context-CObJcW0t.mjs";
3
3
  export { AuthContext, UserNotAuthenticatedError, UserNotAuthorizedError };
@@ -1,3 +1,3 @@
1
- import { a as InferConnectionSchema, i as InferConnectionExtensions, n as DefaultConnectionName, o as InternalDatabaseEventContext, r as InferAnySchema, s as StratalDatabase, t as ConnectionName } from "../types-BZlcRR2M.mjs";
2
- import { A as DatabaseModule, C as UniqueConstraintError, D as connectionSymbol, E as DATABASE_TOKENS, O as DatabaseService, S as fromZenStackError, T as InjectDB, _ as EventPhase, a as DbPushCommand, b as ModelName, c as ZenStackCommand, d as EventEmitterPluginOptions, f as ErrorHandlerPlugin, g as DatabaseOperation, h as DatabaseEvents, i as MigrateDeployCommand, j as DatabaseModuleConfig, k as DatabaseConnectionConfig, l as SchemaSwitcher, m as DatabaseEventName, n as MigrateResetCommand, o as DbPullCommand, p as databaseMessages, r as MigrateDevCommand, s as DbGenerateCommand, t as MigrateStatusCommand, u as EventEmitterPlugin, v as GetData, w as RecordNotFoundError, x as ParseEvent, y as GetResult } from "../index-Cnx6eRgJ.mjs";
1
+ import { a as InferConnectionSchema, i as InferConnectionExtensions, n as DefaultConnectionName, o as InternalDatabaseEventContext, r as InferAnySchema, s as StratalDatabase, t as ConnectionName } from "../types-4uX3XKRM.mjs";
2
+ import { A as DatabaseModule, C as UniqueConstraintError, D as connectionSymbol, E as DATABASE_TOKENS, O as DatabaseService, S as fromZenStackError, T as InjectDB, _ as EventPhase, a as DbPushCommand, b as ModelName, c as ZenStackCommand, d as EventEmitterPluginOptions, f as ErrorHandlerPlugin, g as DatabaseOperation, h as DatabaseEvents, i as MigrateDeployCommand, j as DatabaseModuleConfig, k as DatabaseConnectionConfig, l as SchemaSwitcher, m as DatabaseEventName, n as MigrateResetCommand, o as DbPullCommand, p as databaseMessages, r as MigrateDevCommand, s as DbGenerateCommand, t as MigrateStatusCommand, u as EventEmitterPlugin, v as GetData, w as RecordNotFoundError, x as ParseEvent, y as GetResult } from "../index-NV8eVa_e.mjs";
3
3
  export { ConnectionName, DATABASE_TOKENS, DatabaseConnectionConfig, DatabaseEventName, DatabaseEvents, DatabaseModule, DatabaseModuleConfig, DatabaseOperation, DatabaseService, DbGenerateCommand, DbPullCommand, DbPushCommand, DefaultConnectionName, ErrorHandlerPlugin, EventEmitterPlugin, EventEmitterPluginOptions, EventPhase, GetData, GetResult, InferAnySchema, InferConnectionExtensions, InferConnectionSchema, InjectDB, InternalDatabaseEventContext, MigrateDeployCommand, MigrateDevCommand, MigrateResetCommand, MigrateStatusCommand, ModelName, ParseEvent, RecordNotFoundError, SchemaSwitcher, StratalDatabase, UniqueConstraintError, ZenStackCommand, connectionSymbol, databaseMessages, fromZenStackError };
@@ -1,12 +1,11 @@
1
- import { t as __decorateMetadata } from "../decorateMetadata-D5WUsc6Y.mjs";
2
- import { t as __decorate } from "../decorate-DViXs-0l.mjs";
3
- import { DI_TOKENS, Scope, Transient, delay } from "stratal/di";
1
+ import { t as __decorate } from "../decorate-7CAoTBu4.mjs";
2
+ import { DI_TOKENS, Transient, inject, lazy } from "stratal/di";
4
3
  import { Module } from "stratal/module";
5
4
  import { DatabaseError, HttpException } from "stratal/errors";
6
- import { inject as inject$1 } from "tsyringe";
7
5
  import { I18nModule } from "stratal/i18n";
8
6
  import { Command } from "stratal/quarry";
9
7
  import { ORMError, ORMErrorReason, ZenStackClient } from "@zenstackhq/orm";
8
+ import { AsyncLocalStorage } from "node:async_hooks";
10
9
  import { withZodI18n, z } from "stratal/validation";
11
10
  //#region src/database/commands/zenstack.command.ts
12
11
  /**
@@ -278,12 +277,42 @@ z.object({
278
277
  const names = config.connections.map((c) => c.name);
279
278
  return new Set(names).size === names.length;
280
279
  }, withZodI18n("database.duplicateConnections")).refine((config) => config.connections.some((c) => c.name === config.default), withZodI18n("database.defaultConnectionNotFound"));
280
+ /**
281
+ * Wrap a ZenStack client so `$transaction` is reentrant: when a transaction is
282
+ * already open on this connection (tracked per-connection via
283
+ * {@link AsyncLocalStorage}), nested calls run within the active transaction's
284
+ * client instead of opening a new one. ZenStack only reuses a connection when
285
+ * `$transaction` is called on a transaction client; callers holding the base
286
+ * client (e.g. the better-auth adapter, which since better-auth 1.6.11 nests
287
+ * transactions to atomically consume verification rows) would otherwise open a
288
+ * fresh transaction. On a small pool (e.g. a Hyperdrive-fronted `max: 1` pg
289
+ * pool) that inner transaction blocks forever waiting for the connection the
290
+ * outer one holds — a deadlock surfacing as a backend stuck `idle in
291
+ * transaction`. Reusing the active client is also the correct semantics: nested
292
+ * transactions form a single atomic unit.
293
+ *
294
+ * ZenStackClient's constructor returns a Proxy (for dynamic model accessors), so
295
+ * a subclass method override is shadowed — hence the proxy wrapper here.
296
+ */
297
+ function makeReentrantTransaction(client, activeTransaction) {
298
+ return new Proxy(client, { get(target, prop, receiver) {
299
+ if (prop !== "$transaction") return Reflect.get(target, prop, receiver);
300
+ const transaction = Reflect.get(target, prop);
301
+ return (input, options) => {
302
+ const active = activeTransaction.getStore();
303
+ if (active) return typeof input === "function" ? input(active) : active.$transaction(input, options);
304
+ if (typeof input !== "function") return transaction.call(target, input, options);
305
+ return transaction.call(target, (tx) => activeTransaction.run(tx, () => input(tx)), options);
306
+ };
307
+ } });
308
+ }
281
309
  function createDatabaseService(conn, eventRegistry) {
282
310
  const plugins = [
283
311
  new ErrorHandlerPlugin(),
284
312
  new EventEmitterPlugin({ eventRegistry }),
285
313
  ...conn.plugins ?? []
286
314
  ];
315
+ const activeTransaction = new AsyncLocalStorage();
287
316
  let DatabaseClient = class DatabaseClient extends ZenStackClient {
288
317
  constructor() {
289
318
  const dialect = conn.dialect();
@@ -292,9 +321,10 @@ function createDatabaseService(conn, eventRegistry) {
292
321
  plugins,
293
322
  computedFields: conn.computedFields
294
323
  });
324
+ return makeReentrantTransaction(this, activeTransaction);
295
325
  }
296
326
  };
297
- DatabaseClient = __decorate([Transient(), __decorateMetadata("design:paramtypes", [])], DatabaseClient);
327
+ DatabaseClient = __decorate([Transient()], DatabaseClient);
298
328
  return DatabaseClient;
299
329
  }
300
330
  //#endregion
@@ -338,13 +368,12 @@ let DatabaseModule = _DatabaseModule = class DatabaseModule {
338
368
  }]
339
369
  };
340
370
  }
341
- onInitialize(context) {
371
+ async onInitialize(context) {
342
372
  const config = context.container.resolve(DATABASE_TOKENS.Options);
343
- const eventRegistry = context.container.resolve(DI_TOKENS.EventRegistry);
344
- const container = context.container.getTsyringeContainer();
373
+ const eventRegistry = (await context.container.resolve(DI_TOKENS.LazyModuleLoader).load(() => import("stratal/events").then((m) => m.EventsModule))).get(DI_TOKENS.EventRegistry);
345
374
  for (const conn of config.connections) {
346
375
  const Service = createDatabaseService(conn, eventRegistry);
347
- container.register(connectionSymbol(conn.name), delay(() => Service), { lifecycle: Scope.Request });
376
+ context.container.register(connectionSymbol(conn.name), lazy(() => Service));
348
377
  }
349
378
  context.container.registerExisting(DI_TOKENS.Database, connectionSymbol(config.default));
350
379
  context.logger.info("DatabaseModule initialized");
@@ -368,7 +397,7 @@ DatabaseModule = _DatabaseModule = __decorate([Module({
368
397
  //#endregion
369
398
  //#region src/database/decorators/inject-db.decorator.ts
370
399
  function InjectDB(name) {
371
- return inject$1(connectionSymbol(name));
400
+ return inject(connectionSymbol(name));
372
401
  }
373
402
  //#endregion
374
403
  export { DATABASE_TOKENS, DatabaseModule, DbGenerateCommand, DbPullCommand, DbPushCommand, ErrorHandlerPlugin, EventEmitterPlugin, InjectDB, MigrateDeployCommand, MigrateDevCommand, MigrateResetCommand, MigrateStatusCommand, RecordNotFoundError, SchemaSwitcher, UniqueConstraintError, ZenStackCommand, connectionSymbol, databaseMessages, fromZenStackError };
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["inject"],"sources":["../../src/database/commands/zenstack.command.ts","../../src/database/commands/db-generate.command.ts","../../src/database/commands/db-pull.command.ts","../../src/database/commands/db-push.command.ts","../../src/database/commands/migrate-deploy.command.ts","../../src/database/commands/migrate-dev.command.ts","../../src/database/commands/migrate-reset.command.ts","../../src/database/commands/migrate-status.command.ts","../../src/database/errors/record-not-found.error.ts","../../src/database/errors/unique-constraint.error.ts","../../src/database/errors/from-zenstack-error.ts","../../src/database/plugins/error-handler.plugin.ts","../../src/database/plugins/event-emitter.plugin.ts","../../src/database/plugins/schema-switcher.ts","../../src/database/database.helpers.ts","../../src/database/database.tokens.ts","../../src/database/i18n/en.ts","../../src/database/database.module.ts","../../src/database/decorators/inject-db.decorator.ts"],"sourcesContent":["import { Command } from 'stratal/quarry'\n\n/**\n * Base command for ZenStack CLI wrappers.\n * Uses execFileSync with array arguments to prevent shell injection.\n */\nexport abstract class ZenStackCommand extends Command {\n protected async zenstack(args: string[]): Promise<number> {\n // Dynamic import — node:child_process is only available in the Quarry CLI (Node) context\n const { execFileSync } = await import('node:child_process')\n\n try {\n const output = execFileSync('npx', ['zenstack', ...args], {\n encoding: 'utf-8',\n stdio: 'pipe',\n })\n if (output) this.info(output.trim())\n return 0\n } catch (err) {\n const error = err as { stderr?: string; stdout?: string; status?: number }\n if (error.stderr) this.error(error.stderr.trim())\n if (error.stdout) this.info(error.stdout.trim())\n return error.status ?? 1\n }\n }\n}\n","import { ZenStackCommand } from './zenstack.command'\n\nexport class DbGenerateCommand extends ZenStackCommand {\n static command = 'db:generate {--schema= : Path to schema file} {--watch : Enable watch mode}'\n static description = 'Generate ZenStack ORM client'\n\n async handle(): Promise<number> {\n const args = ['generate']\n const schema = this.string('schema')\n\n if (schema) args.push('--schema', schema)\n if (this.boolean('watch')) args.push('--watch')\n\n return this.zenstack(args)\n }\n}\n","import { ZenStackCommand } from './zenstack.command'\n\nexport class DbPullCommand extends ZenStackCommand {\n static command = 'db:pull {--schema= : Path to schema file}'\n static description = 'Introspect database and generate schema'\n\n async handle(): Promise<number> {\n const args = ['db', 'pull']\n const schema = this.string('schema')\n\n if (schema) args.push('--schema', schema)\n\n return this.zenstack(args)\n }\n}\n","import { ZenStackCommand } from './zenstack.command'\n\nexport class DbPushCommand extends ZenStackCommand {\n static command = 'db:push {--schema= : Path to schema file} {--accept-data-loss : Accept data loss} {--force-reset : Force reset database}'\n static description = 'Push database schema changes'\n\n async handle(): Promise<number> {\n const args = ['db', 'push']\n const schema = this.string('schema')\n\n if (schema) args.push('--schema', schema)\n if (this.boolean('accept-data-loss')) args.push('--accept-data-loss')\n if (this.boolean('force-reset')) args.push('--force-reset')\n\n return this.zenstack(args)\n }\n}\n","import { ZenStackCommand } from './zenstack.command'\n\nexport class MigrateDeployCommand extends ZenStackCommand {\n static command = 'migrate:deploy {--schema= : Path to schema file}'\n static description = 'Deploy pending migrations'\n\n async handle(): Promise<number> {\n const args = ['migrate', 'deploy']\n const schema = this.string('schema')\n\n if (schema) args.push('--schema', schema)\n\n return this.zenstack(args)\n }\n}\n","import { ZenStackCommand } from './zenstack.command'\n\nexport class MigrateDevCommand extends ZenStackCommand {\n static command = 'migrate:dev {--schema= : Path to schema file} {--name= : Migration name} {--create-only : Create without applying}'\n static description = 'Create and apply migration'\n\n async handle(): Promise<number> {\n const args = ['migrate', 'dev']\n const schema = this.string('schema')\n const name = this.string('name')\n\n if (schema) args.push('--schema', schema)\n if (name) args.push('--name', name)\n if (this.boolean('create-only')) args.push('--create-only')\n\n return this.zenstack(args)\n }\n}\n","import { ZenStackCommand } from './zenstack.command'\n\nexport class MigrateResetCommand extends ZenStackCommand {\n static command = 'migrate:reset {--schema= : Path to schema file} {--force : Skip confirmation} {--skip-seed : Skip seeding}'\n static description = 'Reset database'\n\n async handle(): Promise<number> {\n const args = ['migrate', 'reset']\n const schema = this.string('schema')\n\n if (schema) args.push('--schema', schema)\n if (this.boolean('force')) args.push('--force')\n if (this.boolean('skip-seed')) args.push('--skip-seed')\n\n return this.zenstack(args)\n }\n}\n","import { ZenStackCommand } from './zenstack.command'\n\nexport class MigrateStatusCommand extends ZenStackCommand {\n static command = 'migrate:status {--schema= : Path to schema file}'\n static description = 'Check migration status'\n\n async handle(): Promise<number> {\n const args = ['migrate', 'status']\n const schema = this.string('schema')\n\n if (schema) args.push('--schema', schema)\n\n return this.zenstack(args)\n }\n}\n","import { HttpException } from 'stratal/errors'\n\nexport class RecordNotFoundError extends HttpException {\n constructor(public readonly details?: string, cause?: unknown) {\n super(404, 'Record not found', cause)\n }\n}\n","import { HttpException } from 'stratal/errors'\n\nexport class UniqueConstraintError extends HttpException {\n constructor(public readonly fields?: string[], cause?: unknown) {\n super(409, 'Record already exists', cause)\n }\n}\n","import { ORMError, ORMErrorReason } from '@zenstackhq/orm';\nimport { type ApplicationError, DatabaseError } from 'stratal/errors';\nimport { RecordNotFoundError } from './record-not-found.error';\nimport { UniqueConstraintError } from './unique-constraint.error';\n\nexport function fromZenStackError(error: unknown): ApplicationError {\n if (error instanceof ORMError) {\n switch (error.reason) {\n case ORMErrorReason.NOT_FOUND:\n return new RecordNotFoundError(error.model, error)\n case ORMErrorReason.DB_QUERY_ERROR:\n return parseDatabaseError(error)\n case ORMErrorReason.INVALID_INPUT:\n return new DatabaseError('Invalid database query', error)\n case ORMErrorReason.CONFIG_ERROR:\n return new DatabaseError('Database configuration error', error)\n case ORMErrorReason.NOT_SUPPORTED:\n return new DatabaseError('Operation not supported', error)\n case ORMErrorReason.INTERNAL_ERROR:\n return new DatabaseError('Database internal error', error)\n default:\n return new DatabaseError('Database error', error)\n }\n }\n return new DatabaseError('Database error', error)\n}\n\nfunction parseDatabaseError(error: ORMError): ApplicationError {\n const dbErrorCode = error.dbErrorCode as string | undefined\n if (dbErrorCode) {\n if (dbErrorCode === '23505') return new UniqueConstraintError([error.model ?? 'unknown'], error)\n if (dbErrorCode === '23503') return new DatabaseError('Foreign key constraint violation', error)\n if (dbErrorCode === '23502') return new DatabaseError('Required field is missing', error)\n if (dbErrorCode === '23514') return new DatabaseError('Database constraint violated', error)\n if (dbErrorCode === '42P01') return new DatabaseError('Table does not exist', error)\n if (dbErrorCode === '42703') return new DatabaseError('Column does not exist', error)\n if (dbErrorCode.startsWith('42')) return new DatabaseError('Database syntax or access error', error)\n if (dbErrorCode.startsWith('08')) return new DatabaseError('Database connection failed', error)\n if (dbErrorCode === '57014') return new DatabaseError('Database query timeout', error)\n if (dbErrorCode.startsWith('40')) return new DatabaseError('Transaction conflict or deadlock', error)\n if (dbErrorCode === '53300') return new DatabaseError('Too many database connections', error)\n }\n return new DatabaseError('Database error', error)\n}\n","import { type RuntimePlugin } from '@zenstackhq/orm'\nimport { type SchemaDef } from '@zenstackhq/orm/schema'\nimport { fromZenStackError } from '../errors'\n\n/**\n * ZenStack runtime plugin that transforms ORM errors into ApplicationError instances.\n *\n * @example\n * ```typescript\n * super(schema, {\n * dialect: new PostgresDialect({ pool }),\n * plugins: [new ErrorHandlerPlugin()]\n * })\n * ```\n */\nexport class ErrorHandlerPlugin implements RuntimePlugin<SchemaDef, Record<string, unknown>, Record<string, unknown>, {}> {\n readonly id = 'error-handler'\n\n onQuery = async ({ args, proceed }: {\n args: Record<string, unknown> | undefined\n proceed: (args: Record<string, unknown> | undefined) => Promise<unknown>\n }): Promise<unknown> => {\n try {\n return await proceed(args)\n } catch (error) {\n throw fromZenStackError(error)\n }\n }\n}\n","import { type RuntimePlugin } from '@zenstackhq/orm'\nimport { type SchemaDef } from '@zenstackhq/orm/schema'\nimport type { EventName, IEventRegistry } from 'stratal/events'\n\nexport interface EventEmitterPluginOptions {\n eventRegistry: IEventRegistry\n}\n\n/**\n * ZenStack runtime plugin that emits before/after events for database operations.\n *\n * Emits events in the format:\n * - `before.{Model}.{operation}` - Before the database operation\n * - `after.{Model}.{operation}` - After the database operation\n *\n * @example\n * ```typescript\n * super(schema, {\n * dialect: new PostgresDialect({ pool }),\n * plugins: [\n * new EventEmitterPlugin({\n * eventRegistry,\n * })\n * ]\n * })\n * ```\n */\nexport class EventEmitterPlugin implements RuntimePlugin<SchemaDef, Record<string, unknown>, Record<string, unknown>, {}> {\n readonly id = 'event-emitter'\n\n constructor(private options: EventEmitterPluginOptions) { }\n\n onQuery = async ({ model, operation, args, proceed }: {\n model: string\n operation: string\n args: Record<string, unknown> | undefined\n proceed: (args: Record<string, unknown> | undefined) => Promise<unknown>\n }): Promise<unknown> => {\n const { eventRegistry } = this.options\n const eventBase = `${model}.${operation}`\n\n // Emit BEFORE event\n await eventRegistry.emit(`before.${eventBase}` as EventName, {\n data: args,\n })\n\n // Execute the actual database operation\n const result = await proceed(args)\n\n // Emit AFTER event\n await eventRegistry.emit(`after.${eventBase}` as EventName, {\n data: args,\n result,\n })\n\n return result\n }\n}\n","interface SwitchableClient {\n $schema: { provider: { defaultSchema: string } } & Record<string, unknown>\n schema: unknown\n}\n\n/**\n * Switches the active schema on a ZenStack/Kysely database client by mutating\n * `$schema.provider.defaultSchema`. This causes ZenStack's QueryNameMapper to\n * generate fully-qualified table references (e.g. `\"tenant_123\".\"User\"`).\n *\n * Must be called BEFORE any queries are made on the client.\n *\n * Note: The ZenStack RuntimePlugin `onQuery` hook fires after table names are\n * already resolved, so a plugin-based approach cannot set the schema prefix.\n * Direct client mutation is the only supported method.\n */\nexport class SchemaSwitcher {\n static apply<T>(client: T, schemaName: string): T {\n const c = client as unknown as SwitchableClient\n const switched = {\n ...c.$schema,\n provider: { ...c.$schema.provider, defaultSchema: schemaName },\n }\n c.$schema = switched\n c.schema = switched\n return client\n }\n}\n","import { ZenStackClient, type AnyPlugin } from '@zenstackhq/orm'\nimport { Transient } from 'stratal/di'\nimport type { IEventRegistry } from 'stratal/events'\nimport { withZodI18n, z } from 'stratal/validation'\nimport type { DatabaseConnectionConfig } from './database.module'\nimport { ErrorHandlerPlugin, EventEmitterPlugin } from './plugins'\n\nconst databaseConnectionSchema = z.object({\n name: z.string().min(1, withZodI18n('database.connectionNameRequired')),\n schema: z.object({}).loose(),\n dialect: z.function(),\n plugins: z.array(z.object({}).loose()).optional(),\n computedFields: z.object({}).loose().optional(),\n})\n\nexport const databaseModuleConfigSchema = z.object({\n default: z.string().min(1, withZodI18n('database.defaultConnectionRequired')),\n connections: z.array(databaseConnectionSchema).min(1, withZodI18n('database.connectionRequired')),\n}).refine(\n (config) => {\n const names = config.connections.map(c => c.name)\n return new Set(names).size === names.length\n },\n withZodI18n('database.duplicateConnections')\n).refine(\n (config) => config.connections.some(c => c.name === config.default),\n withZodI18n('database.defaultConnectionNotFound')\n)\n\nexport function createDatabaseService(\n conn: DatabaseConnectionConfig,\n eventRegistry: IEventRegistry,\n): new () => InstanceType<typeof ZenStackClient> {\n const plugins: AnyPlugin[] = [\n new ErrorHandlerPlugin(),\n new EventEmitterPlugin({\n eventRegistry,\n }),\n ...(conn.plugins ?? []),\n ]\n\n @Transient()\n class DatabaseClient extends ZenStackClient<typeof conn.schema> {\n constructor() {\n const dialect = conn.dialect()\n // ZenStack 3+ requires `computedFields` whenever the schema declares any\n // `@computed` fields, so pass them through when the consumer provides them.\n super(conn.schema, {\n dialect,\n plugins,\n // @ts-expect-error - ZenStack 3+ requires `computedFields` whenever the schema declares any `@computed` fields, so pass them through when the consumer provides them.\n computedFields: conn.computedFields\n })\n }\n }\n\n return DatabaseClient\n}\n","export const DATABASE_TOKENS = {\n Options: Symbol.for('stratal:database:options'),\n Services: Symbol.for('stratal:database:services'),\n} as const\n\nimport type { ConnectionName } from './types'\n\nexport function connectionSymbol(name: ConnectionName): symbol {\n return Symbol.for(`stratal:database:connection:${name}`)\n}\n","export const databaseMessages = {\n en: {\n connectionNameRequired: 'Connection name is required',\n defaultConnectionRequired: 'Default connection name is required',\n connectionRequired: 'At least one connection is required',\n duplicateConnections: 'Duplicate connection names found',\n defaultConnectionNotFound: 'Default connection not found in connections',\n },\n} as const\n\ndeclare module 'stratal/i18n' {\n interface AppMessageNamespaces {\n database: typeof databaseMessages['en']\n }\n}\n","import type { AnyPlugin, ClientOptions, ComputedFieldsOptions } from '@zenstackhq/orm'\nimport type { SchemaDef } from '@zenstackhq/schema'\nimport { delay, DI_TOKENS, Scope } from 'stratal/di'\nimport type { IEventRegistry } from 'stratal/events'\nimport { I18nModule } from 'stratal/i18n'\nimport {\n Module,\n type AsyncModuleOptions,\n type DynamicModule,\n type InjectionToken,\n type ModuleContext,\n type OnInitialize,\n type OnShutdown,\n} from 'stratal/module'\nimport { DbGenerateCommand } from './commands/db-generate.command'\nimport { DbPullCommand } from './commands/db-pull.command'\nimport { DbPushCommand } from './commands/db-push.command'\nimport { MigrateDeployCommand } from './commands/migrate-deploy.command'\nimport { MigrateDevCommand } from './commands/migrate-dev.command'\nimport { MigrateResetCommand } from './commands/migrate-reset.command'\nimport { MigrateStatusCommand } from './commands/migrate-status.command'\nimport { createDatabaseService } from './database.helpers'\nimport { connectionSymbol, DATABASE_TOKENS } from './database.tokens'\nimport { databaseMessages } from './i18n'\nimport type { ConnectionName, DefaultConnectionName } from './types'\n\nexport interface DatabaseConnectionConfig<\n Schema extends SchemaDef = SchemaDef,\n Name extends ConnectionName = ConnectionName,\n> {\n name: Name\n schema: Schema\n dialect: () => ClientOptions<SchemaDef>['dialect']\n plugins?: AnyPlugin[]\n /**\n * Schema-level @computed field implementations. Required when the schema\n * declares any `@computed` fields. Keyed by uncapitalized model name; values\n * map field name to a Kysely-expression compute callback.\n */\n computedFields?: ComputedFieldsOptions<Schema>\n}\n\nexport interface DatabaseModuleConfig {\n default: DefaultConnectionName\n connections: DatabaseConnectionConfig[]\n}\n\n@Module({\n imports: [\n I18nModule.registerMessages({ en: { database: databaseMessages.en } }),\n ],\n providers: [\n DbGenerateCommand,\n DbPushCommand,\n DbPullCommand,\n MigrateDevCommand,\n MigrateDeployCommand,\n MigrateStatusCommand,\n MigrateResetCommand,\n ],\n})\nexport class DatabaseModule implements OnInitialize, OnShutdown {\n static forRoot(config: DatabaseModuleConfig): DynamicModule {\n return {\n module: DatabaseModule,\n providers: [\n { provide: DATABASE_TOKENS.Options, useValue: config as unknown as object },\n ],\n }\n }\n\n static forRootAsync(options: AsyncModuleOptions<DatabaseModuleConfig>): DynamicModule {\n return {\n module: DatabaseModule,\n providers: [\n {\n provide: DATABASE_TOKENS.Options,\n useFactory: options.useFactory,\n inject: options.inject,\n },\n ],\n }\n }\n\n onInitialize(context: ModuleContext): void {\n const config = context.container.resolve<DatabaseModuleConfig>(DATABASE_TOKENS.Options)\n const eventRegistry = context.container.resolve<IEventRegistry>(DI_TOKENS.EventRegistry)\n const container = context.container.getTsyringeContainer();\n\n for (const conn of config.connections) {\n const Service = createDatabaseService(conn, eventRegistry);\n\n container.register(connectionSymbol(conn.name) as InjectionToken<symbol>,\n // @ts-expect-error Dynamic class type mismatch\n delay(() => Service),\n { lifecycle: Scope.Request })\n }\n\n context.container.registerExisting(DI_TOKENS.Database, connectionSymbol(config.default))\n\n context.logger.info('DatabaseModule initialized')\n }\n\n onShutdown(context: ModuleContext): void {\n context.logger.info('DatabaseModule shutdown')\n }\n}\n","import { inject } from 'tsyringe'\nimport type { ConnectionName } from '../types'\nimport { connectionSymbol } from '../database.tokens'\n\nexport function InjectDB(name: ConnectionName): ParameterDecorator {\n return inject(connectionSymbol(name))\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/database/commands/zenstack.command.ts","../../src/database/commands/db-generate.command.ts","../../src/database/commands/db-pull.command.ts","../../src/database/commands/db-push.command.ts","../../src/database/commands/migrate-deploy.command.ts","../../src/database/commands/migrate-dev.command.ts","../../src/database/commands/migrate-reset.command.ts","../../src/database/commands/migrate-status.command.ts","../../src/database/errors/record-not-found.error.ts","../../src/database/errors/unique-constraint.error.ts","../../src/database/errors/from-zenstack-error.ts","../../src/database/plugins/error-handler.plugin.ts","../../src/database/plugins/event-emitter.plugin.ts","../../src/database/plugins/schema-switcher.ts","../../src/database/database.helpers.ts","../../src/database/database.tokens.ts","../../src/database/i18n/en.ts","../../src/database/database.module.ts","../../src/database/decorators/inject-db.decorator.ts"],"sourcesContent":["import { Command } from 'stratal/quarry'\n\n/**\n * Base command for ZenStack CLI wrappers.\n * Uses execFileSync with array arguments to prevent shell injection.\n */\nexport abstract class ZenStackCommand extends Command {\n protected async zenstack(args: string[]): Promise<number> {\n // Dynamic import — node:child_process is only available in the Quarry CLI (Node) context\n const { execFileSync } = await import('node:child_process')\n\n try {\n const output = execFileSync('npx', ['zenstack', ...args], {\n encoding: 'utf-8',\n stdio: 'pipe',\n })\n if (output) this.info(output.trim())\n return 0\n } catch (err) {\n const error = err as { stderr?: string; stdout?: string; status?: number }\n if (error.stderr) this.error(error.stderr.trim())\n if (error.stdout) this.info(error.stdout.trim())\n return error.status ?? 1\n }\n }\n}\n","import { ZenStackCommand } from './zenstack.command'\n\nexport class DbGenerateCommand extends ZenStackCommand {\n static command = 'db:generate {--schema= : Path to schema file} {--watch : Enable watch mode}'\n static description = 'Generate ZenStack ORM client'\n\n async handle(): Promise<number> {\n const args = ['generate']\n const schema = this.string('schema')\n\n if (schema) args.push('--schema', schema)\n if (this.boolean('watch')) args.push('--watch')\n\n return this.zenstack(args)\n }\n}\n","import { ZenStackCommand } from './zenstack.command'\n\nexport class DbPullCommand extends ZenStackCommand {\n static command = 'db:pull {--schema= : Path to schema file}'\n static description = 'Introspect database and generate schema'\n\n async handle(): Promise<number> {\n const args = ['db', 'pull']\n const schema = this.string('schema')\n\n if (schema) args.push('--schema', schema)\n\n return this.zenstack(args)\n }\n}\n","import { ZenStackCommand } from './zenstack.command'\n\nexport class DbPushCommand extends ZenStackCommand {\n static command = 'db:push {--schema= : Path to schema file} {--accept-data-loss : Accept data loss} {--force-reset : Force reset database}'\n static description = 'Push database schema changes'\n\n async handle(): Promise<number> {\n const args = ['db', 'push']\n const schema = this.string('schema')\n\n if (schema) args.push('--schema', schema)\n if (this.boolean('accept-data-loss')) args.push('--accept-data-loss')\n if (this.boolean('force-reset')) args.push('--force-reset')\n\n return this.zenstack(args)\n }\n}\n","import { ZenStackCommand } from './zenstack.command'\n\nexport class MigrateDeployCommand extends ZenStackCommand {\n static command = 'migrate:deploy {--schema= : Path to schema file}'\n static description = 'Deploy pending migrations'\n\n async handle(): Promise<number> {\n const args = ['migrate', 'deploy']\n const schema = this.string('schema')\n\n if (schema) args.push('--schema', schema)\n\n return this.zenstack(args)\n }\n}\n","import { ZenStackCommand } from './zenstack.command'\n\nexport class MigrateDevCommand extends ZenStackCommand {\n static command = 'migrate:dev {--schema= : Path to schema file} {--name= : Migration name} {--create-only : Create without applying}'\n static description = 'Create and apply migration'\n\n async handle(): Promise<number> {\n const args = ['migrate', 'dev']\n const schema = this.string('schema')\n const name = this.string('name')\n\n if (schema) args.push('--schema', schema)\n if (name) args.push('--name', name)\n if (this.boolean('create-only')) args.push('--create-only')\n\n return this.zenstack(args)\n }\n}\n","import { ZenStackCommand } from './zenstack.command'\n\nexport class MigrateResetCommand extends ZenStackCommand {\n static command = 'migrate:reset {--schema= : Path to schema file} {--force : Skip confirmation} {--skip-seed : Skip seeding}'\n static description = 'Reset database'\n\n async handle(): Promise<number> {\n const args = ['migrate', 'reset']\n const schema = this.string('schema')\n\n if (schema) args.push('--schema', schema)\n if (this.boolean('force')) args.push('--force')\n if (this.boolean('skip-seed')) args.push('--skip-seed')\n\n return this.zenstack(args)\n }\n}\n","import { ZenStackCommand } from './zenstack.command'\n\nexport class MigrateStatusCommand extends ZenStackCommand {\n static command = 'migrate:status {--schema= : Path to schema file}'\n static description = 'Check migration status'\n\n async handle(): Promise<number> {\n const args = ['migrate', 'status']\n const schema = this.string('schema')\n\n if (schema) args.push('--schema', schema)\n\n return this.zenstack(args)\n }\n}\n","import { HttpException } from 'stratal/errors'\n\nexport class RecordNotFoundError extends HttpException {\n constructor(public readonly details?: string, cause?: unknown) {\n super(404, 'Record not found', cause)\n }\n}\n","import { HttpException } from 'stratal/errors'\n\nexport class UniqueConstraintError extends HttpException {\n constructor(public readonly fields?: string[], cause?: unknown) {\n super(409, 'Record already exists', cause)\n }\n}\n","import { ORMError, ORMErrorReason } from '@zenstackhq/orm';\nimport { type ApplicationError, DatabaseError } from 'stratal/errors';\nimport { RecordNotFoundError } from './record-not-found.error';\nimport { UniqueConstraintError } from './unique-constraint.error';\n\nexport function fromZenStackError(error: unknown): ApplicationError {\n if (error instanceof ORMError) {\n switch (error.reason) {\n case ORMErrorReason.NOT_FOUND:\n return new RecordNotFoundError(error.model, error)\n case ORMErrorReason.DB_QUERY_ERROR:\n return parseDatabaseError(error)\n case ORMErrorReason.INVALID_INPUT:\n return new DatabaseError('Invalid database query', error)\n case ORMErrorReason.CONFIG_ERROR:\n return new DatabaseError('Database configuration error', error)\n case ORMErrorReason.NOT_SUPPORTED:\n return new DatabaseError('Operation not supported', error)\n case ORMErrorReason.INTERNAL_ERROR:\n return new DatabaseError('Database internal error', error)\n default:\n return new DatabaseError('Database error', error)\n }\n }\n return new DatabaseError('Database error', error)\n}\n\nfunction parseDatabaseError(error: ORMError): ApplicationError {\n const dbErrorCode = error.dbErrorCode as string | undefined\n if (dbErrorCode) {\n if (dbErrorCode === '23505') return new UniqueConstraintError([error.model ?? 'unknown'], error)\n if (dbErrorCode === '23503') return new DatabaseError('Foreign key constraint violation', error)\n if (dbErrorCode === '23502') return new DatabaseError('Required field is missing', error)\n if (dbErrorCode === '23514') return new DatabaseError('Database constraint violated', error)\n if (dbErrorCode === '42P01') return new DatabaseError('Table does not exist', error)\n if (dbErrorCode === '42703') return new DatabaseError('Column does not exist', error)\n if (dbErrorCode.startsWith('42')) return new DatabaseError('Database syntax or access error', error)\n if (dbErrorCode.startsWith('08')) return new DatabaseError('Database connection failed', error)\n if (dbErrorCode === '57014') return new DatabaseError('Database query timeout', error)\n if (dbErrorCode.startsWith('40')) return new DatabaseError('Transaction conflict or deadlock', error)\n if (dbErrorCode === '53300') return new DatabaseError('Too many database connections', error)\n }\n return new DatabaseError('Database error', error)\n}\n","import { type RuntimePlugin } from '@zenstackhq/orm'\nimport { type SchemaDef } from '@zenstackhq/orm/schema'\nimport { fromZenStackError } from '../errors'\n\n/**\n * ZenStack runtime plugin that transforms ORM errors into ApplicationError instances.\n *\n * @example\n * ```typescript\n * super(schema, {\n * dialect: new PostgresDialect({ pool }),\n * plugins: [new ErrorHandlerPlugin()]\n * })\n * ```\n */\nexport class ErrorHandlerPlugin implements RuntimePlugin<SchemaDef, Record<string, unknown>, Record<string, unknown>, {}> {\n readonly id = 'error-handler'\n\n onQuery = async ({ args, proceed }: {\n args: Record<string, unknown> | undefined\n proceed: (args: Record<string, unknown> | undefined) => Promise<unknown>\n }): Promise<unknown> => {\n try {\n return await proceed(args)\n } catch (error) {\n throw fromZenStackError(error)\n }\n }\n}\n","import { type RuntimePlugin } from '@zenstackhq/orm'\nimport { type SchemaDef } from '@zenstackhq/orm/schema'\nimport type { EventName, IEventRegistry } from 'stratal/events'\n\nexport interface EventEmitterPluginOptions {\n eventRegistry: IEventRegistry\n}\n\n/**\n * ZenStack runtime plugin that emits before/after events for database operations.\n *\n * Emits events in the format:\n * - `before.{Model}.{operation}` - Before the database operation\n * - `after.{Model}.{operation}` - After the database operation\n *\n * @example\n * ```typescript\n * super(schema, {\n * dialect: new PostgresDialect({ pool }),\n * plugins: [\n * new EventEmitterPlugin({\n * eventRegistry,\n * })\n * ]\n * })\n * ```\n */\nexport class EventEmitterPlugin implements RuntimePlugin<SchemaDef, Record<string, unknown>, Record<string, unknown>, {}> {\n readonly id = 'event-emitter'\n\n constructor(private options: EventEmitterPluginOptions) { }\n\n onQuery = async ({ model, operation, args, proceed }: {\n model: string\n operation: string\n args: Record<string, unknown> | undefined\n proceed: (args: Record<string, unknown> | undefined) => Promise<unknown>\n }): Promise<unknown> => {\n const { eventRegistry } = this.options\n const eventBase = `${model}.${operation}`\n\n // Emit BEFORE event\n await eventRegistry.emit(`before.${eventBase}` as EventName, {\n data: args,\n })\n\n // Execute the actual database operation\n const result = await proceed(args)\n\n // Emit AFTER event\n await eventRegistry.emit(`after.${eventBase}` as EventName, {\n data: args,\n result,\n })\n\n return result\n }\n}\n","interface SwitchableClient {\n $schema: { provider: { defaultSchema: string } } & Record<string, unknown>\n schema: unknown\n}\n\n/**\n * Switches the active schema on a ZenStack/Kysely database client by mutating\n * `$schema.provider.defaultSchema`. This causes ZenStack's QueryNameMapper to\n * generate fully-qualified table references (e.g. `\"tenant_123\".\"User\"`).\n *\n * Must be called BEFORE any queries are made on the client.\n *\n * Note: The ZenStack RuntimePlugin `onQuery` hook fires after table names are\n * already resolved, so a plugin-based approach cannot set the schema prefix.\n * Direct client mutation is the only supported method.\n */\nexport class SchemaSwitcher {\n static apply<T>(client: T, schemaName: string): T {\n const c = client as unknown as SwitchableClient\n const switched = {\n ...c.$schema,\n provider: { ...c.$schema.provider, defaultSchema: schemaName },\n }\n c.$schema = switched\n c.schema = switched\n return client\n }\n}\n","import { ZenStackClient, type AnyPlugin } from '@zenstackhq/orm';\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport { Transient } from 'stratal/di';\nimport type { IEventRegistry } from 'stratal/events';\nimport { withZodI18n, z } from 'stratal/validation';\nimport type { DatabaseConnectionConfig } from './database.module';\nimport { ErrorHandlerPlugin, EventEmitterPlugin } from './plugins';\n\nconst databaseConnectionSchema = z.object({\n name: z.string().min(1, withZodI18n('database.connectionNameRequired')),\n schema: z.object({}).loose(),\n dialect: z.function(),\n plugins: z.array(z.object({}).loose()).optional(),\n computedFields: z.object({}).loose().optional(),\n})\n\nexport const databaseModuleConfigSchema = z.object({\n default: z.string().min(1, withZodI18n('database.defaultConnectionRequired')),\n connections: z.array(databaseConnectionSchema).min(1, withZodI18n('database.connectionRequired')),\n}).refine(\n (config) => {\n const names = config.connections.map(c => c.name)\n return new Set(names).size === names.length\n },\n withZodI18n('database.duplicateConnections')\n).refine(\n (config) => config.connections.some(c => c.name === config.default),\n withZodI18n('database.defaultConnectionNotFound')\n)\n\ntype ZenStackClientInstance = InstanceType<typeof ZenStackClient>\n\n/**\n * Wrap a ZenStack client so `$transaction` is reentrant: when a transaction is\n * already open on this connection (tracked per-connection via\n * {@link AsyncLocalStorage}), nested calls run within the active transaction's\n * client instead of opening a new one. ZenStack only reuses a connection when\n * `$transaction` is called on a transaction client; callers holding the base\n * client (e.g. the better-auth adapter, which since better-auth 1.6.11 nests\n * transactions to atomically consume verification rows) would otherwise open a\n * fresh transaction. On a small pool (e.g. a Hyperdrive-fronted `max: 1` pg\n * pool) that inner transaction blocks forever waiting for the connection the\n * outer one holds — a deadlock surfacing as a backend stuck `idle in\n * transaction`. Reusing the active client is also the correct semantics: nested\n * transactions form a single atomic unit.\n *\n * ZenStackClient's constructor returns a Proxy (for dynamic model accessors), so\n * a subclass method override is shadowed — hence the proxy wrapper here.\n */\nfunction makeReentrantTransaction<T extends object>(\n client: T,\n activeTransaction: AsyncLocalStorage<ZenStackClientInstance>,\n): T {\n return new Proxy(client, {\n get(target, prop, receiver) {\n if (prop !== '$transaction') {\n // Forward the receiver so getters/methods resolve `this` against the\n // proxy (correct for layered proxies / accessor properties).\n return Reflect.get(target, prop, receiver)\n }\n // Read the original `$transaction` off the target WITHOUT the receiver — a\n // receiver of the proxy would re-enter this trap and recurse infinitely.\n const transaction = Reflect.get(target, prop) as (\n input: unknown,\n options?: unknown,\n ) => unknown\n return (input: unknown, options?: unknown) => {\n const active = activeTransaction.getStore()\n if (active) {\n return typeof input === 'function'\n ? (input as (tx: ZenStackClientInstance) => unknown)(active)\n : (active.$transaction as (i: unknown, o?: unknown) => unknown)(input, options)\n }\n if (typeof input !== 'function') {\n return transaction.call(target, input, options)\n }\n return transaction.call(\n target,\n (tx: ZenStackClientInstance) =>\n activeTransaction.run(tx, () => (input as (t: ZenStackClientInstance) => unknown)(tx)),\n options,\n )\n }\n },\n })\n}\n\nexport function createDatabaseService(\n conn: DatabaseConnectionConfig,\n eventRegistry: IEventRegistry,\n): new () => InstanceType<typeof ZenStackClient> {\n const plugins: AnyPlugin[] = [\n new ErrorHandlerPlugin(),\n new EventEmitterPlugin({\n eventRegistry,\n }),\n ...(conn.plugins ?? []),\n ]\n\n // Tracks the in-flight interactive transaction client for this connection so\n // nested `$transaction` calls reuse it instead of acquiring a second\n // connection. ZenStack's own reuse only triggers when `$transaction` is\n // invoked on a transaction client; callers that hold the base client (e.g.\n // the better-auth adapter, which since better-auth 1.6.11 nests transactions\n // to atomically consume verification rows) instead open a fresh transaction.\n // On a small pool (e.g. a Hyperdrive-fronted `max: 1` pg pool) the inner\n // transaction then blocks forever waiting for the connection the outer one\n // holds — a deadlock that surfaces as a Postgres backend stuck `idle in\n // transaction`. Reusing the active client makes nested transactions share the\n // single connection, which is also the correct semantics (one atomic unit).\n const activeTransaction = new AsyncLocalStorage<InstanceType<typeof ZenStackClient>>()\n\n @Transient()\n class DatabaseClient extends ZenStackClient<typeof conn.schema> {\n constructor() {\n const dialect = conn.dialect()\n // ZenStack 3+ requires `computedFields` whenever the schema declares any\n // `@computed` fields, so pass them through when the consumer provides them.\n super(conn.schema, {\n dialect,\n plugins,\n // @ts-expect-error - ZenStack 3+ requires `computedFields` whenever the schema declares any `@computed` fields, so pass them through when the consumer provides them.\n computedFields: conn.computedFields\n })\n // ZenStackClient's constructor returns a Proxy (for dynamic model\n // accessors), so subclass method overrides are shadowed. Wrap it in a\n // proxy that makes `$transaction` reentrant. Returning from the\n // constructor replaces the instance DI receives.\n return makeReentrantTransaction(this as InstanceType<typeof ZenStackClient>, activeTransaction)\n }\n }\n\n return DatabaseClient\n}\n","export const DATABASE_TOKENS = {\n Options: Symbol.for('stratal:database:options'),\n Services: Symbol.for('stratal:database:services'),\n} as const\n\nimport type { ConnectionName } from './types'\n\nexport function connectionSymbol(name: ConnectionName): symbol {\n return Symbol.for(`stratal:database:connection:${name}`)\n}\n","export const databaseMessages = {\n en: {\n connectionNameRequired: 'Connection name is required',\n defaultConnectionRequired: 'Default connection name is required',\n connectionRequired: 'At least one connection is required',\n duplicateConnections: 'Duplicate connection names found',\n defaultConnectionNotFound: 'Default connection not found in connections',\n },\n} as const\n\ndeclare module 'stratal/i18n' {\n interface AppMessageNamespaces {\n database: typeof databaseMessages['en']\n }\n}\n","import type { AnyPlugin, ClientOptions, ComputedFieldsOptions } from '@zenstackhq/orm';\nimport type { SchemaDef } from '@zenstackhq/schema';\nimport { DI_TOKENS, lazy } from 'stratal/di';\nimport type { IEventRegistry } from 'stratal/events';\nimport { I18nModule } from 'stratal/i18n';\nimport {\n Module,\n type AsyncModuleOptions,\n type DynamicModule,\n type LazyModuleLoader,\n type ModuleContext,\n type OnInitialize,\n type OnShutdown,\n} from 'stratal/module';\nimport { DbGenerateCommand } from './commands/db-generate.command';\nimport { DbPullCommand } from './commands/db-pull.command';\nimport { DbPushCommand } from './commands/db-push.command';\nimport { MigrateDeployCommand } from './commands/migrate-deploy.command';\nimport { MigrateDevCommand } from './commands/migrate-dev.command';\nimport { MigrateResetCommand } from './commands/migrate-reset.command';\nimport { MigrateStatusCommand } from './commands/migrate-status.command';\nimport { createDatabaseService } from './database.helpers';\nimport { connectionSymbol, DATABASE_TOKENS } from './database.tokens';\nimport { databaseMessages } from './i18n';\nimport type { ConnectionName, DefaultConnectionName } from './types';\n\nexport interface DatabaseConnectionConfig<\n Schema extends SchemaDef = SchemaDef,\n Name extends ConnectionName = ConnectionName,\n> {\n name: Name\n schema: Schema\n dialect: () => ClientOptions<SchemaDef>['dialect']\n plugins?: AnyPlugin[]\n /**\n * Schema-level @computed field implementations. Required when the schema\n * declares any `@computed` fields. Keyed by uncapitalized model name; values\n * map field name to a Kysely-expression compute callback.\n */\n computedFields?: ComputedFieldsOptions<Schema>\n}\n\nexport interface DatabaseModuleConfig {\n default: DefaultConnectionName\n connections: DatabaseConnectionConfig[]\n}\n\n@Module({\n imports: [\n I18nModule.registerMessages({ en: { database: databaseMessages.en } }),\n ],\n providers: [\n DbGenerateCommand,\n DbPushCommand,\n DbPullCommand,\n MigrateDevCommand,\n MigrateDeployCommand,\n MigrateStatusCommand,\n MigrateResetCommand,\n ],\n})\nexport class DatabaseModule implements OnInitialize, OnShutdown {\n static forRoot(config: DatabaseModuleConfig): DynamicModule {\n return {\n module: DatabaseModule,\n providers: [\n { provide: DATABASE_TOKENS.Options, useValue: config as unknown as object },\n ],\n }\n }\n\n static forRootAsync(options: AsyncModuleOptions<DatabaseModuleConfig>): DynamicModule {\n return {\n module: DatabaseModule,\n providers: [\n {\n provide: DATABASE_TOKENS.Options,\n useFactory: options.useFactory,\n inject: options.inject,\n },\n ],\n }\n }\n\n async onInitialize(context: ModuleContext): Promise<void> {\n const config = context.container.resolve<DatabaseModuleConfig>(DATABASE_TOKENS.Options)\n // EventRegistry is loaded on demand — pull in EventsModule via the loader.\n const loader = context.container.resolve<LazyModuleLoader>(DI_TOKENS.LazyModuleLoader)\n const eventsRef = await loader.load(() => import('stratal/events').then((m) => m.EventsModule))\n const eventRegistry = eventsRef.get<IEventRegistry>(DI_TOKENS.EventRegistry)\n for (const conn of config.connections) {\n const Service = createDatabaseService(conn, eventRegistry);\n\n context.container.register(connectionSymbol(conn.name), lazy(() => Service))\n }\n\n context.container.registerExisting(DI_TOKENS.Database, connectionSymbol(config.default))\n\n context.logger.info('DatabaseModule initialized')\n }\n\n onShutdown(context: ModuleContext): void {\n context.logger.info('DatabaseModule shutdown')\n }\n}\n","import { inject } from 'stratal/di'\nimport type { ConnectionName } from '../types'\nimport { connectionSymbol } from '../database.tokens'\n\nexport function InjectDB(name: ConnectionName): ParameterDecorator {\n return inject(connectionSymbol(name))\n}\n"],"mappings":"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"}
@@ -15,4 +15,4 @@ var UserNotAuthorizedError = class extends HttpException {
15
15
  //#endregion
16
16
  export { UserNotAuthenticatedError as n, UserNotAuthorizedError as t };
17
17
 
18
- //# sourceMappingURL=errors-opqKWAoa.mjs.map
18
+ //# sourceMappingURL=errors-MCyrn_V2.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors-opqKWAoa.mjs","names":[],"sources":["../src/context/errors/user-not-authenticated.error.ts","../src/context/errors/user-not-authorized.error.ts"],"sourcesContent":["import { HttpException } from 'stratal/errors'\n\nexport class UserNotAuthenticatedError extends HttpException {\n constructor() { super(401, 'User is not authenticated') }\n}\n","import { HttpException } from 'stratal/errors'\n\nexport class UserNotAuthorizedError extends HttpException {\n constructor() { super(403, 'Unauthorized') }\n}\n"],"mappings":";;AAEA,IAAa,4BAAb,cAA+C,cAAc;CAC3D,cAAc;EAAE,MAAM,KAAK,4BAA4B;;;;;ACDzD,IAAa,yBAAb,cAA4C,cAAc;CACxD,cAAc;EAAE,MAAM,KAAK,eAAe"}
1
+ {"version":3,"file":"errors-MCyrn_V2.mjs","names":[],"sources":["../src/context/errors/user-not-authenticated.error.ts","../src/context/errors/user-not-authorized.error.ts"],"sourcesContent":["import { HttpException } from 'stratal/errors'\n\nexport class UserNotAuthenticatedError extends HttpException {\n constructor() { super(401, 'User is not authenticated') }\n}\n","import { HttpException } from 'stratal/errors'\n\nexport class UserNotAuthorizedError extends HttpException {\n constructor() { super(403, 'Unauthorized') }\n}\n"],"mappings":";;AAEA,IAAa,4BAAb,cAA+C,cAAc;CAC3D,cAAc;EAAE,MAAM,KAAK,4BAA4B;;;;;ACDzD,IAAa,yBAAb,cAA4C,cAAc;CACxD,cAAc;EAAE,MAAM,KAAK,eAAe"}
@@ -1,4 +1,4 @@
1
- import { O as DatabaseService } from "../index-Cnx6eRgJ.mjs";
1
+ import { O as DatabaseService } from "../index-NV8eVa_e.mjs";
2
2
  import { Faker } from "@faker-js/faker";
3
3
 
4
4
  //#region src/factory/factory.d.ts
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/guards/auth.guard.ts"],"mappings":";;;;;AAsDA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAgB,SAAA,CAAU,OAAA,GAAU,kBAAA,GAAmB,YAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/guards/auth.guard.ts"],"mappings":";;;;;AAqDA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAgB,SAAA,CAAU,OAAA,GAAU,kBAAA,GAAmB,YAAA"}
@@ -1,11 +1,9 @@
1
1
  import { n as AC_TOKENS, t as __decorateParam } from "../decorateParam-C_dJ_dIO.mjs";
2
- import { t as __decorateMetadata } from "../decorateMetadata-D5WUsc6Y.mjs";
3
- import { t as __decorate } from "../decorate-DViXs-0l.mjs";
4
- import { n as UserNotAuthenticatedError } from "../errors-opqKWAoa.mjs";
5
- import { t as InsufficientPermissionsError } from "../insufficient-permissions.error-CnzLRFdB.mjs";
6
- import { DI_TOKENS, Transient } from "stratal/di";
2
+ import { t as __decorate } from "../decorate-7CAoTBu4.mjs";
3
+ import { n as UserNotAuthenticatedError } from "../errors-MCyrn_V2.mjs";
4
+ import { t as InsufficientPermissionsError } from "../insufficient-permissions.error-GwrkWnEM.mjs";
5
+ import { DI_TOKENS, Transient, inject } from "stratal/di";
7
6
  import { LOGGER_TOKENS } from "stratal/logger";
8
- import { inject as inject$1 } from "tsyringe";
9
7
  import { GUARD_METADATA_KEY, GuardExecutionService, UseGuards, getControllerGuards, getMethodGuards } from "stratal/guards";
10
8
  //#region src/guards/auth.guard.ts
11
9
  function parsePermissions(raw) {
@@ -88,14 +86,9 @@ function AuthGuard(options) {
88
86
  };
89
87
  ConfiguredAuthGuard = __decorate([
90
88
  Transient(),
91
- __decorateParam(0, inject$1(DI_TOKENS.AuthContext)),
92
- __decorateParam(1, inject$1(LOGGER_TOKENS.LoggerService)),
93
- __decorateParam(2, inject$1(AC_TOKENS.AccessService, { isOptional: true })),
94
- __decorateMetadata("design:paramtypes", [
95
- Object,
96
- Object,
97
- Object
98
- ])
89
+ __decorateParam(0, inject(DI_TOKENS.AuthContext)),
90
+ __decorateParam(1, inject(LOGGER_TOKENS.LoggerService)),
91
+ __decorateParam(2, inject(AC_TOKENS.AccessService, { isOptional: true }))
99
92
  ], ConfiguredAuthGuard);
100
93
  return ConfiguredAuthGuard;
101
94
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["inject"],"sources":["../../src/guards/auth.guard.ts"],"sourcesContent":["import { DI_TOKENS, Transient } from 'stratal/di'\nimport type { AuthGuardOptions, CanActivate, GuardClass } from 'stratal/guards'\nimport { LOGGER_TOKENS, type LoggerService } from 'stratal/logger'\nimport type { RouterContext } from 'stratal/router'\nimport { inject } from 'tsyringe'\nimport { InsufficientPermissionsError } from '../access-control/errors/insufficient-permissions.error'\nimport type { AccessService } from '../access-control/services/access.service'\nimport { AC_TOKENS } from '../access-control/tokens'\nimport type { AuthContext } from '../context/auth-context'\nimport { UserNotAuthenticatedError } from '../context/errors'\n\nfunction parsePermissions(raw: string | string[]): Record<string, string[]> {\n const list = Array.isArray(raw) ? raw : [raw]\n return list.reduce<Record<string, string[]>>((acc, perm) => {\n const colon = perm.indexOf(':')\n const resource = colon === -1 ? perm : perm.slice(0, colon)\n const action = colon === -1 ? '*' : perm.slice(colon + 1)\n ;(acc[resource] ??= []).push(action)\n return acc\n }, {})\n}\n\n\n/**\n * AuthGuard Factory\n *\n * Creates a guard class that enforces authentication and optional authorization.\n *\n * **Authentication (no permissions):**\n * - Checks if user is authenticated via AuthContext.isAuthenticated()\n * - Throws UserNotAuthenticatedError (401) if not authenticated\n *\n * **Authorization (with permissions):**\n * - First verifies authentication\n * - Then checks permissions via AccessService (reads from AuthContext — no DB hit)\n * - Throws InsufficientPermissionsError (403) if unauthorized\n *\n * @param options - Configuration options\n * @param options.permissions - Required permissions keyed by resource\n * @returns Guard class for use with @UseGuards decorator\n *\n * @example Authentication only\n * ```typescript\n * @UseGuards(AuthGuard())\n * export class ProfileController { }\n * ```\n *\n * @example Authentication with permissions\n * ```typescript\n * @UseGuards(AuthGuard({ permissions: 'posts:update' }))\n * @UseGuards(AuthGuard({ permissions: ['posts:update', 'posts:delete'] }))\n * export class PostsController { }\n * ```\n */\nexport function AuthGuard(options?: AuthGuardOptions): GuardClass {\n const rawPermissions = options?.permissions\n const permissions = rawPermissions ? parsePermissions(rawPermissions) : undefined\n\n @Transient()\n class ConfiguredAuthGuard implements CanActivate {\n constructor(\n @inject(DI_TOKENS.AuthContext) private readonly authContext: AuthContext,\n @inject(LOGGER_TOKENS.LoggerService) private readonly logger: LoggerService,\n @inject(AC_TOKENS.AccessService, { isOptional: true }) private readonly accessService?: AccessService\n ) { }\n\n async canActivate(_context: RouterContext): Promise<boolean> {\n if (!this.authContext.isAuthenticated()) {\n this.logger.debug('Auth guard: User not authenticated')\n throw new UserNotAuthenticatedError()\n }\n\n if (!permissions || Object.keys(permissions).length === 0) {\n this.logger.debug('Auth guard: Authentication passed (no permissions required)')\n return true\n }\n\n const userId = this.authContext.getUserId()\n if (!userId) {\n this.logger.debug('Auth guard: No user ID in context')\n throw new InsufficientPermissionsError(rawPermissions!)\n }\n\n if (this.accessService) {\n const allowed = await this.accessService.hasPermission(userId, permissions)\n\n this.logger.debug('Auth guard: Authorization check', {\n userId,\n permissions,\n allowed,\n })\n\n if (!allowed) {\n throw new InsufficientPermissionsError(rawPermissions!, userId)\n }\n }\n\n return true\n }\n }\n\n return ConfiguredAuthGuard\n}\n"],"mappings":";;;;;;;;;;AAWA,SAAS,iBAAiB,KAAkD;CAE1E,QADa,MAAM,QAAQ,IAAI,GAAG,MAAM,CAAC,IAAI,EACjC,QAAkC,KAAK,SAAS;EAC1D,MAAM,QAAQ,KAAK,QAAQ,IAAI;EAC/B,MAAM,WAAW,UAAU,KAAK,OAAO,KAAK,MAAM,GAAG,MAAM;EAC3D,MAAM,SAAS,UAAU,KAAK,MAAM,KAAK,MAAM,QAAQ,EAAE;EACxD,CAAC,IAAI,cAAc,EAAE,EAAE,KAAK,OAAO;EACpC,OAAO;IACN,EAAE,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCR,SAAgB,UAAU,SAAwC;CAChE,MAAM,iBAAiB,SAAS;CAChC,MAAM,cAAc,iBAAiB,iBAAiB,eAAe,GAAG,KAAA;CAExE,IAAA,sBAAA,MACM,oBAA2C;EAEG;EACM;EACkB;EAH1E,YACE,aACA,QACA,eACA;GAHgD,KAAA,cAAA;GACM,KAAA,SAAA;GACkB,KAAA,gBAAA;;EAG1E,MAAM,YAAY,UAA2C;GAC3D,IAAI,CAAC,KAAK,YAAY,iBAAiB,EAAE;IACvC,KAAK,OAAO,MAAM,qCAAqC;IACvD,MAAM,IAAI,2BAA2B;;GAGvC,IAAI,CAAC,eAAe,OAAO,KAAK,YAAY,CAAC,WAAW,GAAG;IACzD,KAAK,OAAO,MAAM,8DAA8D;IAChF,OAAO;;GAGT,MAAM,SAAS,KAAK,YAAY,WAAW;GAC3C,IAAI,CAAC,QAAQ;IACX,KAAK,OAAO,MAAM,oCAAoC;IACtD,MAAM,IAAI,6BAA6B,eAAgB;;GAGzD,IAAI,KAAK,eAAe;IACtB,MAAM,UAAU,MAAM,KAAK,cAAc,cAAc,QAAQ,YAAY;IAE3E,KAAK,OAAO,MAAM,mCAAmC;KACnD;KACA;KACA;KACD,CAAC;IAEF,IAAI,CAAC,SACH,MAAM,IAAI,6BAA6B,gBAAiB,OAAO;;GAInE,OAAO;;;;EAvCV,WAAW;qBAGPA,SAAO,UAAU,YAAY,CAAA;qBAC7BA,SAAO,cAAc,cAAc,CAAA;qBACnCA,SAAO,UAAU,eAAe,EAAE,YAAY,MAAM,CAAC,CAAA;;;;;;;CAsC1D,OAAO"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/guards/auth.guard.ts"],"sourcesContent":["import { DI_TOKENS, inject, Transient } from 'stratal/di'\nimport type { AuthGuardOptions, CanActivate, GuardClass } from 'stratal/guards'\nimport { LOGGER_TOKENS, type LoggerService } from 'stratal/logger'\nimport type { RouterContext } from 'stratal/router'\nimport { InsufficientPermissionsError } from '../access-control/errors/insufficient-permissions.error'\nimport type { AccessService } from '../access-control/services/access.service'\nimport { AC_TOKENS } from '../access-control/tokens'\nimport type { AuthContext } from '../context/auth-context'\nimport { UserNotAuthenticatedError } from '../context/errors'\n\nfunction parsePermissions(raw: string | string[]): Record<string, string[]> {\n const list = Array.isArray(raw) ? raw : [raw]\n return list.reduce<Record<string, string[]>>((acc, perm) => {\n const colon = perm.indexOf(':')\n const resource = colon === -1 ? perm : perm.slice(0, colon)\n const action = colon === -1 ? '*' : perm.slice(colon + 1)\n ;(acc[resource] ??= []).push(action)\n return acc\n }, {})\n}\n\n\n/**\n * AuthGuard Factory\n *\n * Creates a guard class that enforces authentication and optional authorization.\n *\n * **Authentication (no permissions):**\n * - Checks if user is authenticated via AuthContext.isAuthenticated()\n * - Throws UserNotAuthenticatedError (401) if not authenticated\n *\n * **Authorization (with permissions):**\n * - First verifies authentication\n * - Then checks permissions via AccessService (reads from AuthContext — no DB hit)\n * - Throws InsufficientPermissionsError (403) if unauthorized\n *\n * @param options - Configuration options\n * @param options.permissions - Required permissions keyed by resource\n * @returns Guard class for use with @UseGuards decorator\n *\n * @example Authentication only\n * ```typescript\n * @UseGuards(AuthGuard())\n * export class ProfileController { }\n * ```\n *\n * @example Authentication with permissions\n * ```typescript\n * @UseGuards(AuthGuard({ permissions: 'posts:update' }))\n * @UseGuards(AuthGuard({ permissions: ['posts:update', 'posts:delete'] }))\n * export class PostsController { }\n * ```\n */\nexport function AuthGuard(options?: AuthGuardOptions): GuardClass {\n const rawPermissions = options?.permissions\n const permissions = rawPermissions ? parsePermissions(rawPermissions) : undefined\n\n @Transient()\n class ConfiguredAuthGuard implements CanActivate {\n constructor(\n @inject(DI_TOKENS.AuthContext) private readonly authContext: AuthContext,\n @inject(LOGGER_TOKENS.LoggerService) private readonly logger: LoggerService,\n @inject(AC_TOKENS.AccessService, { isOptional: true }) private readonly accessService?: AccessService\n ) { }\n\n async canActivate(_context: RouterContext): Promise<boolean> {\n if (!this.authContext.isAuthenticated()) {\n this.logger.debug('Auth guard: User not authenticated')\n throw new UserNotAuthenticatedError()\n }\n\n if (!permissions || Object.keys(permissions).length === 0) {\n this.logger.debug('Auth guard: Authentication passed (no permissions required)')\n return true\n }\n\n const userId = this.authContext.getUserId()\n if (!userId) {\n this.logger.debug('Auth guard: No user ID in context')\n throw new InsufficientPermissionsError(rawPermissions!)\n }\n\n if (this.accessService) {\n const allowed = await this.accessService.hasPermission(userId, permissions)\n\n this.logger.debug('Auth guard: Authorization check', {\n userId,\n permissions,\n allowed,\n })\n\n if (!allowed) {\n throw new InsufficientPermissionsError(rawPermissions!, userId)\n }\n }\n\n return true\n }\n }\n\n return ConfiguredAuthGuard\n}\n"],"mappings":";;;;;;;;AAUA,SAAS,iBAAiB,KAAkD;CAE1E,QADa,MAAM,QAAQ,IAAI,GAAG,MAAM,CAAC,IAAI,EACjC,QAAkC,KAAK,SAAS;EAC1D,MAAM,QAAQ,KAAK,QAAQ,IAAI;EAC/B,MAAM,WAAW,UAAU,KAAK,OAAO,KAAK,MAAM,GAAG,MAAM;EAC3D,MAAM,SAAS,UAAU,KAAK,MAAM,KAAK,MAAM,QAAQ,EAAE;EACxD,CAAC,IAAI,cAAc,EAAE,EAAE,KAAK,OAAO;EACpC,OAAO;IACN,EAAE,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCR,SAAgB,UAAU,SAAwC;CAChE,MAAM,iBAAiB,SAAS;CAChC,MAAM,cAAc,iBAAiB,iBAAiB,eAAe,GAAG,KAAA;CAExE,IAAA,sBAAA,MACM,oBAA2C;EAEG;EACM;EACkB;EAH1E,YACE,aACA,QACA,eACA;GAHgD,KAAA,cAAA;GACM,KAAA,SAAA;GACkB,KAAA,gBAAA;;EAG1E,MAAM,YAAY,UAA2C;GAC3D,IAAI,CAAC,KAAK,YAAY,iBAAiB,EAAE;IACvC,KAAK,OAAO,MAAM,qCAAqC;IACvD,MAAM,IAAI,2BAA2B;;GAGvC,IAAI,CAAC,eAAe,OAAO,KAAK,YAAY,CAAC,WAAW,GAAG;IACzD,KAAK,OAAO,MAAM,8DAA8D;IAChF,OAAO;;GAGT,MAAM,SAAS,KAAK,YAAY,WAAW;GAC3C,IAAI,CAAC,QAAQ;IACX,KAAK,OAAO,MAAM,oCAAoC;IACtD,MAAM,IAAI,6BAA6B,eAAgB;;GAGzD,IAAI,KAAK,eAAe;IACtB,MAAM,UAAU,MAAM,KAAK,cAAc,cAAc,QAAQ,YAAY;IAE3E,KAAK,OAAO,MAAM,mCAAmC;KACnD;KACA;KACA;KACD,CAAC;IAEF,IAAI,CAAC,SACH,MAAM,IAAI,6BAA6B,gBAAiB,OAAO;;GAInE,OAAO;;;;EAvCV,WAAW;qBAGP,OAAO,UAAU,YAAY,CAAA;qBAC7B,OAAO,cAAc,cAAc,CAAA;qBACnC,OAAO,UAAU,eAAe,EAAE,YAAY,MAAM,CAAC,CAAA;;CAsC1D,OAAO"}
@@ -1,4 +1,4 @@
1
- import { a as InferConnectionSchema, i as InferConnectionExtensions, n as DefaultConnectionName, r as InferAnySchema, t as ConnectionName } from "./types-BZlcRR2M.mjs";
1
+ import { a as InferConnectionSchema, i as InferConnectionExtensions, n as DefaultConnectionName, r as InferAnySchema, t as ConnectionName } from "./types-4uX3XKRM.mjs";
2
2
  import { AsyncModuleOptions, DynamicModule, ModuleContext, OnInitialize, OnShutdown } from "stratal/module";
3
3
  import { ApplicationError, HttpException } from "stratal/errors";
4
4
  import { Command } from "stratal/quarry";
@@ -27,7 +27,7 @@ interface DatabaseModuleConfig {
27
27
  declare class DatabaseModule implements OnInitialize, OnShutdown {
28
28
  static forRoot(config: DatabaseModuleConfig): DynamicModule;
29
29
  static forRootAsync(options: AsyncModuleOptions<DatabaseModuleConfig>): DynamicModule;
30
- onInitialize(context: ModuleContext): void;
30
+ onInitialize(context: ModuleContext): Promise<void>;
31
31
  onShutdown(context: ModuleContext): void;
32
32
  }
33
33
  //#endregion
@@ -363,4 +363,4 @@ declare class MigrateStatusCommand extends ZenStackCommand {
363
363
  }
364
364
  //#endregion
365
365
  export { DatabaseModule as A, UniqueConstraintError as C, connectionSymbol as D, DATABASE_TOKENS as E, DatabaseService as O, fromZenStackError as S, InjectDB as T, EventPhase as _, DbPushCommand as a, ModelName as b, ZenStackCommand as c, EventEmitterPluginOptions as d, ErrorHandlerPlugin as f, DatabaseOperation as g, DatabaseEvents as h, MigrateDeployCommand as i, DatabaseModuleConfig as j, DatabaseConnectionConfig as k, SchemaSwitcher as l, DatabaseEventName as m, MigrateResetCommand as n, DbPullCommand as o, databaseMessages as p, MigrateDevCommand as r, DbGenerateCommand as s, MigrateStatusCommand as t, EventEmitterPlugin as u, GetData as v, RecordNotFoundError as w, ParseEvent as x, GetResult as y };
366
- //# sourceMappingURL=index-Cnx6eRgJ.d.mts.map
366
+ //# sourceMappingURL=index-NV8eVa_e.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-NV8eVa_e.d.mts","names":[],"sources":["../src/database/database.module.ts","../src/database/database.service.ts","../src/database/database.tokens.ts","../src/database/decorators/inject-db.decorator.ts","../src/database/errors/record-not-found.error.ts","../src/database/errors/unique-constraint.error.ts","../src/database/errors/from-zenstack-error.ts","../src/database/event-types.ts","../src/database/i18n/en.ts","../src/database/plugins/error-handler.plugin.ts","../src/database/plugins/event-emitter.plugin.ts","../src/database/plugins/schema-switcher.ts","../src/database/commands/zenstack.command.ts","../src/database/commands/db-generate.command.ts","../src/database/commands/db-pull.command.ts","../src/database/commands/db-push.command.ts","../src/database/commands/migrate-deploy.command.ts","../src/database/commands/migrate-dev.command.ts","../src/database/commands/migrate-reset.command.ts","../src/database/commands/migrate-status.command.ts"],"mappings":"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"}
package/dist/index.d.mts CHANGED
@@ -1,3 +1,3 @@
1
- import { a as InferConnectionSchema, i as InferConnectionExtensions, n as DefaultConnectionName, r as InferAnySchema, s as StratalDatabase, t as ConnectionName } from "./types-BZlcRR2M.mjs";
1
+ import { a as InferConnectionSchema, i as InferConnectionExtensions, n as DefaultConnectionName, r as InferAnySchema, s as StratalDatabase, t as ConnectionName } from "./types-4uX3XKRM.mjs";
2
2
  import { CustomEventRegistry, EventName } from "stratal/events";
3
3
  export { type ConnectionName, type CustomEventRegistry, type DefaultConnectionName, type EventName, type InferAnySchema, type InferConnectionExtensions, type InferConnectionSchema, type StratalDatabase };
@@ -13,4 +13,4 @@ var InsufficientPermissionsError = class extends HttpException {
13
13
  //#endregion
14
14
  export { InsufficientPermissionsError as t };
15
15
 
16
- //# sourceMappingURL=insufficient-permissions.error-CnzLRFdB.mjs.map
16
+ //# sourceMappingURL=insufficient-permissions.error-GwrkWnEM.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"insufficient-permissions.error-CnzLRFdB.mjs","names":[],"sources":["../src/access-control/errors/insufficient-permissions.error.ts"],"sourcesContent":["import { HttpException } from 'stratal/errors'\n\nexport class InsufficientPermissionsError extends HttpException {\n public readonly requiredPermissions: string\n public readonly userId?: string\n\n constructor(requiredPermissions: string | string[], userId?: string) {\n const summary = Array.isArray(requiredPermissions) ? requiredPermissions.join(', ') : requiredPermissions\n super(403, 'Insufficient permissions')\n this.requiredPermissions = summary\n this.userId = userId\n }\n}\n"],"mappings":";;AAEA,IAAa,+BAAb,cAAkD,cAAc;CAC9D;CACA;CAEA,YAAY,qBAAwC,QAAiB;EACnE,MAAM,UAAU,MAAM,QAAQ,oBAAoB,GAAG,oBAAoB,KAAK,KAAK,GAAG;EACtF,MAAM,KAAK,2BAA2B;EACtC,KAAK,sBAAsB;EAC3B,KAAK,SAAS"}
1
+ {"version":3,"file":"insufficient-permissions.error-GwrkWnEM.mjs","names":[],"sources":["../src/access-control/errors/insufficient-permissions.error.ts"],"sourcesContent":["import { HttpException } from 'stratal/errors'\n\nexport class InsufficientPermissionsError extends HttpException {\n public readonly requiredPermissions: string\n public readonly userId?: string\n\n constructor(requiredPermissions: string | string[], userId?: string) {\n const summary = Array.isArray(requiredPermissions) ? requiredPermissions.join(', ') : requiredPermissions\n super(403, 'Insufficient permissions')\n this.requiredPermissions = summary\n this.userId = userId\n }\n}\n"],"mappings":";;AAEA,IAAa,+BAAb,cAAkD,cAAc;CAC9D;CACA;CAEA,YAAY,qBAAwC,QAAiB;EACnE,MAAM,UAAU,MAAM,QAAQ,oBAAoB,GAAG,oBAAoB,KAAK,KAAK,GAAG;EACtF,MAAM,KAAK,2BAA2B;EACtC,KAAK,sBAAsB;EAC3B,KAAK,SAAS"}
@@ -89,4 +89,4 @@ interface InternalDatabaseEventContext {
89
89
  }
90
90
  //#endregion
91
91
  export { InferConnectionSchema as a, InferConnectionExtensions as i, DefaultConnectionName as n, InternalDatabaseEventContext as o, InferAnySchema as r, StratalDatabase as s, ConnectionName as t };
92
- //# sourceMappingURL=types-BZlcRR2M.d.mts.map
92
+ //# sourceMappingURL=types-4uX3XKRM.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types-BZlcRR2M.d.mts","names":[],"sources":["../src/database/types.ts"],"mappings":";;;;;;AA+BA;;;;;AAAmC;;;;;;;;;;;;;;AAIiC;;;;;;;UAJnD,eAAA;;KAGZ,sBAAA,MACH,CAAA,SAAU,aAAA,0CAAuD,CAAA;;KAG9D,0BAAA,MACH,CAAA,SAAU,aAAA,0CAAuD,CAAA;;KAG9D,mBAAA,MACH,CAAA,SAAU,aAAA,0CAAuD,CAAA;;KAG9D,YAAA,8BACH,OAAA;EAEM,YAAA,EAAc,sBAAA,CAAuB,CAAA,IAAK,YAAA,CAAa,IAAA;EACvD,gBAAA,EAAkB,0BAAA,CAA2B,CAAA,IAAK,YAAA,CAAa,IAAA;EAC/D,SAAA,EAAW,mBAAA,CAAoB,CAAA,IAAK,YAAA,CAAa,IAAA;AAAA;EAEjD,YAAA;EAAkB,gBAAA;EAAsB,SAAA;AAAA;;KAGpC,yBAAA,qBACV,eAAA;EAA0B,OAAA;AAAA,IACtB,CAAA,eAAgB,CAAA,GACd,CAAA,CAAE,CAAA,sBACA,YAAA,CAAa,CAAA,CAAE,CAAA;EACb,YAAA;EAAkB,gBAAA;EAAsB,SAAA;AAAA;EAC1C,YAAA;EAAkB,gBAAA;EAAsB,SAAA;AAAA;EAC1C,YAAA;EAAkB,gBAAA;EAAsB,SAAA;AAAA;;KAGpC,qBAAA,qBACV,eAAA;EAA0B,OAAA;AAAA,IACtB,CAAA,eAAgB,CAAA,GAAI,CAAA,CAAE,CAAA,UAAW,SAAA,GAAY,CAAA,CAAE,CAAA,IAAK,SAAA,GAAY,SAAA,GAChE,SAAA;;KAGM,cAAA,GACV,eAAA;EAA0B,OAAA;AAAA,IACtB,CAAA,OAAQ,CAAA,UAAW,SAAA,GAAY,CAAA,OAAQ,CAAA,IAAK,SAAA,GAC5C,SAAA;;KAGM,cAAA,GACV,eAAA;EAA0B,OAAA;AAAA,UAChB,CAAA,0BAA2B,OAAA,OAAc,CAAA;;KAIzC,qBAAA,GACV,eAAA;EAA0B,iBAAA;AAAA,IAA8C,CAAA;;;;;UAMzD,4BAAA;EACf,IAAA;EACA,MAAA;AAAA"}
1
+ {"version":3,"file":"types-4uX3XKRM.d.mts","names":[],"sources":["../src/database/types.ts"],"mappings":";;;;;;AA+BA;;;;;AAAmC;;;;;;;;;;;;;;AAIiC;;;;;;;UAJnD,eAAA;;KAGZ,sBAAA,MACH,CAAA,SAAU,aAAA,0CAAuD,CAAA;;KAG9D,0BAAA,MACH,CAAA,SAAU,aAAA,0CAAuD,CAAA;;KAG9D,mBAAA,MACH,CAAA,SAAU,aAAA,0CAAuD,CAAA;;KAG9D,YAAA,8BACH,OAAA;EAEM,YAAA,EAAc,sBAAA,CAAuB,CAAA,IAAK,YAAA,CAAa,IAAA;EACvD,gBAAA,EAAkB,0BAAA,CAA2B,CAAA,IAAK,YAAA,CAAa,IAAA;EAC/D,SAAA,EAAW,mBAAA,CAAoB,CAAA,IAAK,YAAA,CAAa,IAAA;AAAA;EAEjD,YAAA;EAAkB,gBAAA;EAAsB,SAAA;AAAA;;KAGpC,yBAAA,qBACV,eAAA;EAA0B,OAAA;AAAA,IACtB,CAAA,eAAgB,CAAA,GACd,CAAA,CAAE,CAAA,sBACA,YAAA,CAAa,CAAA,CAAE,CAAA;EACb,YAAA;EAAkB,gBAAA;EAAsB,SAAA;AAAA;EAC1C,YAAA;EAAkB,gBAAA;EAAsB,SAAA;AAAA;EAC1C,YAAA;EAAkB,gBAAA;EAAsB,SAAA;AAAA;;KAGpC,qBAAA,qBACV,eAAA;EAA0B,OAAA;AAAA,IACtB,CAAA,eAAgB,CAAA,GAAI,CAAA,CAAE,CAAA,UAAW,SAAA,GAAY,CAAA,CAAE,CAAA,IAAK,SAAA,GAAY,SAAA,GAChE,SAAA;;KAGM,cAAA,GACV,eAAA;EAA0B,OAAA;AAAA,IACtB,CAAA,OAAQ,CAAA,UAAW,SAAA,GAAY,CAAA,OAAQ,CAAA,IAAK,SAAA,GAC5C,SAAA;;KAGM,cAAA,GACV,eAAA;EAA0B,OAAA;AAAA,UAChB,CAAA,0BAA2B,OAAA,OAAc,CAAA;;KAIzC,qBAAA,GACV,eAAA;EAA0B,iBAAA;AAAA,IAA8C,CAAA;;;;;UAMzD,4BAAA;EACf,IAAA;EACA,MAAA;AAAA"}
@@ -8,4 +8,4 @@ interface AccessControlOptions<TStatements extends Statements = Statements, TRol
8
8
  }
9
9
  //#endregion
10
10
  export { RolePermissions as n, AccessControlOptions as t };
11
- //# sourceMappingURL=types-BLyu9dAd.d.mts.map
11
+ //# sourceMappingURL=types-tu9pTehB.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types-BLyu9dAd.d.mts","names":[],"sources":["../src/access-control/types.ts"],"mappings":";;;KAEY,eAAA,qBAAoC,UAAA,kBAClC,WAAA,aAAwB,WAAA,CAAY,CAAA;AAAA,UAGjC,oBAAA,qBAAyC,UAAA,GAAa,UAAA,iBAA2B,MAAA,SAAe,eAAA,CAAgB,WAAA,KAAgB,MAAA,SAAe,eAAA,CAAgB,WAAA;EAC9K,EAAA,EAAI,aAAA;EACJ,KAAA,gBAAqB,MAAA,GAAS,IAAA;AAAA"}
1
+ {"version":3,"file":"types-tu9pTehB.d.mts","names":[],"sources":["../src/access-control/types.ts"],"mappings":";;;KAEY,eAAA,qBAAoC,UAAA,kBAClC,WAAA,aAAwB,WAAA,CAAY,CAAA;AAAA,UAGjC,oBAAA,qBAAyC,UAAA,GAAa,UAAA,iBAA2B,MAAA,SAAe,eAAA,CAAgB,WAAA,KAAgB,MAAA,SAAe,eAAA,CAAgB,WAAA;EAC9K,EAAA,EAAI,aAAA;EACJ,KAAA,gBAAqB,MAAA,GAAS,IAAA;AAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stratal/framework",
3
- "version": "0.0.0-canary-1658945",
3
+ "version": "0.0.0-canary-13b0e8d",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "author": "Temitayo Fadojutimi",
@@ -57,10 +57,10 @@
57
57
  "generate": "zenstack generate --schema=test/schema.zmodel -o test/zenstack",
58
58
  "generate:types": "wrangler types -c test/wrangler.jsonc",
59
59
  "pretest": "npx dotenv -- yarn generate && yarn generate:types",
60
- "test": "vitest run",
61
- "test:watch": "vitest",
62
- "test:e2e": "vitest run --project e2e",
63
- "test:coverage": "vitest run --coverage",
60
+ "test": "npx dotenv -- vitest run",
61
+ "test:watch": "npx dotenv -- vitest",
62
+ "test:e2e": "npx dotenv -- vitest run --project e2e",
63
+ "test:coverage": "npx dotenv -- vitest run --coverage",
64
64
  "test:db": "docker compose -f test/docker-compose.yml up -d",
65
65
  "typecheck": "yarn tsc --noEmit",
66
66
  "lint": "npx oxlint .",
@@ -77,13 +77,12 @@
77
77
  "@zenstackhq/schema": ">=3.6",
78
78
  "better-auth": ">=1.6",
79
79
  "pg": "^8.0.0",
80
- "reflect-metadata": "^0.2.2",
81
- "stratal": "0.0.0-canary-1658945"
80
+ "stratal": "0.0.0-canary-13b0e8d"
82
81
  },
83
82
  "devDependencies": {
84
83
  "@better-auth/core": "^1.6.11",
85
- "@cloudflare/vitest-pool-workers": "^0.16.3",
86
- "@cloudflare/workers-types": "4.20260510.1",
84
+ "@cloudflare/vitest-pool-workers": "^0.16.10",
85
+ "@cloudflare/workers-types": "4.20260529.1",
87
86
  "@stratal/testing": "workspace:^",
88
87
  "@types/node": "^25.6.2",
89
88
  "@types/pg": "^8.20.0",
@@ -97,11 +96,10 @@
97
96
  "better-auth": "^1.6.11",
98
97
  "dotenv-cli": "^11.0.0",
99
98
  "pg": "^8.20.0",
100
- "reflect-metadata": "^0.2.2",
101
99
  "stratal": "workspace:*",
102
100
  "tsdown": "^0.22.0",
103
101
  "typescript": "^6.0.3",
104
102
  "vitest": "~4.1.5",
105
- "wrangler": "^4.90.0"
103
+ "wrangler": "^4.95.0"
106
104
  }
107
105
  }
@@ -1 +0,0 @@
1
- {"version":3,"file":"access.service-Cb99esfz.mjs","names":[],"sources":["../src/access-control/plugin.ts","../src/access-control/services/access.service.ts"],"sourcesContent":["import type { BetterAuthPlugin } from 'better-auth'\nimport type { AccessControlOptions } from './types'\n\n/**\n * Creates the Stratal access control Better Auth plugin.\n *\n * Ensures the `user.role` schema field exists.\n * No endpoints are added — all permission logic lives in AccessService.\n *\n * Auto-added to Better Auth options when `accessControl` is provided to\n * `AuthModule.forRootAsync()`. Users never call this directly.\n */\nexport function createStratalAcPlugin(_options: AccessControlOptions): BetterAuthPlugin {\n return {\n id: 'stratal-ac',\n schema: {\n user: {\n fields: {\n role: {\n type: 'string',\n required: false,\n input: false,\n defaultValue: 'user',\n },\n },\n },\n },\n }\n}\n","import type { DatabaseService } from '@stratal/framework/database'\nimport { DI_TOKENS, inject, Transient } from 'stratal/di'\nimport type { AuthContext } from '../../context/auth-context'\nimport { AC_TOKENS } from '../tokens'\nimport type { AccessControlOptions } from '../types'\n\nfunction parseRoles(role: string | null | undefined): string[] {\n if (!role) return []\n return role.split(',').map(r => r.trim()).filter(Boolean)\n}\n\n/**\n * AccessService\n *\n * Request-scoped service for role and permission management.\n *\n * Roles for the current user are read from AuthContext (populated by\n * SessionVerificationMiddleware — no DB hit). Other users are resolved\n * from the database.\n *\n * Permission checks use Better Auth's `role.authorize()` locally with\n * OR logic — access is granted if any of the user's roles allows it.\n *\n * @example\n * ```typescript\n * // Check current user\n * await accessService.currentUserHasPermission({ posts: ['update'] })\n *\n * // Check arbitrary user (e.g. from an admin action)\n * await accessService.hasPermission(userId, { admin: ['access'] })\n *\n * // Assign a role\n * await accessService.setUserRole(userId, 'admin')\n *\n * // Assign multiple roles\n * await accessService.setUserRole(userId, ['editor', 'reviewer'])\n * ```\n */\n@Transient(AC_TOKENS.AccessService)\nexport class AccessService {\n constructor(\n @inject(DI_TOKENS.AuthContext)\n private readonly authContext: AuthContext,\n @inject(DI_TOKENS.Database)\n private readonly db: DatabaseService,\n @inject(AC_TOKENS.Options)\n private readonly options: AccessControlOptions\n ) { }\n\n /**\n * Get all roles for a user.\n *\n * Uses AuthContext for the current user (no DB hit).\n * Falls back to DB for other users.\n */\n async getUserRoles(userId: string): Promise<string[]> {\n if (userId === this.authContext.getUserId()) {\n const roles = this.authContext.getRoles()\n if (roles.length > 0) return roles\n }\n const user = await (this.db).user.findUnique({\n where: { id: userId },\n select: { role: true },\n })\n return parseRoles(user?.role)\n }\n\n /**\n * Assign one or more roles to a user.\n *\n * Multiple roles are stored as a comma-separated string in `user.role`.\n */\n async setUserRole(userId: string, role: string | string[]): Promise<void> {\n const roleStr = Array.isArray(role) ? role.join(',') : role\n await this.db.user.update({\n where: { id: userId },\n data: { role: roleStr },\n })\n }\n\n /**\n * Check if a user has the required permissions.\n *\n * Returns true if any of the user's roles grants all of the requested permissions.\n */\n async hasPermission(userId: string, permissions: Record<string, string[]>): Promise<boolean> {\n const roles = await this.getUserRoles(userId)\n return this.checkPermissions(roles, permissions)\n }\n\n /**\n * Get the merged permission set for a user across all their roles.\n * Useful for sending to the frontend.\n */\n async getPermissionsForUser(userId: string): Promise<Record<string, string[]>> {\n const roles = await this.getUserRoles(userId)\n return this.mergePermissions(roles)\n }\n\n /**\n * Get all roles for the currently authenticated user.\n * Reads from AuthContext — no DB hit.\n */\n getCurrentUserRoles(): string[] {\n return this.authContext.getRoles()\n }\n\n /**\n * Check if the currently authenticated user has the required permissions.\n * Reads roles from AuthContext — no DB hit.\n */\n currentUserHasPermission(permissions: Record<string, string[]>): boolean {\n const roles = this.authContext.getRoles()\n if (roles.length === 0) return false\n return this.checkPermissions(roles, permissions)\n }\n\n /**\n * Get merged permissions for the currently authenticated user.\n * Reads roles from AuthContext — no DB hit.\n */\n getCurrentUserPermissions(): Record<string, string[]> {\n const roles = this.authContext.getRoles()\n return this.mergePermissions(roles)\n }\n\n private checkPermissions(roles: string[], permissions: Record<string, string[]>): boolean {\n return roles.some(roleName => {\n const roleObj = this.options.roles[roleName]\n if (!roleObj) return false\n\n const specific: Record<string, string[]> = {}\n\n for (const [resource, actions] of Object.entries(permissions)) {\n if (actions.includes('*')) {\n // Wildcard: role must have at least one action defined for this resource\n const roleActions = (roleObj.statements as Record<string, readonly string[]>)[resource]\n if (!roleActions?.length) return false\n } else {\n specific[resource] = actions\n }\n }\n\n return Object.keys(specific).length === 0 || roleObj.authorize(specific).success\n })\n }\n\n private mergePermissions(roles: string[]): Record<string, string[]> {\n const result: Record<string, string[]> = {}\n for (const roleName of roles) {\n const roleObj = this.options.roles[roleName]\n if (!roleObj) continue\n for (const [resource, actions] of Object.entries(roleObj.statements)) {\n result[resource] ??= []\n for (const action of actions as string[]) {\n if (!result[resource].includes(action)) {\n result[resource].push(action)\n }\n }\n }\n }\n return result\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AAYA,SAAgB,sBAAsB,UAAkD;CACtF,OAAO;EACL,IAAI;EACJ,QAAQ,EACN,MAAM,EACJ,QAAQ,EACN,MAAM;GACJ,MAAM;GACN,UAAU;GACV,OAAO;GACP,cAAc;GACf,EACF,EACF,EACF;EACF;;;;ACrBH,SAAS,WAAW,MAA2C;CAC7D,IAAI,CAAC,MAAM,OAAO,EAAE;CACpB,OAAO,KAAK,MAAM,IAAI,CAAC,KAAI,MAAK,EAAE,MAAM,CAAC,CAAC,OAAO,QAAQ;;AA+BpD,IAAA,gBAAA,MAAM,cAAc;CAGN;CAEA;CAEA;CANnB,YACE,aAEA,IAEA,SAEA;EALiB,KAAA,cAAA;EAEA,KAAA,KAAA;EAEA,KAAA,UAAA;;;;;;;;CASnB,MAAM,aAAa,QAAmC;EACpD,IAAI,WAAW,KAAK,YAAY,WAAW,EAAE;GAC3C,MAAM,QAAQ,KAAK,YAAY,UAAU;GACzC,IAAI,MAAM,SAAS,GAAG,OAAO;;EAM/B,OAAO,YAAW,MAJE,KAAK,GAAI,KAAK,WAAW;GAC3C,OAAO,EAAE,IAAI,QAAQ;GACrB,QAAQ,EAAE,MAAM,MAAM;GACvB,CAAC,GACsB,KAAK;;;;;;;CAQ/B,MAAM,YAAY,QAAgB,MAAwC;EACxE,MAAM,UAAU,MAAM,QAAQ,KAAK,GAAG,KAAK,KAAK,IAAI,GAAG;EACvD,MAAM,KAAK,GAAG,KAAK,OAAO;GACxB,OAAO,EAAE,IAAI,QAAQ;GACrB,MAAM,EAAE,MAAM,SAAS;GACxB,CAAC;;;;;;;CAQJ,MAAM,cAAc,QAAgB,aAAyD;EAC3F,MAAM,QAAQ,MAAM,KAAK,aAAa,OAAO;EAC7C,OAAO,KAAK,iBAAiB,OAAO,YAAY;;;;;;CAOlD,MAAM,sBAAsB,QAAmD;EAC7E,MAAM,QAAQ,MAAM,KAAK,aAAa,OAAO;EAC7C,OAAO,KAAK,iBAAiB,MAAM;;;;;;CAOrC,sBAAgC;EAC9B,OAAO,KAAK,YAAY,UAAU;;;;;;CAOpC,yBAAyB,aAAgD;EACvE,MAAM,QAAQ,KAAK,YAAY,UAAU;EACzC,IAAI,MAAM,WAAW,GAAG,OAAO;EAC/B,OAAO,KAAK,iBAAiB,OAAO,YAAY;;;;;;CAOlD,4BAAsD;EACpD,MAAM,QAAQ,KAAK,YAAY,UAAU;EACzC,OAAO,KAAK,iBAAiB,MAAM;;CAGrC,iBAAyB,OAAiB,aAAgD;EACxF,OAAO,MAAM,MAAK,aAAY;GAC5B,MAAM,UAAU,KAAK,QAAQ,MAAM;GACnC,IAAI,CAAC,SAAS,OAAO;GAErB,MAAM,WAAqC,EAAE;GAE7C,KAAK,MAAM,CAAC,UAAU,YAAY,OAAO,QAAQ,YAAY,EAC3D,IAAI,QAAQ,SAAS,IAAI;QAGnB,CADiB,QAAQ,WAAiD,WAC5D,QAAQ,OAAO;UAEjC,SAAS,YAAY;GAIzB,OAAO,OAAO,KAAK,SAAS,CAAC,WAAW,KAAK,QAAQ,UAAU,SAAS,CAAC;IACzE;;CAGJ,iBAAyB,OAA2C;EAClE,MAAM,SAAmC,EAAE;EAC3C,KAAK,MAAM,YAAY,OAAO;GAC5B,MAAM,UAAU,KAAK,QAAQ,MAAM;GACnC,IAAI,CAAC,SAAS;GACd,KAAK,MAAM,CAAC,UAAU,YAAY,OAAO,QAAQ,QAAQ,WAAW,EAAE;IACpE,OAAO,cAAc,EAAE;IACvB,KAAK,MAAM,UAAU,SACnB,IAAI,CAAC,OAAO,UAAU,SAAS,OAAO,EACpC,OAAO,UAAU,KAAK,OAAO;;;EAKrC,OAAO;;;;CA3HV,UAAU,UAAU,cAAc;oBAG9B,OAAO,UAAU,YAAY,CAAA;oBAE7B,OAAO,UAAU,SAAS,CAAA;oBAE1B,OAAO,UAAU,QAAQ,CAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"auth-context-MWdQipaK.mjs","names":[],"sources":["../src/context/auth-context.ts"],"sourcesContent":["import type { BaseUser } from '@better-auth/core/db'\nimport { Transient, DI_TOKENS } from 'stratal/di'\nimport { AuthError } from 'stratal/errors'\nimport {\n UserNotAuthenticatedError\n} from './errors'\n\n/**\n * Authenticated user shape stored in {@link AuthContext}.\n *\n * Inherits Better Auth's base user fields. Apps whose schema stores\n * `firstName`/`lastName` instead of a `name` column should expose a `name`\n * via a ZenStack result extension (see\n * https://zenstack.dev/docs/orm/plugins/extending-orm-client#adding-fields-to-query-results)\n * so reads return a populated `name` for free.\n *\n * Augment via TypeScript module declaration to add app-specific fields. Match\n * the augmentation to whatever your Better Auth `user.additionalFields` /\n * plugins are configured to return:\n *\n * @example\n * ```ts\n * declare module '@stratal/framework/context' {\n * interface AuthUser {\n * role: string\n * locale: string\n * }\n * }\n * ```\n */\nexport interface AuthUser extends BaseUser {}\n\nexport interface AuthInfo {\n user: AuthUser\n}\n\n@Transient(DI_TOKENS.AuthContext)\nexport class AuthContext {\n protected user?: AuthUser\n\n /**\n * Set authentication context.\n * This should be called once per request with the authenticated user.\n */\n setAuthContext(info: AuthInfo): void {\n this.user = info.user\n }\n\n /**\n * Get the authenticated user if available.\n * Returns undefined if no user is authenticated.\n */\n getUser(): AuthUser | undefined {\n return this.user\n }\n\n /**\n * Get the authenticated user or throw if not authenticated.\n */\n requireUser(): AuthUser {\n if (!this.user) {\n throw new UserNotAuthenticatedError()\n }\n return this.user\n }\n\n /**\n * Get user ID if available.\n * Returns undefined if no user is authenticated.\n */\n getUserId(): string | undefined {\n return this.user?.id\n }\n\n /**\n * Get user ID or throw if not authenticated.\n * Use this when authentication is required.\n */\n requireUserId(): string {\n return this.requireUser().id\n }\n\n /**\n * Get full authentication context or throw if not initialized.\n */\n getAuthInfo(): AuthInfo {\n if (!this.user) {\n throw new AuthError('Auth context has not been initialized')\n }\n return { user: this.user }\n }\n\n /**\n * Get the raw role string from the authenticated user.\n *\n * Reads from `user.role` — apps that use roles should augment {@link AuthUser}\n * with `role: string` (or similar) so this returns a typed value.\n */\n getRole(): string | undefined {\n return (this.user as { role?: string } | undefined)?.role\n }\n\n /**\n * Get the user's roles as an array.\n * Returns an empty array if no role is set or user is not authenticated.\n */\n getRoles(): string[] {\n const role = this.getRole()\n if (!role) return []\n return role.split(',').map(r => r.trim()).filter(Boolean)\n }\n\n /**\n * Check if user is authenticated.\n */\n isAuthenticated(): boolean {\n return !!this.user\n }\n\n /**\n * Clear authentication context.\n * Useful for testing or cleanup.\n */\n clearAuthContext(): void {\n this.user = undefined\n }\n}\n"],"mappings":";;;;;AAqCO,IAAA,cAAA,MAAM,YAAY;CACvB;;;;;CAMA,eAAe,MAAsB;EACnC,KAAK,OAAO,KAAK;;;;;;CAOnB,UAAgC;EAC9B,OAAO,KAAK;;;;;CAMd,cAAwB;EACtB,IAAI,CAAC,KAAK,MACR,MAAM,IAAI,2BAA2B;EAEvC,OAAO,KAAK;;;;;;CAOd,YAAgC;EAC9B,OAAO,KAAK,MAAM;;;;;;CAOpB,gBAAwB;EACtB,OAAO,KAAK,aAAa,CAAC;;;;;CAM5B,cAAwB;EACtB,IAAI,CAAC,KAAK,MACR,MAAM,IAAI,UAAU,wCAAwC;EAE9D,OAAO,EAAE,MAAM,KAAK,MAAM;;;;;;;;CAS5B,UAA8B;EAC5B,OAAQ,KAAK,MAAwC;;;;;;CAOvD,WAAqB;EACnB,MAAM,OAAO,KAAK,SAAS;EAC3B,IAAI,CAAC,MAAM,OAAO,EAAE;EACpB,OAAO,KAAK,MAAM,IAAI,CAAC,KAAI,MAAK,EAAE,MAAM,CAAC,CAAC,OAAO,QAAQ;;;;;CAM3D,kBAA2B;EACzB,OAAO,CAAC,CAAC,KAAK;;;;;;CAOhB,mBAAyB;EACvB,KAAK,OAAO,KAAA;;;0BAxFf,UAAU,UAAU,YAAY,CAAA,EAAA,YAAA"}
@@ -1,6 +0,0 @@
1
- //#region \0@oxc-project+runtime@0.129.0/helpers/decorateMetadata.js
2
- function __decorateMetadata(k, v) {
3
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
4
- }
5
- //#endregion
6
- export { __decorateMetadata as t };
@@ -1 +0,0 @@
1
- {"version":3,"file":"index-Cnx6eRgJ.d.mts","names":[],"sources":["../src/database/database.module.ts","../src/database/database.service.ts","../src/database/database.tokens.ts","../src/database/decorators/inject-db.decorator.ts","../src/database/errors/record-not-found.error.ts","../src/database/errors/unique-constraint.error.ts","../src/database/errors/from-zenstack-error.ts","../src/database/event-types.ts","../src/database/i18n/en.ts","../src/database/plugins/error-handler.plugin.ts","../src/database/plugins/event-emitter.plugin.ts","../src/database/plugins/schema-switcher.ts","../src/database/commands/zenstack.command.ts","../src/database/commands/db-generate.command.ts","../src/database/commands/db-pull.command.ts","../src/database/commands/db-push.command.ts","../src/database/commands/migrate-deploy.command.ts","../src/database/commands/migrate-dev.command.ts","../src/database/commands/migrate-reset.command.ts","../src/database/commands/migrate-status.command.ts"],"mappings":"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"}