@lastshotlabs/bunshot 0.0.8 → 0.0.10

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.
Files changed (44) hide show
  1. package/README.md +97 -7
  2. package/dist/adapters/memoryAuth.d.ts +13 -3
  3. package/dist/adapters/memoryAuth.js +116 -8
  4. package/dist/adapters/sqliteAuth.d.ts +13 -3
  5. package/dist/adapters/sqliteAuth.js +93 -15
  6. package/dist/app.d.ts +39 -2
  7. package/dist/app.js +23 -5
  8. package/dist/cli.js +0 -0
  9. package/dist/entrypoints/mongo.d.ts +3 -0
  10. package/dist/entrypoints/mongo.js +3 -0
  11. package/dist/entrypoints/queue.d.ts +2 -0
  12. package/dist/entrypoints/queue.js +1 -0
  13. package/dist/entrypoints/redis.d.ts +1 -0
  14. package/dist/entrypoints/redis.js +1 -0
  15. package/dist/index.d.ts +6 -8
  16. package/dist/index.js +5 -6
  17. package/dist/lib/appConfig.d.ts +17 -0
  18. package/dist/lib/appConfig.js +20 -0
  19. package/dist/lib/context.d.ts +1 -0
  20. package/dist/lib/emailVerification.js +11 -10
  21. package/dist/lib/jwt.d.ts +1 -1
  22. package/dist/lib/jwt.js +1 -1
  23. package/dist/lib/mongo.d.ts +9 -4
  24. package/dist/lib/mongo.js +61 -10
  25. package/dist/lib/oauth.js +11 -10
  26. package/dist/lib/queue.d.ts +3 -4
  27. package/dist/lib/queue.js +18 -3
  28. package/dist/lib/redis.d.ts +3 -8
  29. package/dist/lib/redis.js +19 -8
  30. package/dist/lib/resetPassword.d.ts +12 -0
  31. package/dist/lib/resetPassword.js +95 -0
  32. package/dist/lib/session.d.ts +20 -3
  33. package/dist/lib/session.js +288 -35
  34. package/dist/middleware/cacheResponse.js +10 -9
  35. package/dist/middleware/identify.js +21 -7
  36. package/dist/models/AuthUser.d.ts +14 -106
  37. package/dist/models/AuthUser.js +31 -14
  38. package/dist/routes/auth.d.ts +3 -2
  39. package/dist/routes/auth.js +139 -4
  40. package/dist/routes/oauth.js +13 -4
  41. package/dist/services/auth.d.ts +3 -2
  42. package/dist/services/auth.js +20 -11
  43. package/dist/ws/index.js +6 -3
  44. package/package.json +39 -9
package/dist/app.js CHANGED
@@ -8,8 +8,9 @@ import { rateLimit } from "./middleware/rateLimit";
8
8
  import { bearerAuth } from "./middleware/bearerAuth";
9
9
  import { identify } from "./middleware/identify";
10
10
  import { HEADER_USER_TOKEN } from "./lib/constants";
11
- import { setAppName, setAppRoles, setDefaultRole, setPrimaryField, setEmailVerificationConfig } from "./lib/appConfig";
11
+ import { setAppName, setAppRoles, setDefaultRole, setPrimaryField, setEmailVerificationConfig, setPasswordResetConfig, setMaxSessions, setPersistSessionMetadata, setIncludeInactiveSessions, setTrackLastActive } from "./lib/appConfig";
12
12
  import { setEmailVerificationStore } from "./lib/emailVerification";
13
+ import { setPasswordResetStore } from "./lib/resetPassword";
13
14
  import { setAuthRateLimitStore } from "./lib/authRateLimit";
14
15
  import { setAuthAdapter } from "./lib/authAdapter";
15
16
  import { mongoAuthAdapter } from "./adapters/mongoAuth";
