@velajs/better-auth 1.0.0 → 1.22.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.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["ACCESS_DENIED","normalizeRoles"],"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/decorators/optional-auth.decorator.ts","../src/guards/auth.guard.ts","../src/decorators/roles.decorator.ts","../src/guards/roles.guard.ts","../src/authz-bridge.ts","../src/decorators/require-permission.decorator.ts","../src/guards/permission.guard.ts","../src/better-auth.module.ts","../src/decorators/current-user.decorator.ts","../src/decorators/current-session.decorator.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\n/**\n * Default-path (`/api/auth`) catch-all controller. Retained for back-compat;\n * `BetterAuthModule` now mounts {@link createBetterAuthCatchallController} with\n * the configured `basePath`. Prefer the factory for a custom base path.\n */\nexport const BetterAuthCatchallController = createBetterAuthCatchallController();\n","import { InjectionToken } from '@velajs/vela';\nimport type { BetterAuthModuleOptions } from './better-auth.types';\n\nexport const BETTER_AUTH_OPTIONS = new InjectionToken<BetterAuthModuleOptions>(\n 'vela.BetterAuthOptions',\n);\n\nexport const AUTH_USER_KEY = Symbol.for('vela.better-auth.user');\nexport const AUTH_SESSION_KEY = Symbol.for('vela.better-auth.session');\nexport const AUTH_ISSUER_KEY = Symbol.for('vela.better-auth.issuer');\nexport const AUTH_PRINCIPAL_TYPE_KEY = Symbol.for('vela.better-auth.principal-type');\n","import type { ExecutionContext } from '@velajs/vela';\nimport type { Session, User } from './better-auth.types';\n\nexport interface AuthenticatedRequestState {\n readonly authenticated: true;\n readonly user: User;\n readonly session: Session;\n readonly issuer: string;\n readonly principalType: 'user';\n}\n\ninterface AnonymousRequestState {\n readonly authenticated: false;\n}\n\nexport type AuthRequestState = AuthenticatedRequestState | AnonymousRequestState;\n\nconst ANONYMOUS: AnonymousRequestState = Object.freeze({ authenticated: false });\n\n// Canonical authentication state is request-local and unforgeable by upstream\n// Hono middleware. It deliberately does not depend on resolving a DI token:\n// guards execute before parameter extraction and test harnesses may load Vela's\n// public/internal entry points as separate module instances. Both execution\n// contexts still expose the same raw Request object.\nconst stateByRequest = new WeakMap<Request, AuthRequestState>();\n\n/** Clear any state before a guard evaluates a request. */\nexport const beginAuthRequest = (context: ExecutionContext): void => {\n stateByRequest.set(context.getRequest(), ANONYMOUS);\n};\n\n/** Publish a fully verified session for downstream guards and parameters. */\nexport const authenticateRequest = (\n context: ExecutionContext,\n state: Omit<AuthenticatedRequestState, 'authenticated'>,\n): AuthenticatedRequestState => {\n const authenticated: AuthenticatedRequestState = Object.freeze({\n authenticated: true,\n ...state,\n });\n stateByRequest.set(context.getRequest(), authenticated);\n return authenticated;\n};\n\n/** Missing state is anonymous: no guard means no ambient identity. */\nexport const getAuthRequestState = (context: ExecutionContext): AuthRequestState =>\n context.getType() === 'http'\n ? (stateByRequest.get(context.getRequest()) ?? ANONYMOUS)\n : ANONYMOUS;\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 REQUEST_CONTEXT,\n Reflector,\n UnauthorizedException,\n clearTrustedRequestIdentity,\n setTrustedRequestIdentity,\n type CanActivate,\n type ExecutionContext,\n type RequestContext,\n} from '@velajs/vela';\nimport {\n AUTH_ISSUER_KEY,\n AUTH_PRINCIPAL_TYPE_KEY,\n AUTH_SESSION_KEY,\n AUTH_USER_KEY,\n BETTER_AUTH_OPTIONS,\n} from '../better-auth.tokens';\nimport { BetterAuthService } from '../better-auth.service';\nimport type { BetterAuthModuleOptions, Session, User } from '../better-auth.types';\nimport {\n authenticateRequest,\n beginAuthRequest,\n type AuthRequestState,\n} from '../auth-request-state';\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 rather than the raw better-auth instance.\n // The service's lazy `.auth` getter defers construction to first use, so\n // forRootAsync factories that depend on values only available at\n // request time (Cloudflare D1/KV bindings, etc.) build safely on the\n // first canActivate — not at module-load bootstrap.\n @Inject(BetterAuthService) private readonly auth: BetterAuthService,\n @Inject(BETTER_AUTH_OPTIONS) private readonly opts: BetterAuthModuleOptions,\n ) {}\n\n async canActivate(context: ExecutionContext): Promise<boolean> {\n // WebSocket upgrades are authenticated before allocation. Frames reuse\n // only that normalized attachment identity; never call HTTP-only context\n // accessors or re-read ambient cookies after the connection is established.\n if (context.getType() === 'ws') {\n if (hasValidWebSocketIdentity(context)) return true;\n throw new AuthenticationRequiredException();\n }\n\n // The guard owns this request's identity epoch. Clear first so public,\n // optional, malformed, and throwing auth paths can never inherit state.\n beginAuthRequest(context);\n mirrorRequestContext(context, { authenticated: false });\n\n if (this.reflector.getAllAndOverride(Public, context)) return true;\n\n const request = context.getRequest();\n const raw = await this.auth.api.getSession({ headers: request.headers });\n const data = validateSessionData(raw);\n\n if (data) {\n const state = authenticateRequest(context, {\n user: data.user,\n session: data.session,\n issuer: this.opts.issuer ?? 'better-auth',\n principalType: 'user',\n });\n mirrorRequestContext(context, state);\n return true;\n }\n\n if (this.reflector.getAllAndOverride(OptionalAuth, context)) {\n return true;\n }\n\n throw new AuthenticationRequiredException();\n }\n}\n\nfunction hasValidWebSocketIdentity(context: ExecutionContext): boolean {\n try {\n const client = context.switchToWs().getClient<{ data?: unknown }>();\n const data = client?.data;\n if (!data || typeof data !== 'object') return false;\n const record = data as Record<string, unknown>;\n const principal = record.principal;\n if (!principal || typeof principal !== 'object') return false;\n const fields = principal as Record<string, unknown>;\n return (\n typeof fields.issuer === 'string' &&\n fields.issuer.length > 0 &&\n typeof fields.subject === 'string' &&\n fields.subject.length > 0 &&\n (fields.principalType === 'user' || fields.principalType === 'service') &&\n typeof record.tenantId === 'string' &&\n record.tenantId.length > 0 &&\n typeof record.expiresAtMs === 'number' &&\n Number.isSafeInteger(record.expiresAtMs) &&\n record.expiresAtMs > Date.now()\n );\n } catch {\n return false;\n }\n}\n\ninterface ContainerLike {\n resolve<T>(token: unknown): T;\n}\n\n/**\n * Preserve the public REQUEST_CONTEXT symbols for applications that consume\n * them directly. The private Request-keyed state above remains canonical: a\n * missing/duplicated framework token must not prevent a verified guard from\n * publishing identity to its own downstream decorators and guards.\n */\nfunction mirrorRequestContext(context: ExecutionContext, state: AuthRequestState): void {\n const request = context.getRequest();\n if (!state.authenticated) {\n clearTrustedRequestIdentity(request);\n } else {\n const tenantId = readActiveOrganizationId(state.session);\n setTrustedRequestIdentity(request, {\n principal: {\n issuer: state.issuer,\n subject: state.user.id,\n principalType: state.principalType,\n },\n ...(tenantId === undefined ? {} : { tenantId }),\n });\n }\n\n const honoCtx = context.getContext() as { get: (k: string) => ContainerLike | undefined };\n const container = honoCtx.get('container');\n if (!container) return;\n\n let reqCtx: RequestContext;\n try {\n reqCtx = container.resolve<RequestContext>(REQUEST_CONTEXT);\n } catch {\n return;\n }\n\n if (!state.authenticated) {\n reqCtx.set<User | undefined>(AUTH_USER_KEY, undefined);\n reqCtx.set<Session | undefined>(AUTH_SESSION_KEY, undefined);\n reqCtx.set<string | undefined>(AUTH_ISSUER_KEY, undefined);\n reqCtx.set<'user' | undefined>(AUTH_PRINCIPAL_TYPE_KEY, undefined);\n return;\n }\n\n reqCtx.set(AUTH_USER_KEY, state.user);\n reqCtx.set(AUTH_SESSION_KEY, state.session);\n reqCtx.set(AUTH_ISSUER_KEY, state.issuer);\n reqCtx.set(AUTH_PRINCIPAL_TYPE_KEY, state.principalType);\n}\n\nfunction readActiveOrganizationId(session: Session): string | undefined {\n const descriptor = Object.getOwnPropertyDescriptor(session, 'activeOrganizationId');\n if (descriptor === undefined || !('value' in descriptor)) return undefined;\n const value: unknown = descriptor.value;\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\ninterface SessionData {\n user: User;\n session: Session;\n}\n\n/** A test/provider override is trusted code, but its runtime result is not. */\nfunction validateSessionData(value: unknown): SessionData | undefined {\n if (value === null || typeof value !== 'object') return undefined;\n\n try {\n const userDescriptor = Object.getOwnPropertyDescriptor(value, 'user');\n const sessionDescriptor = Object.getOwnPropertyDescriptor(value, 'session');\n if (\n userDescriptor === undefined ||\n !('value' in userDescriptor) ||\n sessionDescriptor === undefined ||\n !('value' in sessionDescriptor)\n ) {\n return undefined;\n }\n\n const user: unknown = userDescriptor.value;\n const session: unknown = sessionDescriptor.value;\n if (\n user === null ||\n typeof user !== 'object' ||\n session === null ||\n typeof session !== 'object'\n ) {\n return undefined;\n }\n\n const userIdDescriptor = Object.getOwnPropertyDescriptor(user, 'id');\n const sessionIdDescriptor = Object.getOwnPropertyDescriptor(session, 'id');\n const sessionUserIdDescriptor = Object.getOwnPropertyDescriptor(session, 'userId');\n const userId =\n userIdDescriptor !== undefined && 'value' in userIdDescriptor\n ? userIdDescriptor.value\n : undefined;\n const sessionId =\n sessionIdDescriptor !== undefined && 'value' in sessionIdDescriptor\n ? sessionIdDescriptor.value\n : undefined;\n const sessionUserId =\n sessionUserIdDescriptor !== undefined && 'value' in sessionUserIdDescriptor\n ? sessionUserIdDescriptor.value\n : undefined;\n\n if (\n typeof userId !== 'string' ||\n userId.length === 0 ||\n typeof sessionId !== 'string' ||\n sessionId.length === 0 ||\n sessionUserId !== userId\n ) {\n return undefined;\n }\n\n // The identity-bearing fields above are validated as own data properties.\n // Remaining Better Auth/plugin fields stay intact for typed consumers.\n return { user: user as User, session: session as Session };\n } catch {\n return undefined;\n }\n}\n\n/**\n * `UnauthorizedException` preserves Nest-style direct behavior. The structural\n * VelaError brand also survives test/runtime package duplication, so the\n * central renderer still maps this to 401 rather than treating it as a foreign\n * 500 error.\n */\nclass AuthenticationRequiredException extends UnauthorizedException {\n readonly type = 'VelaError' as const;\n readonly code = 'unauthorized';\n readonly status = 401;\n\n constructor() {\n super('Authentication required');\n }\n}\n","import { Reflector } from '@velajs/vela';\n\nexport const Roles = Reflector.createDecorator<string[]>({ key: 'vela.auth.roles' });\nexport const ROLES_KEY = Roles.KEY;\n","import {\n ForbiddenException,\n Injectable,\n Reflector,\n type CanActivate,\n type ExecutionContext,\n} from '@velajs/vela';\nimport { getAuthRequestState } from '../auth-request-state';\nimport { Roles } from '../decorators/roles.decorator';\n\nconst ACCESS_DENIED = 'Access denied';\n\n@Injectable()\nexport class RolesGuard implements CanActivate {\n private readonly reflector = new Reflector();\n\n canActivate(context: ExecutionContext): boolean {\n const required = this.reflector.getAllAndOverride(Roles, context);\n if (!required || required.length === 0) return true;\n\n const state = getAuthRequestState(context);\n if (!state.authenticated) {\n throw new ForbiddenException(ACCESS_DENIED);\n }\n\n const userRoles = normalizeRoles((state.user as { role?: string | string[] }).role);\n const ok = required.some((r) => userRoles.includes(r));\n if (!ok) {\n throw new ForbiddenException(ACCESS_DENIED);\n }\n return true;\n }\n}\n\nfunction normalizeRoles(role: string | string[] | undefined): string[] {\n if (!role) return [];\n if (Array.isArray(role)) return role;\n return role\n .split(',')\n .map((r) => r.trim())\n .filter(Boolean);\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 = User & { 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 * Adapts a 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","import { Reflector } from '@velajs/vela';\n\n/**\n * Declares the `@velajs/authz` permission(s) required to reach a controller or\n * route handler. Read via `Reflector` in an authorization guard, then checked\n * against the caller's `Identity` with `authz.can(...)`.\n *\n * ```ts\n * @RequirePermission(['posts:write'])\n * @Post()\n * create() { ... }\n * ```\n *\n * The metadata is a plain `string[]` of permission strings in the granted-side\n * format `@velajs/authz` matches (`resource:action`, or wildcards like\n * `posts:*`). Handler-level metadata overrides class-level (standard\n * `Reflector.getAllAndOverride` precedence).\n *\n * Semantics are **require-ALL** (AND): every listed permission must be granted\n * for access — the `PermissionGuard` denies if any one is missing. This\n * contrasts with `@Roles`, which is **OR** (any one of the listed roles\n * suffices).\n */\nexport const RequirePermission = Reflector.createDecorator<string[]>({\n key: 'vela.authz.permissions',\n});\n\nexport const REQUIRE_PERMISSION_KEY = RequirePermission.KEY;\n","import {\n ForbiddenException,\n Injectable,\n Reflector,\n type CanActivate,\n type ExecutionContext,\n type InjectionToken,\n} from '@velajs/vela';\nimport { AUTHZ } from '@velajs/authz/vela';\nimport type { Authz, Identity } from '@velajs/authz';\nimport { getAuthRequestState } from '../auth-request-state';\nimport { identityFromUser } from '../authz-bridge';\nimport { RequirePermission } from '../decorators/require-permission.decorator';\n\n// The linked `@velajs/authz` is built against its own (newer) `@velajs/vela`\n// copy, so the `AUTHZ` token's `InjectionToken` type is nominally distinct from\n// this package's `InjectionToken` — even though it is the very same runtime\n// token object (the DI container matches tokens by object identity). Re-type it\n// to the local `InjectionToken` so the request-time `container.resolve(...)`\n// accepts it without a structural clash. This is purely a compile-time alias;\n// it changes nothing at runtime. (Version-skew workaround until both publish.)\nconst AUTHZ_TOKEN = AUTHZ as unknown as InjectionToken<Authz>;\nconst ACCESS_DENIED = 'Access denied';\n\ninterface ContainerLike {\n resolve<T>(token: unknown, requestingModuleId?: string): T;\n resolveAll?<T>(token: unknown, requestingModuleId?: string): T[];\n}\n\nfunction resolveSingleAuthz(container: ContainerLike, moduleId: string): Authz | undefined {\n try {\n if (typeof container.resolveAll === 'function') {\n const candidates = container.resolveAll<Authz>(AUTHZ_TOKEN, moduleId);\n return candidates.length === 1 ? candidates[0] : undefined;\n }\n return container.resolve<Authz>(AUTHZ_TOKEN, moduleId);\n } catch {\n return undefined;\n }\n}\n\n/**\n * Enforces the `@RequirePermission(...)` metadata against the `@velajs/authz`\n * engine. For each required permission it calls `authz.can(identity, perm)`,\n * requiring **all** of them (AND semantics — contrast {@link RolesGuard}, which\n * is OR over roles). The caller's `Identity` is derived from the better-auth\n * user that {@link AuthGuard} placed in canonical request-local auth state, so this guard must\n * run *after* `AuthGuard` (e.g. `@UseGuards(AuthGuard, PermissionGuard)`).\n *\n * `AUTHZ` is resolved at request time from the per-request container. Exactly\n * one reachable engine is required; zero or multiple registrations deny rather\n * than selecting one by import order.\n *\n * Fail-closed on every abnormal path — no branch grants access on missing\n * wiring or a missing caller:\n * - no required permissions → allow (nothing to enforce);\n * - `AUTHZ` unresolvable (`AuthzModule` not registered) → deny (`ForbiddenException`);\n * - no authenticated user in request-local auth state → deny;\n * - any single required permission not granted → deny.\n *\n * The guard is stateless (no injected dependencies), so it is safe to register\n * as a plain provided guard.\n */\n@Injectable()\nexport class PermissionGuard implements CanActivate {\n private readonly reflector = new Reflector();\n\n async canActivate(context: ExecutionContext): Promise<boolean> {\n const required = this.reflector.getAllAndOverride(RequirePermission, context);\n if (!required || required.length === 0) return true;\n\n const container = resolveContextContainer(context);\n const moduleId = context.getModuleId();\n\n // Resolve all visible AUTHZ registrations and accept only an unambiguous\n // single engine. This prevents import order from selecting another tenant's\n // or feature module's authorization policy.\n const authz =\n container === undefined || moduleId === undefined\n ? undefined\n : resolveSingleAuthz(container, moduleId);\n if (!authz) {\n throw new ForbiddenException(ACCESS_DENIED);\n }\n\n const identity = resolveContextIdentity(context);\n if (identity === undefined) throw new ForbiddenException(ACCESS_DENIED);\n for (const permission of required) {\n if (!(await authz.can(identity, permission))) {\n throw new ForbiddenException(ACCESS_DENIED);\n }\n }\n return true;\n }\n}\n\nfunction resolveContextContainer(context: ExecutionContext): ContainerLike | undefined {\n const direct = context.getContainer?.<ContainerLike>();\n if (direct !== undefined) return direct;\n if (context.getType() !== 'http') return undefined;\n try {\n const honoCtx = context.getContext() as { get: (key: string) => ContainerLike | undefined };\n return honoCtx.get('container');\n } catch {\n return undefined;\n }\n}\n\nfunction resolveContextIdentity(context: ExecutionContext): Identity | undefined {\n if (context.getType() === 'ws') {\n try {\n const data = context.switchToWs().getClient<{ data?: unknown }>()?.data;\n if (!data || typeof data !== 'object') return undefined;\n const record = data as Record<string, unknown>;\n const principal = record.principal;\n if (!principal || typeof principal !== 'object') return undefined;\n const fields = principal as Record<string, unknown>;\n if (\n typeof fields.issuer !== 'string' ||\n fields.issuer.length === 0 ||\n typeof fields.subject !== 'string' ||\n fields.subject.length === 0 ||\n (fields.principalType !== 'user' && fields.principalType !== 'service') ||\n typeof record.tenantId !== 'string' ||\n record.tenantId.length === 0 ||\n typeof record.expiresAtMs !== 'number' ||\n !Number.isSafeInteger(record.expiresAtMs) ||\n record.expiresAtMs <= Date.now()\n ) {\n return undefined;\n }\n return {\n issuer: fields.issuer,\n subject: fields.subject,\n principalType: fields.principalType,\n userId: fields.subject,\n roles: [],\n };\n } catch {\n return undefined;\n }\n }\n\n const state = getAuthRequestState(context);\n return state.authenticated\n ? identityFromUser(state.user, state.issuer, state.principalType)\n : undefined;\n}\n","import {\n defineModule,\n lazyProvider,\n provideGlobal,\n stableHash,\n type DynamicModule,\n type InferTokens,\n type ProviderOptions,\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 { BetterAuthInstance, BetterAuthModuleOptions } from './better-auth.types';\nimport { AuthGuard } from './guards/auth.guard';\nimport { RolesGuard } from './guards/roles.guard';\nimport { PermissionGuard } from './guards/permission.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 defaultPolicy: 'deny';\n mountHandler: boolean;\n}\n\nfunction normalize(options: Partial<BetterAuthModuleOptions>): 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 if (options.defaultPolicy !== undefined && options.defaultPolicy !== 'deny') {\n throw new Error(\n '@velajs/better-auth: defaultPolicy is deny-only; mark anonymous routes with @Public() or @OptionalAuth()',\n );\n }\n return {\n basePath,\n issuer,\n isGlobal: options.isGlobal ?? true,\n defaultPolicy: 'deny',\n mountHandler: options.mountHandler ?? true,\n };\n}\n\n/** Providers, controllers, and exports shared by both entry points. */\nfunction commonContributions(n: NormalizedOptions): {\n providers: Array<Type | ProviderOptions>;\n controllers: Type[];\n exports: DynamicModule['exports'];\n} {\n return {\n providers: [BetterAuthService, AuthGuard, RolesGuard, PermissionGuard],\n controllers: n.mountHandler ? [createBetterAuthCatchallController(n.basePath)] : [],\n exports: [BetterAuthService, BETTER_AUTH_OPTIONS, AuthGuard, RolesGuard, PermissionGuard],\n };\n}\n\n/**\n * The blessed engine generates `forRoot`. `setup` runs once per instance at\n * call time: it re-provides {@link BETTER_AUTH_OPTIONS} with defaults applied,\n * derives the auth builder from those options, mounts the catch-all controller,\n * and — via the `global:` slot — registers the app-wide guard when `isGlobal`.\n *\n * `isGlobal` here means \"apply AuthGuard app-wide\", NOT \"make this a global\n * module\", so the default `isGlobal → global: true` extras transform is\n * replaced with identity; the flag reaches `setup` through the options bag.\n */\nconst authModuleHost = defineModule<BetterAuthModuleOptions>({\n name: 'BetterAuth',\n optionsToken: BETTER_AUTH_OPTIONS,\n transform: (definition) => definition,\n // Public entry points always supply an identity-aware key. Keep this fallback\n // for direct host use in tests and future refactors.\n key: (options) => stableHash(normalize(options)),\n setup: ({ OPTIONS, options }) => {\n const n = normalize(options);\n const common = commonContributions(n);\n const auth = (options as BetterAuthModuleOptions).auth;\n return {\n providers: [\n // Override the auto-provided raw bag with the normalized shape so\n // BETTER_AUTH_OPTIONS consumers always see defaults + the auth instance.\n { provide: OPTIONS, useValue: { ...n, auth } },\n // Eager auth: the builder hands back the instance the caller passed in.\n lazyProvider({\n provide: BETTER_AUTH_BUILDER,\n inject: [OPTIONS],\n useFactory: (o: BetterAuthModuleOptions) => o.auth,\n }),\n ...common.providers,\n ],\n controllers: common.controllers,\n exports: common.exports,\n global: n.isGlobal ? { guards: [AuthGuard] } : undefined,\n };\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: [D1Service, ConfigService], // captured as readonly tuple\n * useFactory: (d1, config) => // d1: D1Service, config: ConfigService\n * betterAuth({ database: drizzleAdapter(drizzle(d1.database), ...) }),\n * });\n * ```\n */\ninterface ForRootAsyncOptions<\n Inject extends readonly Token<unknown>[] = readonly Token<unknown>[],\n> {\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 /** @deprecated Authentication is deny-by-default. Only `'deny'` is accepted. */\n defaultPolicy?: 'deny';\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 // Delegate to the generated static, then rebrand the module identity so the\n // public `BetterAuthModule` class is the one registered (consistent with\n // `forRootAsync` and better diagnostics).\n return {\n ...authModuleHost.ConfigurableModuleClass.forRoot({ ...options, key }),\n module: BetterAuthModule,\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 behind {@link lazyProvider}'s memoized thunk. This is what makes\n * Cloudflare bindings (D1, KV, R2) work: the binding isn't ready at boot, but\n * it IS by the time a request flows through and the guard / catch-all reads\n * the service. Inject deps resolve at module load (cheap BindingRef wrappers);\n * their *values* are read at first auth use, inside your factory body.\n */\n static forRootAsync<const Inject extends readonly Token<unknown>[] = readonly Token<unknown>[]>(\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 { provide: BETTER_AUTH_OPTIONS, useValue: n },\n // The deferred auth builder: `lazyProvider` wraps the user factory in a\n // memoized thunk, replacing the hand-rolled `(...deps) => () => f(...deps)`.\n lazyProvider({\n provide: BETTER_AUTH_BUILDER,\n inject: options.inject,\n useFactory: options.useFactory,\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.authenticated ? state.user : undefined;\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.authenticated ? state.session : undefined;\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/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 getTrustedRequestIdentity,\n setTrustedRequestIdentity,\n type ExecutionContext,\n type TrustedRequestIdentity,\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 = new WeakMap<TrustedRequestIdentity, 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 const identity = getTrustedRequestIdentity(request);\n if (identity) sessions.set(identity, data);\n}\n\nexport function getAuthRequestState(context: ExecutionContext): SessionData | undefined {\n if (context.getType() !== 'http') return undefined;\n const identity = getTrustedRequestIdentity(context.getRequest());\n return identity && sessions.get(identity);\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 { 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 } 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 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,3 +1,4 @@
1
+ import { t as BetterAuthService } from "../better-auth.service-NOlWXxd4.js";
1
2
  //#region src/testing/acting-as.d.ts
