@pedr0ni/nestjs-better-auth 1.0.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/dist/index.mjs ADDED
@@ -0,0 +1,506 @@
1
+ import { createParamDecorator, SetMetadata, ConfigurableModuleBuilder, Inject, Injectable, ForbiddenException, UnauthorizedException, Module, Logger } from '@nestjs/common';
2
+ import { Reflector, DiscoveryModule, ApplicationConfig, DiscoveryService, MetadataScanner, HttpAdapterHost, APP_GUARD } from '@nestjs/core';
3
+ import { fromNodeHeaders, toNodeHandler } from 'better-auth/node';
4
+ import { createAuthMiddleware } from 'better-auth/plugins';
5
+ import * as express from 'express';
6
+ import { normalizePath } from '@nestjs/common/utils/shared.utils.js';
7
+ import { mapToExcludeRoute } from '@nestjs/core/middleware/utils.js';
8
+
9
+ const BEFORE_HOOK_KEY = Symbol("BEFORE_HOOK");
10
+ const AFTER_HOOK_KEY = Symbol("AFTER_HOOK");
11
+ const HOOK_KEY = Symbol("HOOK");
12
+ const AUTH_MODULE_OPTIONS_KEY = Symbol("AUTH_MODULE_OPTIONS");
13
+
14
+ let GqlExecutionContext;
15
+ function getGqlExecutionContext() {
16
+ if (!GqlExecutionContext) {
17
+ GqlExecutionContext = require("@nestjs/graphql").GqlExecutionContext;
18
+ }
19
+ return GqlExecutionContext;
20
+ }
21
+ function getRequestFromContext(context) {
22
+ const contextType = context.getType();
23
+ if (contextType === "graphql") {
24
+ return getGqlExecutionContext().create(context).getContext().req;
25
+ }
26
+ if (contextType === "ws") {
27
+ return context.switchToWs().getClient();
28
+ }
29
+ return context.switchToHttp().getRequest();
30
+ }
31
+
32
+ const AllowAnonymous = () => SetMetadata("PUBLIC", true);
33
+ const OptionalAuth = () => SetMetadata("OPTIONAL", true);
34
+ const Roles = (roles) => SetMetadata("ROLES", roles);
35
+ const OrgRoles = (roles) => SetMetadata("ORG_ROLES", roles);
36
+ const Public = AllowAnonymous;
37
+ const Optional = OptionalAuth;
38
+ const Session = createParamDecorator((_data, context) => {
39
+ const request = getRequestFromContext(context);
40
+ return request.session;
41
+ });
42
+ const BeforeHook = (path) => SetMetadata(BEFORE_HOOK_KEY, path);
43
+ const AfterHook = (path) => SetMetadata(AFTER_HOOK_KEY, path);
44
+ const Hook = () => SetMetadata(HOOK_KEY, true);
45
+
46
+ const MODULE_OPTIONS_TOKEN = Symbol("AUTH_MODULE_OPTIONS");
47
+ const { ConfigurableModuleClass, OPTIONS_TYPE, ASYNC_OPTIONS_TYPE } = new ConfigurableModuleBuilder({
48
+ optionsInjectionToken: MODULE_OPTIONS_TOKEN
49
+ }).setClassMethodName("forRoot").setExtras(
50
+ {
51
+ isGlobal: true,
52
+ disableGlobalAuthGuard: false,
53
+ disableControllers: false
54
+ },
55
+ (def, extras) => {
56
+ return {
57
+ ...def,
58
+ exports: [MODULE_OPTIONS_TOKEN],
59
+ global: extras.isGlobal
60
+ };
61
+ }
62
+ ).build();
63
+
64
+ var __getOwnPropDesc$2 = Object.getOwnPropertyDescriptor;
65
+ var __decorateClass$2 = (decorators, target, key, kind) => {
66
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$2(target, key) : target;
67
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
68
+ if (decorator = decorators[i])
69
+ result = (decorator(result)) || result;
70
+ return result;
71
+ };
72
+ var __decorateParam$2 = (index, decorator) => (target, key) => decorator(target, key, index);
73
+ let AuthService = class {
74
+ constructor(options) {
75
+ this.options = options;
76
+ }
77
+ /**
78
+ * Returns the API endpoints provided by the auth instance
79
+ */
80
+ get api() {
81
+ return this.options.auth.api;
82
+ }
83
+ /**
84
+ * Returns the complete auth instance
85
+ * Access this for plugin-specific functionality
86
+ */
87
+ get instance() {
88
+ return this.options.auth;
89
+ }
90
+ };
91
+ AuthService = __decorateClass$2([
92
+ __decorateParam$2(0, Inject(MODULE_OPTIONS_TOKEN))
93
+ ], AuthService);
94
+
95
+ var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
96
+ var __decorateClass$1 = (decorators, target, key, kind) => {
97
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target;
98
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
99
+ if (decorator = decorators[i])
100
+ result = (decorator(result)) || result;
101
+ return result;
102
+ };
103
+ var __decorateParam$1 = (index, decorator) => (target, key) => decorator(target, key, index);
104
+ let GraphQLErrorClass;
105
+ function getGraphQLError() {
106
+ if (!GraphQLErrorClass) {
107
+ try {
108
+ GraphQLErrorClass = require("graphql").GraphQLError;
109
+ } catch (_error) {
110
+ throw new Error(
111
+ "graphql is required for GraphQL support. Please install it: npm install graphql"
112
+ );
113
+ }
114
+ }
115
+ return GraphQLErrorClass;
116
+ }
117
+ let WsException;
118
+ function getWsException() {
119
+ if (!WsException) {
120
+ try {
121
+ WsException = require("@nestjs/websockets").WsException;
122
+ } catch (_error) {
123
+ throw new Error(
124
+ "@nestjs/websockets is required for WebSocket support. Please install it: npm install @nestjs/websockets @nestjs/platform-socket.io"
125
+ );
126
+ }
127
+ }
128
+ return WsException;
129
+ }
130
+ const AuthContextErrorMap = {
131
+ http: {
132
+ UNAUTHORIZED: (args) => new UnauthorizedException(
133
+ args ?? {
134
+ code: "UNAUTHORIZED",
135
+ message: "Unauthorized"
136
+ }
137
+ ),
138
+ FORBIDDEN: (args) => new ForbiddenException(
139
+ args ?? {
140
+ code: "FORBIDDEN",
141
+ message: "Insufficient permissions"
142
+ }
143
+ )
144
+ },
145
+ graphql: {
146
+ UNAUTHORIZED: (args) => {
147
+ const GraphQLError = getGraphQLError();
148
+ if (typeof args === "string") {
149
+ return new GraphQLError(args);
150
+ } else if (typeof args === "object") {
151
+ return new GraphQLError(
152
+ // biome-ignore lint: if `message` is not set, a default is already in place.
153
+ args?.message ?? "Unauthorized",
154
+ args
155
+ );
156
+ }
157
+ return new GraphQLError("Unauthorized");
158
+ },
159
+ FORBIDDEN: (args) => {
160
+ const GraphQLError = getGraphQLError();
161
+ if (typeof args === "string") {
162
+ return new GraphQLError(args);
163
+ } else if (typeof args === "object") {
164
+ return new GraphQLError(
165
+ // biome-ignore lint: if `message` is not set, a default is already in place.
166
+ args?.message ?? "Forbidden",
167
+ args
168
+ );
169
+ }
170
+ return new GraphQLError("Forbidden");
171
+ }
172
+ },
173
+ ws: {
174
+ UNAUTHORIZED: (args) => {
175
+ const WsExceptionClass = getWsException();
176
+ return new WsExceptionClass(args ?? "UNAUTHORIZED");
177
+ },
178
+ FORBIDDEN: (args) => {
179
+ const WsExceptionClass = getWsException();
180
+ return new WsExceptionClass(args ?? "FORBIDDEN");
181
+ }
182
+ },
183
+ rpc: {
184
+ UNAUTHORIZED: () => new Error("UNAUTHORIZED"),
185
+ FORBIDDEN: () => new Error("FORBIDDEN")
186
+ }
187
+ };
188
+ let AuthGuard = class {
189
+ constructor(reflector, options) {
190
+ this.reflector = reflector;
191
+ this.options = options;
192
+ }
193
+ /**
194
+ * Validates if the current request is authenticated
195
+ * Attaches session and user information to the request object
196
+ * Supports HTTP, GraphQL and WebSocket execution contexts
197
+ * @param context - The execution context of the current request
198
+ * @returns True if the request is authorized to proceed, throws an error otherwise
199
+ */
200
+ async canActivate(context) {
201
+ const request = getRequestFromContext(context);
202
+ const session = await this.options.auth.api.getSession({
203
+ headers: fromNodeHeaders(
204
+ request.headers || request?.handshake?.headers || []
205
+ )
206
+ });
207
+ request.session = session;
208
+ request.user = session?.user ?? null;
209
+ const isPublic = this.reflector.getAllAndOverride("PUBLIC", [
210
+ context.getHandler(),
211
+ context.getClass()
212
+ ]);
213
+ if (isPublic) return true;
214
+ const isOptional = this.reflector.getAllAndOverride("OPTIONAL", [
215
+ context.getHandler(),
216
+ context.getClass()
217
+ ]);
218
+ if (!session && isOptional) return true;
219
+ const ctxType = context.getType();
220
+ if (!session) throw AuthContextErrorMap[ctxType].UNAUTHORIZED();
221
+ const headers = fromNodeHeaders(
222
+ request.headers || request?.handshake?.headers || []
223
+ );
224
+ const requiredRoles = this.reflector.getAllAndOverride("ROLES", [
225
+ context.getHandler(),
226
+ context.getClass()
227
+ ]);
228
+ if (requiredRoles && requiredRoles.length > 0) {
229
+ const hasRole = this.checkUserRole(session, requiredRoles);
230
+ if (!hasRole) throw AuthContextErrorMap[ctxType].FORBIDDEN();
231
+ }
232
+ const requiredOrgRoles = this.reflector.getAllAndOverride(
233
+ "ORG_ROLES",
234
+ [context.getHandler(), context.getClass()]
235
+ );
236
+ if (requiredOrgRoles && requiredOrgRoles.length > 0) {
237
+ const hasOrgRole = await this.checkOrgRole(
238
+ session,
239
+ headers,
240
+ requiredOrgRoles
241
+ );
242
+ if (!hasOrgRole) throw AuthContextErrorMap[ctxType].FORBIDDEN();
243
+ }
244
+ return true;
245
+ }
246
+ /**
247
+ * Checks if a role value matches any of the required roles
248
+ * Handles both array and comma-separated string role formats
249
+ * @param role - The role value to check (string, array, or undefined)
250
+ * @param requiredRoles - Array of roles that grant access
251
+ * @returns True if the role matches any required role
252
+ */
253
+ matchesRequiredRole(role, requiredRoles) {
254
+ if (!role) return false;
255
+ if (Array.isArray(role)) {
256
+ return role.some((r) => requiredRoles.includes(r));
257
+ }
258
+ if (typeof role === "string") {
259
+ return role.split(",").some((r) => requiredRoles.includes(r.trim()));
260
+ }
261
+ return false;
262
+ }
263
+ /**
264
+ * Fetches the user's role within an organization from the member table
265
+ * Uses Better Auth's organization plugin API if available
266
+ * @param headers - The request headers containing session cookies
267
+ * @returns The member's role in the organization, or undefined if not found
268
+ */
269
+ async getMemberRoleInOrganization(headers) {
270
+ try {
271
+ const authApi = this.options.auth.api;
272
+ if (typeof authApi.getActiveMemberRole === "function") {
273
+ const result = await authApi.getActiveMemberRole({ headers });
274
+ return result?.role;
275
+ }
276
+ if (typeof authApi.getActiveMember === "function") {
277
+ const member = await authApi.getActiveMember({ headers });
278
+ return member?.role;
279
+ }
280
+ return void 0;
281
+ } catch (error) {
282
+ throw error;
283
+ }
284
+ }
285
+ /**
286
+ * Checks if the user has any of the required roles in user.role only.
287
+ * Used by @Roles() decorator for system-level role checks (admin plugin).
288
+ * @param session - The user's session
289
+ * @param requiredRoles - Array of roles that grant access
290
+ * @returns True if user.role matches any required role
291
+ */
292
+ checkUserRole(session, requiredRoles) {
293
+ return this.matchesRequiredRole(session.user.role, requiredRoles);
294
+ }
295
+ /**
296
+ * Checks if the user has any of the required roles in their organization.
297
+ * Used by @OrgRoles() decorator for organization-level role checks.
298
+ * Requires an active organization in the session.
299
+ * @param session - The user's session
300
+ * @param headers - The request headers for API calls
301
+ * @param requiredRoles - Array of roles that grant access
302
+ * @returns True if org member role matches any required role
303
+ */
304
+ async checkOrgRole(session, headers, requiredRoles) {
305
+ const activeOrgId = session.session?.activeOrganizationId;
306
+ if (!activeOrgId) {
307
+ return false;
308
+ }
309
+ try {
310
+ const memberRole = await this.getMemberRoleInOrganization(headers);
311
+ return this.matchesRequiredRole(memberRole, requiredRoles);
312
+ } catch (error) {
313
+ console.error("Organization plugin error:", error);
314
+ return false;
315
+ }
316
+ }
317
+ };
318
+ AuthGuard = __decorateClass$1([
319
+ Injectable(),
320
+ __decorateParam$1(0, Inject(Reflector)),
321
+ __decorateParam$1(1, Inject(MODULE_OPTIONS_TOKEN))
322
+ ], AuthGuard);
323
+
324
+ const rawBodyParser = (req, _res, buffer) => {
325
+ if (Buffer.isBuffer(buffer)) {
326
+ req.rawBody = buffer;
327
+ }
328
+ return true;
329
+ };
330
+ function SkipBodyParsingMiddleware(options = {}) {
331
+ const { basePath = "/api/auth", enableRawBodyParser = false } = options;
332
+ const jsonParserOptions = enableRawBodyParser ? { verify: rawBodyParser } : {};
333
+ return (req, res, next) => {
334
+ if (req.baseUrl.startsWith(basePath)) {
335
+ next();
336
+ return;
337
+ }
338
+ express.json(jsonParserOptions)(req, res, (err) => {
339
+ if (err) {
340
+ next(err);
341
+ return;
342
+ }
343
+ express.urlencoded({ extended: true })(req, res, next);
344
+ });
345
+ };
346
+ }
347
+
348
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
349
+ var __decorateClass = (decorators, target, key, kind) => {
350
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
351
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
352
+ if (decorator = decorators[i])
353
+ result = (decorator(result)) || result;
354
+ return result;
355
+ };
356
+ var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index);
357
+ const HOOKS = [
358
+ { metadataKey: BEFORE_HOOK_KEY, hookType: "before" },
359
+ { metadataKey: AFTER_HOOK_KEY, hookType: "after" }
360
+ ];
361
+ let AuthModule = class extends ConfigurableModuleClass {
362
+ constructor(applicationConfig, discoveryService, metadataScanner, adapter, options) {
363
+ super();
364
+ this.applicationConfig = applicationConfig;
365
+ this.discoveryService = discoveryService;
366
+ this.metadataScanner = metadataScanner;
367
+ this.adapter = adapter;
368
+ this.options = options;
369
+ this.basePath = normalizePath(
370
+ this.options.auth.options.basePath ?? "/api/auth"
371
+ );
372
+ const globalPrefixOptions = this.applicationConfig.getGlobalPrefixOptions();
373
+ this.applicationConfig.setGlobalPrefixOptions({
374
+ exclude: [
375
+ ...globalPrefixOptions.exclude ?? [],
376
+ ...mapToExcludeRoute([this.basePath])
377
+ ]
378
+ });
379
+ }
380
+ logger = new Logger(AuthModule.name);
381
+ basePath;
382
+ onModuleInit() {
383
+ const providers = this.discoveryService.getProviders().filter(
384
+ ({ metatype }) => metatype && Reflect.getMetadata(HOOK_KEY, metatype)
385
+ );
386
+ const hasHookProviders = providers.length > 0;
387
+ const hooksConfigured = typeof this.options.auth?.options?.hooks === "object";
388
+ if (hasHookProviders && !hooksConfigured)
389
+ throw new Error(
390
+ "Detected @Hook providers but Better Auth 'hooks' are not configured. Add 'hooks: {}' to your betterAuth(...) options."
391
+ );
392
+ if (!hooksConfigured) return;
393
+ for (const provider of providers) {
394
+ const providerPrototype = Object.getPrototypeOf(provider.instance);
395
+ const methods = this.metadataScanner.getAllMethodNames(providerPrototype);
396
+ for (const method of methods) {
397
+ const providerMethod = providerPrototype[method];
398
+ this.setupHooks(providerMethod, provider.instance);
399
+ }
400
+ }
401
+ }
402
+ configure(consumer) {
403
+ const trustedOrigins = this.options.auth.options.trustedOrigins;
404
+ const isNotFunctionBased = trustedOrigins && Array.isArray(trustedOrigins);
405
+ if (!this.options.disableTrustedOriginsCors && isNotFunctionBased) {
406
+ this.adapter.httpAdapter.enableCors({
407
+ origin: trustedOrigins,
408
+ methods: ["GET", "POST", "PUT", "DELETE"],
409
+ credentials: true
410
+ });
411
+ } else if (trustedOrigins && !this.options.disableTrustedOriginsCors && !isNotFunctionBased)
412
+ throw new Error(
413
+ "Function-based trustedOrigins not supported in NestJS. Use string array or disable CORS with disableTrustedOriginsCors: true."
414
+ );
415
+ if (!this.options.disableBodyParser) {
416
+ consumer.apply(
417
+ SkipBodyParsingMiddleware({
418
+ basePath: this.basePath,
419
+ enableRawBodyParser: this.options.enableRawBodyParser
420
+ })
421
+ ).forRoutes("*path");
422
+ }
423
+ const handler = toNodeHandler(this.options.auth);
424
+ consumer.apply((req, res) => {
425
+ if (this.options.middleware) {
426
+ return this.options.middleware(req, res, () => handler(req, res));
427
+ }
428
+ return handler(req, res);
429
+ }).forRoutes(this.basePath);
430
+ this.logger.log(`AuthModule initialized BetterAuth on '${this.basePath}'`);
431
+ }
432
+ setupHooks(providerMethod, providerClass) {
433
+ if (!this.options.auth.options.hooks) return;
434
+ for (const { metadataKey, hookType } of HOOKS) {
435
+ const hasHook = Reflect.hasMetadata(metadataKey, providerMethod);
436
+ if (!hasHook) continue;
437
+ const hookPath = Reflect.getMetadata(metadataKey, providerMethod);
438
+ const originalHook = this.options.auth.options.hooks[hookType];
439
+ this.options.auth.options.hooks[hookType] = createAuthMiddleware(
440
+ async (ctx) => {
441
+ if (originalHook) {
442
+ await originalHook(ctx);
443
+ }
444
+ if (hookPath && hookPath !== ctx.path) return;
445
+ await providerMethod.apply(providerClass, [ctx]);
446
+ }
447
+ );
448
+ }
449
+ }
450
+ static forRootAsync(options) {
451
+ const forRootAsyncResult = super.forRootAsync(options);
452
+ const { module } = forRootAsyncResult;
453
+ return {
454
+ ...forRootAsyncResult,
455
+ module: options.disableControllers ? AuthModuleWithoutControllers : module,
456
+ controllers: options.disableControllers ? [] : forRootAsyncResult.controllers,
457
+ providers: [
458
+ ...forRootAsyncResult.providers ?? [],
459
+ ...!options.disableGlobalAuthGuard ? [
460
+ {
461
+ provide: APP_GUARD,
462
+ useClass: AuthGuard
463
+ }
464
+ ] : []
465
+ ]
466
+ };
467
+ }
468
+ static forRoot(arg1, arg2) {
469
+ const normalizedOptions = typeof arg1 === "object" && arg1 !== null && "auth" in arg1 ? arg1 : { ...arg2 ?? {}, auth: arg1 };
470
+ const forRootResult = super.forRoot(normalizedOptions);
471
+ const { module } = forRootResult;
472
+ return {
473
+ ...forRootResult,
474
+ module: normalizedOptions.disableControllers ? AuthModuleWithoutControllers : module,
475
+ controllers: normalizedOptions.disableControllers ? [] : forRootResult.controllers,
476
+ providers: [
477
+ ...forRootResult.providers ?? [],
478
+ ...!normalizedOptions.disableGlobalAuthGuard ? [
479
+ {
480
+ provide: APP_GUARD,
481
+ useClass: AuthGuard
482
+ }
483
+ ] : []
484
+ ]
485
+ };
486
+ }
487
+ };
488
+ AuthModule = __decorateClass([
489
+ Module({
490
+ imports: [DiscoveryModule],
491
+ providers: [AuthService],
492
+ exports: [AuthService]
493
+ }),
494
+ __decorateParam(0, Inject(ApplicationConfig)),
495
+ __decorateParam(1, Inject(DiscoveryService)),
496
+ __decorateParam(2, Inject(MetadataScanner)),
497
+ __decorateParam(3, Inject(HttpAdapterHost)),
498
+ __decorateParam(4, Inject(MODULE_OPTIONS_TOKEN))
499
+ ], AuthModule);
500
+ class AuthModuleWithoutControllers extends AuthModule {
501
+ configure() {
502
+ return;
503
+ }
504
+ }
505
+
506
+ export { AFTER_HOOK_KEY, AUTH_MODULE_OPTIONS_KEY, AfterHook, AllowAnonymous, AuthGuard, AuthModule, AuthService, BEFORE_HOOK_KEY, BeforeHook, HOOK_KEY, Hook, Optional, OptionalAuth, OrgRoles, Public, Roles, Session };
package/package.json ADDED
@@ -0,0 +1,80 @@
1
+ {
2
+ "name": "@pedr0ni/nestjs-better-auth",
3
+ "version": "1.0.0",
4
+ "description": "Better Auth for NestJS",
5
+ "author": "Matheus Pedroni",
6
+ "license": "MIT",
7
+ "keywords": [
8
+ "nestjs",
9
+ "better-auth",
10
+ "auth",
11
+ "nestjs-better-auth"
12
+ ],
13
+ "type": "module",
14
+ "types": "dist/index.d.ts",
15
+ "main": "./dist/index.cjs",
16
+ "exports": {
17
+ ".": {
18
+ "import": "./dist/index.mjs",
19
+ "require": "./dist/index.cjs"
20
+ }
21
+ },
22
+ "files": [
23
+ "dist"
24
+ ],
25
+ "devDependencies": {
26
+ "@apollo/server": "^5.0.0",
27
+ "@as-integrations/express5": "^1.1.2",
28
+ "@biomejs/biome": "2.2.4",
29
+ "@faker-js/faker": "^10.0.0",
30
+ "@nestjs/apollo": "^13.1.0",
31
+ "@nestjs/graphql": "^13.1.0",
32
+ "@nestjs/platform-express": "^11.1.6",
33
+ "@nestjs/platform-socket.io": "^11.1.6",
34
+ "@nestjs/testing": "^11.1.6",
35
+ "@nestjs/websockets": "^11.1.6",
36
+ "@swc/cli": "^0.7.8",
37
+ "@swc/core": "^1.13.20",
38
+ "@tsconfig/node22": "^22.0.2",
39
+ "@types/bun": "latest",
40
+ "@types/express": "^5.0.3",
41
+ "@types/supertest": "^6.0.3",
42
+ "@vitest/coverage-v8": "^3.2.4",
43
+ "graphql": "^16.11.0",
44
+ "reflect-metadata": "^0.2.2",
45
+ "socket.io": "^4.8.1",
46
+ "socket.io-client": "^4.8.1",
47
+ "supertest": "^7.1.4",
48
+ "unbuild": "^3.6.1",
49
+ "unplugin-swc": "^1.5.7",
50
+ "vitest": "^3.2.4"
51
+ },
52
+ "peerDependencies": {
53
+ "@nestjs/common": "^11.1.6",
54
+ "@nestjs/core": "^11.1.6",
55
+ "@nestjs/graphql": "^13.1.0",
56
+ "@nestjs/websockets": "^11.1.6",
57
+ "better-auth": ">=1.3.8 <2.0.0",
58
+ "express": "^5.1.0",
59
+ "graphql": "^16.11.0",
60
+ "typescript": "^5.9.2"
61
+ },
62
+ "peerDependenciesMeta": {
63
+ "@nestjs/graphql": {
64
+ "optional": true
65
+ },
66
+ "@nestjs/websockets": {
67
+ "optional": true
68
+ },
69
+ "graphql": {
70
+ "optional": true
71
+ }
72
+ },
73
+ "scripts": {
74
+ "build": "unbuild",
75
+ "lint": "biome lint",
76
+ "format": "biome format",
77
+ "check": "biome check",
78
+ "test": "vitest"
79
+ }
80
+ }