@@ -39,7 +40,9 @@ export const createApp = async (config) => {
39
40
  const defaultRole = authConfig.defaultRole;
40
41
  const primaryField = authConfig.primaryField ?? "email";
41
42
  const emailVerification = authConfig.emailVerification;
43
+ const passwordReset = authConfig.passwordReset;
42
44
  const authRateLimit = authConfig.rateLimit;
45
+ const sessionPolicy = authConfig.sessionPolicy ?? {};
43
46
  const { sqlite, mongo = "single", redis: enableRedis = true } = db;
44
47
  // Smart fallback: pick the best available store rather than blindly defaulting to "redis"
45
48
  const defaultStore = enableRedis
@@ -81,16 +84,31 @@ export const createApp = async (config) => {
81
84
  else {
82
85
  authAdapter = mongoAuthAdapter;
83
86
  }
87
+ if (defaultRole && !authAdapter.setRoles) {
88
+ throw new Error(`createApp: "defaultRole" is set to "${defaultRole}" but the auth adapter does not implement setRoles. Add setRoles to your adapter or remove defaultRole.`);
89
+ }
90
+ if (emailVerification && primaryField !== "email") {
91
+ throw new Error(`createApp: "emailVerification" is only supported when primaryField is "email". Either set primaryField to "email" or remove emailVerification.`);
92
+ }
93
+ if (passwordReset && primaryField !== "email") {
94
+ throw new Error(`createApp: "passwordReset" is only supported when primaryField is "email". Either set primaryField to "email" or remove passwordReset.`);
95
+ }
96
+ if (passwordReset && !authAdapter.setPassword) {
97
+ throw new Error(`createApp: "passwordReset" is configured but the auth adapter does not implement setPassword. Add setPassword to your adapter or remove passwordReset.`);
98
+ }
84
99
  setAuthAdapter(authAdapter);
85
100
  setAppRoles(roles);
86
101
  setDefaultRole(defaultRole ?? null);
87
102
  setPrimaryField(primaryField);
88
103
  setEmailVerificationConfig(emailVerification ?? null);
89
104
  setEmailVerificationStore(sessions);
105
+ setPasswordResetConfig(passwordReset ?? null);
106
+ setPasswordResetStore(sessions);
90
107
  setAuthRateLimitStore(authRateLimit?.store ?? (enableRedis ? "redis" : "memory"));
91
- if (defaultRole && !authAdapter.setRoles) {
92
- throw new Error(`createApp: "defaultRole" is set to "${defaultRole}" but the auth adapter does not implement setRoles. Add setRoles to your adapter or remove defaultRole.`);
93
- }
108
+ setMaxSessions(sessionPolicy.maxSessions ?? 6);
109
+ setPersistSessionMetadata(sessionPolicy.persistSessionMetadata ?? true);
110
+ setIncludeInactiveSessions(sessionPolicy.includeInactiveSessions ?? false);
111
+ setTrackLastActive(sessionPolicy.trackLastActive ?? false);
94
112
  if (oauthProviders)
95
113
  initOAuthProviders(oauthProviders);
96
114
  const configuredOAuth = getConfiguredOAuthProviders();
@@ -139,7 +157,7 @@ export const createApp = async (config) => {
139
157
  }
140
158
  if (enableAuthRoutes) {
141
159
  const { createAuthRouter } = await import(`${coreRoutesDir}/auth`);
142
- app.route("/", createAuthRouter({ primaryField, emailVerification, rateLimit: authRateLimit }));
160
+ app.route("/", createAuthRouter({ primaryField, emailVerification, passwordReset, rateLimit: authRateLimit }));
143
161
  }
144
162
  if (configuredOAuth.length > 0) {
145
163
  app.route("/", createOAuthRouter(configuredOAuth, postOAuthRedirect));
package/dist/cli.js CHANGED
File without changes
@@ -0,0 +1,3 @@
1
+ export { connectMongo, connectAuthMongo, connectAppMongo, disconnectMongo, authConnection, appConnection, mongoose } from "../lib/mongo";
2
+ export { mongoAuthAdapter } from "../adapters/mongoAuth";
3
+ export { AuthUser } from "../models/AuthUser";
@@ -0,0 +1,3 @@
1
+ export { connectMongo, connectAuthMongo, connectAppMongo, disconnectMongo, authConnection, appConnection, mongoose } from "../lib/mongo";
2
+ export { mongoAuthAdapter } from "../adapters/mongoAuth";
3
+ export { AuthUser } from "../models/AuthUser";
@@ -0,0 +1,2 @@
1
+ export { createQueue, createWorker } from "../lib/queue";
2
+ export type { Job } from "../lib/queue";
@@ -0,0 +1 @@
1
+ export { createQueue, createWorker } from "../lib/queue";
@@ -0,0 +1 @@
1
+ export { connectRedis, disconnectRedis, getRedis } from "../lib/redis";
@@ -0,0 +1 @@
1
+ export { connectRedis, disconnectRedis, getRedis } from "../lib/redis";
package/dist/index.d.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  export { createApp } from "./app";
2
2
  export { createServer } from "./server";
3
- export type { CreateAppConfig, DbConfig, AppMeta, AuthConfig, AuthRateLimitConfig, OAuthConfig, SecurityConfig, BotProtectionConfig, PrimaryField, EmailVerificationConfig } from "./app";
3
+ export type { CreateAppConfig, DbConfig, AppMeta, AuthConfig, AuthRateLimitConfig, OAuthConfig, SecurityConfig, BotProtectionConfig, PrimaryField, EmailVerificationConfig, PasswordResetConfig } from "./app";
4
4
  export type { CreateServerConfig, WsConfig } from "./server";
5
+ export { appConnection, authConnection, mongoose, connectMongo, connectAuthMongo, connectAppMongo, disconnectMongo } from "./lib/mongo";
6
+ export { connectRedis, disconnectRedis, getRedis } from "./lib/redis";
5
7
  export { getAppRoles } from "./lib/appConfig";
6
8
  export { HttpError } from "./lib/HttpError";
7
9
  export { COOKIE_TOKEN, HEADER_USER_TOKEN } from "./lib/constants";
@@ -9,11 +11,9 @@ export { createRouter } from "./lib/context";
9
11
  export type { AppEnv, AppVariables } from "./lib/context";
10
12
  export { signToken, verifyToken } from "./lib/jwt";
11
13
  export { log } from "./lib/logger";
12
- export { connectMongo, connectAuthMongo, connectAppMongo, disconnectMongo, authConnection, appConnection, mongoose } from "./lib/mongo";
13
- export { connectRedis, disconnectRedis, getRedis } from "./lib/redis";
14
- export { createQueue, createWorker } from "./lib/queue";
15
- export type { Job } from "./lib/queue";
16
- export { createSession, getSession, deleteSession, setSessionStore } from "./lib/session";
14
+ export { createResetToken, consumeResetToken, setPasswordResetStore } from "./lib/resetPassword";
15
+ export { createSession, getSession, deleteSession, getUserSessions, getActiveSessionCount, evictOldestSession, updateSessionLastActive, setSessionStore } from "./lib/session";
16
+ export type { SessionMetadata, SessionInfo } from "./lib/session";
17
17
  export { createVerificationToken, getVerificationToken, deleteVerificationToken } from "./lib/emailVerification";
18
18
  export { bustAuthLimit, trackAttempt, isLimited } from "./lib/authRateLimit";
19
19
  export type { LimitOpts } from "./lib/authRateLimit";
@@ -29,8 +29,6 @@ export { requireRole } from "./middleware/requireRole";
29
29
  export { requireVerifiedEmail } from "./middleware/requireVerifiedEmail";
30
30
  export { cacheResponse, bustCache, bustCachePattern, setCacheStore } from "./middleware/cacheResponse";
31
31
  export { buildFingerprint } from "./lib/fingerprint";
32
- export { AuthUser } from "./models/AuthUser";
33
- export { mongoAuthAdapter } from "./adapters/mongoAuth";
34
32
  export { sqliteAuthAdapter, setSqliteDb, startSqliteCleanup } from "./adapters/sqliteAuth";
35
33
  export { memoryAuthAdapter, clearMemoryStore } from "./adapters/memoryAuth";
36
34
  export { setUserRoles, addUserRole, removeUserRole } from "./lib/roles";
package/dist/index.js CHANGED
@@ -1,6 +1,9 @@
1
1
  // App factory
2
2
  export { createApp } from "./app";
3
3
  export { createServer } from "./server";
4
+ // Database
5
+ export { appConnection, authConnection, mongoose, connectMongo, connectAuthMongo, connectAppMongo, disconnectMongo } from "./lib/mongo";
6
+ export { connectRedis, disconnectRedis, getRedis } from "./lib/redis";
4
7
  // Lib utilities
5
8
  export { getAppRoles } from "./lib/appConfig";
6
9
  export { HttpError } from "./lib/HttpError";
@@ -8,10 +11,8 @@ export { COOKIE_TOKEN, HEADER_USER_TOKEN } from "./lib/constants";
8
11
  export { createRouter } from "./lib/context";
9
12
  export { signToken, verifyToken } from "./lib/jwt";
10
13
  export { log } from "./lib/logger";
11
- export { connectMongo, connectAuthMongo, connectAppMongo, disconnectMongo, authConnection, appConnection, mongoose } from "./lib/mongo";
12
- export { connectRedis, disconnectRedis, getRedis } from "./lib/redis";
13
- export { createQueue, createWorker } from "./lib/queue";
14
- export { createSession, getSession, deleteSession, setSessionStore } from "./lib/session";
14
+ export { createResetToken, consumeResetToken, setPasswordResetStore } from "./lib/resetPassword";
15
+ export { createSession, getSession, deleteSession, getUserSessions, getActiveSessionCount, evictOldestSession, updateSessionLastActive, setSessionStore } from "./lib/session";
15
16
  export { createVerificationToken, getVerificationToken, deleteVerificationToken } from "./lib/emailVerification";
16
17
  export { bustAuthLimit, trackAttempt, isLimited } from "./lib/authRateLimit";
17
18
  export { validate } from "./lib/validate";
@@ -27,8 +28,6 @@ export { cacheResponse, bustCache, bustCachePattern, setCacheStore } from "./mid
27
28
  // Lib utilities (bot protection)
28
29
  export { buildFingerprint } from "./lib/fingerprint";
29
30
  // Models
30
- export { AuthUser } from "./models/AuthUser";
31
- export { mongoAuthAdapter } from "./adapters/mongoAuth";
32
31
  export { sqliteAuthAdapter, setSqliteDb, startSqliteCleanup } from "./adapters/sqliteAuth";
33
32
  export { memoryAuthAdapter, clearMemoryStore } from "./adapters/memoryAuth";
34
33
  export { setUserRoles, addUserRole, removeUserRole } from "./lib/roles";
@@ -7,6 +7,12 @@ export interface EmailVerificationConfig {
7
7
  /** Called after registration with the identifier and verification token. Use to send the email. */
8
8
  onSend: (email: string, token: string) => Promise<void>;
9
9
  }
10
+ export interface PasswordResetConfig {
11
+ /** Token time-to-live in seconds. Defaults to 3 600 (1 hour). */
12
+ tokenExpiry?: number;
13
+ /** Called with the user's email and the reset token. Use to send the reset email. */
14
+ onSend: (email: string, token: string) => Promise<void>;
15
+ }
10
16
  export declare const setAppName: (name: string) => void;
11
17
  export declare const getAppName: () => string;
12
18
  export declare const setAppRoles: (roles: string[]) => void;
@@ -18,3 +24,14 @@ export declare const getPrimaryField: () => PrimaryField;
18
24
  export declare const setEmailVerificationConfig: (config: EmailVerificationConfig | null) => void;
19
25
  export declare const getEmailVerificationConfig: () => EmailVerificationConfig | null;
20
26
  export declare const getTokenExpiry: () => number;
27
+ export declare const setPasswordResetConfig: (config: PasswordResetConfig | null) => void;
28
+ export declare const getPasswordResetConfig: () => PasswordResetConfig | null;
29
+ export declare const getResetTokenExpiry: () => number;
30
+ export declare const setMaxSessions: (n: number) => void;
31
+ export declare const getMaxSessions: () => number;
32
+ export declare const setPersistSessionMetadata: (v: boolean) => void;
33
+ export declare const getPersistSessionMetadata: () => boolean;
34
+ export declare const setIncludeInactiveSessions: (v: boolean) => void;
35
+ export declare const getIncludeInactiveSessions: () => boolean;
36
+ export declare const setTrackLastActive: (v: boolean) => void;
37
+ export declare const getTrackLastActive: () => boolean;
@@ -3,6 +3,7 @@ let appRoles = [];
3
3
  let defaultRole = null;
4
4
  let _primaryField = "email";
5
5
  let _emailVerificationConfig = null;
6
+ let _passwordResetConfig = null;
6
7
  export const setAppName = (name) => { appName = name; };
7
8
  export const getAppName = () => appName;
8
9
  export const setAppRoles = (roles) => { appRoles = roles; };
@@ -15,3 +16,22 @@ export const setEmailVerificationConfig = (config) => { _emailVerificationConfig
15
16
  export const getEmailVerificationConfig = () => _emailVerificationConfig;
16
17
  const DEFAULT_TOKEN_EXPIRY = 60 * 60 * 24; // 24 hours
17
18
  export const getTokenExpiry = () => _emailVerificationConfig?.tokenExpiry ?? DEFAULT_TOKEN_EXPIRY;
19
+ export const setPasswordResetConfig = (config) => { _passwordResetConfig = config; };
20
+ export const getPasswordResetConfig = () => _passwordResetConfig;
21
+ const DEFAULT_RESET_TOKEN_EXPIRY = 60 * 60; // 1 hour
22
+ export const getResetTokenExpiry = () => _passwordResetConfig?.tokenExpiry ?? DEFAULT_RESET_TOKEN_EXPIRY;
23
+ // ---------------------------------------------------------------------------
24
+ // Session policy
25
+ // ---------------------------------------------------------------------------
26
+ let _maxSessions = 6;
27
+ let _persistSessionMetadata = true;
28
+ let _includeInactiveSessions = false;
29
+ let _trackLastActive = false;
30
+ export const setMaxSessions = (n) => { _maxSessions = Number.isFinite(n) && n >= 1 ? Math.floor(n) : 1; };
31
+ export const getMaxSessions = () => _maxSessions;
32
+ export const setPersistSessionMetadata = (v) => { _persistSessionMetadata = v; };
33
+ export const getPersistSessionMetadata = () => _persistSessionMetadata;
34
+ export const setIncludeInactiveSessions = (v) => { _includeInactiveSessions = v; };
35
+ export const getIncludeInactiveSessions = () => _includeInactiveSessions;
36
+ export const setTrackLastActive = (v) => { _trackLastActive = v; };
37
+ export const getTrackLastActive = () => _trackLastActive;
@@ -2,6 +2,7 @@ import { OpenAPIHono } from "@hono/zod-openapi";
2
2
  export type AppVariables = {
3
3
  authUserId: string | null;
4
4
  roles: string[] | null;
5
+ sessionId: string | null;
5
6
  };
6
7
  export type AppEnv = {
7
8
  Variables: AppVariables;
@@ -1,18 +1,19 @@
1
1
  import { getRedis } from "./redis";
2
- import { appConnection } from "./mongo";
2
+ import { appConnection, mongoose } from "./mongo";
3
3
  import { getAppName, getTokenExpiry } from "./appConfig";
4
- import { Schema } from "mongoose";
5
4
  import { sqliteCreateVerificationToken, sqliteGetVerificationToken, sqliteDeleteVerificationToken, } from "../adapters/sqliteAuth";
6
5
  import { memoryCreateVerificationToken, memoryGetVerificationToken, memoryDeleteVerificationToken, } from "../adapters/memoryAuth";
7
- const verificationSchema = new Schema({
8
- token: { type: String, required: true, unique: true },
9
- userId: { type: String, required: true },
10
- email: { type: String, required: true },
11
- expiresAt: { type: Date, required: true, index: { expireAfterSeconds: 0 } },
12
- }, { collection: "email_verifications" });
13
6
  function getVerificationModel() {
14
- return appConnection.models["EmailVerification"] ??
15
- appConnection.model("EmailVerification", verificationSchema);
7
+ if (appConnection.models["EmailVerification"])
8
+ return appConnection.models["EmailVerification"];
9
+ const { Schema } = mongoose;
10
+ const verificationSchema = new Schema({
11
+ token: { type: String, required: true, unique: true },
12
+ userId: { type: String, required: true },
13
+ email: { type: String, required: true },
14
+ expiresAt: { type: Date, required: true, index: { expireAfterSeconds: 0 } },
15
+ }, { collection: "email_verifications" });
16
+ return appConnection.model("EmailVerification", verificationSchema);
16
17
  }
17
18
  let _store = "redis";
18
19
  export const setEmailVerificationStore = (store) => { _store = store; };
package/dist/lib/jwt.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const signToken: (userId: string) => Promise<string>;
1
+ export declare const signToken: (userId: string, sessionId: string) => Promise<string>;
2
2
  export declare const verifyToken: (token: string) => Promise<import("jose").JWTPayload>;
package/dist/lib/jwt.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { SignJWT, jwtVerify } from "jose";
2
2
  const isProd = process.env.NODE_ENV === "production";
3
3
  const secret = new TextEncoder().encode(isProd ? process.env.JWT_SECRET_PROD : process.env.JWT_SECRET_DEV);
4
- export const signToken = async (userId) => new SignJWT({ sub: userId })
4
+ export const signToken = async (userId, sessionId) => new SignJWT({ sub: userId, sid: sessionId })
5
5
  .setProtectedHeader({ alg: "HS256" })
6
6
  .setExpirationTime("7d")
7
7
  .sign(secret);
@@ -1,15 +1,20 @@
1
- import mongoose from "mongoose";
1
+ import type { Connection, Mongoose } from "mongoose";
2
+ type MongooseModule = Mongoose;
2
3
  /**
3
4
  * Named connection used exclusively for auth data (AuthUser model).
4
5
  * Connected via connectAuthMongo() or connectMongo() (backward compat).
5
6
  */
6
- export declare const authConnection: mongoose.Connection;
7
+ export declare const authConnection: Connection;
7
8
  /**
8
9
  * Named connection for app/tenant data.
9
10
  * Connected via connectAppMongo() or connectMongo() (backward compat).
10
11
  * Use this when registering your own models: appConnection.model("Product", schema).
11
12
  */
12
- export declare const appConnection: mongoose.Connection;
13
+ export declare const appConnection: Connection;
14
+ /**
15
+ * The mongoose instance. Available after connectMongo() / connectAuthMongo() is called.
16
+ */
17
+ export declare const mongoose: MongooseModule;
13
18
  /**
14
19
  * Connect the auth connection to its dedicated MongoDB server.
15
20
  * Uses MONGO_AUTH_USER_*, MONGO_AUTH_PW_*, MONGO_AUTH_HOST_*, MONGO_AUTH_DB_* env vars.
@@ -31,4 +36,4 @@ export declare const connectMongo: () => Promise<void>;
31
36
  * Useful for one-off scripts that need a clean exit.
32
37
  */
33
38
  export declare const disconnectMongo: () => Promise<void>;
34
- export { mongoose };
39
+ export {};
package/dist/lib/mongo.js CHANGED
@@ -1,32 +1,74 @@
1
- import mongoose from "mongoose";
2
1
  import { log } from "./logger";
3
2
  const isProd = process.env.NODE_ENV === "production";
3
+ function requireMongoose() {
4
+ try {
5
+ // Bun supports require() in ESM; this defers the import to call time
6
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
7
+ const mod = require("mongoose");
8
+ return mod.default ?? mod;
9
+ }
10
+ catch {
11
+ throw new Error("mongoose is not installed. Run: bun add mongoose");
12
+ }
13
+ }
4
14
  function buildUri(user, password, host, db) {
5
15
  const [hostPart, queryPart] = host.split("?");
6
16
  return `mongodb+srv://${user}:${password}@${hostPart.replace(/\/$/, "")}/${db}${queryPart ? `?${queryPart}` : ""}`;
7
17
  }
18
+ // Internal mutable references — set inside connect functions
19
+ let _authConn = null;
20
+ let _appConn = null;
21
+ let _mongoose = null;
22
+ function makeConnectionProxy(label, getConn, setConn) {
23
+ return new Proxy({}, {
24
+ get(_, prop) {
25
+ let conn = getConn();
26
+ if (!conn) {
27
+ // Lazily create a disconnected connection so appConnection.model() works at module
28
+ // load time. Mongoose buffers queries until openUri() is called by connectMongo().
29
+ conn = requireMongoose().createConnection();
30
+ setConn(conn);
31
+ }
32
+ const val = conn[prop];
33
+ return typeof val === "function" ? val.bind(conn) : val;
34
+ },
35
+ });
36
+ }
8
37
  /**
9
38
  * Named connection used exclusively for auth data (AuthUser model).
10
39
  * Connected via connectAuthMongo() or connectMongo() (backward compat).
11
40
  */
12
- export const authConnection = mongoose.createConnection();
41
+ export const authConnection = makeConnectionProxy("auth", () => _authConn, (c) => { _authConn = c; });
13
42
  /**
14
43
  * Named connection for app/tenant data.
15
44
  * Connected via connectAppMongo() or connectMongo() (backward compat).
16
45
  * Use this when registering your own models: appConnection.model("Product", schema).
17
46
  */
18
- export const appConnection = mongoose.createConnection();
47
+ export const appConnection = makeConnectionProxy("app", () => _appConn, (c) => { _appConn = c; });
48
+ /**
49
+ * The mongoose instance. Available after connectMongo() / connectAuthMongo() is called.
50
+ */
51
+ export const mongoose = new Proxy({}, {
52
+ get(_, prop) {
53
+ const mg = _mongoose ?? requireMongoose();
54
+ return mg[prop];
55
+ },
56
+ });
19
57
  /**
20
58
  * Connect the auth connection to its dedicated MongoDB server.
21
59
  * Uses MONGO_AUTH_USER_*, MONGO_AUTH_PW_*, MONGO_AUTH_HOST_*, MONGO_AUTH_DB_* env vars.
22
60
  */
23
61
  export const connectAuthMongo = async () => {
62
+ const mg = requireMongoose();
63
+ _mongoose = mg;
64
+ if (!_authConn)
65
+ _authConn = mg.createConnection();
24
66
  const user = isProd ? process.env.MONGO_AUTH_USER_PROD : process.env.MONGO_AUTH_USER_DEV;
25
67
  const password = isProd ? process.env.MONGO_AUTH_PW_PROD : process.env.MONGO_AUTH_PW_DEV;
26
68
  const host = isProd ? process.env.MONGO_AUTH_HOST_PROD : process.env.MONGO_AUTH_HOST_DEV;
27
69
  const db = isProd ? process.env.MONGO_AUTH_DB_PROD : process.env.MONGO_AUTH_DB_DEV;
28
70
  const uri = buildUri(user, password, host, db);
29
- await authConnection.openUri(uri);
71
+ await _authConn.openUri(uri);
30
72
  log(`[mongo] auth connected to ${host} as ${user}`);
31
73
  };
32
74
  /**
@@ -34,12 +76,16 @@ export const connectAuthMongo = async () => {
34
76
  * Uses MONGO_USER_*, MONGO_PW_*, MONGO_HOST_*, MONGO_DB_* env vars.
35
77
  */
36
78
  export const connectAppMongo = async () => {
79
+ const mg = requireMongoose();
80
+ _mongoose = mg;
81
+ if (!_appConn)
82
+ _appConn = mg.createConnection();
37
83
  const user = isProd ? process.env.MONGO_USER_PROD : process.env.MONGO_USER_DEV;
38
84
  const password = isProd ? process.env.MONGO_PW_PROD : process.env.MONGO_PW_DEV;
39
85
  const host = isProd ? process.env.MONGO_HOST_PROD : process.env.MONGO_HOST_DEV;
40
86
  const db = isProd ? process.env.MONGO_DB_PROD : process.env.MONGO_DB_DEV;
41
87
  const uri = buildUri(user, password, host, db);
42
- await appConnection.openUri(uri);
88
+ await _appConn.openUri(uri);
43
89
  log(`[mongo] app connected to ${host} as ${user}`);
44
90
  };
45
91
  /**
@@ -48,14 +94,20 @@ export const connectAppMongo = async () => {
48
94
  * Uses MONGO_USER_*, MONGO_PW_*, MONGO_HOST_*, MONGO_DB_* env vars.
49
95
  */
50
96
  export const connectMongo = async () => {
97
+ const mg = requireMongoose();
98
+ _mongoose = mg;
99
+ if (!_authConn)
100
+ _authConn = mg.createConnection();
101
+ if (!_appConn)
102
+ _appConn = mg.createConnection();
51
103
  const user = isProd ? process.env.MONGO_USER_PROD : process.env.MONGO_USER_DEV;
52
104
  const password = isProd ? process.env.MONGO_PW_PROD : process.env.MONGO_PW_DEV;
53
105
  const host = isProd ? process.env.MONGO_HOST_PROD : process.env.MONGO_HOST_DEV;
54
106
  const db = isProd ? process.env.MONGO_DB_PROD : process.env.MONGO_DB_DEV;
55
107
  const uri = buildUri(user, password, host, db);
56
108
  await Promise.all([
57
- authConnection.openUri(uri),
58
- appConnection.openUri(uri),
109
+ _authConn.openUri(uri),
110
+ _appConn.openUri(uri),
59
111
  ]);
60
112
  log(`[mongo] connected to ${host} as ${user}`);
61
113
  };
@@ -65,9 +117,8 @@ export const connectMongo = async () => {
65
117
  */
66
118
  export const disconnectMongo = async () => {
67
119
  await Promise.all([
68
- authConnection.readyState !== 0 ? authConnection.close() : Promise.resolve(),
69
- appConnection.readyState !== 0 ? appConnection.close() : Promise.resolve(),
120
+ _authConn && _authConn.readyState !== 0 ? _authConn.close() : Promise.resolve(),
121
+ _appConn && _appConn.readyState !== 0 ? _appConn.close() : Promise.resolve(),
70
122
  ]);
71
123
  log("[mongo] disconnected");
72
124
  };
73
- export { mongoose };
package/dist/lib/oauth.js CHANGED
@@ -1,8 +1,7 @@
1
1
  import { Google, Apple, generateState, generateCodeVerifier } from "arctic";
2
2
  import { getRedis } from "./redis";
3
- import { appConnection } from "./mongo";
3
+ import { appConnection, mongoose } from "./mongo";
4
4
  import { getAppName } from "./appConfig";
5
- import { Schema } from "mongoose";
6
5
  import { sqliteStoreOAuthState, sqliteConsumeOAuthState } from "../adapters/sqliteAuth";
7
6
  import { memoryStoreOAuthState, memoryConsumeOAuthState } from "../adapters/memoryAuth";
8
7
  let _providers = {};
@@ -29,15 +28,17 @@ export const getApple = () => {
29
28
  export const getConfiguredOAuthProviders = () => Object.entries(_providers)
30
29
  .filter(([, v]) => v != null)
31
30
  .map(([k]) => k);
32
- const oauthStateSchema = new Schema({
33
- state: { type: String, required: true, unique: true },
34
- codeVerifier: { type: String },
35
- linkUserId: { type: String },
36
- expiresAt: { type: Date, required: true, index: { expireAfterSeconds: 0 } },
37
- }, { collection: "oauth_states" });
38
31
  function getOAuthStateModel() {
39
- return appConnection.models["OAuthState"] ??
40
- appConnection.model("OAuthState", oauthStateSchema);
32
+ if (appConnection.models["OAuthState"])
33
+ return appConnection.models["OAuthState"];
34
+ const { Schema } = mongoose;
35
+ const oauthStateSchema = new Schema({
36
+ state: { type: String, required: true, unique: true },
37
+ codeVerifier: { type: String },
38
+ linkUserId: { type: String },
39
+ expiresAt: { type: Date, required: true, index: { expireAfterSeconds: 0 } },
40
+ }, { collection: "oauth_states" });
41
+ return appConnection.model("OAuthState", oauthStateSchema);
41
42
  }
42
43
  let _oauthStore = "redis";
43
44
  export const setOAuthStateStore = (store) => { _oauthStore = store; };
@@ -1,5 +1,4 @@
1
- import { Queue, Worker } from "bullmq";
2
- import type { Processor, QueueOptions, WorkerOptions, Job } from "bullmq";
3
- export declare const createQueue: <T = unknown, R = unknown>(name: string, options?: Omit<QueueOptions, "connection">) => Queue<T, R>;
4
- export declare const createWorker: <T = unknown, R = unknown>(name: string, processor: Processor<T, R>, options?: Omit<WorkerOptions, "connection">) => Worker<T, R>;
1
+ import type { Queue as QueueType, Worker as WorkerType, Processor, QueueOptions, WorkerOptions, Job } from "bullmq";
2
+ export declare const createQueue: <T = unknown, R = unknown>(name: string, options?: Omit<QueueOptions, "connection">) => QueueType<T, R>;
3
+ export declare const createWorker: <T = unknown, R = unknown>(name: string, processor: Processor<T, R>, options?: Omit<WorkerOptions, "connection">) => WorkerType<T, R>;
5
4
  export type { Job };
package/dist/lib/queue.js CHANGED
@@ -1,4 +1,19 @@
1
- import { Queue, Worker } from "bullmq";
2
1
  import { getRedisConnectionOptions } from "./redis";
3
- export const createQueue = (name, options) => new Queue(name, { connection: getRedisConnectionOptions(), ...options });
4
- export const createWorker = (name, processor, options) => new Worker(name, processor, { connection: getRedisConnectionOptions(), ...options });
2
+ function requireBullMQ() {
3
+ try {
4
+ // Bun supports require() in ESM; this defers the import to call time
5
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
6
+ return require("bullmq");
7
+ }
8
+ catch {
9
+ throw new Error("bullmq is not installed. Run: bun add bullmq");
10
+ }
11
+ }
12
+ export const createQueue = (name, options) => {
13
+ const { Queue } = requireBullMQ();
14
+ return new Queue(name, { connection: getRedisConnectionOptions(), ...options });
15
+ };
16
+ export const createWorker = (name, processor, options) => {
17
+ const { Worker } = requireBullMQ();
18
+ return new Worker(name, processor, { connection: getRedisConnectionOptions(), ...options });
19
+ };
@@ -1,14 +1,9 @@
1
- import Redis from "ioredis";
2
- export declare const getRedisConnectionOptions: () => {
3
- password?: string | undefined;
4
- username?: string | undefined;
5
- host: string;
6
- port: number;
7
- };
1
+ import type { default as RedisClass, RedisOptions } from "ioredis";
2
+ export declare const getRedisConnectionOptions: () => RedisOptions;
8
3
  export declare const connectRedis: () => Promise<void>;
9
4
  /**
10
5
  * Gracefully close the Redis connection.
11
6
  * Useful for one-off scripts that need a clean exit.
12
7
  */
13
8
  export declare const disconnectRedis: () => Promise<void>;
14
- export declare const getRedis: () => Redis;
9
+ export declare const getRedis: () => RedisClass;
package/dist/lib/redis.js CHANGED
@@ -1,6 +1,16 @@
1
- import Redis from "ioredis";
2
1
  import { log } from "./logger";
3
2
  const isProd = process.env.NODE_ENV === "production";
3
+ function requireIoredis() {
4
+ try {
5
+ // Bun supports require() in ESM; this defers the import to call time
6
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
7
+ const mod = require("ioredis");
8
+ return mod.default ?? mod;
9
+ }
10
+ catch {
11
+ throw new Error("ioredis is not installed. Run: bun add ioredis");
12
+ }
13
+ }
4
14
  export const getRedisConnectionOptions = () => {
5
15
  const host_port = isProd ? process.env.REDIS_HOST_PROD : process.env.REDIS_HOST_DEV;
6
16
  if (!host_port)
@@ -18,17 +28,18 @@ export const getRedisConnectionOptions = () => {
18
28
  };
19
29
  };
20
30
  let client = null;
21
- const createClient = () => {
22
- const redis = new Redis(getRedisConnectionOptions());
23
- redis.on("error", (err) => log(`[redis] error: ${err.message}`));
24
- return redis;
25
- };
26
31
  export const connectRedis = () => {
27
32
  if (client)
28
33
  return Promise.resolve();
29
- client = createClient();
34
+ const Redis = requireIoredis();
35
+ client = new Redis(getRedisConnectionOptions());
36
+ client.on("error", (err) => log(`[redis] error: ${err.message}`));
30
37
  return new Promise((resolve, reject) => {
31
- client.once("ready", () => { log(`[redis] connected to ${getRedisConnectionOptions().host}:${getRedisConnectionOptions().port} as ${getRedisConnectionOptions().username || "default user"}`); resolve(); });
38
+ client.once("ready", () => {
39
+ const opts = getRedisConnectionOptions();
40
+ log(`[redis] connected to ${opts.host}:${opts.port} as ${opts.username || "default user"}`);
41
+ resolve();
42
+ });
32
43
  client.once("error", reject);
33
44
  });
34
45
  };
@@ -0,0 +1,12 @@
1
+ type ResetStore = "redis" | "mongo" | "sqlite" | "memory";
2
+ export declare const setPasswordResetStore: (store: ResetStore) => void;
3
+ /** Create a reset token. Returns the raw token (to embed in the email link).
4
+ * Only the SHA-256 hash is persisted in the store. */
5
+ export declare const createResetToken: (userId: string, email: string) => Promise<string>;
6
+ /** Atomically consume a reset token — returns its payload and deletes it in one operation.
7
+ * Returns null if the token is invalid, expired, or already used. */
8
+ export declare const consumeResetToken: (token: string) => Promise<{
9
+ userId: string;
10
+ email: string;
11
+ } | null>;
12
+ export {};