2
3
  /**
3
4
  * The slice of `@velajs/testing`'s `TestingModule` this resolver depends on.
@@ -6,7 +7,7 @@
6
7
  * it, and the resolver stays assignable to `@velajs/testing`'s `ActingAsResolver`.
7
8
  */
8
9
  interface TestModuleLike {
9
- get<T>(token: unknown): T;
10
+ get(token: typeof BetterAuthService): BetterAuthService;
10
11
  }
11
12
  /**
12
13
  * A test principal. Opaque `Record<string, unknown>` to stay compatible with
@@ -44,7 +45,7 @@ type ActingAsPrincipal = Record<string, unknown>;
44
45
  * await module.http.get('/me').actingAs({ id: existingUserId }, actingAs).send();
45
46
  * ```
46
47
  */
47
- declare function actingAs(module: TestModuleLike, principal: ActingAsPrincipal): Promise<Headers>;
48
+ export declare function actingAs(module: TestModuleLike, principal: ActingAsPrincipal): Promise<Headers>;
48
49
  //#endregion
49
- export { type ActingAsPrincipal, type TestModuleLike, actingAs };
50
+ export type { ActingAsPrincipal, TestModuleLike };
50
51
  //# sourceMappingURL=index.d.ts.map
@@ -1,4 +1,4 @@
1
- import { n as BetterAuthService } from "../better-auth.service-BMkyFX-w.js";
1
+ import { n as BetterAuthService } from "../better-auth.service-DurQ4JQf.js";
2
2
  import { makeSignature } from "better-auth/crypto";
