@velajs/better-auth 0.1.3 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  import type { Context } from 'hono';
2
- import type { BetterAuthInstance } from './better-auth.types';
2
+ import { BetterAuthService } from './better-auth.service';
3
3
  export declare class BetterAuthCatchallController {
4
4
  private readonly auth;
5
- constructor(auth: BetterAuthInstance);
5
+ constructor(auth: BetterAuthService);
6
6
  handle(c: Context): Promise<Response>;
7
7
  }
@@ -13,10 +13,13 @@ function _ts_param(paramIndex, decorator) {
13
13
  };
14
14
  }
15
15
  import { All, Controller, Inject, Injectable, Req } from "@velajs/vela";
16
- import { BETTER_AUTH } from "./better-auth.tokens.js";
16
+ import { BetterAuthService } from "./better-auth.service.js";
17
17
  import { Public } from "./decorators/public.decorator.js";
18
18
  export class BetterAuthCatchallController {
19
19
  auth;
20
+ // Inject the service — its `.handler` getter triggers lazy construction
21
+ // of the underlying betterAuth() instance on first access, AFTER any
22
+ // runtime adapter middleware (Cloudflare env capture) has run.
20
23
  constructor(auth){
21
24
  this.auth = auth;
22
25
  }
@@ -37,9 +40,9 @@ BetterAuthCatchallController = _ts_decorate([
37
40
  Public(true),
38
41
  Controller('/api/auth'),
39
42
  Injectable(),
40
- _ts_param(0, Inject(BETTER_AUTH)),
43
+ _ts_param(0, Inject(BetterAuthService)),
41
44
  _ts_metadata("design:type", Function),
42
45
  _ts_metadata("design:paramtypes", [
43
- typeof BetterAuthInstance === "undefined" ? Object : BetterAuthInstance
46
+ typeof BetterAuthService === "undefined" ? Object : BetterAuthService
44
47
  ])
45
48
  ], BetterAuthCatchallController);
@@ -1,16 +1,58 @@
1
- import { type AsyncModuleOptions, type DynamicModule } from '@velajs/vela';
1
+ import { type DynamicModule, type InferTokens, type Token } from '@velajs/vela';
2
2
  import type { BetterAuthInstance, BetterAuthModuleOptions } from './better-auth.types';
3
- type ForRootAsyncOptions = AsyncModuleOptions<{
4
- auth: BetterAuthInstance;
5
- basePath?: string;
6
- defaultPolicy?: 'deny' | 'allow';
7
- }> & {
3
+ /**
4
+ * Options for {@link BetterAuthModule.forRootAsync}.
5
+ *
6
+ * The `Inject` type parameter captures the literal `inject` tuple at the
7
+ * call site (via `const` inference on the consuming generic) so
8
+ * `useFactory` parameters are typed from the inject array, position-by-position.
9
+ * No `as const`, no `(...deps: any[])` workaround:
10
+ *
11
+ * ```ts
12
+ * BetterAuthModule.forRootAsync({
13
+ * inject: [D1Service, ConfigService], // captured as readonly tuple
14
+ * useFactory: (d1, config) => // d1: D1Service, config: ConfigService
15
+ * betterAuth({ database: drizzleAdapter(drizzle(d1.database), ...) }),
16
+ * });
17
+ * ```
18
+ *
19
+ * When `inject` isn't a literal tuple (or is omitted), `Inject` falls back
20
+ * to `readonly Token<unknown>[]` and `useFactory` accepts variadic
21
+ * `unknown[]` — the historical loose-typing behavior.
22
+ */
23
+ interface ForRootAsyncOptions<Inject extends readonly Token<unknown>[] = readonly Token<unknown>[]> {
24
+ inject?: Inject;
25
+ imports?: DynamicModule['imports'];
26
+ useFactory: (...deps: InferTokens<Inject>) => BetterAuthInstance;
8
27
  isGlobal?: boolean;
9
28
  mountHandler?: boolean;
29
+ basePath?: string;
30
+ defaultPolicy?: 'deny' | 'allow';
10
31
  key?: string;
11
- };
32
+ }
12
33
  export declare class BetterAuthModule {
34
+ /**
35
+ * Synchronous registration. The auth instance is constructed by the
36
+ * consumer at module-load time and passed in directly. Use this when the
37
+ * inputs to `betterAuth({...})` are available at startup (Node apps with
38
+ * a static DB connection, in-memory adapters, etc.).
39
+ */
13
40
  static forRoot(options: BetterAuthModuleOptions): DynamicModule;
14
- static forRootAsync(options: ForRootAsyncOptions): DynamicModule;
41
+ /**
42
+ * Deferred / DI-driven registration. The user factory runs **lazily**, on
43
+ * the first time anything reads `BetterAuthService.auth` (or `.api` /
44
+ * `.handler`). In normal request handling, that's `AuthGuard.canActivate`
45
+ * or the catch-all controller's `.handle`. At module load, the factory
46
+ * does NOT run — it's only captured as a closure inside the builder
47
+ * thunk. This is what makes Cloudflare bindings (D1, KV, R2) work: the
48
+ * binding isn't ready at boot, but it IS by the time a request flows
49
+ * through and the guard / catch-all reads the service.
50
+ *
51
+ * Inject deps are resolved at module load (cheap; e.g. D1Service is a
52
+ * thin BindingRef wrapper). Their *values* are read at first auth use,
53
+ * inside your factory body — `(d1: D1Service) => betterAuth({ database:
54
+ * drizzleAdapter(drizzle(d1.database), ...) })`.
55
+ */
56
+ static forRootAsync<const Inject extends readonly Token<unknown>[] = readonly Token<unknown>[]>(options: ForRootAsyncOptions<Inject>): DynamicModule;
15
57
  }
16
58
  export {};
@@ -1,13 +1,19 @@
1
1
  import { APP_GUARD, stableHash } from "@velajs/vela";
2
2
  import { BetterAuthCatchallController } from "./better-auth.controller.js";
3
- import { BETTER_AUTH, BETTER_AUTH_OPTIONS } from "./better-auth.tokens.js";
3
+ import { BetterAuthService, BETTER_AUTH_BUILDER } from "./better-auth.service.js";
4
+ import { BETTER_AUTH_OPTIONS } from "./better-auth.tokens.js";
4
5
  import { AuthGuard } from "./guards/auth.guard.js";
5
6
  import { RolesGuard } from "./guards/roles.guard.js";
6
7
  const DEFAULT_BASE_PATH = '/api/auth';
7
8
  export class BetterAuthModule {
8
- static forRoot(options) {
9
+ /**
10
+ * Synchronous registration. The auth instance is constructed by the
11
+ * consumer at module-load time and passed in directly. Use this when the
12
+ * inputs to `betterAuth({...})` are available at startup (Node apps with
13
+ * a static DB connection, in-memory adapters, etc.).
14
+ */ static forRoot(options) {
9
15
  const normalized = normalize(options);
10
- const providers = baseProviders(normalized, options.auth);
16
+ const providers = buildProviders(normalized, ()=>options.auth);
11
17
  return {
12
18
  module: BetterAuthModule,
13
19
  key: stableHash({
@@ -21,40 +27,58 @@ export class BetterAuthModule {
21
27
  BetterAuthCatchallController
22
28
  ] : [],
23
29
  exports: [
24
- BETTER_AUTH,
30
+ BetterAuthService,
25
31
  BETTER_AUTH_OPTIONS,
26
32
  AuthGuard,
27
33
  RolesGuard
28
34
  ]
29
35
  };
30
36
  }
31
- static forRootAsync(options) {
37
+ /**
38
+ * Deferred / DI-driven registration. The user factory runs **lazily**, on
39
+ * the first time anything reads `BetterAuthService.auth` (or `.api` /
40
+ * `.handler`). In normal request handling, that's `AuthGuard.canActivate`
41
+ * or the catch-all controller's `.handle`. At module load, the factory
42
+ * does NOT run — it's only captured as a closure inside the builder
43
+ * thunk. This is what makes Cloudflare bindings (D1, KV, R2) work: the
44
+ * binding isn't ready at boot, but it IS by the time a request flows
45
+ * through and the guard / catch-all reads the service.
46
+ *
47
+ * Inject deps are resolved at module load (cheap; e.g. D1Service is a
48
+ * thin BindingRef wrapper). Their *values* are read at first auth use,
49
+ * inside your factory body — `(d1: D1Service) => betterAuth({ database:
50
+ * drizzleAdapter(drizzle(d1.database), ...) })`.
51
+ */ static forRootAsync(options) {
32
52
  const mountHandler = options.mountHandler !== false;
33
53
  const isGlobal = options.isGlobal === true;
34
- const optsProvider = {
35
- provide: BETTER_AUTH_OPTIONS,
36
- useFactory: async (...deps)=>{
37
- const value = await options.useFactory(...deps);
38
- return {
39
- auth: value.auth,
40
- basePath: value.basePath ?? DEFAULT_BASE_PATH,
41
- defaultPolicy: value.defaultPolicy ?? 'deny',
54
+ const basePath = options.basePath ?? DEFAULT_BASE_PATH;
55
+ const defaultPolicy = options.defaultPolicy ?? 'deny';
56
+ const providers = [
57
+ {
58
+ provide: BETTER_AUTH_OPTIONS,
59
+ useValue: {
60
+ basePath,
61
+ defaultPolicy,
42
62
  isGlobal,
43
63
  mountHandler
44
- };
64
+ }
45
65
  },
46
- inject: options.inject ?? []
47
- };
48
- const authProvider = {
49
- provide: BETTER_AUTH,
50
- useFactory: (opts)=>opts.auth,
51
- inject: [
52
- BETTER_AUTH_OPTIONS
53
- ]
54
- };
55
- const providers = [
56
- optsProvider,
57
- authProvider,
66
+ {
67
+ // The builder is a zero-arg closure capturing the DI'd deps. It is
68
+ // invoked lazily by BetterAuthService.auth on first access; vela
69
+ // can resolve this provider at module load without running the user
70
+ // factory — the factory body lives behind the closure.
71
+ //
72
+ // The inner factory is typed `unknown[]` (vela's runtime contract:
73
+ // deps are resolved by token order). The outer user-facing
74
+ // `options.useFactory` is statically typed via `InferTokens<Inject>`.
75
+ // We narrow at the boundary with a single cast — runtime order
76
+ // matches the declared `inject` order by construction.
77
+ provide: BETTER_AUTH_BUILDER,
78
+ useFactory: (...deps)=>()=>options.useFactory(...deps),
79
+ inject: options.inject ?? []
80
+ },
81
+ BetterAuthService,
58
82
  AuthGuard,
59
83
  RolesGuard
60
84
  ];
@@ -69,7 +93,9 @@ export class BetterAuthModule {
69
93
  key: options.key ?? stableHash({
70
94
  inject: options.inject,
71
95
  isGlobal,
72
- mountHandler
96
+ mountHandler,
97
+ basePath,
98
+ defaultPolicy
73
99
  }),
74
100
  imports: options.imports ?? [],
75
101
  providers,
@@ -77,7 +103,7 @@ export class BetterAuthModule {
77
103
  BetterAuthCatchallController
78
104
  ] : [],
79
105
  exports: [
80
- BETTER_AUTH,
106
+ BetterAuthService,
81
107
  BETTER_AUTH_OPTIONS,
82
108
  AuthGuard,
83
109
  RolesGuard
@@ -94,16 +120,17 @@ function normalize(options) {
94
120
  mountHandler: options.mountHandler ?? true
95
121
  };
96
122
  }
97
- function baseProviders(normalized, authInstance) {
123
+ function buildProviders(normalized, builder) {
98
124
  const providers = [
99
125
  {
100
126
  provide: BETTER_AUTH_OPTIONS,
101
127
  useValue: normalized
102
128
  },
103
129
  {
104
- provide: BETTER_AUTH,
105
- useValue: authInstance
130
+ provide: BETTER_AUTH_BUILDER,
131
+ useValue: builder
106
132
  },
133
+ BetterAuthService,
107
134
  AuthGuard,
108
135
  RolesGuard
109
136
  ];
@@ -0,0 +1,42 @@
1
+ import { InjectionToken } from '@velajs/vela';
2
+ import type { BetterAuthInstance } from './better-auth.types';
3
+ /**
4
+ * Internal token holding the auth-construction closure with its inject deps
5
+ * closed over. Resolves cheaply at module load (just captures references);
6
+ * the inner call happens lazily on first auth use (see `BetterAuthService`).
7
+ *
8
+ * Not exported from the public surface — only the service consumes it.
9
+ */
10
+ export declare const BETTER_AUTH_BUILDER: InjectionToken<() => BetterAuthInstance>;
11
+ /**
12
+ * The single injectable consumers reach for to interact with better-auth.
13
+ * Wraps the underlying `betterAuth({...})` instance with lazy construction:
14
+ *
15
+ * - `forRoot({ auth })` — the builder returns the eagerly-provided instance,
16
+ * so the first `.auth` / `.api` / `.handler` access is effectively a
17
+ * read-and-cache.
18
+ * - `forRootAsync({ inject, useFactory })` — the builder wraps the user's
19
+ * factory + inject deps. First access triggers `useFactory(...deps)`. This
20
+ * is what makes Cloudflare D1/KV bindings work: at module load the factory
21
+ * doesn't run; on first request (when AuthGuard or the catch-all calls
22
+ * `service.api` / `service.handler`), the bindings are populated and the
23
+ * factory can read them safely.
24
+ *
25
+ * Used directly by AuthGuard and the catch-all controller. Consumers in
26
+ * application code inject the same way: `@Inject(BetterAuthService)`.
27
+ */
28
+ export declare class BetterAuthService {
29
+ private readonly build;
30
+ private cached;
31
+ constructor(build: () => BetterAuthInstance);
32
+ /**
33
+ * The underlying better-auth instance. Constructed once on first access.
34
+ * Safe to call from any request-time code path (guards, controllers,
35
+ * services invoked from handlers).
36
+ */
37
+ get auth(): BetterAuthInstance;
38
+ /** Convenience accessor — equivalent to `service.auth.api`. */
39
+ get api(): BetterAuthInstance['api'];
40
+ /** Convenience accessor — equivalent to `service.auth.handler`. */
41
+ get handler(): BetterAuthInstance['handler'];
42
+ }
@@ -0,0 +1,51 @@
1
+ function _ts_decorate(decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ }
7
+ function _ts_metadata(k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ }
10
+ function _ts_param(paramIndex, decorator) {
11
+ return function(target, key) {
12
+ decorator(target, key, paramIndex);
13
+ };
14
+ }
15
+ import { Inject, Injectable, InjectionToken } from "@velajs/vela";
16
+ /**
17
+ * Internal token holding the auth-construction closure with its inject deps
18
+ * closed over. Resolves cheaply at module load (just captures references);
19
+ * the inner call happens lazily on first auth use (see `BetterAuthService`).
20
+ *
21
+ * Not exported from the public surface — only the service consumes it.
22
+ */ export const BETTER_AUTH_BUILDER = new InjectionToken('vela.better-auth.Builder');
23
+ export class BetterAuthService {
24
+ build;
25
+ cached;
26
+ constructor(build){
27
+ this.build = build;
28
+ }
29
+ /**
30
+ * The underlying better-auth instance. Constructed once on first access.
31
+ * Safe to call from any request-time code path (guards, controllers,
32
+ * services invoked from handlers).
33
+ */ get auth() {
34
+ if (!this.cached) this.cached = this.build();
35
+ return this.cached;
36
+ }
37
+ /** Convenience accessor — equivalent to `service.auth.api`. */ get api() {
38
+ return this.auth.api;
39
+ }
40
+ /** Convenience accessor — equivalent to `service.auth.handler`. */ get handler() {
41
+ return this.auth.handler;
42
+ }
43
+ }
44
+ BetterAuthService = _ts_decorate([
45
+ Injectable(),
46
+ _ts_param(0, Inject(BETTER_AUTH_BUILDER)),
47
+ _ts_metadata("design:type", Function),
48
+ _ts_metadata("design:paramtypes", [
49
+ Function
50
+ ])
51
+ ], BetterAuthService);
@@ -1,6 +1,5 @@
1
1
  import { InjectionToken } from '@velajs/vela';
2
- import type { BetterAuthInstance, BetterAuthModuleOptions } from './better-auth.types';
3
- export declare const BETTER_AUTH: InjectionToken<BetterAuthInstance>;
2
+ import type { BetterAuthModuleOptions } from './better-auth.types';
4
3
  export declare const BETTER_AUTH_OPTIONS: InjectionToken<BetterAuthModuleOptions>;
5
4
  export declare const AUTH_USER_KEY: unique symbol;
6
5
  export declare const AUTH_SESSION_KEY: unique symbol;
@@ -1,5 +1,4 @@
1
1
  import { InjectionToken } from "@velajs/vela";
2
- export const BETTER_AUTH = new InjectionToken('vela.BetterAuth');
3
2
  export const BETTER_AUTH_OPTIONS = new InjectionToken('vela.BetterAuthOptions');
4
3
  export const AUTH_USER_KEY = Symbol.for('vela.better-auth.user');
5
4
  export const AUTH_SESSION_KEY = Symbol.for('vela.better-auth.session');
@@ -1,9 +1,10 @@
1
1
  import { type CanActivate, type ExecutionContext } from '@velajs/vela';
2
- import type { BetterAuthInstance, BetterAuthModuleOptions } from '../better-auth.types';
2
+ import { BetterAuthService } from '../better-auth.service';
3
+ import type { BetterAuthModuleOptions } from '../better-auth.types';
3
4
  export declare class AuthGuard implements CanActivate {
4
5
  private readonly auth;
5
6
  private readonly opts;
6
7
  private readonly reflector;
7
- constructor(auth: BetterAuthInstance, opts: BetterAuthModuleOptions);
8
+ constructor(auth: BetterAuthService, opts: BetterAuthModuleOptions);
8
9
  canActivate(context: ExecutionContext): Promise<boolean>;
9
10
  }
@@ -13,14 +13,20 @@ function _ts_param(paramIndex, decorator) {
13
13
  };
14
14
  }
15
15
  import { Inject, Injectable, REQUEST_CONTEXT, Reflector, UnauthorizedException } from "@velajs/vela";
16
- import { AUTH_SESSION_KEY, AUTH_USER_KEY, BETTER_AUTH, BETTER_AUTH_OPTIONS } from "../better-auth.tokens.js";
16
+ import { AUTH_SESSION_KEY, AUTH_USER_KEY, BETTER_AUTH_OPTIONS } from "../better-auth.tokens.js";
17
+ import { BetterAuthService } from "../better-auth.service.js";
17
18
  import { OptionalAuth } from "../decorators/optional-auth.decorator.js";
18
19
  import { Public } from "../decorators/public.decorator.js";
19
20
  export class AuthGuard {
20
21
  auth;
21
22
  opts;
22
23
  reflector = new Reflector();
23
- constructor(auth, opts){
24
+ constructor(// Inject BetterAuthService rather than the raw better-auth instance.
25
+ // The service's lazy `.auth` getter defers construction to first use, so
26
+ // forRootAsync factories that depend on values only available at
27
+ // request time (Cloudflare D1/KV bindings, etc.) build safely on the
28
+ // first canActivate — not at module-load bootstrap.
29
+ auth, opts){
24
30
  this.auth = auth;
25
31
  this.opts = opts;
26
32
  }
@@ -47,11 +53,11 @@ export class AuthGuard {
47
53
  }
48
54
  AuthGuard = _ts_decorate([
49
55
  Injectable(),
50
- _ts_param(0, Inject(BETTER_AUTH)),
56
+ _ts_param(0, Inject(BetterAuthService)),
51
57
  _ts_param(1, Inject(BETTER_AUTH_OPTIONS)),
52
58
  _ts_metadata("design:type", Function),
53
59
  _ts_metadata("design:paramtypes", [
54
- typeof BetterAuthInstance === "undefined" ? Object : BetterAuthInstance,
60
+ typeof BetterAuthService === "undefined" ? Object : BetterAuthService,
55
61
  typeof BetterAuthModuleOptions === "undefined" ? Object : BetterAuthModuleOptions
56
62
  ])
57
63
  ], AuthGuard);
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export { BetterAuthModule } from './better-auth.module';
2
+ export { BetterAuthService } from './better-auth.service';
2
3
  export { BetterAuthCatchallController } from './better-auth.controller';
3
- export { BETTER_AUTH, BETTER_AUTH_OPTIONS, AUTH_USER_KEY, AUTH_SESSION_KEY, } from './better-auth.tokens';
4
+ export { BETTER_AUTH_OPTIONS, AUTH_USER_KEY, AUTH_SESSION_KEY, } from './better-auth.tokens';
4
5
  export { AuthGuard } from './guards/auth.guard';
5
6
  export { RolesGuard } from './guards/roles.guard';
6
7
  export { CurrentUser } from './decorators/current-user.decorator';
package/dist/index.js CHANGED
@@ -1,8 +1,9 @@
1
1
  // Module
2
2
  export { BetterAuthModule } from "./better-auth.module.js";
3
+ export { BetterAuthService } from "./better-auth.service.js";
3
4
  export { BetterAuthCatchallController } from "./better-auth.controller.js";
4
5
  // Tokens & symbols
5
- export { BETTER_AUTH, BETTER_AUTH_OPTIONS, AUTH_USER_KEY, AUTH_SESSION_KEY } from "./better-auth.tokens.js";
6
+ export { BETTER_AUTH_OPTIONS, AUTH_USER_KEY, AUTH_SESSION_KEY } from "./better-auth.tokens.js";
6
7
  // Guards
7
8
  export { AuthGuard } from "./guards/auth.guard.js";
8
9
  export { RolesGuard } from "./guards/roles.guard.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velajs/better-auth",
3
- "version": "0.1.3",
3
+ "version": "0.2.1",
4
4
  "description": "better-auth integration for the Vela framework",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -42,7 +42,7 @@
42
42
  "node": ">=20"
43
43
  },
44
44
  "peerDependencies": {
45
- "@velajs/vela": ">=1.6.0",
45
+ "@velajs/vela": ">=1.8.2",
46
46
  "better-auth": ">=1.2.0",
47
47
  "hono": ">=4"
48
48
  },
@@ -50,7 +50,7 @@
50
50
  "@swc/cli": "^0.8.0",
51
51
  "@swc/core": "^1.15.11",
52
52
  "@velajs/testing": "^0.2.1",
53
- "@velajs/vela": "^1.6.0",
53
+ "@velajs/vela": "^1.8.2",
54
54
  "better-auth": "^1.2.0",
55
55
  "hono": "^4",
56
56
  "typescript": "^5",