@velajs/better-auth 0.3.0 → 0.5.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,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.0 (2026-07-04)
4
+
5
+ - Rebuilt on vela 1.11 `defineModule` + `lazyProvider` + `provideGlobal` (lazy auth-builder deferral preserved; public API unchanged). Requires `@velajs/vela >=1.11.0`.
6
+
7
+
3
8
  All notable changes to `@velajs/better-auth` are documented here. The format
4
9
  follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
10
 
@@ -3,22 +3,17 @@ import type { BetterAuthInstance, BetterAuthModuleOptions } from './better-auth.
3
3
  /**
4
4
  * Options for {@link BetterAuthModule.forRootAsync}.
5
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:
6
+ * The `Inject` type parameter captures the literal `inject` tuple at the call
7
+ * site (via `const` inference) so `useFactory` parameters are typed from the
8
+ * inject array, position-by-position — no `as const`, no `(...deps: any[])`:
10
9
  *
11
10
  * ```ts
12
11
  * BetterAuthModule.forRootAsync({
13
12
  * inject: [D1Service, ConfigService], // captured as readonly tuple
14
- * useFactory: (d1, config) => // d1: D1Service, config: ConfigService
13
+ * useFactory: (d1, config) => // d1: D1Service, config: ConfigService
15
14
  * betterAuth({ database: drizzleAdapter(drizzle(d1.database), ...) }),
16
15
  * });
17
16
  * ```
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
17
  */
23
18
  interface ForRootAsyncOptions<Inject extends readonly Token<unknown>[] = readonly Token<unknown>[]> {
24
19
  inject?: Inject;
@@ -32,26 +27,25 @@ interface ForRootAsyncOptions<Inject extends readonly Token<unknown>[] = readonl
32
27
  }
33
28
  export declare class BetterAuthModule {
34
29
  /**
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.).
30
+ * Synchronous registration. The auth instance is constructed by the consumer
31
+ * at module-load time and passed in directly. Use this when the inputs to
32
+ * `betterAuth({...})` are available at startup (Node apps with a static DB
33
+ * connection, in-memory adapters, etc.).
39
34
  */
40
- static forRoot(options: BetterAuthModuleOptions): DynamicModule;
35
+ static forRoot(options: BetterAuthModuleOptions & {
36
+ isGlobal?: boolean;
37
+ key?: string;
38
+ }): DynamicModule;
41
39
  /**
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), ...) })`.
40
+ * Deferred / DI-driven registration. The user factory runs **lazily**, on the
41
+ * first time anything reads `BetterAuthService.auth` (or `.api` / `.handler`).
42
+ * In normal request handling that's `AuthGuard.canActivate` or the catch-all
43
+ * controller's `.handle`. At module load the factory does NOT run — it's only
44
+ * captured behind {@link lazyProvider}'s memoized thunk. This is what makes
45
+ * Cloudflare bindings (D1, KV, R2) work: the binding isn't ready at boot, but
46
+ * it IS by the time a request flows through and the guard / catch-all reads
47
+ * the service. Inject deps resolve at module load (cheap BindingRef wrappers);
48
+ * their *values* are read at first auth use, inside your factory body.
55
49
  */
56
50
  static forRootAsync<const Inject extends readonly Token<unknown>[] = readonly Token<unknown>[]>(options: ForRootAsyncOptions<Inject>): DynamicModule;
57
51
  }
@@ -1,144 +1,138 @@
1
- import { APP_GUARD, stableHash } from "@velajs/vela";
1
+ import { defineModule, lazyProvider, provideGlobal, stableHash } from "@velajs/vela";
2
2
  import { createBetterAuthCatchallController } from "./better-auth.controller.js";
3
3
  import { BetterAuthService, BETTER_AUTH_BUILDER } from "./better-auth.service.js";
4
4
  import { BETTER_AUTH_OPTIONS } from "./better-auth.tokens.js";
5
5
  import { AuthGuard } from "./guards/auth.guard.js";
6
6
  import { RolesGuard } from "./guards/roles.guard.js";
7
7
  const DEFAULT_BASE_PATH = '/api/auth';
