@stratal/framework 0.0.26 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/CHANGELOG.md +596 -0
  2. package/README.md +166 -24
  3. package/dist/access-control/index.d.mts +43 -16
  4. package/dist/access-control/index.d.mts.map +1 -1
  5. package/dist/access-control/index.mjs +5 -5
  6. package/dist/access-control/index.mjs.map +1 -1
  7. package/dist/{access.service-BmDhE-re.mjs → access.service-BjmnWBEo.mjs} +35 -17
  8. package/dist/access.service-BjmnWBEo.mjs.map +1 -0
  9. package/dist/auth/index.d.mts +105 -103
  10. package/dist/auth/index.d.mts.map +1 -1
  11. package/dist/auth/index.mjs +123 -24
  12. package/dist/auth/index.mjs.map +1 -1
  13. package/dist/{auth-context-CGVbiSX3.d.mts → auth-context-C1om3Zsr.d.mts} +1 -2
  14. package/dist/auth-context-C1om3Zsr.d.mts.map +1 -0
  15. package/dist/{auth-context-C8NBfiMa.mjs → auth-context-cNSS1rmh.mjs} +2 -2
  16. package/dist/{auth-context-C8NBfiMa.mjs.map → auth-context-cNSS1rmh.mjs.map} +1 -1
  17. package/dist/auth.service-Onf3JkyL.d.mts +44 -0
  18. package/dist/auth.service-Onf3JkyL.d.mts.map +1 -0
  19. package/dist/context/index.d.mts +4 -5
  20. package/dist/context/index.d.mts.map +1 -1
  21. package/dist/context/index.mjs +1 -1
  22. package/dist/database/index.d.mts +3 -3
  23. package/dist/database/index.mjs +413 -34
  24. package/dist/database/index.mjs.map +1 -1
  25. package/dist/{decorate-B7nr7eBl.mjs → decorate-RQD1h28J.mjs} +1 -1
  26. package/dist/{decorateParam-DwV9LSPl.mjs → decorateParam-xwTkq9gO.mjs} +2 -2
  27. package/dist/{decorateParam-DwV9LSPl.mjs.map → decorateParam-xwTkq9gO.mjs.map} +1 -1
  28. package/dist/factory/index.d.mts +3 -5
  29. package/dist/factory/index.d.mts.map +1 -1
  30. package/dist/factory/index.mjs.map +1 -1
  31. package/dist/guards/index.d.mts +3 -4
  32. package/dist/guards/index.d.mts.map +1 -1
  33. package/dist/guards/index.mjs +4 -4
  34. package/dist/guards/index.mjs.map +1 -1
  35. package/dist/index-e_u1SRyd.d.mts +921 -0
  36. package/dist/index-e_u1SRyd.d.mts.map +1 -0
  37. package/dist/index.d.mts +1 -1
  38. package/dist/{types-CWZ9q74G.d.mts → types-B35g-lXi.d.mts} +18 -4
  39. package/dist/types-B35g-lXi.d.mts.map +1 -0
  40. package/package.json +31 -27
  41. package/dist/access.service-BmDhE-re.mjs.map +0 -1
  42. package/dist/auth-context-CGVbiSX3.d.mts.map +0 -1
  43. package/dist/index-Dt0YUA7r.d.mts +0 -446
  44. package/dist/index-Dt0YUA7r.d.mts.map +0 -1
  45. package/dist/types-CWZ9q74G.d.mts.map +0 -1
  46. package/dist/types-DabF8LGz.d.mts +0 -11
  47. package/dist/types-DabF8LGz.d.mts.map +0 -1
@@ -1,12 +1,11 @@
1
- import { t as AccessControlOptions } from "../types-DabF8LGz.mjs";
2
- import { r as AuthUser } from "../auth-context-CGVbiSX3.mjs";
1
+ import { n as AccessControlOptions, t as AuthService } from "../auth.service-Onf3JkyL.mjs";
2
+ import { r as AuthUser } from "../auth-context-C1om3Zsr.mjs";
3
3
  import { AsyncModuleOptions, DynamicModule } from "stratal/module";
4
4
  import { ApplicationError, HttpException } from "stratal/errors";
5
5
  import { Middleware, Next, RouteConfigurable, Router, RouterContext } from "stratal/router";
6
6
  import { LoggerService } from "stratal/logger";
7
7
  import { APIError } from "better-auth/api";
8
- import { Auth, BetterAuthOptions } from "better-auth";
9
-
8
+ import { BetterAuthOptions } from "better-auth";
10
9
  //#region src/context/router-context.augment.d.ts
