@velajs/better-auth 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,39 @@
1
- import { type AsyncModuleOptions, type DynamicModule } from '@velajs/vela';
1
+ import { type DynamicModule, 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
+ type AsyncAuthFactory = (...deps: any[]) => BetterAuthInstance;
4
+ interface ForRootAsyncOptions {
5
+ inject?: Token[];
6
+ imports?: DynamicModule['imports'];
7
+ useFactory: AsyncAuthFactory;
8
8
  isGlobal?: boolean;
9
9
  mountHandler?: boolean;
10
+ basePath?: string;
11
+ defaultPolicy?: 'deny' | 'allow';
10
12
  key?: string;
11
- };
13
+ }
12
14
  export declare class BetterAuthModule {
15
+ /**
16
+ * Synchronous registration. The auth instance is constructed by the
17
+ * consumer at module-load time and passed in directly. Use this when the
18
+ * inputs to `betterAuth({...})` are available at startup (Node apps with
19
+ * a static DB connection, in-memory adapters, etc.).
20
+ */
13
21
  static forRoot(options: BetterAuthModuleOptions): DynamicModule;
22
+ /**
23
+ * Deferred / DI-driven registration. The user factory runs **lazily**, on
24
+ * the first time anything reads `BetterAuthService.auth` (or `.api` /
25
+ * `.handler`). In normal request handling, that's `AuthGuard.canActivate`
26
+ * or the catch-all controller's `.handle`. At module load, the factory
27
+ * does NOT run — it's only captured as a closure inside the builder
28
+ * thunk. This is what makes Cloudflare bindings (D1, KV, R2) work: the
29
+ * binding isn't ready at boot, but it IS by the time a request flows
30
+ * through and the guard / catch-all reads the service.
31
+ *
32
+ * Inject deps are resolved at module load (cheap; e.g. D1Service is a
33
+ * thin BindingRef wrapper). Their *values* are read at first auth use,
34
+ * inside your factory body — `(d1: D1Service) => betterAuth({ database:
35
+ * drizzleAdapter(drizzle(d1.database), ...) })`.
36
+ */
14
37
  static forRootAsync(options: ForRootAsyncOptions): DynamicModule;
15
38
  }
16
39
  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,52 @@ 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
+ provide: BETTER_AUTH_BUILDER,
72
+ useFactory: (...deps)=>()=>options.useFactory(...deps),
73
+ inject: options.inject ?? []
74
+ },
75
+ BetterAuthService,
58
76
  AuthGuard,
59
77
  RolesGuard
60
78
  ];
@@ -69,7 +87,9 @@ export class BetterAuthModule {
69
87
  key: options.key ?? stableHash({
70
88
  inject: options.inject,
71
89
  isGlobal,
72
- mountHandler
90
+ mountHandler,
91
+ basePath,
92
+ defaultPolicy
73
93
  }),
74
94
  imports: options.imports ?? [],
75
95
  providers,
@@ -77,7 +97,7 @@ export class BetterAuthModule {
77
97
  BetterAuthCatchallController
78
98
  ] : [],
79
99
  exports: [
80
- BETTER_AUTH,
100
+ BetterAuthService,
81
101
  BETTER_AUTH_OPTIONS,
82
102
  AuthGuard,
83
103
  RolesGuard
@@ -94,16 +114,17 @@ function normalize(options) {
94
114
  mountHandler: options.mountHandler ?? true
95
115
  };
96
116
  }
97
- function baseProviders(normalized, authInstance) {
117
+ function buildProviders(normalized, builder) {
98
118
  const providers = [
99
119
  {
100
120
  provide: BETTER_AUTH_OPTIONS,
101
121
  useValue: normalized
102
122
  },
103
123
  {
104
- provide: BETTER_AUTH,
105
- useValue: authInstance
124
+ provide: BETTER_AUTH_BUILDER,
125
+ useValue: builder
106
126
  },
127
+ BetterAuthService,
107
128
  AuthGuard,
108
129
  RolesGuard
109
130
  ];
@@ -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.2",
3
+ "version": "0.2.0",
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.1",
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.1",
54
54
  "better-auth": "^1.2.0",
55
55
  "hono": "^4",
56
56
  "typescript": "^5",