8
+ function normalize(options) {
9
+ return {
10
+ basePath: options.basePath ?? DEFAULT_BASE_PATH,
11
+ isGlobal: options.isGlobal ?? false,
12
+ defaultPolicy: options.defaultPolicy ?? 'deny',
13
+ mountHandler: options.mountHandler ?? true
14
+ };
15
+ }
16
+ /** Providers, controllers, and exports shared by both entry points. */ function commonContributions(n) {
17
+ return {
18
+ providers: [
19
+ BetterAuthService,
20
+ AuthGuard,
21
+ RolesGuard
22
+ ],
23
+ controllers: n.mountHandler ? [
24
+ createBetterAuthCatchallController(n.basePath)
25
+ ] : [],
26
+ exports: [
27
+ BetterAuthService,
28
+ BETTER_AUTH_OPTIONS,
29
+ AuthGuard,
30
+ RolesGuard
31
+ ]
32
+ };
33
+ }
34
+ /**
35
+ * The blessed engine generates `forRoot`. `setup` runs once per instance at
36
+ * call time: it re-provides {@link BETTER_AUTH_OPTIONS} with defaults applied,
37
+ * derives the auth builder from those options, mounts the catch-all controller,
38
+ * and — via the `global:` slot — registers the app-wide guard when `isGlobal`.
39
+ *
40
+ * `isGlobal` here means "apply AuthGuard app-wide", NOT "make this a global
41
+ * module", so the default `isGlobal → global: true` extras transform is
42
+ * replaced with identity; the flag reaches `setup` through the options bag.
43
+ */ const authModuleHost = defineModule({
44
+ name: 'BetterAuth',
45
+ optionsToken: BETTER_AUTH_OPTIONS,
46
+ transform: (definition)=>definition,
47
+ // The auth instance is a stateful value — key off the structural subset only.
48
+ key: (options)=>stableHash(normalize(options)),
49
+ setup: ({ OPTIONS, options })=>{
50
+ const n = normalize(options);
51
+ const common = commonContributions(n);
52
+ const auth = options.auth;
53
+ return {
54
+ providers: [
55
+ // Override the auto-provided raw bag with the normalized shape so
56
+ // BETTER_AUTH_OPTIONS consumers always see defaults + the auth instance.
57
+ {
58
+ provide: OPTIONS,
59
+ useValue: {
60
+ ...n,
61
+ auth
62
+ }
63
+ },
64
+ // Eager auth: the builder hands back the instance the caller passed in.
65
+ lazyProvider({
66
+ provide: BETTER_AUTH_BUILDER,
67
+ inject: [
68
+ OPTIONS
69
+ ],
70
+ useFactory: (o)=>o.auth
71
+ }),
72
+ ...common.providers
73
+ ],
74
+ controllers: common.controllers,
75
+ exports: common.exports,
76
+ global: n.isGlobal ? {
77
+ guards: [
78
+ AuthGuard
79
+ ]
80
+ } : undefined
81
+ };
82
+ }
83
+ });
8
84
  export class BetterAuthModule {
9
85
  /**
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.).
86
+ * Synchronous registration. The auth instance is constructed by the consumer
87
+ * at module-load time and passed in directly. Use this when the inputs to
88
+ * `betterAuth({...})` are available at startup (Node apps with a static DB
89
+ * connection, in-memory adapters, etc.).
14
90
  */ static forRoot(options) {
15
- const normalized = normalize(options);
16
- const providers = buildProviders(normalized, ()=>options.auth);
91
+ // Delegate to the generated static, then rebrand the module identity so the
92
+ // public `BetterAuthModule` class is the one registered (consistent with
93
+ // `forRootAsync` and better diagnostics).
17
94
  return {
18
- module: BetterAuthModule,
19
- key: stableHash({
20
- basePath: normalized.basePath,
21
- defaultPolicy: normalized.defaultPolicy,
22
- isGlobal: !!options.isGlobal,
23
- mountHandler: !!normalized.mountHandler
24
- }),
25
- providers,
26
- controllers: normalized.mountHandler ? [
27
- createBetterAuthCatchallController(normalized.basePath)
28
- ] : [],
29
- exports: [
30
- BetterAuthService,
31
- BETTER_AUTH_OPTIONS,
32
- AuthGuard,
33
- RolesGuard
34
- ]
95
+ ...authModuleHost.ConfigurableModuleClass.forRoot(options),
96
+ module: BetterAuthModule
35
97
  };
36
98
  }
37
99
  /**
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), ...) })`.
100
+ * Deferred / DI-driven registration. The user factory runs **lazily**, on the
101
+ * first time anything reads `BetterAuthService.auth` (or `.api` / `.handler`).
102
+ * In normal request handling that's `AuthGuard.canActivate` or the catch-all
103
+ * controller's `.handle`. At module load the factory does NOT run — it's only
104
+ * captured behind {@link lazyProvider}'s memoized thunk. This is what makes
105
+ * Cloudflare bindings (D1, KV, R2) work: the binding isn't ready at boot, but
106
+ * it IS by the time a request flows through and the guard / catch-all reads
107
+ * the service. Inject deps resolve at module load (cheap BindingRef wrappers);
108
+ * their *values* are read at first auth use, inside your factory body.
51
109
  */ static forRootAsync(options) {
52
- const mountHandler = options.mountHandler !== false;
53
- const isGlobal = options.isGlobal === true;
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,
62
- isGlobal,
63
- mountHandler
64
- }
65
- },
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,
82
- AuthGuard,
83
- RolesGuard
84
- ];
85
- if (isGlobal) {
86
- providers.push({
87
- provide: APP_GUARD,
88
- useExisting: AuthGuard
89
- });
90
- }
110
+ const n = normalize(options);
111
+ const common = commonContributions(n);
91
112
  return {
92
113
  module: BetterAuthModule,
93
114
  key: options.key ?? stableHash({
94
- inject: options.inject,
95
- isGlobal,
96
- mountHandler,
97
- basePath,
98
- defaultPolicy
115
+ ...n,
116
+ inject: options.inject
99
117
  }),
100
118
  imports: options.imports ?? [],
101
- providers,
102
- controllers: mountHandler ? [
103
- createBetterAuthCatchallController(basePath)
104
- ] : [],
105
- exports: [
106
- BetterAuthService,
107
- BETTER_AUTH_OPTIONS,
108
- AuthGuard,
109
- RolesGuard
110
- ]
119
+ providers: [
120
+ {
121
+ provide: BETTER_AUTH_OPTIONS,
122
+ useValue: n
123
+ },
124
+ // The deferred auth builder: `lazyProvider` wraps the user factory in a
125
+ // memoized thunk, replacing the hand-rolled `(...deps) => () => f(...deps)`.
126
+ lazyProvider({
127
+ provide: BETTER_AUTH_BUILDER,
128
+ inject: options.inject,
129
+ useFactory: options.useFactory
130
+ }),
131
+ ...common.providers,
132
+ ...n.isGlobal ? provideGlobal('guard', AuthGuard) : []
133
+ ],
134
+ controllers: common.controllers,
135
+ exports: common.exports
111
136
  };