3
3
  //#region src/testing/acting-as.ts
4
4
  /**
@@ -27,6 +27,7 @@ import { makeSignature } from "better-auth/crypto";
27
27
  */
28
28
  async function actingAs(module, principal) {
29
29
  const ctx = await module.get(BetterAuthService).auth.$context;
30
+ if (!ctx) throw new Error("actingAs: the configured authentication provider has no Better Auth context");
30
31
  const internalAdapter = ctx.internalAdapter;
31
32
  const id = typeof principal.id === "string" ? principal.id : void 0;
32
33
  const email = typeof principal.email === "string" ? principal.email : void 0;
@@ -42,7 +43,7 @@ async function actingAs(module, principal) {
42
43
  email,
43
44
  name: name ?? email,
44
45
  ...id ? { id } : {}
45
- });
46
+ }, { method: "admin" });
46
47
  }
47
48
  const session = await internalAdapter.createSession(user.id, false, {
48
49
  ipAddress: "127.0.0.1",
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/testing/acting-as.ts"],"sourcesContent":["// Mechanics adapted from @stratal/testing (MIT, © Temitayo Fadojutimi):\n// mint a real better-auth session via `$context.internalAdapter` and hand back\n// a signed session-cookie header. Adapted to better-auth >=1.6: the session\n// cookie is signed with the package's own `makeSignature` (better-auth/crypto)\n// — the same primitive better-auth's built-in test cookie builder uses — so no\n// endpoint-context shim or `setSessionCookie` mock is needed.\nimport { makeSignature } from 'better-auth/crypto';\nimport { BetterAuthService } from '../better-auth.service';\n\n/**\n * The slice of `@velajs/testing`'s `TestingModule` this resolver depends on.\n * Declared structurally so `@velajs/better-auth/testing` carries NO runtime\n * (or type) dependency on `@velajs/testing` — a real `TestingModule` satisfies\n * it, and the resolver stays assignable to `@velajs/testing`'s `ActingAsResolver`.\n */\nexport interface TestModuleLike {\n get<T>(token: unknown): T;\n}\n\n/**\n * A test principal. Opaque `Record<string, unknown>` to stay compatible with\n * `@velajs/testing`'s `TestPrincipal`. Recognized fields:\n *\n * - `id` — reuse the user with this id if it already exists.\n * - `email` — reuse the user with this email, else create one.\n * - `name` — display name for a created user (defaults to the email).\n *\n * Any other fields are forwarded to `internalAdapter.createUser` (e.g. `role`)\n * when a new user is minted, so role-guarded routes can be exercised.\n */\nexport type ActingAsPrincipal = Record<string, unknown>;\n\n/**\n * actingAs — a `@velajs/testing` auth resolver for better-auth.\n *\n * Resolves {@link BetterAuthService} from the module, mints a REAL better-auth\n * session for `principal` through `auth.$context.internalAdapter`, and returns\n * a `Headers` carrying a properly signed session cookie. Guarded routes\n * (`AuthGuard`) then accept requests carrying those headers because\n * `auth.api.getSession` validates the cookie against the same session store.\n *\n * The signature `(module, principal) => Promise<Headers>` is exactly\n * `@velajs/testing`'s `ActingAsResolver`, so it plugs straight in:\n *\n * @example\n * ```ts\n * import { actingAs } from '@velajs/better-auth/testing';\n *\n * // As the default resolver for the module:\n * module.setAuthResolver(actingAs);\n * await module.http.get('/me').actingAs({ email: 'ada@example.com' }).send();\n *\n * // Or passed per-request:\n * await module.http.get('/me').actingAs({ id: existingUserId }, actingAs).send();\n * ```\n */\nexport async function actingAs(\n module: TestModuleLike,\n principal: ActingAsPrincipal,\n): Promise<Headers> {\n const auth = module.get<BetterAuthService>(BetterAuthService).auth;\n const ctx = await auth.$context;\n const internalAdapter = ctx.internalAdapter;\n\n const id = typeof principal.id === 'string' ? principal.id : undefined;\n const email = typeof principal.email === 'string' ? principal.email : undefined;\n const name = typeof principal.name === 'string' ? principal.name : undefined;\n\n // `findUserById`/`createUser` yield a bare user; `findUserByEmail` nests it\n // under `{ user, accounts }` — normalize to the id we need.\n let user: { id: string } | null = null;\n if (id) user = await internalAdapter.findUserById(id);\n if (!user && email) {\n const found = await internalAdapter.findUserByEmail(email);\n user = found?.user ?? null;\n }\n if (!user) {\n if (!email) {\n throw new Error(\n 'actingAs: principal must carry an `email` (to create a user) or an ' +\n '`id` matching an existing user.',\n );\n }\n const { id: _id, email: _email, name: _name, ...extra } = principal;\n user = await internalAdapter.createUser({\n ...extra,\n email,\n name: name ?? email,\n ...(id ? { id } : {}),\n });\n }\n\n const session = await internalAdapter.createSession(user.id, false, {\n ipAddress: '127.0.0.1',\n userAgent: 'vela-test',\n });\n\n const cookieName = ctx.authCookies.sessionToken.name;\n const signedToken = `${session.token}.${await makeSignature(session.token, ctx.secret)}`;\n\n const headers = new Headers();\n headers.set('Cookie', `${cookieName}=${signedToken}`);\n return headers;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAwDA,eAAsB,SACpB,QACA,WACkB;CAElB,MAAM,MAAM,MADC,OAAO,IAAuB,iBAAiB,CAAC,CAAC,KACvC;CACvB,MAAM,kBAAkB,IAAI;CAE5B,MAAM,KAAK,OAAO,UAAU,OAAO,WAAW,UAAU,KAAK,KAAA;CAC7D,MAAM,QAAQ,OAAO,UAAU,UAAU,WAAW,UAAU,QAAQ,KAAA;CACtE,MAAM,OAAO,OAAO,UAAU,SAAS,WAAW,UAAU,OAAO,KAAA;CAInE,IAAI,OAA8B;CAClC,IAAI,IAAI,OAAO,MAAM,gBAAgB,aAAa,EAAE;CACpD,IAAI,CAAC,QAAQ,OAEX,QAAO,MADa,gBAAgB,gBAAgB,KAAK,EAAA,EAC3C,QAAQ;CAExB,IAAI,CAAC,MAAM;EACT,IAAI,CAAC,OACH,MAAM,IAAI,MACR,oGAEF;EAEF,MAAM,EAAE,IAAI,KAAK,OAAO,QAAQ,MAAM,OAAO,GAAG,UAAU;EAC1D,OAAO,MAAM,gBAAgB,WAAW;GACtC,GAAG;GACH;GACA,MAAM,QAAQ;GACd,GAAI,KAAK,EAAE,GAAG,IAAI,CAAC;EACrB,CAAC;CACH;CAEA,MAAM,UAAU,MAAM,gBAAgB,cAAc,KAAK,IAAI,OAAO;EAClE,WAAW;EACX,WAAW;CACb,CAAC;CAED,MAAM,aAAa,IAAI,YAAY,aAAa;CAChD,MAAM,cAAc,GAAG,QAAQ,MAAM,GAAG,MAAM,cAAc,QAAQ,OAAO,IAAI,MAAM;CAErF,MAAM,UAAU,IAAI,QAAQ;CAC5B,QAAQ,IAAI,UAAU,GAAG,WAAW,GAAG,aAAa;CACpD,OAAO;AACT"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/testing/acting-as.ts"],"sourcesContent":["// Mechanics adapted from @stratal/testing (MIT, © Temitayo Fadojutimi):\n// mint a real better-auth session via `$context.internalAdapter` and hand back\n// a signed session-cookie header. Adapted to better-auth >=1.6: the session\n// cookie is signed with the package's own `makeSignature` (better-auth/crypto)\n// — the same primitive better-auth's built-in test cookie builder uses — so no\n// endpoint-context shim or `setSessionCookie` mock is needed.\nimport { makeSignature } from 'better-auth/crypto';\nimport { BetterAuthService } from '../better-auth.service';\n\n/**\n * The slice of `@velajs/testing`'s `TestingModule` this resolver depends on.\n * Declared structurally so `@velajs/better-auth/testing` carries NO runtime\n * (or type) dependency on `@velajs/testing` — a real `TestingModule` satisfies\n * it, and the resolver stays assignable to `@velajs/testing`'s `ActingAsResolver`.\n */\nexport interface TestModuleLike {\n get(token: typeof BetterAuthService): BetterAuthService;\n}\n\n/**\n * A test principal. Opaque `Record<string, unknown>` to stay compatible with\n * `@velajs/testing`'s `TestPrincipal`. Recognized fields:\n *\n * - `id` — reuse the user with this id if it already exists.\n * - `email` — reuse the user with this email, else create one.\n * - `name` — display name for a created user (defaults to the email).\n *\n * Any other fields are forwarded to `internalAdapter.createUser` (e.g. `role`)\n * when a new user is minted, so role-guarded routes can be exercised.\n */\nexport type ActingAsPrincipal = Record<string, unknown>;\n\n/**\n * actingAs — a `@velajs/testing` auth resolver for better-auth.\n *\n * Resolves {@link BetterAuthService} from the module, mints a REAL better-auth\n * session for `principal` through `auth.$context.internalAdapter`, and returns\n * a `Headers` carrying a properly signed session cookie. Guarded routes\n * (`AuthGuard`) then accept requests carrying those headers because\n * `auth.api.getSession` validates the cookie against the same session store.\n *\n * The signature `(module, principal) => Promise<Headers>` is exactly\n * `@velajs/testing`'s `ActingAsResolver`, so it plugs straight in:\n *\n * @example\n * ```ts\n * import { actingAs } from '@velajs/better-auth/testing';\n *\n * // As the default resolver for the module:\n * module.setAuthResolver(actingAs);\n * await module.http.get('/me').actingAs({ email: 'ada@example.com' }).send();\n *\n * // Or passed per-request:\n * await module.http.get('/me').actingAs({ id: existingUserId }, actingAs).send();\n * ```\n */\nexport async function actingAs(\n module: TestModuleLike,\n principal: ActingAsPrincipal,\n): Promise<Headers> {\n const auth = module.get(BetterAuthService).auth;\n const ctx = await auth.$context;\n if (!ctx)\n throw new Error('actingAs: the configured authentication provider has no Better Auth context');\n const internalAdapter = ctx.internalAdapter;\n\n const id = typeof principal.id === 'string' ? principal.id : undefined;\n const email = typeof principal.email === 'string' ? principal.email : undefined;\n const name = typeof principal.name === 'string' ? principal.name : undefined;\n\n // `findUserById`/`createUser` yield a bare user; `findUserByEmail` nests it\n // under `{ user, accounts }` — normalize to the id we need.\n let user: { id: string } | null = null;\n if (id) user = await internalAdapter.findUserById(id);\n if (!user && email) {\n const found = await internalAdapter.findUserByEmail(email);\n user = found?.user ?? null;\n }\n if (!user) {\n if (!email) {\n throw new Error(\n 'actingAs: principal must carry an `email` (to create a user) or an ' +\n '`id` matching an existing user.',\n );\n }\n const { id: _id, email: _email, name: _name, ...extra } = principal;\n user = await internalAdapter.createUser(\n {\n ...extra,\n email,\n name: name ?? email,\n ...(id ? { id } : {}),\n },\n { method: 'admin' },\n );\n }\n\n const session = await internalAdapter.createSession(user.id, false, {\n ipAddress: '127.0.0.1',\n userAgent: 'vela-test',\n });\n\n const cookieName = ctx.authCookies.sessionToken.name;\n const signedToken = `${session.token}.${await makeSignature(session.token, ctx.secret)}`;\n\n const headers = new Headers();\n headers.set('Cookie', `${cookieName}=${signedToken}`);\n return headers;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAwDA,eAAsB,SACpB,QACA,WACkB;CAElB,MAAM,MAAM,MADC,OAAO,IAAI,iBAAiB,CAAC,CAAC,KACpB;CACvB,IAAI,CAAC,KACH,MAAM,IAAI,MAAM,6EAA6E;CAC/F,MAAM,kBAAkB,IAAI;CAE5B,MAAM,KAAK,OAAO,UAAU,OAAO,WAAW,UAAU,KAAK,KAAA;CAC7D,MAAM,QAAQ,OAAO,UAAU,UAAU,WAAW,UAAU,QAAQ,KAAA;CACtE,MAAM,OAAO,OAAO,UAAU,SAAS,WAAW,UAAU,OAAO,KAAA;CAInE,IAAI,OAA8B;CAClC,IAAI,IAAI,OAAO,MAAM,gBAAgB,aAAa,EAAE;CACpD,IAAI,CAAC,QAAQ,OAEX,QAAO,MADa,gBAAgB,gBAAgB,KAAK,EAAA,EAC3C,QAAQ;CAExB,IAAI,CAAC,MAAM;EACT,IAAI,CAAC,OACH,MAAM,IAAI,MACR,oGAEF;EAEF,MAAM,EAAE,IAAI,KAAK,OAAO,QAAQ,MAAM,OAAO,GAAG,UAAU;EAC1D,OAAO,MAAM,gBAAgB,WAC3B;GACE,GAAG;GACH;GACA,MAAM,QAAQ;GACd,GAAI,KAAK,EAAE,GAAG,IAAI,CAAC;EACrB,GACA,EAAE,QAAQ,QAAQ,CACpB;CACF;CAEA,MAAM,UAAU,MAAM,gBAAgB,cAAc,KAAK,IAAI,OAAO;EAClE,WAAW;EACX,WAAW;CACb,CAAC;CAED,MAAM,aAAa,IAAI,YAAY,aAAa;CAChD,MAAM,cAAc,GAAG,QAAQ,MAAM,GAAG,MAAM,cAAc,QAAQ,OAAO,IAAI,MAAM;CAErF,MAAM,UAAU,IAAI,QAAQ;CAC5B,QAAQ,IAAI,UAAU,GAAG,WAAW,GAAG,aAAa;CACpD,OAAO;AACT"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velajs/better-auth",
3
- "version": "1.0.0",
3
+ "version": "1.22.1",
4
4
  "description": "better-auth integration for the Vela framework",
5
5
  "keywords": [
6
6
  "auth",
@@ -12,15 +12,16 @@
12
12
  "session",
13
13
  "vela"
14
14
  ],
15
- "homepage": "https://github.com/velajs/better-auth#readme",
15
+ "homepage": "https://github.com/velajs/vela/tree/main/packages/better-auth#readme",
16
16
  "bugs": {
17
- "url": "https://github.com/velajs/better-auth/issues"
17
+ "url": "https://github.com/velajs/vela/issues"
18
18
  },
19
19
  "license": "MIT",
20
20
  "author": "ksh",
21
21
  "repository": {
22
22
  "type": "git",
23
- "url": "git+https://github.com/velajs/better-auth.git"
23
+ "url": "git+https://github.com/velajs/vela.git",
24
+ "directory": "packages/better-auth"
24
25
  },
25
26
  "files": [
26
27
  "dist",
@@ -43,33 +44,36 @@
43
44
  }
44
45
  },
45
46
  "dependencies": {
46
- "@velajs/authz": "^1.1.0"
47
+ "@velajs/authz": "1.22.1"
47
48
  },
48
49
  "devDependencies": {
49
- "@arethetypeswrong/cli": "^0.18.5",
50
- "@changesets/cli": "^2.31.0",
51
- "@swc/core": "^1.15.43",
52
- "@velajs/testing": "^1.0.0",
53
- "@velajs/vela": "^1.21.0",
50
+ "@arethetypeswrong/cli": "0.18.5",
51
+ "@changesets/cli": "3.0.1",
52
+ "@swc/core": "1.15.43",
54
53
  "better-auth": "^1.6.20",
55
- "hono": "^4.12.26",
56
- "oxfmt": "^0.58.0",
57
- "oxlint": "^1.73.0",
58
- "publint": "^0.3.21",
59
- "tsdown": "^0.22.4",
60
- "typescript": "^7.0.2",
61
- "unplugin-swc": "^1.5.9",
54
+ "hono": "4.13.8",
55
+ "oxfmt": "0.58.0",
56
+ "oxlint": "1.73.0",
57
+ "publint": "0.3.21",
58
+ "tsdown": "0.23.0",
59
+ "typescript": "7.0.2",
60
+ "unplugin-swc": "1.5.9",
62
61
  "vite": "^8.0.16",
63
- "vitest": "^4.1.10"
62
+ "vitest": "4.1.10",
63
+ "@velajs/testing": "1.22.1",
64
+ "@velajs/vela": "1.22.1"
64
65
  },
65
66
  "peerDependencies": {
66
- "@velajs/vela": ">=1.21.0 <2",
67
67
  "better-auth": ">=1.2.0",
68
- "hono": ">=4"
68
+ "hono": ">=4",
69
+ "@velajs/vela": "^1.22.1"
69
70
  },
70
71
  "engines": {
71
72
  "node": ">=24"
72
73
  },
74
+ "publishConfig": {
75
+ "access": "public"
76
+ },
73
77
  "scripts": {
74
78
  "build": "tsdown",
75
79
  "test": "vitest run",
@@ -79,11 +83,6 @@
79
83
  "format:check": "oxfmt --check .",
80
84
  "publint": "publint",
81
85
  "attw": "attw --pack . --profile esm-only",
82
- "changeset": "changeset",
83
- "version-packages": "changeset version",
84
- "release:preflight": "npm view @velajs/vela@1.21.0 version && npm view @velajs/authz@1.1.0 version && npm view @velajs/testing@1.0.0 version",
85
- "release:check": "node scripts/check-release-lock.mjs && pnpm verify && pnpm audit --audit-level=high",
86
- "release": "pnpm release:preflight && pnpm release:check && changeset publish",
87
86
  "verify": "pnpm lint && pnpm format:check && pnpm build && pnpm typecheck && pnpm test && pnpm publint && pnpm attw"
88
87
  }
89
88
  }