11
10
  declare module 'stratal/router' {
12
11
  interface RouterContext {
@@ -18,22 +17,26 @@ declare module 'stratal/router' {
18
17
  */
19
18
  user(): AuthUser;
20
19
  }
21
- } //# sourceMappingURL=router-context.augment.d.ts.map
20
+ }
22
21
  //#endregion
23
22
  //#region src/auth/auth.module.d.ts
24
- interface AuthModuleAsyncOptions<TOptions extends BetterAuthOptions = BetterAuthOptions> extends AsyncModuleOptions<TOptions> {
23
+ export interface AuthModuleAsyncOptions<TOptions extends BetterAuthOptions = BetterAuthOptions> extends AsyncModuleOptions<TOptions> {
25
24
  /**
26
25
  * Optional access control configuration.
27
26
  * When provided, registers AccessService and auto-adds the Stratal AC plugin to Better Auth.
28
27
  */
29
28
  accessControl?: AccessControlOptions;
30
29
  }
31
- declare class AuthModule implements RouteConfigurable {
30
+ export declare class AuthModule implements RouteConfigurable {
32
31
  /**
33
32
  * Configure auth middleware globally.
34
33
  *
35
34
  * SessionVerificationMiddleware verifies the session and populates the
36
35
  * request-scoped AuthContext with the authenticated user.
36
+ *
37
+ * AccessShareMiddleware then shares the resulting roles and permissions to
38
+ * Inertia. Order matters: it reads what session verification wrote. Both
39
+ * no-op when their dependencies are absent.
37
40
  */
38
41
  configureRoutes(router: Router): void;
39
42
  /**
@@ -49,214 +52,213 @@ declare class AuthModule implements RouteConfigurable {
49
52
  static forRootAsync<TOptions extends BetterAuthOptions>(options: AuthModuleAsyncOptions<TOptions>): DynamicModule;
50
53
  }
51
54
  //#endregion
55
+ //#region src/auth/middleware/session-verification.middleware.d.ts
56
+ /**
57
+ * Session Verification Middleware
58
+ *
59
+ * Verifies user session via Better Auth and populates AuthContext with
60
+ * the authenticated user.
61
+ *
62
+ * **Responsibilities:**
63
+ * - Calls Better Auth's getSession() API
64
+ * - Populates AuthContext with the user record if the session is valid
65
+ * - Carries any cookie that read issued through to the response
66
+ * - Continues request chain regardless of session status
67
+ */
68
+ export declare class SessionVerificationMiddleware implements Middleware {
69
+ private readonly authService;
70
+ private logger;
71
+ constructor(authService: AuthService, logger: LoggerService);
72
+ handle(ctx: RouterContext, next: Next): Promise<Response | void>;
73
+ }
74
+ //#endregion
75
+ //#region src/auth/auth.primers.d.ts
76
+ /**
77
+ * Middleware the response-cache gateway must run before resolving partitions.
78
+ *
79
+ * `ctx.user()` reads the request-scoped `AuthContext`, which
80
+ * `SessionVerificationMiddleware` populates. The gateway runs outside the
81
+ * app's middleware chain, so without this a partition resolver calling
82
+ * `ctx.user()` would throw `UserNotAuthenticatedError` on every request.
83
+ *
84
+ * Pass it alongside `gateway: { entrypoint }` — `primers` without a configured
85
+ * gateway is a boot error, because nothing would ever run the middleware
86
+ * listed here.
87
+ *
88
+ * The cost lands in the primer, not the accessor: `ctx.user()` is a memory
89
+ * read, while `SessionVerificationMiddleware` calls
90
+ * `authService.auth.api.getSession()`. On a cache **miss** that call happens
91
+ * twice — once in the gateway to resolve the partition, once in the app's own
92
+ * chain, since each runs in its own request scope. On a **hit** the app never
93
+ * runs, so only the gateway's single call is paid.
94
+ *
95
+ * @example
96
+ * ```typescript
97
+ * ResponseCacheModule.forRoot({
98
+ * gateway: { entrypoint: 'Cached' },
99
+ * primers: AUTH_GATEWAY_PRIMERS,
100
+ * partitions: { user: (ctx) => ctx.user().id },
101
+ * })
102
+ * ```
103
+ */
104
+ export declare const AUTH_GATEWAY_PRIMERS: readonly [typeof SessionVerificationMiddleware];
105
+ //#endregion
52
106
  //#region src/auth/auth.tokens.d.ts
53
107
  /** Token for AuthService - core authentication service */
54
- declare const AUTH_SERVICE: unique symbol;
108
+ export declare const AUTH_SERVICE: unique symbol;
55
109
  /** Token for Better Auth options configuration */
56
- declare const AUTH_OPTIONS: unique symbol;
110
+ export declare const AUTH_OPTIONS: unique symbol;
57
111
  //#endregion
58
112
  //#region src/auth/errors/auth-errors.d.ts
59
- declare class UserNotFoundError extends HttpException {
113
+ export declare class UserNotFoundError extends HttpException {
60
114
  readonly email?: string | undefined;
61
115
  constructor(email?: string | undefined);
62
116
  }
63
- declare class InvalidCredentialsError extends HttpException {
117
+ export declare class InvalidCredentialsError extends HttpException {
64
118
  constructor();
65
119
  }
66
- declare class InvalidPasswordError extends HttpException {
120
+ export declare class InvalidPasswordError extends HttpException {
67
121
  constructor();
68
122
  }
69
- declare class InvalidEmailError extends HttpException {
123
+ export declare class InvalidEmailError extends HttpException {
70
124
  readonly email?: string | undefined;
71
125
  constructor(email?: string | undefined);
72
126
  }
73
- declare class SessionExpiredError extends HttpException {
127
+ export declare class SessionExpiredError extends HttpException {
74
128
  constructor();
75
129
  }
76
- declare class FreshSessionRequiredError extends HttpException {
130
+ export declare class FreshSessionRequiredError extends HttpException {
77
131
  constructor();
78
132
  }
79
- declare class EmailNotVerifiedError extends HttpException {
133
+ export declare class EmailNotVerifiedError extends HttpException {
80
134
  readonly email?: string | undefined;
81
135
  constructor(email?: string | undefined);
82
136
  }
83
- declare class PasswordTooShortError extends HttpException {
137
+ export declare class PasswordTooShortError extends HttpException {
84
138
  readonly minLength?: number | undefined;
85
139
  constructor(minLength?: number | undefined);
86
140
  }
87
- declare class PasswordTooLongError extends HttpException {
141
+ export declare class PasswordTooLongError extends HttpException {
88
142
  readonly maxLength?: number | undefined;
89
143
  constructor(maxLength?: number | undefined);
90
144
  }
91
- declare class AccountAlreadyExistsError extends HttpException {
145
+ export declare class AccountAlreadyExistsError extends HttpException {
92
146
  readonly email?: string | undefined;
93
147
  constructor(email?: string | undefined);
94
148
  }
95
- declare class SocialAccountLinkedError extends HttpException {
149
+ export declare class SocialAccountLinkedError extends HttpException {
96
150
  readonly provider?: string | undefined;
97
151
  constructor(provider?: string | undefined);
98
152
  }
99
- declare class CannotUnlinkLastAccountError extends HttpException {
153
+ export declare class CannotUnlinkLastAccountError extends HttpException {
100
154
  constructor();
101
155
  }
102
- declare class ProviderNotFoundError extends HttpException {
156
+ export declare class ProviderNotFoundError extends HttpException {
103
157
  readonly provider?: string | undefined;
104
158
  constructor(provider?: string | undefined);
105
159
  }
106
- declare class UserEmailNotFoundError extends HttpException {
160
+ export declare class UserEmailNotFoundError extends HttpException {
107
161
  constructor();
108
162
  }
109
- declare class AccountNotFoundError extends HttpException {
163
+ export declare class AccountNotFoundError extends HttpException {
110
164
  constructor();
111
165
  }
112
- declare class CredentialAccountNotFoundError extends HttpException {
166
+ export declare class CredentialAccountNotFoundError extends HttpException {
113
167
  constructor();
114
168
  }
115
- declare class UserAlreadyHasPasswordError extends HttpException {
169
+ export declare class UserAlreadyHasPasswordError extends HttpException {
116
170
  constructor();
117
171
  }
118
- declare class EmailCannotBeUpdatedError extends HttpException {
172
+ export declare class EmailCannotBeUpdatedError extends HttpException {
119
173
  readonly reason?: string | undefined;
120
174
  constructor(reason?: string | undefined);
121
175
  }
122
- declare class IdTokenNotSupportedError extends HttpException {
176
+ export declare class IdTokenNotSupportedError extends HttpException {
123
177
  constructor();
124
178
  }
125
- declare class TokenExpiredError extends HttpException {
179
+ export declare class TokenExpiredError extends HttpException {
126
180
  constructor();
127
181
  }
128
- declare class InvalidCallbackUrlError extends HttpException {
182
+ export declare class InvalidCallbackUrlError extends HttpException {
129
183
  constructor();
130
184
  }
131
- declare class InvalidOriginError extends HttpException {
185
+ export declare class InvalidOriginError extends HttpException {
132
186
  constructor();
133
187
  }
134
- declare class AuthValidationFailedError extends HttpException {
188
+ export declare class AuthValidationFailedError extends HttpException {
135
189
  constructor();
136
190
  }
137
- declare class EmailAlreadyVerifiedError extends HttpException {
191
+ export declare class EmailAlreadyVerifiedError extends HttpException {
138
192
  constructor();
139
193
  }
140
- declare class EmailMismatchError extends HttpException {
194
+ export declare class EmailMismatchError extends HttpException {
141
195
  constructor();
142
196
  }
143
197
  //#endregion
144
198
  //#region src/auth/errors/invalid-token.error.d.ts
145
- declare class InvalidTokenError extends HttpException {
199
+ export declare class InvalidTokenError extends HttpException {
146
200
  constructor();
147
201
  }
148
202
  //#endregion
149
203
  //#region src/auth/errors/organization-errors.d.ts
150
- declare class OrganizationNotFoundError extends HttpException {
204
+ export declare class OrganizationNotFoundError extends HttpException {
151
205
  constructor();
152
206
  }
153
- declare class OrganizationMemberNotFoundError extends HttpException {
207
+ export declare class OrganizationMemberNotFoundError extends HttpException {
154
208
  constructor();
155
209
  }
156
- declare class OrganizationInvitationNotFoundError extends HttpException {
210
+ export declare class OrganizationInvitationNotFoundError extends HttpException {
157
211
  constructor();
158
212
  }
159
- declare class OrganizationPermissionDeniedError extends HttpException {
213
+ export declare class OrganizationPermissionDeniedError extends HttpException {
160
214
  constructor();
161
215
  }
162
- declare class OrganizationInvitationRecipientMismatchError extends HttpException {
216
+ export declare class OrganizationInvitationRecipientMismatchError extends HttpException {
163
217
  constructor();
164
218
  }
165
- declare class OrganizationConflictError extends HttpException {
219
+ export declare class OrganizationConflictError extends HttpException {
166
220
  constructor();
167
221
  }
168
- declare class OrganizationLimitReachedError extends HttpException {
222
+ export declare class OrganizationLimitReachedError extends HttpException {
169
223
  constructor();
170
224
  }
171
- declare class OrganizationMembershipError extends HttpException {
225
+ export declare class OrganizationMembershipError extends HttpException {
172
226
  constructor();
173
227
  }
174
- declare class OrganizationTeamNotFoundError extends HttpException {
228
+ export declare class OrganizationTeamNotFoundError extends HttpException {
175
229
  constructor();
176
230
  }
177
- declare class OrganizationRoleNotFoundError extends HttpException {
231
+ export declare class OrganizationRoleNotFoundError extends HttpException {
178
232
  constructor();
179
233
  }
180
234
  //#endregion
181
235
  //#region src/auth/errors/token-required.error.d.ts
182
- declare class TokenRequiredError extends HttpException {
236
+ export declare class TokenRequiredError extends HttpException {
183
237
  constructor();
184
238
  }
185
239
  //#endregion
186
- //#region src/auth/services/auth.service.d.ts
187
- /**
188
- * AuthService
189
- *
190
- * Base authentication service using Better Auth.
191
- * Configured via AuthModule.forRootAsync() from the application layer.
192
- *
193
- * **Extensibility:**
194
- * Extend this class to add custom methods. Subclasses inherit
195
- * `@Request(AUTH_SERVICE)` scope automatically — no decorator needed.
196
- *
197
- * @example
198
- * ```typescript
199
- * @Request(AUTH_SERVICE)
200
- * export class AppAuthService extends AuthService<AuthOptions> {
201
- * async signInMagicLink(email: string) {
202
- * return wrapBetterAuth(async () => {
203
- * return this.auth.api.signInMagicLink({ body: { email }, headers: new Headers() })
204
- * })
205
- * }
206
- * }
207
- * ```
208
- */
209
- declare class AuthService<TOptions extends BetterAuthOptions = BetterAuthOptions> {
210
- protected readonly options: TOptions;
211
- private _authInstance?;
212
- constructor(options: TOptions);
213
- /**
214
- * Get the Better Auth instance.
215
- */
216
- get auth(): Auth<TOptions>;
217
- }
218
- //#endregion
219
- //#region src/auth/middleware/session-verification.middleware.d.ts
220
- /**
221
- * Session Verification Middleware
222
- *
223
- * Verifies user session via Better Auth and populates AuthContext with
224
- * the authenticated user.
225
- *
226
- * **Responsibilities:**
227
- * - Calls Better Auth's getSession() API
228
- * - Populates AuthContext with the user record if the session is valid
229
- * - Continues request chain regardless of session status
230
- */
231
- declare class SessionVerificationMiddleware implements Middleware {
232
- private readonly authService;
233
- private logger;
234
- constructor(authService: AuthService, logger: LoggerService);
235
- handle(ctx: RouterContext, next: Next): Promise<void>;
236
- }
237
- //#endregion
238
240
  //#region src/auth/utils/auth-helpers.d.ts
239
241
  /**
240
242
  * Get shared Better Auth error handler configuration.
241
243
  * Use this in Better Auth config's onAPIError option.
242
244
  */
243
- declare function getErrorHandlerConfig(): BetterAuthOptions['onAPIError'];
245
+ export declare function getErrorHandlerConfig(): BetterAuthOptions['onAPIError'];
244
246
  /**
245
247
  * Wrap a Better Auth function in a try/catch block and map errors to ApplicationError.
246
248
  */
247
- declare const wrapBetterAuth: <T>(fn: () => Promise<T>) => Promise<T>;
249
+ export declare const wrapBetterAuth: <T>(fn: () => Promise<T>) => Promise<T>;
248
250
  //#endregion
249
251
  //#region src/auth/utils/better-auth-error-handler.d.ts
250
252
  /**
251
253
  * Maps Better Auth API error codes to ApplicationError instances.
252
254
  */
253
- declare function mapBetterAuthError(error: APIError): ApplicationError;
255
+ export declare function mapBetterAuthError(error: APIError): ApplicationError;
254
256
  /**
255
257
  * Type guard to check if an error is a Better Auth APIError.
256
258
  * Uses duck typing to handle bundler environments (e.g. Vite)
257
259
  * where instanceof may fail across module boundaries.
258
260
  */
259
- declare function isAPIError(error: unknown): error is APIError;
261
+ export declare function isAPIError(error: unknown): error is APIError;
260
262
  //#endregion
261
- export { AUTH_OPTIONS, AUTH_SERVICE, AccountAlreadyExistsError, AccountNotFoundError, AuthModule, AuthModuleAsyncOptions, AuthService, AuthValidationFailedError, CannotUnlinkLastAccountError, CredentialAccountNotFoundError, EmailAlreadyVerifiedError, EmailCannotBeUpdatedError, EmailMismatchError, EmailNotVerifiedError, FreshSessionRequiredError, 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 };
263
+ export { AuthService };
262
264
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
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
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/context/router-context.augment.ts","../../src/auth/auth.module.ts","../../src/auth/middleware/session-verification.middleware.ts","../../src/auth/auth.primers.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/utils/auth-helpers.ts","../../src/auth/utils/better-auth-error-handler.ts"],"mappings":";;;;;;;;;;YAaY;;;;;;;IAOR,QAAQ;;;;;iBCsDK,uBAAuB,iBAAiB,oBAAoB,2BACnE,mBAAmB;;;;;EAK3B,gBAAgB;;qBAML,sBAAsB;;;;;;;;;;;EAWjC,gBAAgB,QAAQ;;;;;;;;;;;SAejB,aAAa,iBAAiB,mBACnC,SAAS,uBAAuB,YAC/B;;;;;;;;;;;;;;;;qBCxFQ,yCAAyC;mBAGjC;UAC4B;EAH/C,YAEmB,aAAa,aACe,QAAQ;EAGjD,OAAO,KAAK,eAAe,MAAM,OAAO,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBCH3C,uCAAoB;;;;qBC7BpB;;qBAGA;;;qBCFA,0BAA0B;WACT;EAA5B,YAA4B;;qBAKjB,gCAAgC;EAC3C;;qBAGW,6BAA6B;EACxC;;qBAGW,0BAA0B;WACT;EAA5B,YAA4B;;qBAKjB,4BAA4B;EACvC;;qBAGW,kCAAkC;EAC7C;;qBAGW,8BAA8B;WACb;EAA5B,YAA4B;;qBAKjB,8BAA8B;WACb;EAA5B,YAA4B;;qBAKjB,6BAA6B;WACZ;EAA5B,YAA4B;;qBAKjB,kCAAkC;WACjB;EAA5B,YAA4B;;qBAKjB,iCAAiC;WAChB;EAA5B,YAA4B;;qBAKjB,qCAAqC;EAChD;;qBAGW,8BAA8B;WACb;EAA5B,YAA4B;;qBAKjB,+BAA+B;EAC1C;;qBAGW,6BAA6B;EACxC;;qBAGW,uCAAuC;EAClD;;qBAGW,oCAAoC;EAC/C;;qBAGW,kCAAkC;WACjB;EAA5B,YAA4B;;qBAKjB,iCAAiC;EAC5C;;qBAGW,0BAA0B;EACrC;;qBAGW,gCAAgC;EAC3C;;qBAGW,2BAA2B;EACtC;;qBAGW,kCAAkC;EAC7C;;qBAGW,kCAAkC;EAC7C;;qBAGW,2BAA2B;EACtC;;;;qBCnHW,0BAA0B;EACrC;;;;qBCDW,kCAAkC;EAC7C;;qBAEW,wCAAwC;EACnD;;qBAEW,4CAA4C;EACvD;;qBAEW,0CAA0C;EACrD;;qBAEW,qDAAqD;EAChE;;qBAEW,kCAAkC;EAC7C;;qBAEW,sCAAsC;EACjD;;qBAEW,oCAAoC;EAC/C;;qBAEW,sCAAsC;EACjD;;qBAEW,sCAAsC;EACjD;;;;qBC5BW,2BAA2B;EACtC;;;;;;;;wBCIc,yBAAyB;;;;qBAe5B,iBAAwB,GAAC,UAAY,QAAQ,OAAK,QAAQ;;;;;;wBCuBvD,mBAAmB,OAAO,WAAW;;;;;;wBAkNrC,WAAW,iBAAiB,SAAS"}
@@ -1,12 +1,13 @@
1
- import { n as createStratalAcPlugin, t as AccessService } from "../access.service-BmDhE-re.mjs";
2
- import { n as AC_TOKENS, t as __decorateParam } from "../decorateParam-DwV9LSPl.mjs";
3
- import { t as __decorate } from "../decorate-B7nr7eBl.mjs";
4
- import { t as AuthContext } from "../auth-context-C8NBfiMa.mjs";
1
+ import { n as AC_TOKENS, t as __decorateParam } from "../decorateParam-xwTkq9gO.mjs";
2
+ import { t as __decorate } from "../decorate-RQD1h28J.mjs";
3
+ import { a as AccessShareMiddleware, i as createStratalAcPlugin, n as AUTH_OPTIONS, r as AUTH_SERVICE, t as AccessService } from "../access.service-BjmnWBEo.mjs";
4
+ import { t as AuthContext } from "../auth-context-cNSS1rmh.mjs";
5
5
  import { CONTAINER_TOKEN, DI_TOKENS, Request, Transient, inject } from "stratal/di";
6
6
  import { Module } from "stratal/module";
7
7
  import { RATE_LIMITER_TOKENS, RateLimiterRegistry } from "stratal/rate-limiter";
8
8
  import { AuthError, HttpException } from "stratal/errors";
9
9
  import { RouterContext } from "stratal/router";
10
+ import { parseSetCookieHeader } from "better-auth/cookies";
10
11
  import { LOGGER_TOKENS } from "stratal/logger";
11
12
  import { betterAuth } from "better-auth/minimal";
12
13
  import { APIError } from "better-auth/api";
@@ -23,13 +24,11 @@ RouterContext.macro("user", function() {
23
24
  return this.getContainer().resolve(DI_TOKENS.AuthContext).requireUser();
24
25
  });
25
26
  //#endregion
26
- //#region src/auth/auth.tokens.ts
27
- /** Token for AuthService - core authentication service */
28
- const AUTH_SERVICE = Symbol.for("stratal:auth:service");
29
- /** Token for Better Auth options configuration */
30
- const AUTH_OPTIONS = Symbol.for("stratal:auth:options");
31
- //#endregion
32
27
  //#region src/auth/middleware/session-verification.middleware.ts
28
+ /** Every cookie name a `Set-Cookie` value assigns, read with Better Auth's own parser. */
29
+ function namesIn(setCookie) {
30
+ return [...parseSetCookieHeader(setCookie).keys()];
31
+ }
33
32
  let SessionVerificationMiddleware = class SessionVerificationMiddleware {
34
33
  authService;
35
34
  logger;
@@ -38,13 +37,45 @@ let SessionVerificationMiddleware = class SessionVerificationMiddleware {
38
37
  this.logger = logger;
39
38
  }
40
39
  async handle(ctx, next) {
40
+ /**
41
+ * The cookies Better Auth wrote while reading the session.
42
+ *
43
+ * Reading one is not always free of side effects: under `session.cookieCache`
44
+ * it mints the cached-session cookie, and once past `session.updateAge` it
45
+ * reissues the session cookie against the extended expiry. Both are `Set-Cookie`
46
+ * on a response Better Auth builds internally, which is discarded unless asked
47
+ * for — so left unforwarded, the cache is written on every request and read on
48
+ * none, and a sliding expiry never reaches the browser at all.
49
+ */
50
+ let issued = [];
41
51
  try {
42
- const result = await this.authService.auth.api.getSession({ headers: ctx.c.req.raw.headers });
43
- if (result) ctx.getContainer().resolve(DI_TOKENS.AuthContext).setAuthContext({ user: result.user });
52
+ const { headers, response } = await this.authService.auth.api.getSession({
53
+ headers: ctx.c.req.raw.headers,
54
+ returnHeaders: true
55
+ });
56
+ if (response) ctx.getContainer().resolve(DI_TOKENS.AuthContext).setAuthContext({ user: response.user });
57
+ issued = headers.getSetCookie();
44
58
  } catch (error) {
45
59
  this.logger.debug("Session validation failed (e.g., invalidated in DB)", { error });
46
60
  }
47
- return next();
61
+ await next();
62
+ if (issued.length === 0) return;
63
+ const status = ctx.c.res?.status;
64
+ if (typeof status !== "number" || status < 200 || status > 599) return;
65
+ /**
66
+ * A name the handler has already spoken for, which this must not overwrite.
67
+ *
68
+ * Sign-out is the case that makes this load-bearing rather than tidy. The session
69
+ * is still valid when it is read, so a cached-session cookie can be minted on the
70
+ * way in; the handler then clears that same cookie on the way out. Appending
71
+ * afterwards would put it back — a signed-out browser holding a cached session,
72
+ * which is the one thing the cache must never survive.
73
+ */
74
+ const spokenFor = new Set(ctx.c.res.headers.getSetCookie().flatMap(namesIn));
75
+ for (const cookie of issued) {
76
+ if (namesIn(cookie).some((name) => spokenFor.has(name))) continue;
77
+ ctx.c.header("set-cookie", cookie, { append: true });
78
+ }
48
79
  }
49
80
  };
50
81
  SessionVerificationMiddleware = __decorate([
@@ -95,22 +126,54 @@ RateLimiterRegistry.macro("forPath", function(path, resolver) {
95
126
  RateLimiterRegistry.macro("pathEntries", function() {
96
127
  return (pathResolvers.get(this) ?? /* @__PURE__ */ new Map()).entries();
97
128
  });
98
- const BETTER_AUTH_TTL_SECONDS = 86400;
99
129
  const BETTER_AUTH_KEY_PREFIX = "ba-rl:";
100
130
  /**
101
131
  * Adapt Stratal's `IRateLimiterStore` into better-auth's `customStorage` shape.
102
- * Better-auth supplies its own `RateLimit` records (`{ key, count, lastRequest }`);
103
- * the adapter just persists them under a separate key namespace.
132
+ *
133
+ * Better-auth requires a single atomic `consume(key, rule)` rather than the
134
+ * separate `get`/`set` pair it used to accept, because a split read and write
135
+ * cannot hold a limit under concurrent requests. `IRateLimiterStore` is a plain
136
+ * typed KV with no compare-and-set, so this is better-auth's documented
137
+ * read-decide-write fallback for backends lacking an atomic primitive, and the
138
+ * decision below mirrors their reference implementation exactly.
139
+ *
140
+ * Accuracy therefore follows the configured store, and matches what Stratal's
141
+ * own throttling already does on the same backend: exact on
142
+ * `InMemoryRateLimiterStore` (read-decide-write is atomic in a single isolate),
143
+ * best-effort on `KvRateLimiterStore`, where concurrent writes from different
144
+ * edge locations may undercount. Register a Durable Object store for strict
145
+ * accuracy across edges — the same escape hatch the KV store documents.
104
146
  */
105
147
  function createBetterAuthRateLimitStorage(store) {
106
- return {
107
- async get(key) {
108
- return await store.get(`${BETTER_AUTH_KEY_PREFIX}${key}`);
109
- },
110
- async set(key, value, _update) {
111
- await store.set(`${BETTER_AUTH_KEY_PREFIX}${key}`, value, BETTER_AUTH_TTL_SECONDS);
148
+ return { async consume(key, rule) {
149
+ const storageKey = `${BETTER_AUTH_KEY_PREFIX}${key}`;
150
+ const windowMs = rule.window * 1e3;
151
+ const now = Date.now();
152
+ const record = await store.get(storageKey);
153
+ const write = async (count, lastRequest) => {
154
+ await store.set(storageKey, {
155
+ key,
156
+ count,
157
+ lastRequest
158
+ }, rule.window);
159
+ };
160
+ if (!record || now - record.lastRequest >= windowMs) {
161
+ await write(1, now);
162
+ return {
163
+ allowed: true,
164
+ retryAfter: null
165
+ };
112
166
  }
113
- };
167
+ if (record.count >= rule.max) return {
168
+ allowed: false,
169
+ retryAfter: Math.ceil((record.lastRequest + windowMs - now) / 1e3)
170
+ };
171
+ await write(record.count + 1, now);
172
+ return {
173
+ allowed: true,
174
+ retryAfter: null
175
+ };
176
+ } };
114
177
  }
115
178
  /**
116
179
  * Project every `forPath` entry on the registry into better-auth's
@@ -471,9 +534,14 @@ let AuthModule = _AuthModule = class AuthModule {
471
534
  *
472
535
  * SessionVerificationMiddleware verifies the session and populates the
473
536
  * request-scoped AuthContext with the authenticated user.
537
+ *
538
+ * AccessShareMiddleware then shares the resulting roles and permissions to
539
+ * Inertia. Order matters: it reads what session verification wrote. Both
540
+ * no-op when their dependencies are absent.
474
541
  */
475
542
  configureRoutes(router) {
476
543
  router.use(SessionVerificationMiddleware);
544
+ router.use(AccessShareMiddleware);
477
545
  }
478
546
  /**
479
547
  * Configure AuthModule with async options factory.
@@ -538,6 +606,37 @@ let AuthModule = _AuthModule = class AuthModule {
538
606
  };
539
607
  AuthModule = _AuthModule = __decorate([Module({ providers: [AuthContext] })], AuthModule);
540
608
  //#endregion
541
- export { AUTH_OPTIONS, AUTH_SERVICE, AccountAlreadyExistsError, AccountNotFoundError, AuthModule, AuthService, AuthValidationFailedError, CannotUnlinkLastAccountError, CredentialAccountNotFoundError, EmailAlreadyVerifiedError, EmailCannotBeUpdatedError, EmailMismatchError, EmailNotVerifiedError, FreshSessionRequiredError, 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 };
609
+ //#region src/auth/auth.primers.ts
610
+ /**
611
+ * Middleware the response-cache gateway must run before resolving partitions.
612
+ *
613
+ * `ctx.user()` reads the request-scoped `AuthContext`, which
614
+ * `SessionVerificationMiddleware` populates. The gateway runs outside the
615
+ * app's middleware chain, so without this a partition resolver calling
616
+ * `ctx.user()` would throw `UserNotAuthenticatedError` on every request.
617
+ *
618
+ * Pass it alongside `gateway: { entrypoint }` — `primers` without a configured
619
+ * gateway is a boot error, because nothing would ever run the middleware
620
+ * listed here.
621
+ *
622
+ * The cost lands in the primer, not the accessor: `ctx.user()` is a memory
623
+ * read, while `SessionVerificationMiddleware` calls
624
+ * `authService.auth.api.getSession()`. On a cache **miss** that call happens
625
+ * twice — once in the gateway to resolve the partition, once in the app's own
626
+ * chain, since each runs in its own request scope. On a **hit** the app never
627
+ * runs, so only the gateway's single call is paid.
628
+ *
629
+ * @example
630
+ * ```typescript
631
+ * ResponseCacheModule.forRoot({
632
+ * gateway: { entrypoint: 'Cached' },
633
+ * primers: AUTH_GATEWAY_PRIMERS,
634
+ * partitions: { user: (ctx) => ctx.user().id },
635
+ * })
636
+ * ```
637
+ */
638
+ const AUTH_GATEWAY_PRIMERS = [SessionVerificationMiddleware];
639
+ //#endregion
640
+ export { AUTH_GATEWAY_PRIMERS, AUTH_OPTIONS, AUTH_SERVICE, AccountAlreadyExistsError, AccountNotFoundError, AuthModule, AuthService, AuthValidationFailedError, CannotUnlinkLastAccountError, CredentialAccountNotFoundError, EmailAlreadyVerifiedError, EmailCannotBeUpdatedError, EmailMismatchError, EmailNotVerifiedError, FreshSessionRequiredError, 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 };
542
641
 
543
642
  //# sourceMappingURL=index.mjs.map