@velajs/better-auth 1.28.0 → 1.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,88 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.30.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 4d0342b: Build on the tiered `@velajs/vela` entry points: module-author seams such as `Container`, `MetadataRegistry`, `DiscoveryService`, `PipelineRunner`, trusted request identity and entrypoint scopes come from `@velajs/vela/module-kit`, and features from their subpaths. The package's own exports are unchanged.
8
+
9
+ **Behavior change:** this release requires the `@velajs/vela` release that introduces `@velajs/vela/module-kit` and the feature subpaths; upgrade both together. Application code that imported these framework names from the root moves them as follows (the `@velajs/vela` changelog lists every name):
10
+
11
+ | Old import | New import | Examples |
12
+ |---|---|---|
13
+ | `@velajs/vela` | `@velajs/vela/module-kit` | `Container`, `MetadataRegistry`, `DiscoveryService`, `createDiscoverableDecorator`, `registerEntrypointKind`, `runInEntrypointScope`, `PipelineRunner`, `RuntimeAdapter`, `invokeScheduledJob`, `getRequestContainer`, `setTrustedRequestIdentity`, `resolveErrorReporter`, `lazyProvider`, `stableHash`, `defineMetadata` |
14
+ | `@velajs/vela` | `@velajs/vela/cache`, `/throttler`, `/schedule`, `/events`, `/health`, `/logging`, `/http-client` | `CacheModule`, `ResponseCacheModule`, `ThrottlerModule`, `ScheduleModule`, `Cron`, `EventEmitterModule`, `HealthModule`, `LoggingModule`, `HttpModule` |
15
+ | `@velajs/vela` | `@velajs/vela/openapi` | `Endpoint`, `defineEndpoint`, `createOpenApiDocument`, `ApiDoc`, `ApiTags`, `ApiResponse` |
16
+ | `@velajs/vela` | `@velajs/vela/security`, `/dispatch` | `SecurityModule`, `CorsModule`, `signUrl`, `NONCE_STORE`; `InternalDispatcher`, `SignedInvocation` |
17
+ | `@velajs/vela` | `@velajs/vela/validation`, `/websocket` | `ValidationPipe`, `defineDto`, `parseSchemaAsync`; `WebSocketGateway`, `WebSocketModule` |
18
+ | `@velajs/vela/internal` | `@velajs/vela/module-kit` | `Container`, `MetadataRegistry` |
19
+
20
+ ### Patch Changes
21
+
22
+ - Updated dependencies [4d0342b]
23
+ - Updated dependencies [4467619]
24
+ - Updated dependencies [7372d90]
25
+ - Updated dependencies [c101033]
26
+ - Updated dependencies [4d0342b]
27
+ - @velajs/authz@1.30.0
28
+ - @velajs/vela@1.30.0
29
+
30
+ ## 1.29.0
31
+
32
+ ### Minor Changes
33
+
34
+ - 2dd809d: `BetterAuthModule.forRoot({ key })` and `forRootAsync({ key })` no longer throw when a root is rebuilt with the same explicit key in one isolate, for example per Worker environment or per Durable Object. Within an application, the same registration still deduplicates.
35
+
36
+ **Behavior change:** an explicit `key` no longer rejects a different registration that reuses it within one application. The key now labels a registration instead of claiming it: the options shape and the auth instance (or `useFactory`) stay part of the module's identity, so each distinct registration under the same key, one with a different auth instance, factory or options, becomes its own module instance, as a registration without a key does. Previously the second registration threw. Use distinct keys, or pass the same auth instance or factory and options, when one application should hold a single registration.
37
+ - 8a3016c: Add `BetterAuthUpgradeAuthenticator` for `@WebSocketGateway({ authenticator: BetterAuthUpgradeAuthenticator })`. It reads the session cookie through the module's `BetterAuthService`, validates the full session, and issues a WebSocket identity under the `BetterAuthModule` issuer, as `AuthGuard` does for HTTP, expiring with the session. The tenant is the session's active organization; provide `BETTER_AUTH_UPGRADE_TENANT` in the module that declares the gateway to choose it per connection (`(session, context, request) => tenantId | undefined`). Without a tenant, the upgrade is refused.
38
+ - 4071cb7: The built-in guards inject the application `Reflector` instead of constructing their own: `AuthGuard` (`@velajs/better-auth`), `FeatureFlagGuard` (`@velajs/feature-flags`), `PermissionGuard` and `RolesGuard` (`@velajs/authz/vela`), `TenantGuard` (`@velajs/tenant/vela`) and `CedarGuard` (`@velajs/authz-cedar/vela`). `PermissionGuard`, `RolesGuard` and `TenantGuard` are now injectable, so `@UseGuards()` builds them in the declaring module through DI, like any provider.
39
+
40
+ **Behavior change:** each guard takes the `Reflector` as a constructor parameter, after any dependencies it already had: `new PermissionGuard(reflector)`, `new RolesGuard(reflector)`, `new TenantGuard(reflector)`, `new CedarGuard(reflector)`, `new AuthGuard(auth, options, reflector)` and `new FeatureFlagGuard(flags, reflector)`. Code that constructs a guard itself, such as `app.useGlobalGuards(new TenantGuard(app.get(Reflector)))` or a unit test, passes the application's `Reflector` or a `new Reflector()`, and a subclass that declares its own constructor passes it to `super()`. A guard class passed to `app.useGlobalGuards()` is built through DI only when a module registers it, including the copy a module registers for a `@UseGuards()` reference; otherwise pass an instance or register it as `{ provide: APP_GUARD, useClass: PermissionGuard }`. Guards resolved through DI need no change.
41
+ - 4071cb7: A factory without parameters may omit `inject` in `BetterAuthModule.forRootAsync()`, `MailModule.forRootAsync()`, `StorageModule.forRootAsync()`, `RpcClientModule.registerAsync()` and `overrideProvider(token).useFactory({ factory })`, as in the `@velajs/vela` factories. A factory with parameters still names their tokens in `inject`, and a dependency tuple given as a type argument still needs a matching `inject`.
42
+
43
+ `StorageModule.forRootAsync()` factories may return `{ driver, multipartGrantSecret }` instead of a bare driver, so the multipart grant secret comes through DI, for example from `ENV`, now that roots are static. The factory still runs once, on first use, or again on the next use until it succeeds; its secret takes precedence over `http.multipartGrantSecret` and is validated with the rest of the factory result on each such use, and multipart endpoints without any secret keep refusing every request. Adds the `StorageAsyncResult` and `StorageControllerOptions` types, and `createStorageController()` takes an optional token for the values it resolves per application.
44
+
45
+ **Behavior change:** a multipart grant secret shorter than 32 bytes is a server configuration error instead of a client error. `StorageModule.forRoot()` throws for such an `http.multipartGrantSecret` when the module is set up, and a `forRootAsync()` factory that returns one fails every storage operation and multipart request until the factory returns a valid result, which the controller answers with a redacted 502 `upstream_error`. Multipart requests no longer answer 400 `invalid_request` with a message that names the setting.
46
+
47
+ **Behavior change:** `MailModule.forRootAsync()` and `StorageModule.forRootAsync()` throw when called with a factory that declares parameters but no `inject`, naming the method, instead of running it with `undefined` arguments. `MailModuleAsyncOptions`, `StorageModuleAsyncOptions` and `RpcClientAsyncOptions` are type aliases instead of interfaces, so an interface can no longer extend them: intersect them instead (`RpcClientAsyncOptions<Inject> & { region: string }`). `MailModuleAsyncOptions.imports` is typed `ModuleImport[]`, so a caller that enables `exactOptionalPropertyTypes` omits it instead of passing `undefined`.
48
+
49
+ ### Patch Changes
50
+
51
+ - Updated dependencies [07d1713]
52
+ - Updated dependencies [db18d3a]
53
+ - Updated dependencies [07d1713]
54
+ - Updated dependencies [4071cb7]
55
+ - Updated dependencies [bacaacd]
56
+ - Updated dependencies [a814199]
57
+ - Updated dependencies [1838474]
58
+ - Updated dependencies [8a3016c]
59
+ - Updated dependencies [d803a49]
60
+ - Updated dependencies [b235935]
61
+ - Updated dependencies [08a81c8]
62
+ - Updated dependencies [5b5b81d]
63
+ - Updated dependencies [7daf4fc]
64
+ - Updated dependencies [35e8e0d]
65
+ - Updated dependencies [4420501]
66
+ - Updated dependencies [ff44b6a]
67
+ - Updated dependencies [6d4f0c0]
68
+ - Updated dependencies [e3bda2a]
69
+ - Updated dependencies [bd7e3c9]
70
+ - Updated dependencies [2b74880]
71
+ - Updated dependencies [5ba8635]
72
+ - Updated dependencies [db0c834]
73
+ - Updated dependencies [d6f6a65]
74
+ - Updated dependencies [8a3016c]
75
+ - Updated dependencies [d5a3ec8]
76
+ - Updated dependencies [0f7e8e7]
77
+ - Updated dependencies [41ec70d]
78
+ - Updated dependencies [b265297]
79
+ - Updated dependencies [bdfff47]
80
+ - Updated dependencies [28c7d07]
81
+ - Updated dependencies [8a3016c]
82
+ - Updated dependencies [44efdde]
83
+ - @velajs/vela@1.29.0
84
+ - @velajs/authz@1.29.0
85
+
3
86
  ## 1.28.0
4
87
 
5
88
  ### Minor Changes