112
137
  }
113
138
  }
114
- function normalize(options) {
115
- return {
116
- auth: options.auth,
117
- basePath: options.basePath ?? DEFAULT_BASE_PATH,
118
- isGlobal: options.isGlobal ?? false,
119
- defaultPolicy: options.defaultPolicy ?? 'deny',
120
- mountHandler: options.mountHandler ?? true
121
- };
122
- }
123
- function buildProviders(normalized, builder) {
124
- const providers = [
125
- {
126
- provide: BETTER_AUTH_OPTIONS,
127
- useValue: normalized
128
- },
129
- {
130
- provide: BETTER_AUTH_BUILDER,
131
- useValue: builder
132
- },
133
- BetterAuthService,
134
- AuthGuard,
135
- RolesGuard
136
- ];
137
- if (normalized.isGlobal) {
138
- providers.push({
139
- provide: APP_GUARD,
140
- useExisting: AuthGuard
141
- });
142
- }
143
- return providers;
144
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velajs/better-auth",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "better-auth integration for the Vela framework",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -17,13 +17,6 @@
17
17
  "LICENSE",
18
18
  "CHANGELOG.md"
19
19
  ],
20
- "scripts": {
21
- "build": "rm -rf dist && swc src -d dist --strip-leading-paths && tsc --emitDeclarationOnly",
22
- "test": "vitest run",
23
- "typecheck": "tsc --noEmit",
24
- "prepare": "swc src -d dist --strip-leading-paths && tsc --emitDeclarationOnly",
25
- "prepublishOnly": "pnpm run typecheck && pnpm test"
26
- },
27
20
  "sideEffects": false,
28
21
  "keywords": [
29
22
  "vela",
@@ -49,26 +42,24 @@
49
42
  "node": ">=20"
50
43
  },
51
44
  "peerDependencies": {
52
- "@velajs/vela": ">=1.8.2",
45
+ "@velajs/vela": ">=1.11.0",
53
46
  "better-auth": ">=1.2.0",
54
47
  "hono": ">=4"
55
48
  },
56
49
  "devDependencies": {
57
50
  "@swc/cli": "^0.8.1",
58
51
  "@swc/core": "^1.15.41",
59
- "@velajs/testing": "^0.2.1",
60
- "@velajs/vela": "^1.8.5",
52
+ "@velajs/testing": "^0.4.0",
53
+ "@velajs/vela": "^1.12.0",
61
54
  "better-auth": "^1.6.20",
62
55
  "hono": "^4.12.26",
63
56
  "typescript": "^6.0.3",
64
57
  "unplugin-swc": "^1.5.9",
65
58
  "vitest": "^4.1.9"
66
59
  },
67
- "packageManager": "pnpm@10.20.0",
68
- "pnpm": {
69
- "onlyBuiltDependencies": [
70
- "@swc/core",
71
- "esbuild"
72
- ]
60
+ "scripts": {
61
+ "build": "rm -rf dist && swc src -d dist --strip-leading-paths && tsc --emitDeclarationOnly",
62
+ "test": "vitest run",
63
+ "typecheck": "tsc --noEmit"
73
64
  }
74
- }
65
+ }