@@ -1 +0,0 @@
1
- {"version":3,"file":"better-auth.service-BMkyFX-w.js","names":[],"sources":["../src/better-auth.service.ts"],"sourcesContent":["import { Inject, Injectable, InjectionToken } from '@velajs/vela';\nimport type { BetterAuthInstance } from './better-auth.types';\n\n/**\n * Internal token holding the auth-construction closure with its inject deps\n * closed over. Resolves cheaply at module load (just captures references);\n * the inner call happens lazily on first auth use (see `BetterAuthService`).\n *\n * Not exported from the public surface — only the service consumes it.\n */\nexport const BETTER_AUTH_BUILDER = new InjectionToken<() => BetterAuthInstance>(\n 'vela.better-auth.Builder',\n);\n\n/**\n * The single injectable consumers reach for to interact with better-auth.\n * Wraps the underlying `betterAuth({...})` instance with lazy construction:\n *\n * - `forRoot({ auth })` — the builder returns the eagerly-provided instance,\n * so the first `.auth` / `.api` / `.handler` access is effectively a\n * read-and-cache.\n * - `forRootAsync({ inject, useFactory })` — the builder wraps the user's\n * factory + inject deps. First access triggers `useFactory(...deps)`. This\n * is what makes Cloudflare D1/KV bindings work: at module load the factory\n * doesn't run; on first request (when AuthGuard or the catch-all calls\n * `service.api` / `service.handler`), the bindings are populated and the\n * factory can read them safely.\n *\n * Used directly by AuthGuard and the catch-all controller. Consumers in\n * application code inject the same way: `@Inject(BetterAuthService)`.\n */\n@Injectable()\nexport class BetterAuthService {\n private cached: BetterAuthInstance | undefined;\n\n constructor(@Inject(BETTER_AUTH_BUILDER) private readonly build: () => BetterAuthInstance) {}\n\n /**\n * The underlying better-auth instance. Constructed once on first access.\n * Safe to call from any request-time code path (guards, controllers,\n * services invoked from handlers).\n */\n get auth(): BetterAuthInstance {\n if (!this.cached) this.cached = this.build();\n return this.cached;\n }\n\n /** Convenience accessor — equivalent to `service.auth.api`. */\n get api(): BetterAuthInstance['api'] {\n return this.auth.api;\n }\n\n /** Convenience accessor — equivalent to `service.auth.handler`. */\n get handler(): BetterAuthInstance['handler'] {\n return this.auth.handler;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAUA,MAAa,sBAAsB,IAAI,eACrC,0BACF;AAoBO,IAAA,oBAAA,MAAM,kBAAkB;CAG6B;CAF1D;CAEA,YAAY,OAA+E;EAAjC,KAAA,QAAA;CAAkC;;;;;;CAO5F,IAAI,OAA2B;EAC7B,IAAI,CAAC,KAAK,QAAQ,KAAK,SAAS,KAAK,MAAM;EAC3C,OAAO,KAAK;CACd;;CAGA,IAAI,MAAiC;EACnC,OAAO,KAAK,KAAK;CACnB;;CAGA,IAAI,UAAyC;EAC3C,OAAO,KAAK,KAAK;CACnB;AACF;;CAzBC,WAAW;oBAIG,OAAO,mBAAmB,CAAA"}