package/README.md CHANGED
@@ -74,12 +74,12 @@ imports: [
74
74
 
75
75
  ### Pattern B — DI'd plugin construction
76
76
 
77
- `forRootAsync` requires an explicit `inject` tuple (use `inject: []` when there are no dependencies), so factory parameter types always have matching runtime tokens. It lets Vela services participate in your Better Auth configuration. On Workers, declare `WORKER_ENV = new InjectionToken<WorkerEnv>('app.Env')` and pass that token to `createCloudflareWorker(AppModule, { envToken: WORKER_ENV })`; the native event environment is available before DI factories run.
77
+ `forRootAsync` types the factory's parameters from its `inject` tuple, so they always have matching runtime tokens; a factory without parameters may omit `inject`. It lets Vela services participate in your Better Auth configuration. On Workers, inject the framework `ENV` from `@velajs/vela`: `createCloudflareWorker(AppModule)` seeds the native event environment before DI factories run, and `wrangler types` types its bindings.
78
78
 
79
79
  ```ts
80
80
  imports: [
81
81
  BetterAuthModule.forRootAsync({
82
- inject: [WORKER_ENV, EmailService],
82
+ inject: [ENV, EmailService],
83
83
  useFactory: (env, email) => betterAuth({
84
84
  database: drizzleAdapter(drizzle(env.DB), { provider: 'sqlite' }),
85
85
  plugins: [
@@ -191,6 +191,41 @@ falls back to a platform-attested client address.
191
191
 
192
192
  `BETTER_AUTH_OPTIONS` contains runtime configuration; read the auth instance from `BetterAuthService`. Use `createBetterAuthCatchallController()` for a manually mounted catch-all.
193
193
 
194
+ ## WebSocket upgrades
195
+
196
+ `BetterAuthUpgradeAuthenticator` authenticates a WebSocket upgrade from the Better Auth session cookie. The application resolves it from the module that declares the gateway, so that module must see `BetterAuthModule`'s exports:
197
+
198
+ ```ts
199
+ import { defineProvider, Module } from '@velajs/vela';
200
+ import { WebSocketGateway } from '@velajs/vela/websocket';
201
+ import {
202
+ BETTER_AUTH_UPGRADE_TENANT,
203
+ BetterAuthModule,
204
+ BetterAuthUpgradeAuthenticator,
205
+ } from '@velajs/better-auth';
206
+
207
+ @WebSocketGateway({
208
+ path: '/boards/:board/ws',
209
+ roomParam: 'board',
210
+ authenticator: BetterAuthUpgradeAuthenticator,
211
+ })
212
+ class BoardGateway {}
213
+
214
+ @Module({
215
+ imports: [BetterAuthModule.forRoot({ auth, issuer: 'boards' })],
216
+ providers: [
217
+ BoardGateway,
218
+ // Optional: choose the tenant per connection. Returning undefined refuses it.
219
+ defineProvider(BETTER_AUTH_UPGRADE_TENANT, {
220
+ useValue: (_session, context) => (context.room === 'lobby' ? 'public' : undefined),
221
+ }),
222
+ ],
223
+ })
224
+ class BoardsModule {}
225
+ ```
226
+
227
+ The principal carries the module's `issuer`, as `AuthGuard` does for HTTP, and the identity expires with the session. Every WebSocket identity needs a tenant: by default it is the session's active organization, and a session without one is refused unless a `BETTER_AUTH_UPGRADE_TENANT` resolver supplies a tenant.
228
+
194
229
  ## Trusted identity and typing
195
230
 
196
231
  `@CurrentUser()` and `@CurrentSession()` expose only validated Better Auth data tied to the exact current trusted identity. Public routes, missing/rejected sessions, logout, expiry, and another provider replacing the identity invalidate those values. Hono user variables cannot grant roles or permissions.
@@ -221,12 +256,11 @@ If you deploy to Cloudflare Workers, smoke-test your bundle for `node:` imports.
221
256
  Set `mountHandler: false` and mount the catch-all yourself if you need a base path other than `/api/auth`:
222
257
 
223
258
  ```ts
224
- import { Controller, All, Req, Inject, Injectable } from '@velajs/vela';
259
+ import { Controller, All, Req, Inject } from '@velajs/vela';
225
260
  import { BetterAuthService, Public } from '@velajs/better-auth';
226
261
 
227
262
  @Public(true)
228
263
  @Controller('/auth')
229
- @Injectable()
230
264
  class CustomCatchallController {
231
265
  constructor(@Inject(BetterAuthService) private auth: BetterAuthService) {}
232
266
  @All('/*') handle(@Req() c: Context) { return this.auth.handler(c.req.raw); }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import { a as Session, i as BetterAuthRuntimeOptions, n as BetterAuthInstance, o as User, r as BetterAuthModuleOptions, t as BetterAuthService } from "./better-auth.service-CvmboUID.js";
2
- import { CanActivate, DynamicModule, ExecutionContext, InferTokens, InjectionToken, Token, Type } from "@velajs/vela";
2
+ import { CanActivate, DynamicModule, ExecutionContext, InjectionToken, Reflector, Token, Type } from "@velajs/vela";
3
+ import { FactoryInject, InferTokens } from "@velajs/vela/module-kit";
4
+ import { UpgradeAuthenticator, WebSocketUpgradeAuthenticationContext, WebSocketUpgradeIdentity } from "@velajs/vela/websocket";
3
5
  import { Identity, PermissionResolver } from "@velajs/authz";
4
6
  //#region src/better-auth.module.d.ts
5
7
  /**
@@ -11,14 +13,15 @@ import { Identity, PermissionResolver } from "@velajs/authz";
11
13
  *
12
14
  * ```ts
13
15
  * BetterAuthModule.forRootAsync({
14
- * inject: [WORKER_ENV, ConfigService], // captured as readonly tuple
16
+ * inject: [ENV, ConfigService], // captured as readonly tuple
15
17
  * useFactory: (env, config) => // inferred from the tokens
16
18
  * betterAuth({ database: drizzleAdapter(drizzle(env.DB), ...) }),
17
19
  * });
18
20
  * ```
21
+ *
22
+ * A factory without parameters may omit `inject`.
19
23
  */
20
- interface ForRootAsyncOptions<Inject extends readonly Token[] = readonly Token[]> {
21
- inject: Inject;
24
+ type ForRootAsyncOptions<Inject extends readonly Token[] = readonly Token[]> = {
22
25
  imports?: DynamicModule['imports'];
23
26
  useFactory: (...deps: InferTokens<Inject>) => BetterAuthInstance;
24
27
  isGlobal?: boolean;
@@ -26,7 +29,7 @@ interface ForRootAsyncOptions<Inject extends readonly Token[] = readonly Token[]
26
29
  basePath?: string;
27
30
  issuer?: string;
28
31
  key?: string;
29
- }
32
+ } & FactoryInject<Inject>;
30
33
  export declare class BetterAuthModule {
31
34
  /**
32
35
  * Synchronous registration. The auth instance is constructed by the consumer
@@ -74,15 +77,49 @@ export declare class AuthGuard implements CanActivate {
74
77
  private readonly auth;
75
78
  private readonly opts;
76
79
  private readonly reflector;
77
- constructor(auth: BetterAuthService, opts: BetterAuthRuntimeOptions);
80
+ constructor(auth: BetterAuthService, opts: BetterAuthRuntimeOptions, reflector: Reflector);
78
81
  canActivate(context: ExecutionContext): Promise<boolean>;
79
82
  }
80
83
  //#endregion
84
+ //#region src/upgrade-authenticator.d.ts
85
+ /** The validated Better Auth session behind a WebSocket upgrade. */
86
+ interface BetterAuthUpgradeSession {
87
+ readonly user: User;
88
+ readonly session: Session;
89
+ }
90
+ /**
91
+ * Chooses the tenant a WebSocket connection joins. Returning no tenant (or
92
+ * throwing) refuses the upgrade.
93
+ */
94
+ type BetterAuthUpgradeTenantResolver = (session: BetterAuthUpgradeSession, context: WebSocketUpgradeAuthenticationContext, request: Request) => string | undefined | Promise<string | undefined>;
95
+ /**
96
+ * Optional tenant resolver for {@link BetterAuthUpgradeAuthenticator}. Provide it
97
+ * in the module that declares the gateway; without one, the session's active
98
+ * organization is the tenant.
99
+ */
100
+ export declare const BETTER_AUTH_UPGRADE_TENANT: InjectionToken<BetterAuthUpgradeTenantResolver>;
101
+ /**
102
+ * WebSocket upgrade authenticator backed by the Better Auth session cookie:
103
+ * `@WebSocketGateway({ authenticator: BetterAuthUpgradeAuthenticator })`.
104
+ *
105
+ * The principal uses the `BetterAuthModule` issuer, as `AuthGuard` does for
106
+ * HTTP. The identity expires with the session. Every WebSocket identity carries
107
+ * a tenant, so a session without an active organization is refused unless a
108
+ * {@link BETTER_AUTH_UPGRADE_TENANT} resolver supplies one.
109
+ */
110
+ export declare class BetterAuthUpgradeAuthenticator implements UpgradeAuthenticator {
111
+ private readonly auth;
112
+ private readonly options;
113
+ private readonly resolveTenant?;
114
+ constructor(auth: BetterAuthService, options: BetterAuthRuntimeOptions, resolveTenant?: BetterAuthUpgradeTenantResolver | undefined);
115
+ authenticate(request: Request, context: WebSocketUpgradeAuthenticationContext): Promise<WebSocketUpgradeIdentity | false>;
116
+ }
117
+ //#endregion
81
118
  //#region src/decorators/current-user.decorator.d.ts
82
- export declare const CurrentUser: (data?: unknown, ...pipes: import("@velajs/vela").PipeType[]) => ParameterDecorator;
119
+ export declare const CurrentUser: (data?: unknown, ...pipes: import("@velajs/vela/module-kit").PipeType[]) => ParameterDecorator;
83
120
  //#endregion
84
121
  //#region src/decorators/current-session.decorator.d.ts
85
- export declare const CurrentSession: (data?: unknown, ...pipes: import("@velajs/vela").PipeType[]) => ParameterDecorator;
122
+ export declare const CurrentSession: (data?: unknown, ...pipes: import("@velajs/vela/module-kit").PipeType[]) => ParameterDecorator;
86
123
  //#endregion
87
124
  //#region src/decorators/public.decorator.d.ts
88
125
  export declare const Public: import("@velajs/vela").ReflectableDecorator<boolean>;
@@ -147,5 +184,5 @@ export declare const permissionsFromAcRole: (role: BetterAuthAcRole) => string[]
147
184
  */
148
185
  export declare const betterAuthAcResolver: (roles: Readonly<Record<string, BetterAuthAcRole>>) => PermissionResolver;
149
186
  //#endregion
150
- export { type AuthUser, type BetterAuthAcRole, type BetterAuthInstance, type BetterAuthModuleOptions, type BetterAuthRuntimeOptions, BetterAuthService, type Session, type User };
187
+ export { type AuthUser, type BetterAuthAcRole, type BetterAuthInstance, type BetterAuthModuleOptions, type BetterAuthRuntimeOptions, BetterAuthService, type BetterAuthUpgradeSession, type BetterAuthUpgradeTenantResolver, type Session, type User };
151
188
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { a as __decorateMetadata, i as __decorateParam, n as BetterAuthService, r as __decorate, t as BETTER_AUTH_BUILDER } from "./better-auth.service-SNg1YFTK.js";
2
- import { All, Controller, Inject, Injectable, InjectionToken, Reflector, Req, UnauthorizedException, clearTrustedRequestIdentity, createParamDecorator, createTrustedRequestIdentityStore, defineProvider, getTrustedContextRequest, provideGlobal, setTrustedRequestIdentity, stableHash } from "@velajs/vela";
2
+ import { All, Controller, Inject, Injectable, InjectionToken, Optional, Reflector, Req, UnauthorizedException, createParamDecorator, defineProvider } from "@velajs/vela";
3
+ import { clearTrustedRequestIdentity, createTrustedRequestIdentityStore, getTrustedContextRequest, lazyProvider, provideGlobal, setTrustedRequestIdentity, stableHash } from "@velajs/vela/module-kit";
3
4
  import { getContextIdentity } from "@velajs/authz/vela";
4
5
  //#region src/decorators/public.decorator.ts
5
6
  const Public = Reflector.createDecorator({ key: "vela.auth.public" });
@@ -53,7 +54,6 @@ function createBetterAuthCatchallController(basePath = "/api/auth") {
53
54
  BetterAuthCatchallController = __decorate([
54
55
  Public(true),
55
56
  Controller(normalizedBasePath),
56
- Injectable(),
57
57
  __decorateParam(0, Inject(BetterAuthService)),
58
58
  __decorateMetadata("design:paramtypes", [typeof BetterAuthService === "undefined" ? Object : BetterAuthService])
59
59
  ], BetterAuthCatchallController);
@@ -174,10 +174,11 @@ const OPTIONAL_AUTH_KEY = OptionalAuth.KEY;
174
174
  let AuthGuard = class AuthGuard {
175
175
  auth;
176
176
  opts;
177
- reflector = new Reflector();
178
- constructor(auth, opts) {
177
+ reflector;
178
+ constructor(auth, opts, reflector) {
179
179
  this.auth = auth;
180
180
  this.opts = opts;
181
+ this.reflector = reflector;
181
182
  }
182
183
  async canActivate(context) {
183
184
  if (context.getType() === "ws") {
@@ -203,12 +204,16 @@ AuthGuard = __decorate([
203
204
  Injectable(),
204
205
  __decorateParam(0, Inject(BetterAuthService)),
205
206
  __decorateParam(1, Inject(BETTER_AUTH_OPTIONS)),
206
- __decorateMetadata("design:paramtypes", [typeof BetterAuthService === "undefined" ? Object : BetterAuthService, Object])
207
+ __decorateParam(2, Inject(Reflector)),
208
+ __decorateMetadata("design:paramtypes", [
209
+ typeof BetterAuthService === "undefined" ? Object : BetterAuthService,
210
+ Object,
211
+ typeof Reflector === "undefined" ? Object : Reflector
212
+ ])
207
213
  ], AuthGuard);
208
214
  //#endregion
209
215
  //#region src/better-auth.module.ts
210
216
  const referenceIds = /* @__PURE__ */ new WeakMap();
211
- const explicitKeyClaims = /* @__PURE__ */ new Map();
212
217
  let nextReferenceId = 1;
213
218
  function referenceId(reference) {
214
219
  const existing = referenceIds.get(reference);
@@ -217,16 +222,16 @@ function referenceId(reference) {
217
222
  referenceIds.set(reference, id);
218
223
  return id;
219
224
  }
220
- function claimExplicitKey(key, kind, reference, shape) {
225
+ /**
226
+ * An explicit key names a registration without widening its identity: the
227
+ * options shape and auth reference stay in the module key, so the same
228
+ * registration deduplicates within an application while a different one
229
+ * (including a root rebuilt per environment) becomes its own instance. No
230
+ * process-wide state is kept, so repeated bootstraps in one isolate succeed.
231
+ */
232
+ function explicitKey(key, shape, reference) {
221
233
  if (key.length === 0 || key !== key.trim()) throw new Error("@velajs/better-auth: an explicit module key must be a non-empty string");
222
- const existing = explicitKeyClaims.get(key);
223
- if (existing !== void 0 && (existing.kind !== kind || existing.reference !== reference || existing.shape !== shape)) throw new Error(`@velajs/better-auth: explicit module key "${key}" is already bound to a different auth registration`);
224
- explicitKeyClaims.set(key, {
225
- kind,
226
- reference,
227
- shape
228
- });
229
- return `explicit:${key}:ref:${referenceId(reference)}`;
234
+ return `explicit:${key}:${shape}:ref:${referenceId(reference)}`;
230
235
  }
231
236
  function normalize(options) {
232
237
  const basePath = normalizeBetterAuthBasePath(options.basePath);
@@ -261,7 +266,7 @@ var BetterAuthModule = class BetterAuthModule {
261
266
  static forRoot(options) {
262
267
  const normalized = normalize(options);
263
268
  const shape = stableHash(normalized);
264
- const key = options.key === void 0 ? `${shape}:auth:${referenceId(options.auth)}` : claimExplicitKey(options.key, "auth", options.auth, shape);
269
+ const key = options.key === void 0 ? `${shape}:auth:${referenceId(options.auth)}` : explicitKey(options.key, shape, options.auth);
265
270
  const common = commonContributions(normalized);
266
271
  return {
267
272
  module: BetterAuthModule,
@@ -292,16 +297,17 @@ var BetterAuthModule = class BetterAuthModule {
292
297
  ...n,
293
298
  inject: options.inject
294
299
  });
295
- const key = options.key === void 0 ? `${shape}:factory:${referenceId(options.useFactory)}` : claimExplicitKey(options.key, "factory", options.useFactory, shape);
300
+ const key = options.key === void 0 ? `${shape}:factory:${referenceId(options.useFactory)}` : explicitKey(options.key, shape, options.useFactory);
296
301
  return {
297
302
  module: BetterAuthModule,
298
303
  key,
299
304
  imports: options.imports ?? [],
300
305
  providers: [
301
306
  defineProvider(BETTER_AUTH_OPTIONS, { useValue: n }),
302
- defineProvider(BETTER_AUTH_BUILDER, {
303
- inject: options.inject,
304
- useFactory: (...deps) => () => options.useFactory(...deps)
307
+ lazyProvider({
308
+ ...options,
309
+ provide: BETTER_AUTH_BUILDER,
310
+ memoize: false
305
311
  }),
306
312
  ...common.providers,
307
313
  ...n.isGlobal ? provideGlobal("guard", AuthGuard) : []
@@ -312,6 +318,54 @@ var BetterAuthModule = class BetterAuthModule {
312
318
  }
313
319
  };
314
320
  //#endregion
321
+ //#region src/upgrade-authenticator.ts
322
+ /**
323
+ * Optional tenant resolver for {@link BetterAuthUpgradeAuthenticator}. Provide it
324
+ * in the module that declares the gateway; without one, the session's active
325
+ * organization is the tenant.
326
+ */
327
+ const BETTER_AUTH_UPGRADE_TENANT = new InjectionToken("vela.better-auth.UpgradeTenant");
328
+ let BetterAuthUpgradeAuthenticator = class BetterAuthUpgradeAuthenticator {
329
+ auth;
330
+ options;
331
+ resolveTenant;
332
+ constructor(auth, options, resolveTenant) {
333
+ this.auth = auth;
334
+ this.options = options;
335
+ this.resolveTenant = resolveTenant;
336
+ }
337
+ async authenticate(request, context) {
338
+ const data = validateSessionData(await this.auth.api.getSession({ headers: request.headers }));
339
+ if (!data) return false;
340
+ const tenantId = this.resolveTenant ? await this.resolveTenant({
341
+ user: data.user,
342
+ session: data.session
343
+ }, context, request) : data.tenantId;
344
+ if (typeof tenantId !== "string" || tenantId.length === 0) return false;
345
+ return {
346
+ principal: {
347
+ issuer: this.options.issuer ?? "better-auth",
348
+ subject: data.user.id,
349
+ principalType: "user"
350
+ },
351
+ tenantId,
352
+ expiresAtMs: data.session.expiresAt.getTime()
353
+ };
354
+ }
355
+ };
356
+ BetterAuthUpgradeAuthenticator = __decorate([
357
+ Injectable(),
358
+ __decorateParam(0, Inject(BetterAuthService)),
359
+ __decorateParam(1, Inject(BETTER_AUTH_OPTIONS)),
360
+ __decorateParam(2, Optional()),
361
+ __decorateParam(2, Inject(BETTER_AUTH_UPGRADE_TENANT)),
362
+ __decorateMetadata("design:paramtypes", [
363
+ typeof BetterAuthService === "undefined" ? Object : BetterAuthService,
364
+ Object,
365
+ Object
366
+ ])
367
+ ], BetterAuthUpgradeAuthenticator);
368
+ //#endregion
315
369
  //#region src/decorators/current-user.decorator.ts
316
370
  const CurrentUser = createParamDecorator((_data, ctx) => {
317
371
  return getAuthRequestState(ctx)?.user;
@@ -386,6 +440,6 @@ const betterAuthAcResolver = (roles) => {
386
440
  } };
387
441
  };
388
442
  //#endregion
389
- export { AuthGuard, BETTER_AUTH_ISSUER, BETTER_AUTH_OPTIONS, BetterAuthModule, BetterAuthService, CurrentSession, CurrentUser, OPTIONAL_AUTH_KEY, OptionalAuth, PUBLIC_KEY, Public, betterAuthAcResolver, createBetterAuthCatchallController, identityFromUser, permissionsFromAcRole };
443
+ export { AuthGuard, BETTER_AUTH_ISSUER, BETTER_AUTH_OPTIONS, BETTER_AUTH_UPGRADE_TENANT, BetterAuthModule, BetterAuthService, BetterAuthUpgradeAuthenticator, CurrentSession, CurrentUser, OPTIONAL_AUTH_KEY, OptionalAuth, PUBLIC_KEY, Public, betterAuthAcResolver, createBetterAuthCatchallController, identityFromUser, permissionsFromAcRole };
390
444
 
391
445
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/decorators/public.decorator.ts","../src/base-path.ts","../src/better-auth.controller.ts","../src/better-auth.tokens.ts","../src/auth-request-state.ts","../src/session-data.ts","../src/decorators/optional-auth.decorator.ts","../src/guards/auth.guard.ts","../src/better-auth.module.ts","../src/decorators/current-user.decorator.ts","../src/decorators/current-session.decorator.ts","../src/authz-bridge.ts"],"sourcesContent":["import { Reflector } from '@velajs/vela';\n\nexport const Public = Reflector.createDecorator<boolean>({ key: 'vela.auth.public' });\nexport const PUBLIC_KEY = Public.KEY;\n","export const DEFAULT_BETTER_AUTH_BASE_PATH = '/api/auth';\n\n/** Validate and canonicalize the route prefix used by the public auth controller. */\nexport function normalizeBetterAuthBasePath(value?: string): string {\n const basePath = value ?? DEFAULT_BETTER_AUTH_BASE_PATH;\n if (\n basePath.length === 0 ||\n basePath !== basePath.trim() ||\n !basePath.startsWith('/') ||\n basePath.startsWith('//') ||\n basePath === '/' ||\n basePath.endsWith('/') ||\n /[\\\\?#*]/u.test(basePath) ||\n /%(?:2e|2f|5c)/iu.test(basePath)\n ) {\n throw new Error(\n '@velajs/better-auth: basePath must be a canonical absolute path such as \"/api/auth\"',\n );\n }\n\n let decoded: string;\n try {\n decoded = decodeURIComponent(basePath);\n } catch {\n throw new Error('@velajs/better-auth: basePath contains invalid percent encoding');\n }\n if (decoded.split('/').some((segment) => segment === '.' || segment === '..')) {\n throw new Error('@velajs/better-auth: basePath must not contain dot segments');\n }\n return basePath;\n}\n","import { All, Controller, Inject, Injectable, Req, type Type } from '@velajs/vela';\nimport type { Context } from 'hono';\nimport { BetterAuthService } from './better-auth.service';\nimport { Public } from './decorators/public.decorator';\nimport { normalizeBetterAuthBasePath } from './base-path';\n\n/**\n * Build a catch-all controller that mounts better-auth's handler at `basePath`\n * (default `/api/auth`). This is a factory because vela reads a controller's\n * route off the class at decoration time, so a custom base path needs its own\n * decorated class — the path can't be parametrized on a single shared class.\n *\n * Two base paths to keep consistent:\n * - this `basePath` is RELATIVE to vela's `globalPrefix` (always prepended);\n * - better-auth routes against its OWN absolute `basePath` (the one you pass to\n * `betterAuth({ basePath })`), which must equal `globalPrefix + basePath`.\n *\n * Both default to `/api/auth`, so the no-prefix / no-config case just works.\n */\nexport function createBetterAuthCatchallController(basePath: string = '/api/auth'): Type {\n const normalizedBasePath = normalizeBetterAuthBasePath(basePath);\n @Public(true)\n @Controller(normalizedBasePath)\n @Injectable()\n class BetterAuthCatchallController {\n // Inject the service — its `.handler` getter triggers lazy construction\n // of the underlying betterAuth() instance on first access, AFTER any\n // runtime adapter middleware (Cloudflare env capture) has run.\n constructor(@Inject(BetterAuthService) private readonly auth: BetterAuthService) {}\n\n @All('/*')\n async handle(@Req() c: Context): Promise<Response> {\n return this.auth.handler(c.req.raw);\n }\n }\n return BetterAuthCatchallController;\n}\n","import { InjectionToken } from '@velajs/vela';\nimport type { BetterAuthRuntimeOptions } from './better-auth.types';\n\nexport const BETTER_AUTH_OPTIONS = new InjectionToken<BetterAuthRuntimeOptions>(\n 'vela.BetterAuthOptions',\n);\n","import {\n clearTrustedRequestIdentity,\n createTrustedRequestIdentityStore,\n getTrustedContextRequest,\n setTrustedRequestIdentity,\n type ExecutionContext,\n} from '@velajs/vela';\nimport type { SessionData } from './session-data';\n\n// This map stores provider payload, never a second authentication authority.\n// A clear, expiry, or identity replacement makes the payload unreachable.\nconst sessions = createTrustedRequestIdentityStore<SessionData>();\n\nexport function beginAuthRequest(context: ExecutionContext): void {\n clearTrustedRequestIdentity(context.getRequest());\n}\n\nexport function authenticateRequest(\n context: ExecutionContext,\n data: SessionData,\n issuer: string,\n): void {\n const request = context.getRequest();\n setTrustedRequestIdentity(request, {\n principal: { issuer, subject: data.user.id, principalType: 'user' },\n expiresAtMs: data.session.expiresAt.getTime(),\n roles: data.roles,\n ...(data.tenantId === undefined ? {} : { tenantId: data.tenantId }),\n });\n sessions.set(request, data);\n}\n\nexport function getAuthRequestState(context: ExecutionContext): SessionData | undefined {\n const request = getTrustedContextRequest(context);\n return request ? sessions.get(request) : undefined;\n}\n","import type { Session, User } from './better-auth.types';\n\nexport interface SessionData {\n readonly user: User;\n readonly session: Session;\n readonly roles: readonly string[];\n readonly tenantId?: string;\n}\n\nfunction own(value: unknown, key: string): unknown {\n if (typeof value !== 'object' || value === null) return undefined;\n const descriptor = Object.getOwnPropertyDescriptor(value, key);\n return descriptor && 'value' in descriptor ? descriptor.value : undefined;\n}\n\nfunction requiredString(value: unknown): value is string {\n return typeof value === 'string' && value.length > 0;\n}\n\nfunction date(value: unknown): Date | undefined {\n return value instanceof Date && Number.isFinite(value.getTime()) ? new Date(value) : undefined;\n}\n\nfunction optionalString(value: unknown): value is string | null | undefined {\n return value === undefined || value === null || typeof value === 'string';\n}\n\n/** Keep plugin data opaque; base model fields below are validated individually. */\nfunction dataProperties(value: object): Record<string, unknown> {\n return Object.fromEntries(Object.keys(value).map((key) => [key, own(value, key)]));\n}\n\n/** Validate the full Better Auth base models, not merely ids masquerading as them. */\nexport function validateSessionData(value: unknown): SessionData | undefined {\n try {\n const user = own(value, 'user');\n const session = own(value, 'session');\n if (\n typeof user !== 'object' ||\n user === null ||\n typeof session !== 'object' ||\n session === null\n ) {\n return undefined;\n }\n const id = own(user, 'id');\n const email = own(user, 'email');\n const name = own(user, 'name');\n const emailVerified = own(user, 'emailVerified');\n const image = own(user, 'image');\n const createdAt = date(own(user, 'createdAt'));\n const updatedAt = date(own(user, 'updatedAt'));\n const sessionId = own(session, 'id');\n const userId = own(session, 'userId');\n const token = own(session, 'token');\n const sessionCreatedAt = date(own(session, 'createdAt'));\n const sessionUpdatedAt = date(own(session, 'updatedAt'));\n const expiresAt = date(own(session, 'expiresAt'));\n const ipAddress = own(session, 'ipAddress');\n const userAgent = own(session, 'userAgent');\n const organization = own(session, 'activeOrganizationId');\n const role = own(user, 'role');\n let roles: string[] = [];\n if (role !== undefined && role !== null) {\n if (typeof role === 'string')\n roles = role\n .split(',')\n .map((part) => part.trim())\n .filter(Boolean);\n else if (\n Array.isArray(role) &&\n role.every((entry: unknown): entry is string => typeof entry === 'string')\n ) {\n roles = role.map((entry: string) => entry.trim()).filter(Boolean);\n } else return undefined;\n }\n if (\n !requiredString(id) ||\n !requiredString(email) ||\n typeof name !== 'string' ||\n typeof emailVerified !== 'boolean' ||\n !optionalString(image) ||\n !createdAt ||\n !updatedAt ||\n !requiredString(sessionId) ||\n userId !== id ||\n !requiredString(token) ||\n !sessionCreatedAt ||\n !sessionUpdatedAt ||\n !expiresAt ||\n expiresAt.getTime() <= Date.now() ||\n !optionalString(ipAddress) ||\n !optionalString(userAgent) ||\n (organization !== undefined && organization !== null && !requiredString(organization))\n )\n return undefined;\n return {\n user: Object.freeze({\n ...dataProperties(user),\n id,\n email,\n name,\n emailVerified,\n createdAt,\n updatedAt,\n image,\n }),\n session: Object.freeze({\n ...dataProperties(session),\n id: sessionId,\n userId: id,\n token,\n createdAt: sessionCreatedAt,\n updatedAt: sessionUpdatedAt,\n expiresAt,\n ipAddress,\n userAgent,\n }),\n roles: Object.freeze(roles),\n ...(typeof organization === 'string' ? { tenantId: organization } : {}),\n };\n } catch {\n return undefined;\n }\n}\n","import { Reflector } from '@velajs/vela';\n\nexport const OptionalAuth = Reflector.createDecorator<boolean>({ key: 'vela.auth.optional' });\nexport const OPTIONAL_AUTH_KEY = OptionalAuth.KEY;\n","import {\n Inject,\n Injectable,\n Reflector,\n UnauthorizedException,\n getTrustedContextRequest,\n type CanActivate,\n type ExecutionContext,\n} from '@velajs/vela';\nimport { getContextIdentity } from '@velajs/authz/vela';\nimport { BETTER_AUTH_OPTIONS } from '../better-auth.tokens';\nimport { BetterAuthService } from '../better-auth.service';\nimport type { BetterAuthRuntimeOptions } from '../better-auth.types';\nimport { authenticateRequest, beginAuthRequest, getAuthRequestState } from '../auth-request-state';\nimport { validateSessionData } from '../session-data';\nimport { OptionalAuth } from '../decorators/optional-auth.decorator';\nimport { Public } from '../decorators/public.decorator';\n\n@Injectable()\nexport class AuthGuard implements CanActivate {\n private readonly reflector = new Reflector();\n\n constructor(\n @Inject(BetterAuthService) private readonly auth: BetterAuthService,\n @Inject(BETTER_AUTH_OPTIONS) private readonly opts: BetterAuthRuntimeOptions,\n ) {}\n\n async canActivate(context: ExecutionContext): Promise<boolean> {\n // Frames trust only the authenticated connection attachment; HTTP cookies\n // and request payload never become ambient WebSocket authentication.\n if (context.getType() === 'ws') {\n if (getContextIdentity(context)) return true;\n throw new UnauthorizedException('Authentication required');\n }\n // Custom HTTP-backed dispatchers authenticate once at the outer boundary.\n // Never re-read credentials or clear shared authority across sibling fields.\n if (context.getType() !== 'http') {\n if (\n getTrustedContextRequest(context) &&\n (getAuthRequestState(context) ||\n this.reflector.getAllAndOverride(Public, context) ||\n this.reflector.getAllAndOverride(OptionalAuth, context))\n )\n return true;\n throw new UnauthorizedException('Authentication required');\n }\n beginAuthRequest(context);\n if (this.reflector.getAllAndOverride(Public, context)) return true;\n\n const raw = await this.auth.api.getSession({ headers: context.getRequest().headers });\n const data = validateSessionData(raw);\n if (data) {\n authenticateRequest(context, data, this.opts.issuer ?? 'better-auth');\n return true;\n }\n if (this.reflector.getAllAndOverride(OptionalAuth, context)) return true;\n throw new UnauthorizedException('Authentication required');\n }\n}\n","import {\n defineProvider,\n provideGlobal,\n stableHash,\n type DynamicModule,\n type InferTokens,\n type Token,\n type Type,\n} from '@velajs/vela';\nimport { createBetterAuthCatchallController } from './better-auth.controller';\nimport { BetterAuthService, BETTER_AUTH_BUILDER } from './better-auth.service';\nimport { BETTER_AUTH_OPTIONS } from './better-auth.tokens';\nimport type {\n BetterAuthInstance,\n BetterAuthModuleOptions,\n BetterAuthRuntimeOptions,\n} from './better-auth.types';\nimport { AuthGuard } from './guards/auth.guard';\nimport { normalizeBetterAuthBasePath } from './base-path';\n\nconst referenceIds = new WeakMap<object, number>();\nconst explicitKeyClaims = new Map<\n string,\n { readonly kind: 'auth' | 'factory'; readonly reference: object; readonly shape: string }\n>();\nlet nextReferenceId = 1;\n\nfunction referenceId(reference: object): number {\n const existing = referenceIds.get(reference);\n if (existing !== undefined) return existing;\n const id = nextReferenceId++;\n referenceIds.set(reference, id);\n return id;\n}\n\nfunction claimExplicitKey(\n key: string,\n kind: 'auth' | 'factory',\n reference: object,\n shape: string,\n): string {\n if (key.length === 0 || key !== key.trim()) {\n throw new Error('@velajs/better-auth: an explicit module key must be a non-empty string');\n }\n const existing = explicitKeyClaims.get(key);\n if (\n existing !== undefined &&\n (existing.kind !== kind || existing.reference !== reference || existing.shape !== shape)\n ) {\n throw new Error(\n `@velajs/better-auth: explicit module key \"${key}\" is already bound to a different auth registration`,\n );\n }\n explicitKeyClaims.set(key, { kind, reference, shape });\n return `explicit:${key}:ref:${referenceId(reference)}`;\n}\n\n/** Structural options with defaults applied (everything but the auth instance). */\ninterface NormalizedOptions {\n basePath: string;\n issuer: string;\n isGlobal: boolean;\n mountHandler: boolean;\n}\n\nfunction normalize(options: BetterAuthRuntimeOptions): NormalizedOptions {\n const basePath = normalizeBetterAuthBasePath(options.basePath);\n const issuer = options.issuer ?? `better-auth:${basePath}`;\n if (issuer.length === 0 || issuer !== issuer.trim()) {\n throw new Error('@velajs/better-auth: issuer must be a non-empty stable namespace');\n }\n return {\n basePath,\n issuer,\n isGlobal: options.isGlobal ?? true,\n mountHandler: options.mountHandler ?? true,\n };\n}\n\n/** Providers, controllers, and exports shared by both entry points. */\nfunction commonContributions(n: NormalizedOptions): {\n providers: NonNullable<DynamicModule['providers']>;\n controllers: Type[];\n exports: DynamicModule['exports'];\n} {\n return {\n providers: [BetterAuthService, AuthGuard],\n controllers: n.mountHandler ? [createBetterAuthCatchallController(n.basePath)] : [],\n exports: [BetterAuthService, BETTER_AUTH_OPTIONS, AuthGuard],\n };\n}\n\n/**\n * Options for {@link BetterAuthModule.forRootAsync}.\n *\n * The `Inject` type parameter captures the literal `inject` tuple at the call\n * site (via `const` inference) so `useFactory` parameters are typed from the\n * inject array, position-by-position — no `as const`, no `(...deps: any[])`:\n *\n * ```ts\n * BetterAuthModule.forRootAsync({\n * inject: [WORKER_ENV, ConfigService], // captured as readonly tuple\n * useFactory: (env, config) => // inferred from the tokens\n * betterAuth({ database: drizzleAdapter(drizzle(env.DB), ...) }),\n * });\n * ```\n */\ninterface ForRootAsyncOptions<Inject extends readonly Token[] = readonly Token[]> {\n inject: Inject;\n imports?: DynamicModule['imports'];\n useFactory: (...deps: InferTokens<Inject>) => BetterAuthInstance;\n isGlobal?: boolean;\n mountHandler?: boolean;\n basePath?: string;\n issuer?: string;\n key?: string;\n}\n\nexport class BetterAuthModule {\n /**\n * Synchronous registration. The auth instance is constructed by the consumer\n * at module-load time and passed in directly. Use this when the inputs to\n * `betterAuth({...})` are available at startup (Node apps with a static DB\n * connection, in-memory adapters, etc.).\n */\n static forRoot(\n options: BetterAuthModuleOptions & { isGlobal?: boolean; key?: string },\n ): DynamicModule {\n const normalized = normalize(options);\n const shape = stableHash(normalized);\n const key =\n options.key === undefined\n ? `${shape}:auth:${referenceId(options.auth)}`\n : claimExplicitKey(options.key, 'auth', options.auth, shape);\n const common = commonContributions(normalized);\n return {\n module: BetterAuthModule,\n key,\n providers: [\n defineProvider(BETTER_AUTH_OPTIONS, { useValue: normalized }),\n defineProvider(BETTER_AUTH_BUILDER, { useValue: () => options.auth }),\n ...common.providers,\n ...(normalized.isGlobal ? provideGlobal('guard', AuthGuard) : []),\n ],\n controllers: common.controllers,\n exports: common.exports,\n };\n }\n\n /**\n * Deferred / DI-driven registration. The user factory runs **lazily**, on the\n * first time anything reads `BetterAuthService.auth` (or `.api` / `.handler`).\n * In normal request handling that's `AuthGuard.canActivate` or the catch-all\n * controller's `.handle`. At module load the factory does NOT run — it's only\n * captured in the checked builder provider. The Workers adapter supplies its\n * native environment before DI; the lazily constructed auth instance belongs\n * to that environment's application and never captures another app's bindings.\n */\n static forRootAsync<const Inject extends readonly Token[] = readonly Token[]>(\n options: ForRootAsyncOptions<Inject>,\n ): DynamicModule {\n const n = normalize(options);\n const common = commonContributions(n);\n const shape = stableHash({ ...n, inject: options.inject });\n const key =\n options.key === undefined\n ? `${shape}:factory:${referenceId(options.useFactory)}`\n : claimExplicitKey(options.key, 'factory', options.useFactory, shape);\n return {\n module: BetterAuthModule,\n key,\n imports: options.imports ?? [],\n providers: [\n defineProvider(BETTER_AUTH_OPTIONS, { useValue: n }),\n defineProvider(BETTER_AUTH_BUILDER, {\n inject: options.inject,\n useFactory:\n (...deps: InferTokens<Inject>) =>\n () =>\n options.useFactory(...deps),\n }),\n ...common.providers,\n ...(n.isGlobal ? provideGlobal('guard', AuthGuard) : []),\n ],\n controllers: common.controllers,\n exports: common.exports,\n };\n }\n}\n","import { createParamDecorator, type ExecutionContext } from '@velajs/vela';\nimport { getAuthRequestState } from '../auth-request-state';\nimport type { User } from '../better-auth.types';\n\nexport const CurrentUser = createParamDecorator(\n (_data: unknown, ctx: ExecutionContext): User | undefined => {\n const state = getAuthRequestState(ctx);\n return state?.user;\n },\n);\n","import { createParamDecorator, type ExecutionContext } from '@velajs/vela';\nimport { getAuthRequestState } from '../auth-request-state';\nimport type { Session } from '../better-auth.types';\n\nexport const CurrentSession = createParamDecorator(\n (_data: unknown, ctx: ExecutionContext): Session | undefined => {\n const state = getAuthRequestState(ctx);\n return state?.session;\n },\n);\n","import type { Identity, PermissionResolver } from '@velajs/authz';\nimport type { User } from './better-auth.types';\n\n/**\n * A better-auth user carrying the optional `role` field contributed by the\n * admin plugin. `role` may be a single role, a comma-separated list, or an\n * array — {@link identityFromUser} normalizes all three.\n */\nexport type AuthUser = Pick<User, 'id'> & { role?: string | string[] | null };\n\n/** Stable issuer namespace used for better-auth session principals. */\nexport const BETTER_AUTH_ISSUER = 'better-auth';\n\nconst normalizeRoles = (role: string | string[] | null | undefined): string[] => {\n if (!role) return [];\n // Strip empty entries and return a fresh array (never alias the caller's\n // input), matching the comma-string path below.\n if (Array.isArray(role)) return role.filter(Boolean);\n return role\n .split(',')\n .map((r) => r.trim())\n .filter(Boolean);\n};\n\n/**\n * Pure authorization projection; this does not authenticate or publish trusted state.\n * Adapts an already verified better-auth user into a stable `@velajs/authz` {@link Identity}.\n * The issuer scopes `user.id` as both `subject` and the compatibility `userId`;\n * the admin-plugin `role` field supplies local roles.\n *\n * Fail-closed: a missing user (`null`/`undefined`, i.e. an unauthenticated\n * request) maps to the zero-privilege identity `{ roles: [] }`, so downstream\n * `can()` checks grant nothing.\n */\nexport const identityFromUser = (\n user: AuthUser | null | undefined,\n issuer: string = BETTER_AUTH_ISSUER,\n principalType: 'user' | 'service' = 'user',\n): Identity => {\n if (!user || typeof user.id !== 'string' || user.id.length === 0) return { roles: [] };\n if (issuer.length === 0)\n throw new Error('@velajs/better-auth: identity issuer must be non-empty');\n return {\n issuer,\n subject: user.id,\n principalType,\n userId: user.id,\n roles: normalizeRoles(user.role),\n };\n};\n\n/**\n * The minimal slice of a better-auth access-control role consumed here. Both\n * `createAccessControl(...).newRole(...)` and the standalone `role(...)` return\n * `{ authorize, statements }`; `statements` is the `{ resource: actions[] }`\n * grant map for that role — the only accessor {@link betterAuthAcResolver}\n * reads.\n */\nexport interface BetterAuthAcRole {\n readonly statements: Readonly<Record<string, readonly string[]>>;\n}\n\n/**\n * Flattens a better-auth AC role's `statements` into `resource:action`\n * permission strings — the granted-side format `@velajs/authz` matches\n * (wildcards included).\n */\nexport const permissionsFromAcRole = (role: BetterAuthAcRole): string[] => {\n const permissions: string[] = [];\n for (const [resource, actions] of Object.entries(role.statements ?? {})) {\n for (const action of actions ?? []) permissions.push(`${resource}:${action}`);\n }\n return permissions;\n};\n\n/**\n * Builds a fail-closed `@velajs/authz` {@link PermissionResolver} from a\n * better-auth access-control role table (`{ roleName: acRole }` — the same map\n * shape passed to better-auth's admin/organization plugins). An identity's\n * `roles` are unioned into their granted permission strings; unknown roles\n * contribute nothing.\n *\n * ```ts\n * const ac = createAccessControl({ posts: ['read', 'write'] });\n * const authz = createAuthz({\n * resolver: betterAuthAcResolver({ editor: ac.newRole({ posts: ['write'] }) }),\n * });\n * await authz.can(identityFromUser(user), 'posts:write');\n * ```\n */\nexport const betterAuthAcResolver = (\n roles: Readonly<Record<string, BetterAuthAcRole>>,\n): PermissionResolver => {\n const grantsByRole = new Map<string, string[]>();\n for (const [name, role] of Object.entries(roles)) {\n grantsByRole.set(name, permissionsFromAcRole(role));\n }\n return {\n grants(identity: Identity): Set<string> {\n const out = new Set<string>();\n for (const name of identity.roles ?? []) {\n for (const permission of grantsByRole.get(name) ?? []) out.add(permission);\n }\n return out;\n },\n };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/decorators/public.decorator.ts","../src/base-path.ts","../src/better-auth.controller.ts","../src/better-auth.tokens.ts","../src/auth-request-state.ts","../src/session-data.ts","../src/decorators/optional-auth.decorator.ts","../src/guards/auth.guard.ts","../src/better-auth.module.ts","../src/upgrade-authenticator.ts","../src/decorators/current-user.decorator.ts","../src/decorators/current-session.decorator.ts","../src/authz-bridge.ts"],"sourcesContent":["import { Reflector } from '@velajs/vela';\n\nexport const Public = Reflector.createDecorator<boolean>({ key: 'vela.auth.public' });\nexport const PUBLIC_KEY = Public.KEY;\n","export const DEFAULT_BETTER_AUTH_BASE_PATH = '/api/auth';\n\n/** Validate and canonicalize the route prefix used by the public auth controller. */\nexport function normalizeBetterAuthBasePath(value?: string): string {\n const basePath = value ?? DEFAULT_BETTER_AUTH_BASE_PATH;\n if (\n basePath.length === 0 ||\n basePath !== basePath.trim() ||\n !basePath.startsWith('/') ||\n basePath.startsWith('//') ||\n basePath === '/' ||\n basePath.endsWith('/') ||\n /[\\\\?#*]/u.test(basePath) ||\n /%(?:2e|2f|5c)/iu.test(basePath)\n ) {\n throw new Error(\n '@velajs/better-auth: basePath must be a canonical absolute path such as \"/api/auth\"',\n );\n }\n\n let decoded: string;\n try {\n decoded = decodeURIComponent(basePath);\n } catch {\n throw new Error('@velajs/better-auth: basePath contains invalid percent encoding');\n }\n if (decoded.split('/').some((segment) => segment === '.' || segment === '..')) {\n throw new Error('@velajs/better-auth: basePath must not contain dot segments');\n }\n return basePath;\n}\n","import { All, Controller, Inject, Req, type Type } from '@velajs/vela';\nimport type { Context } from 'hono';\nimport { BetterAuthService } from './better-auth.service';\nimport { Public } from './decorators/public.decorator';\nimport { normalizeBetterAuthBasePath } from './base-path';\n\n/**\n * Build a catch-all controller that mounts better-auth's handler at `basePath`\n * (default `/api/auth`). This is a factory because vela reads a controller's\n * route off the class at decoration time, so a custom base path needs its own\n * decorated class — the path can't be parametrized on a single shared class.\n *\n * Two base paths to keep consistent:\n * - this `basePath` is RELATIVE to vela's `globalPrefix` (always prepended);\n * - better-auth routes against its OWN absolute `basePath` (the one you pass to\n * `betterAuth({ basePath })`), which must equal `globalPrefix + basePath`.\n *\n * Both default to `/api/auth`, so the no-prefix / no-config case just works.\n */\nexport function createBetterAuthCatchallController(basePath: string = '/api/auth'): Type {\n const normalizedBasePath = normalizeBetterAuthBasePath(basePath);\n @Public(true)\n @Controller(normalizedBasePath)\n class BetterAuthCatchallController {\n // Inject the service — its `.handler` getter triggers lazy construction\n // of the underlying betterAuth() instance on first access, AFTER any\n // runtime adapter middleware (Cloudflare env capture) has run.\n constructor(@Inject(BetterAuthService) private readonly auth: BetterAuthService) {}\n\n @All('/*')\n async handle(@Req() c: Context): Promise<Response> {\n return this.auth.handler(c.req.raw);\n }\n }\n return BetterAuthCatchallController;\n}\n","import { InjectionToken } from '@velajs/vela';\nimport type { BetterAuthRuntimeOptions } from './better-auth.types';\n\nexport const BETTER_AUTH_OPTIONS = new InjectionToken<BetterAuthRuntimeOptions>(\n 'vela.BetterAuthOptions',\n);\n","import type { ExecutionContext } from '@velajs/vela';\nimport {\n clearTrustedRequestIdentity,\n createTrustedRequestIdentityStore,\n getTrustedContextRequest,\n setTrustedRequestIdentity,\n} from '@velajs/vela/module-kit';\nimport type { SessionData } from './session-data';\n\n// This map stores provider payload, never a second authentication authority.\n// A clear, expiry, or identity replacement makes the payload unreachable.\nconst sessions = createTrustedRequestIdentityStore<SessionData>();\n\nexport function beginAuthRequest(context: ExecutionContext): void {\n clearTrustedRequestIdentity(context.getRequest());\n}\n\nexport function authenticateRequest(\n context: ExecutionContext,\n data: SessionData,\n issuer: string,\n): void {\n const request = context.getRequest();\n setTrustedRequestIdentity(request, {\n principal: { issuer, subject: data.user.id, principalType: 'user' },\n expiresAtMs: data.session.expiresAt.getTime(),\n roles: data.roles,\n ...(data.tenantId === undefined ? {} : { tenantId: data.tenantId }),\n });\n sessions.set(request, data);\n}\n\nexport function getAuthRequestState(context: ExecutionContext): SessionData | undefined {\n const request = getTrustedContextRequest(context);\n return request ? sessions.get(request) : undefined;\n}\n","import type { Session, User } from './better-auth.types';\n\nexport interface SessionData {\n readonly user: User;\n readonly session: Session;\n readonly roles: readonly string[];\n readonly tenantId?: string;\n}\n\nfunction own(value: unknown, key: string): unknown {\n if (typeof value !== 'object' || value === null) return undefined;\n const descriptor = Object.getOwnPropertyDescriptor(value, key);\n return descriptor && 'value' in descriptor ? descriptor.value : undefined;\n}\n\nfunction requiredString(value: unknown): value is string {\n return typeof value === 'string' && value.length > 0;\n}\n\nfunction date(value: unknown): Date | undefined {\n return value instanceof Date && Number.isFinite(value.getTime()) ? new Date(value) : undefined;\n}\n\nfunction optionalString(value: unknown): value is string | null | undefined {\n return value === undefined || value === null || typeof value === 'string';\n}\n\n/** Keep plugin data opaque; base model fields below are validated individually. */\nfunction dataProperties(value: object): Record<string, unknown> {\n return Object.fromEntries(Object.keys(value).map((key) => [key, own(value, key)]));\n}\n\n/** Validate the full Better Auth base models, not merely ids masquerading as them. */\nexport function validateSessionData(value: unknown): SessionData | undefined {\n try {\n const user = own(value, 'user');\n const session = own(value, 'session');\n if (\n typeof user !== 'object' ||\n user === null ||\n typeof session !== 'object' ||\n session === null\n ) {\n return undefined;\n }\n const id = own(user, 'id');\n const email = own(user, 'email');\n const name = own(user, 'name');\n const emailVerified = own(user, 'emailVerified');\n const image = own(user, 'image');\n const createdAt = date(own(user, 'createdAt'));\n const updatedAt = date(own(user, 'updatedAt'));\n const sessionId = own(session, 'id');\n const userId = own(session, 'userId');\n const token = own(session, 'token');\n const sessionCreatedAt = date(own(session, 'createdAt'));\n const sessionUpdatedAt = date(own(session, 'updatedAt'));\n const expiresAt = date(own(session, 'expiresAt'));\n const ipAddress = own(session, 'ipAddress');\n const userAgent = own(session, 'userAgent');\n const organization = own(session, 'activeOrganizationId');\n const role = own(user, 'role');\n let roles: string[] = [];\n if (role !== undefined && role !== null) {\n if (typeof role === 'string')\n roles = role\n .split(',')\n .map((part) => part.trim())\n .filter(Boolean);\n else if (\n Array.isArray(role) &&\n role.every((entry: unknown): entry is string => typeof entry === 'string')\n ) {\n roles = role.map((entry: string) => entry.trim()).filter(Boolean);\n } else return undefined;\n }\n if (\n !requiredString(id) ||\n !requiredString(email) ||\n typeof name !== 'string' ||\n typeof emailVerified !== 'boolean' ||\n !optionalString(image) ||\n !createdAt ||\n !updatedAt ||\n !requiredString(sessionId) ||\n userId !== id ||\n !requiredString(token) ||\n !sessionCreatedAt ||\n !sessionUpdatedAt ||\n !expiresAt ||\n expiresAt.getTime() <= Date.now() ||\n !optionalString(ipAddress) ||\n !optionalString(userAgent) ||\n (organization !== undefined && organization !== null && !requiredString(organization))\n )\n return undefined;\n return {\n user: Object.freeze({\n ...dataProperties(user),\n id,\n email,\n name,\n emailVerified,\n createdAt,\n updatedAt,\n image,\n }),\n session: Object.freeze({\n ...dataProperties(session),\n id: sessionId,\n userId: id,\n token,\n createdAt: sessionCreatedAt,\n updatedAt: sessionUpdatedAt,\n expiresAt,\n ipAddress,\n userAgent,\n }),\n roles: Object.freeze(roles),\n ...(typeof organization === 'string' ? { tenantId: organization } : {}),\n };\n } catch {\n return undefined;\n }\n}\n","import { Reflector } from '@velajs/vela';\n\nexport const OptionalAuth = Reflector.createDecorator<boolean>({ key: 'vela.auth.optional' });\nexport const OPTIONAL_AUTH_KEY = OptionalAuth.KEY;\n","import {\n Inject,\n Injectable,\n Reflector,\n UnauthorizedException,\n type CanActivate,\n type ExecutionContext,\n} from '@velajs/vela';\nimport { getTrustedContextRequest } from '@velajs/vela/module-kit';\nimport { getContextIdentity } from '@velajs/authz/vela';\nimport { BETTER_AUTH_OPTIONS } from '../better-auth.tokens';\nimport { BetterAuthService } from '../better-auth.service';\nimport type { BetterAuthRuntimeOptions } from '../better-auth.types';\nimport { authenticateRequest, beginAuthRequest, getAuthRequestState } from '../auth-request-state';\nimport { validateSessionData } from '../session-data';\nimport { OptionalAuth } from '../decorators/optional-auth.decorator';\nimport { Public } from '../decorators/public.decorator';\n\n@Injectable()\nexport class AuthGuard implements CanActivate {\n constructor(\n @Inject(BetterAuthService) private readonly auth: BetterAuthService,\n @Inject(BETTER_AUTH_OPTIONS) private readonly opts: BetterAuthRuntimeOptions,\n @Inject(Reflector) private readonly reflector: Reflector,\n ) {}\n\n async canActivate(context: ExecutionContext): Promise<boolean> {\n // Frames trust only the authenticated connection attachment; HTTP cookies\n // and request payload never become ambient WebSocket authentication.\n if (context.getType() === 'ws') {\n if (getContextIdentity(context)) return true;\n throw new UnauthorizedException('Authentication required');\n }\n // Custom HTTP-backed dispatchers authenticate once at the outer boundary.\n // Never re-read credentials or clear shared authority across sibling fields.\n if (context.getType() !== 'http') {\n if (\n getTrustedContextRequest(context) &&\n (getAuthRequestState(context) ||\n this.reflector.getAllAndOverride(Public, context) ||\n this.reflector.getAllAndOverride(OptionalAuth, context))\n )\n return true;\n throw new UnauthorizedException('Authentication required');\n }\n beginAuthRequest(context);\n if (this.reflector.getAllAndOverride(Public, context)) return true;\n\n const raw = await this.auth.api.getSession({ headers: context.getRequest().headers });\n const data = validateSessionData(raw);\n if (data) {\n authenticateRequest(context, data, this.opts.issuer ?? 'better-auth');\n return true;\n }\n if (this.reflector.getAllAndOverride(OptionalAuth, context)) return true;\n throw new UnauthorizedException('Authentication required');\n }\n}\n","import { defineProvider, type DynamicModule, type Token, type Type } from '@velajs/vela';\nimport {\n lazyProvider,\n provideGlobal,\n stableHash,\n type FactoryInject,\n type InferTokens,\n} from '@velajs/vela/module-kit';\nimport { createBetterAuthCatchallController } from './better-auth.controller';\nimport { BetterAuthService, BETTER_AUTH_BUILDER } from './better-auth.service';\nimport { BETTER_AUTH_OPTIONS } from './better-auth.tokens';\nimport type {\n BetterAuthInstance,\n BetterAuthModuleOptions,\n BetterAuthRuntimeOptions,\n} from './better-auth.types';\nimport { AuthGuard } from './guards/auth.guard';\nimport { normalizeBetterAuthBasePath } from './base-path';\n\nconst referenceIds = new WeakMap<object, number>();\nlet nextReferenceId = 1;\n\nfunction referenceId(reference: object): number {\n const existing = referenceIds.get(reference);\n if (existing !== undefined) return existing;\n const id = nextReferenceId++;\n referenceIds.set(reference, id);\n return id;\n}\n\n/**\n * An explicit key names a registration without widening its identity: the\n * options shape and auth reference stay in the module key, so the same\n * registration deduplicates within an application while a different one\n * (including a root rebuilt per environment) becomes its own instance. No\n * process-wide state is kept, so repeated bootstraps in one isolate succeed.\n */\nfunction explicitKey(key: string, shape: string, reference: object): string {\n if (key.length === 0 || key !== key.trim()) {\n throw new Error('@velajs/better-auth: an explicit module key must be a non-empty string');\n }\n return `explicit:${key}:${shape}:ref:${referenceId(reference)}`;\n}\n\n/** Structural options with defaults applied (everything but the auth instance). */\ninterface NormalizedOptions {\n basePath: string;\n issuer: string;\n isGlobal: boolean;\n mountHandler: boolean;\n}\n\nfunction normalize(options: BetterAuthRuntimeOptions): NormalizedOptions {\n const basePath = normalizeBetterAuthBasePath(options.basePath);\n const issuer = options.issuer ?? `better-auth:${basePath}`;\n if (issuer.length === 0 || issuer !== issuer.trim()) {\n throw new Error('@velajs/better-auth: issuer must be a non-empty stable namespace');\n }\n return {\n basePath,\n issuer,\n isGlobal: options.isGlobal ?? true,\n mountHandler: options.mountHandler ?? true,\n };\n}\n\n/** Providers, controllers, and exports shared by both entry points. */\nfunction commonContributions(n: NormalizedOptions): {\n providers: NonNullable<DynamicModule['providers']>;\n controllers: Type[];\n exports: DynamicModule['exports'];\n} {\n return {\n providers: [BetterAuthService, AuthGuard],\n controllers: n.mountHandler ? [createBetterAuthCatchallController(n.basePath)] : [],\n exports: [BetterAuthService, BETTER_AUTH_OPTIONS, AuthGuard],\n };\n}\n\n/**\n * Options for {@link BetterAuthModule.forRootAsync}.\n *\n * The `Inject` type parameter captures the literal `inject` tuple at the call\n * site (via `const` inference) so `useFactory` parameters are typed from the\n * inject array, position-by-position — no `as const`, no `(...deps: any[])`:\n *\n * ```ts\n * BetterAuthModule.forRootAsync({\n * inject: [ENV, ConfigService], // captured as readonly tuple\n * useFactory: (env, config) => // inferred from the tokens\n * betterAuth({ database: drizzleAdapter(drizzle(env.DB), ...) }),\n * });\n * ```\n *\n * A factory without parameters may omit `inject`.\n */\ntype ForRootAsyncOptions<Inject extends readonly Token[] = readonly Token[]> = {\n imports?: DynamicModule['imports'];\n useFactory: (...deps: InferTokens<Inject>) => BetterAuthInstance;\n isGlobal?: boolean;\n mountHandler?: boolean;\n basePath?: string;\n issuer?: string;\n key?: string;\n} & FactoryInject<Inject>;\n\nexport class BetterAuthModule {\n /**\n * Synchronous registration. The auth instance is constructed by the consumer\n * at module-load time and passed in directly. Use this when the inputs to\n * `betterAuth({...})` are available at startup (Node apps with a static DB\n * connection, in-memory adapters, etc.).\n */\n static forRoot(\n options: BetterAuthModuleOptions & { isGlobal?: boolean; key?: string },\n ): DynamicModule {\n const normalized = normalize(options);\n const shape = stableHash(normalized);\n const key =\n options.key === undefined\n ? `${shape}:auth:${referenceId(options.auth)}`\n : explicitKey(options.key, shape, options.auth);\n const common = commonContributions(normalized);\n return {\n module: BetterAuthModule,\n key,\n providers: [\n defineProvider(BETTER_AUTH_OPTIONS, { useValue: normalized }),\n defineProvider(BETTER_AUTH_BUILDER, { useValue: () => options.auth }),\n ...common.providers,\n ...(normalized.isGlobal ? provideGlobal('guard', AuthGuard) : []),\n ],\n controllers: common.controllers,\n exports: common.exports,\n };\n }\n\n /**\n * Deferred / DI-driven registration. The user factory runs **lazily**, on the\n * first time anything reads `BetterAuthService.auth` (or `.api` / `.handler`).\n * In normal request handling that's `AuthGuard.canActivate` or the catch-all\n * controller's `.handle`. At module load the factory does NOT run — it's only\n * captured in the checked builder provider. The Workers adapter supplies its\n * native environment before DI; the lazily constructed auth instance belongs\n * to that environment's application and never captures another app's bindings.\n */\n static forRootAsync<const Inject extends readonly Token[] = readonly Token[]>(\n options: ForRootAsyncOptions<Inject>,\n ): DynamicModule {\n const n = normalize(options);\n const common = commonContributions(n);\n const shape = stableHash({ ...n, inject: options.inject });\n const key =\n options.key === undefined\n ? `${shape}:factory:${referenceId(options.useFactory)}`\n : explicitKey(options.key, shape, options.useFactory);\n return {\n module: BetterAuthModule,\n key,\n imports: options.imports ?? [],\n providers: [\n defineProvider(BETTER_AUTH_OPTIONS, { useValue: n }),\n // Unmemoized: BetterAuthService caches the instance it builds.\n lazyProvider<BetterAuthInstance, Inject>({\n ...options,\n provide: BETTER_AUTH_BUILDER,\n memoize: false,\n }),\n ...common.providers,\n ...(n.isGlobal ? provideGlobal('guard', AuthGuard) : []),\n ],\n controllers: common.controllers,\n exports: common.exports,\n };\n }\n}\n","import { Inject, Injectable, InjectionToken, Optional } from '@velajs/vela';\nimport type {\n UpgradeAuthenticator,\n WebSocketUpgradeAuthenticationContext,\n WebSocketUpgradeIdentity,\n} from '@velajs/vela/websocket';\nimport { BetterAuthService } from './better-auth.service';\nimport { BETTER_AUTH_OPTIONS } from './better-auth.tokens';\nimport type { BetterAuthRuntimeOptions, Session, User } from './better-auth.types';\nimport { validateSessionData } from './session-data';\n\n/** The validated Better Auth session behind a WebSocket upgrade. */\nexport interface BetterAuthUpgradeSession {\n readonly user: User;\n readonly session: Session;\n}\n\n/**\n * Chooses the tenant a WebSocket connection joins. Returning no tenant (or\n * throwing) refuses the upgrade.\n */\nexport type BetterAuthUpgradeTenantResolver = (\n session: BetterAuthUpgradeSession,\n context: WebSocketUpgradeAuthenticationContext,\n request: Request,\n) => string | undefined | Promise<string | undefined>;\n\n/**\n * Optional tenant resolver for {@link BetterAuthUpgradeAuthenticator}. Provide it\n * in the module that declares the gateway; without one, the session's active\n * organization is the tenant.\n */\nexport const BETTER_AUTH_UPGRADE_TENANT = new InjectionToken<BetterAuthUpgradeTenantResolver>(\n 'vela.better-auth.UpgradeTenant',\n);\n\n/**\n * WebSocket upgrade authenticator backed by the Better Auth session cookie:\n * `@WebSocketGateway({ authenticator: BetterAuthUpgradeAuthenticator })`.\n *\n * The principal uses the `BetterAuthModule` issuer, as `AuthGuard` does for\n * HTTP. The identity expires with the session. Every WebSocket identity carries\n * a tenant, so a session without an active organization is refused unless a\n * {@link BETTER_AUTH_UPGRADE_TENANT} resolver supplies one.\n */\n@Injectable()\nexport class BetterAuthUpgradeAuthenticator implements UpgradeAuthenticator {\n constructor(\n @Inject(BetterAuthService) private readonly auth: BetterAuthService,\n @Inject(BETTER_AUTH_OPTIONS) private readonly options: BetterAuthRuntimeOptions,\n @Optional()\n @Inject(BETTER_AUTH_UPGRADE_TENANT)\n private readonly resolveTenant?: BetterAuthUpgradeTenantResolver,\n ) {}\n\n async authenticate(\n request: Request,\n context: WebSocketUpgradeAuthenticationContext,\n ): Promise<WebSocketUpgradeIdentity | false> {\n const data = validateSessionData(await this.auth.api.getSession({ headers: request.headers }));\n if (!data) return false;\n const tenantId = this.resolveTenant\n ? await this.resolveTenant({ user: data.user, session: data.session }, context, request)\n : data.tenantId;\n if (typeof tenantId !== 'string' || tenantId.length === 0) return false;\n return {\n principal: {\n issuer: this.options.issuer ?? 'better-auth',\n subject: data.user.id,\n principalType: 'user',\n },\n tenantId,\n expiresAtMs: data.session.expiresAt.getTime(),\n };\n }\n}\n","import { createParamDecorator, type ExecutionContext } from '@velajs/vela';\nimport { getAuthRequestState } from '../auth-request-state';\nimport type { User } from '../better-auth.types';\n\nexport const CurrentUser = createParamDecorator(\n (_data: unknown, ctx: ExecutionContext): User | undefined => {\n const state = getAuthRequestState(ctx);\n return state?.user;\n },\n);\n","import { createParamDecorator, type ExecutionContext } from '@velajs/vela';\nimport { getAuthRequestState } from '../auth-request-state';\nimport type { Session } from '../better-auth.types';\n\nexport const CurrentSession = createParamDecorator(\n (_data: unknown, ctx: ExecutionContext): Session | undefined => {\n const state = getAuthRequestState(ctx);\n return state?.session;\n },\n);\n","import type { Identity, PermissionResolver } from '@velajs/authz';\nimport type { User } from './better-auth.types';\n\n/**\n * A better-auth user carrying the optional `role` field contributed by the\n * admin plugin. `role` may be a single role, a comma-separated list, or an\n * array — {@link identityFromUser} normalizes all three.\n */\nexport type AuthUser = Pick<User, 'id'> & { role?: string | string[] | null };\n\n/** Stable issuer namespace used for better-auth session principals. */\nexport const BETTER_AUTH_ISSUER = 'better-auth';\n\nconst normalizeRoles = (role: string | string[] | null | undefined): string[] => {\n if (!role) return [];\n // Strip empty entries and return a fresh array (never alias the caller's\n // input), matching the comma-string path below.\n if (Array.isArray(role)) return role.filter(Boolean);\n return role\n .split(',')\n .map((r) => r.trim())\n .filter(Boolean);\n};\n\n/**\n * Pure authorization projection; this does not authenticate or publish trusted state.\n * Adapts an already verified better-auth user into a stable `@velajs/authz` {@link Identity}.\n * The issuer scopes `user.id` as both `subject` and the compatibility `userId`;\n * the admin-plugin `role` field supplies local roles.\n *\n * Fail-closed: a missing user (`null`/`undefined`, i.e. an unauthenticated\n * request) maps to the zero-privilege identity `{ roles: [] }`, so downstream\n * `can()` checks grant nothing.\n */\nexport const identityFromUser = (\n user: AuthUser | null | undefined,\n issuer: string = BETTER_AUTH_ISSUER,\n principalType: 'user' | 'service' = 'user',\n): Identity => {\n if (!user || typeof user.id !== 'string' || user.id.length === 0) return { roles: [] };\n if (issuer.length === 0)\n throw new Error('@velajs/better-auth: identity issuer must be non-empty');\n return {\n issuer,\n subject: user.id,\n principalType,\n userId: user.id,\n roles: normalizeRoles(user.role),\n };\n};\n\n/**\n * The minimal slice of a better-auth access-control role consumed here. Both\n * `createAccessControl(...).newRole(...)` and the standalone `role(...)` return\n * `{ authorize, statements }`; `statements` is the `{ resource: actions[] }`\n * grant map for that role — the only accessor {@link betterAuthAcResolver}\n * reads.\n */\nexport interface BetterAuthAcRole {\n readonly statements: Readonly<Record<string, readonly string[]>>;\n}\n\n/**\n * Flattens a better-auth AC role's `statements` into `resource:action`\n * permission strings — the granted-side format `@velajs/authz` matches\n * (wildcards included).\n */\nexport const permissionsFromAcRole = (role: BetterAuthAcRole): string[] => {\n const permissions: string[] = [];\n for (const [resource, actions] of Object.entries(role.statements ?? {})) {\n for (const action of actions ?? []) permissions.push(`${resource}:${action}`);\n }\n return permissions;\n};\n\n/**\n * Builds a fail-closed `@velajs/authz` {@link PermissionResolver} from a\n * better-auth access-control role table (`{ roleName: acRole }` — the same map\n * shape passed to better-auth's admin/organization plugins). An identity's\n * `roles` are unioned into their granted permission strings; unknown roles\n * contribute nothing.\n *\n * ```ts\n * const ac = createAccessControl({ posts: ['read', 'write'] });\n * const authz = createAuthz({\n * resolver: betterAuthAcResolver({ editor: ac.newRole({ posts: ['write'] }) }),\n * });\n * await authz.can(identityFromUser(user), 'posts:write');\n * ```\n */\nexport const betterAuthAcResolver = (\n roles: Readonly<Record<string, BetterAuthAcRole>>,\n): PermissionResolver => {\n const grantsByRole = new Map<string, string[]>();\n for (const [name, role] of Object.entries(roles)) {\n grantsByRole.set(name, permissionsFromAcRole(role));\n }\n return {\n grants(identity: Identity): Set<string> {\n const out = new Set<string>();\n for (const name of identity.roles ?? []) {\n for (const permission of grantsByRole.get(name) ?? []) out.add(permission);\n }\n return out;\n },\n };\n};\n"],"mappings":";;;;;AAEA,MAAa,SAAS,UAAU,gBAAyB,EAAE,KAAK,mBAAmB,CAAC;AACpF,MAAa,aAAa,OAAO;;ACAjC,SAAgB,4BAA4B,OAAwB;CAClE,MAAM,WAAW,SAAA;CACjB,IACE,SAAS,WAAW,KACpB,aAAa,SAAS,KAAK,KAC3B,CAAC,SAAS,WAAW,GAAG,KACxB,SAAS,WAAW,IAAI,KACxB,aAAa,OACb,SAAS,SAAS,GAAG,KACrB,WAAW,KAAK,QAAQ,KACxB,kBAAkB,KAAK,QAAQ,GAE/B,MAAM,IAAI,MACR,uFACF;CAGF,IAAI;CACJ,IAAI;EACF,UAAU,mBAAmB,QAAQ;CACvC,QAAQ;EACN,MAAM,IAAI,MAAM,iEAAiE;CACnF;CACA,IAAI,QAAQ,MAAM,GAAG,CAAC,CAAC,MAAM,YAAY,YAAY,OAAO,YAAY,IAAI,GAC1E,MAAM,IAAI,MAAM,6DAA6D;CAE/E,OAAO;AACT;;;;;;;;;;;;;;;;ACXA,SAAgB,mCAAmC,WAAmB,aAAmB;CACvF,MAAM,qBAAqB,4BAA4B,QAAQ;CAC/D,IAEM,+BAFN,MAEM,6BAA6B;EAIuB;EAAxD,YAAY,MAAqE;GAAzB,KAAA,OAAA;EAA0B;EAElF,MACM,OAAO,GAAsC;GACjD,OAAO,KAAK,KAAK,QAAQ,EAAE,IAAI,GAAG;EACpC;CACF;;EAJG,IAAI,IAAI;EACK,gBAAA,GAAA,IAAI,CAAA;;;;;;EATnB,OAAO,IAAI;EACX,WAAW,kBAAkB;EAKf,gBAAA,GAAA,OAAO,iBAAiB,CAAA;;;CAOvC,OAAO;AACT;;;AChCA,MAAa,sBAAsB,IAAI,eACrC,wBACF;;;ACMA,MAAM,WAAW,kCAA+C;AAEhE,SAAgB,iBAAiB,SAAiC;CAChE,4BAA4B,QAAQ,WAAW,CAAC;AAClD;AAEA,SAAgB,oBACd,SACA,MACA,QACM;CACN,MAAM,UAAU,QAAQ,WAAW;CACnC,0BAA0B,SAAS;EACjC,WAAW;GAAE;GAAQ,SAAS,KAAK,KAAK;GAAI,eAAe;EAAO;EAClE,aAAa,KAAK,QAAQ,UAAU,QAAQ;EAC5C,OAAO,KAAK;EACZ,GAAI,KAAK,aAAa,KAAA,IAAY,CAAC,IAAI,EAAE,UAAU,KAAK,SAAS;CACnE,CAAC;CACD,SAAS,IAAI,SAAS,IAAI;AAC5B;AAEA,SAAgB,oBAAoB,SAAoD;CACtF,MAAM,UAAU,yBAAyB,OAAO;CAChD,OAAO,UAAU,SAAS,IAAI,OAAO,IAAI,KAAA;AAC3C;;;AC1BA,SAAS,IAAI,OAAgB,KAAsB;CACjD,IAAI,OAAO,UAAU,YAAY,UAAU,MAAM,OAAO,KAAA;CACxD,MAAM,aAAa,OAAO,yBAAyB,OAAO,GAAG;CAC7D,OAAO,cAAc,WAAW,aAAa,WAAW,QAAQ,KAAA;AAClE;AAEA,SAAS,eAAe,OAAiC;CACvD,OAAO,OAAO,UAAU,YAAY,MAAM,SAAS;AACrD;AAEA,SAAS,KAAK,OAAkC;CAC9C,OAAO,iBAAiB,QAAQ,OAAO,SAAS,MAAM,QAAQ,CAAC,IAAI,IAAI,KAAK,KAAK,IAAI,KAAA;AACvF;AAEA,SAAS,eAAe,OAAoD;CAC1E,OAAO,UAAU,KAAA,KAAa,UAAU,QAAQ,OAAO,UAAU;AACnE;;AAGA,SAAS,eAAe,OAAwC;CAC9D,OAAO,OAAO,YAAY,OAAO,KAAK,KAAK,CAAC,CAAC,KAAK,QAAQ,CAAC,KAAK,IAAI,OAAO,GAAG,CAAC,CAAC,CAAC;AACnF;;AAGA,SAAgB,oBAAoB,OAAyC;CAC3E,IAAI;EACF,MAAM,OAAO,IAAI,OAAO,MAAM;EAC9B,MAAM,UAAU,IAAI,OAAO,SAAS;EACpC,IACE,OAAO,SAAS,YAChB,SAAS,QACT,OAAO,YAAY,YACnB,YAAY,MAEZ;EAEF,MAAM,KAAK,IAAI,MAAM,IAAI;EACzB,MAAM,QAAQ,IAAI,MAAM,OAAO;EAC/B,MAAM,OAAO,IAAI,MAAM,MAAM;EAC7B,MAAM,gBAAgB,IAAI,MAAM,eAAe;EAC/C,MAAM,QAAQ,IAAI,MAAM,OAAO;EAC/B,MAAM,YAAY,KAAK,IAAI,MAAM,WAAW,CAAC;EAC7C,MAAM,YAAY,KAAK,IAAI,MAAM,WAAW,CAAC;EAC7C,MAAM,YAAY,IAAI,SAAS,IAAI;EACnC,MAAM,SAAS,IAAI,SAAS,QAAQ;EACpC,MAAM,QAAQ,IAAI,SAAS,OAAO;EAClC,MAAM,mBAAmB,KAAK,IAAI,SAAS,WAAW,CAAC;EACvD,MAAM,mBAAmB,KAAK,IAAI,SAAS,WAAW,CAAC;EACvD,MAAM,YAAY,KAAK,IAAI,SAAS,WAAW,CAAC;EAChD,MAAM,YAAY,IAAI,SAAS,WAAW;EAC1C,MAAM,YAAY,IAAI,SAAS,WAAW;EAC1C,MAAM,eAAe,IAAI,SAAS,sBAAsB;EACxD,MAAM,OAAO,IAAI,MAAM,MAAM;EAC7B,IAAI,QAAkB,CAAC;EACvB,IAAI,SAAS,KAAA,KAAa,SAAS,MAAM;GACvC,IAAI,OAAO,SAAS,UAClB,QAAQ,KACL,MAAM,GAAG,CAAC,CACV,KAAK,SAAS,KAAK,KAAK,CAAC,CAAC,CAC1B,OAAO,OAAO;QACd,IACH,MAAM,QAAQ,IAAI,KAClB,KAAK,OAAO,UAAoC,OAAO,UAAU,QAAQ,GAEzE,QAAQ,KAAK,KAAK,UAAkB,MAAM,KAAK,CAAC,CAAC,CAAC,OAAO,OAAO;QAC3D,OAAO,KAAA;EAChB;EACA,IACE,CAAC,eAAe,EAAE,KAClB,CAAC,eAAe,KAAK,KACrB,OAAO,SAAS,YAChB,OAAO,kBAAkB,aACzB,CAAC,eAAe,KAAK,KACrB,CAAC,aACD,CAAC,aACD,CAAC,eAAe,SAAS,KACzB,WAAW,MACX,CAAC,eAAe,KAAK,KACrB,CAAC,oBACD,CAAC,oBACD,CAAC,aACD,UAAU,QAAQ,KAAK,KAAK,IAAI,KAChC,CAAC,eAAe,SAAS,KACzB,CAAC,eAAe,SAAS,KACxB,iBAAiB,KAAA,KAAa,iBAAiB,QAAQ,CAAC,eAAe,YAAY,GAEpF,OAAO,KAAA;EACT,OAAO;GACL,MAAM,OAAO,OAAO;IAClB,GAAG,eAAe,IAAI;IACtB;IACA;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,SAAS,OAAO,OAAO;IACrB,GAAG,eAAe,OAAO;IACzB,IAAI;IACJ,QAAQ;IACR;IACA,WAAW;IACX,WAAW;IACX;IACA;IACA;GACF,CAAC;GACD,OAAO,OAAO,OAAO,KAAK;GAC1B,GAAI,OAAO,iBAAiB,WAAW,EAAE,UAAU,aAAa,IAAI,CAAC;EACvE;CACF,QAAQ;EACN;CACF;AACF;;;AC1HA,MAAa,eAAe,UAAU,gBAAyB,EAAE,KAAK,qBAAqB,CAAC;AAC5F,MAAa,oBAAoB,aAAa;;;ACgBvC,IAAM,YAAN,MAAM,UAAiC;CAEE;CACE;CACV;CAHtC,YACE,MACA,MACA,WACA;EAH4C,KAAA,OAAA;EACE,KAAA,OAAA;EACV,KAAA,YAAA;CACnC;CAEH,MAAM,YAAY,SAA6C;EAG7D,IAAI,QAAQ,QAAQ,MAAM,MAAM;GAC9B,IAAI,mBAAmB,OAAO,GAAG,OAAO;GACxC,MAAM,IAAI,sBAAsB,yBAAyB;EAC3D;EAGA,IAAI,QAAQ,QAAQ,MAAM,QAAQ;GAChC,IACE,yBAAyB,OAAO,MAC/B,oBAAoB,OAAO,KAC1B,KAAK,UAAU,kBAAkB,QAAQ,OAAO,KAChD,KAAK,UAAU,kBAAkB,cAAc,OAAO,IAExD,OAAO;GACT,MAAM,IAAI,sBAAsB,yBAAyB;EAC3D;EACA,iBAAiB,OAAO;EACxB,IAAI,KAAK,UAAU,kBAAkB,QAAQ,OAAO,GAAG,OAAO;EAG9D,MAAM,OAAO,oBAAoB,MADf,KAAK,KAAK,IAAI,WAAW,EAAE,SAAS,QAAQ,WAAW,CAAC,CAAC,QAAQ,CAAC,CAChD;EACpC,IAAI,MAAM;GACR,oBAAoB,SAAS,MAAM,KAAK,KAAK,UAAU,aAAa;GACpE,OAAO;EACT;EACA,IAAI,KAAK,UAAU,kBAAkB,cAAc,OAAO,GAAG,OAAO;EACpE,MAAM,IAAI,sBAAsB,yBAAyB;CAC3D;AACF;;CAvCC,WAAW;CAGP,gBAAA,GAAA,OAAO,iBAAiB,CAAA;CACxB,gBAAA,GAAA,OAAO,mBAAmB,CAAA;CAC1B,gBAAA,GAAA,OAAO,SAAS,CAAA;;;;;;;;;ACJrB,MAAM,+BAAe,IAAI,QAAwB;AACjD,IAAI,kBAAkB;AAEtB,SAAS,YAAY,WAA2B;CAC9C,MAAM,WAAW,aAAa,IAAI,SAAS;CAC3C,IAAI,aAAa,KAAA,GAAW,OAAO;CACnC,MAAM,KAAK;CACX,aAAa,IAAI,WAAW,EAAE;CAC9B,OAAO;AACT;;;;;;;;AASA,SAAS,YAAY,KAAa,OAAe,WAA2B;CAC1E,IAAI,IAAI,WAAW,KAAK,QAAQ,IAAI,KAAK,GACvC,MAAM,IAAI,MAAM,wEAAwE;CAE1F,OAAO,YAAY,IAAI,GAAG,MAAM,OAAO,YAAY,SAAS;AAC9D;AAUA,SAAS,UAAU,SAAsD;CACvE,MAAM,WAAW,4BAA4B,QAAQ,QAAQ;CAC7D,MAAM,SAAS,QAAQ,UAAU,eAAe;CAChD,IAAI,OAAO,WAAW,KAAK,WAAW,OAAO,KAAK,GAChD,MAAM,IAAI,MAAM,kEAAkE;CAEpF,OAAO;EACL;EACA;EACA,UAAU,QAAQ,YAAY;EAC9B,cAAc,QAAQ,gBAAgB;CACxC;AACF;;AAGA,SAAS,oBAAoB,GAI3B;CACA,OAAO;EACL,WAAW,CAAC,mBAAmB,SAAS;EACxC,aAAa,EAAE,eAAe,CAAC,mCAAmC,EAAE,QAAQ,CAAC,IAAI,CAAC;EAClF,SAAS;GAAC;GAAmB;GAAqB;EAAS;CAC7D;AACF;AA6BA,IAAa,mBAAb,MAAa,iBAAiB;;;;;;;CAO5B,OAAO,QACL,SACe;EACf,MAAM,aAAa,UAAU,OAAO;EACpC,MAAM,QAAQ,WAAW,UAAU;EACnC,MAAM,MACJ,QAAQ,QAAQ,KAAA,IACZ,GAAG,MAAM,QAAQ,YAAY,QAAQ,IAAI,MACzC,YAAY,QAAQ,KAAK,OAAO,QAAQ,IAAI;EAClD,MAAM,SAAS,oBAAoB,UAAU;EAC7C,OAAO;GACL,QAAQ;GACR;GACA,WAAW;IACT,eAAe,qBAAqB,EAAE,UAAU,WAAW,CAAC;IAC5D,eAAe,qBAAqB,EAAE,gBAAgB,QAAQ,KAAK,CAAC;IACpE,GAAG,OAAO;IACV,GAAI,WAAW,WAAW,cAAc,SAAS,SAAS,IAAI,CAAC;GACjE;GACA,aAAa,OAAO;GACpB,SAAS,OAAO;EAClB;CACF;;;;;;;;;;CAWA,OAAO,aACL,SACe;EACf,MAAM,IAAI,UAAU,OAAO;EAC3B,MAAM,SAAS,oBAAoB,CAAC;EACpC,MAAM,QAAQ,WAAW;GAAE,GAAG;GAAG,QAAQ,QAAQ;EAAO,CAAC;EACzD,MAAM,MACJ,QAAQ,QAAQ,KAAA,IACZ,GAAG,MAAM,WAAW,YAAY,QAAQ,UAAU,MAClD,YAAY,QAAQ,KAAK,OAAO,QAAQ,UAAU;EACxD,OAAO;GACL,QAAQ;GACR;GACA,SAAS,QAAQ,WAAW,CAAC;GAC7B,WAAW;IACT,eAAe,qBAAqB,EAAE,UAAU,EAAE,CAAC;IAEnD,aAAyC;KACvC,GAAG;KACH,SAAS;KACT,SAAS;IACX,CAAC;IACD,GAAG,OAAO;IACV,GAAI,EAAE,WAAW,cAAc,SAAS,SAAS,IAAI,CAAC;GACxD;GACA,aAAa,OAAO;GACpB,SAAS,OAAO;EAClB;CACF;AACF;;;;;;;;AC/IA,MAAa,6BAA6B,IAAI,eAC5C,gCACF;AAYO,IAAM,iCAAN,MAAM,+BAA+D;CAE5B;CACE;CAG7B;CALnB,YACE,MACA,SACA,eAGA;EAL4C,KAAA,OAAA;EACE,KAAA,UAAA;EAG7B,KAAA,gBAAA;CAChB;CAEH,MAAM,aACJ,SACA,SAC2C;EAC3C,MAAM,OAAO,oBAAoB,MAAM,KAAK,KAAK,IAAI,WAAW,EAAE,SAAS,QAAQ,QAAQ,CAAC,CAAC;EAC7F,IAAI,CAAC,MAAM,OAAO;EAClB,MAAM,WAAW,KAAK,gBAClB,MAAM,KAAK,cAAc;GAAE,MAAM,KAAK;GAAM,SAAS,KAAK;EAAQ,GAAG,SAAS,OAAO,IACrF,KAAK;EACT,IAAI,OAAO,aAAa,YAAY,SAAS,WAAW,GAAG,OAAO;EAClE,OAAO;GACL,WAAW;IACT,QAAQ,KAAK,QAAQ,UAAU;IAC/B,SAAS,KAAK,KAAK;IACnB,eAAe;GACjB;GACA;GACA,aAAa,KAAK,QAAQ,UAAU,QAAQ;EAC9C;CACF;AACF;;CA9BC,WAAW;CAGP,gBAAA,GAAA,OAAO,iBAAiB,CAAA;CACxB,gBAAA,GAAA,OAAO,mBAAmB,CAAA;CAC1B,gBAAA,GAAA,SAAS,CAAA;CACT,gBAAA,GAAA,OAAO,0BAA0B,CAAA;;;;;;;;;AC/CtC,MAAa,cAAc,sBACxB,OAAgB,QAA4C;CAE3D,OADc,oBAAoB,GACvB,CAAC,EAAE;AAChB,CACF;;;ACLA,MAAa,iBAAiB,sBAC3B,OAAgB,QAA+C;CAE9D,OADc,oBAAoB,GACvB,CAAC,EAAE;AAChB,CACF;;;;ACEA,MAAa,qBAAqB;AAElC,MAAM,kBAAkB,SAAyD;CAC/E,IAAI,CAAC,MAAM,OAAO,CAAC;CAGnB,IAAI,MAAM,QAAQ,IAAI,GAAG,OAAO,KAAK,OAAO,OAAO;CACnD,OAAO,KACJ,MAAM,GAAG,CAAC,CACV,KAAK,MAAM,EAAE,KAAK,CAAC,CAAC,CACpB,OAAO,OAAO;AACnB;;;;;;;;;;;AAYA,MAAa,oBACX,MACA,SAAiB,oBACjB,gBAAoC,WACvB;CACb,IAAI,CAAC,QAAQ,OAAO,KAAK,OAAO,YAAY,KAAK,GAAG,WAAW,GAAG,OAAO,EAAE,OAAO,CAAC,EAAE;CACrF,IAAI,OAAO,WAAW,GACpB,MAAM,IAAI,MAAM,wDAAwD;CAC1E,OAAO;EACL;EACA,SAAS,KAAK;EACd;EACA,QAAQ,KAAK;EACb,OAAO,eAAe,KAAK,IAAI;CACjC;AACF;;;;;;AAkBA,MAAa,yBAAyB,SAAqC;CACzE,MAAM,cAAwB,CAAC;CAC/B,KAAK,MAAM,CAAC,UAAU,YAAY,OAAO,QAAQ,KAAK,cAAc,CAAC,CAAC,GACpE,KAAK,MAAM,UAAU,WAAW,CAAC,GAAG,YAAY,KAAK,GAAG,SAAS,GAAG,QAAQ;CAE9E,OAAO;AACT;;;;;;;;;;;;;;;;AAiBA,MAAa,wBACX,UACuB;CACvB,MAAM,+BAAe,IAAI,IAAsB;CAC/C,KAAK,MAAM,CAAC,MAAM,SAAS,OAAO,QAAQ,KAAK,GAC7C,aAAa,IAAI,MAAM,sBAAsB,IAAI,CAAC;CAEpD,OAAO,EACL,OAAO,UAAiC;EACtC,MAAM,sBAAM,IAAI,IAAY;EAC5B,KAAK,MAAM,QAAQ,SAAS,SAAS,CAAC,GACpC,KAAK,MAAM,cAAc,aAAa,IAAI,IAAI,KAAK,CAAC,GAAG,IAAI,IAAI,UAAU;EAE3E,OAAO;CACT,EACF;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velajs/better-auth",
3
- "version": "1.28.0",
3
+ "version": "1.30.0",
4
4
  "description": "better-auth integration for the Vela framework",
5
5
  "keywords": [
6
6
  "auth",
@@ -47,7 +47,7 @@
47
47
  "access": "public"
48
48
  },
49
49
  "dependencies": {
50
- "@velajs/authz": "1.28.0"
50
+ "@velajs/authz": "1.30.0"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@arethetypeswrong/cli": "0.18.5",
@@ -62,15 +62,15 @@
62
62
  "typescript": "7.0.2",
63
63
  "unplugin-swc": "1.5.9",
64
64
  "vite": "^8.0.16",
65
- "vitest": "4.1.10",
66
- "@velajs/tenant": "1.28.0",
67
- "@velajs/testing": "1.28.0",
68
- "@velajs/vela": "1.28.0"
65
+ "vitest": "4.1.11",
66
+ "@velajs/tenant": "1.30.0",
67
+ "@velajs/vela": "1.30.0",
68
+ "@velajs/testing": "1.30.0"
69
69
  },
70
70
  "peerDependencies": {
71
71
  "better-auth": ">=1.2.0",
72
72
  "hono": ">=4",
73
- "@velajs/vela": "^1.28.0"
73
+ "@velajs/vela": "^1.30.0"
74
74
  },
75
75
  "engines": {
76
76
  "node": ">=24"
@@ -78,7 +78,7 @@
78
78
  "scripts": {
79
79
  "build": "tsdown",
80
80
  "test": "vitest run",
81
- "typecheck": "tsc --noEmit",
81
+ "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json",
82
82
  "lint": "oxlint .",
83
83
  "format": "oxfmt .",
84
84
  "format:check": "oxfmt --check .",