@lastshotlabs/bunshot 0.0.9 → 0.0.13

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.
@@ -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,18 +1,18 @@
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, ModelSchemasConfig, 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";
8
10
  export { createRouter } from "./lib/context";
11
+ export { createRoute, withSecurity, registerSchema, registerSchemas } from "./lib/createRoute";
9
12
  export type { AppEnv, AppVariables } from "./lib/context";
10
13
  export { signToken, verifyToken } from "./lib/jwt";
11
14
  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";
15
+ export { createResetToken, consumeResetToken, setPasswordResetStore } from "./lib/resetPassword";
16
16
  export { createSession, getSession, deleteSession, getUserSessions, getActiveSessionCount, evictOldestSession, updateSessionLastActive, setSessionStore } from "./lib/session";
17
17
  export type { SessionMetadata, SessionInfo } from "./lib/session";
18
18
  export { createVerificationToken, getVerificationToken, deleteVerificationToken } from "./lib/emailVerification";
@@ -30,8 +30,6 @@ export { requireRole } from "./middleware/requireRole";
30
30
  export { requireVerifiedEmail } from "./middleware/requireVerifiedEmail";
31
31
  export { cacheResponse, bustCache, bustCachePattern, setCacheStore } from "./middleware/cacheResponse";
32
32
  export { buildFingerprint } from "./lib/fingerprint";
33
- export { AuthUser } from "./models/AuthUser";
34
- export { mongoAuthAdapter } from "./adapters/mongoAuth";
35
33
  export { sqliteAuthAdapter, setSqliteDb, startSqliteCleanup } from "./adapters/sqliteAuth";
36
34
  export { memoryAuthAdapter, clearMemoryStore } from "./adapters/memoryAuth";
37
35
  export { setUserRoles, addUserRole, removeUserRole } from "./lib/roles";
package/dist/index.js CHANGED
@@ -1,16 +1,18 @@
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";
7
10
  export { COOKIE_TOKEN, HEADER_USER_TOKEN } from "./lib/constants";
8
11
  export { createRouter } from "./lib/context";
12
+ export { createRoute, withSecurity, registerSchema, registerSchemas } from "./lib/createRoute";
9
13
  export { signToken, verifyToken } from "./lib/jwt";
10
14
  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";
15
+ export { createResetToken, consumeResetToken, setPasswordResetStore } from "./lib/resetPassword";
14
16
  export { createSession, getSession, deleteSession, getUserSessions, getActiveSessionCount, evictOldestSession, updateSessionLastActive, setSessionStore } from "./lib/session";
15
17
  export { createVerificationToken, getVerificationToken, deleteVerificationToken } from "./lib/emailVerification";
16
18
  export { bustAuthLimit, trackAttempt, isLimited } from "./lib/authRateLimit";
@@ -27,8 +29,6 @@ export { cacheResponse, bustCache, bustCachePattern, setCacheStore } from "./mid
27
29
  // Lib utilities (bot protection)
28
30
  export { buildFingerprint } from "./lib/fingerprint";
29
31
  // Models
30
- export { AuthUser } from "./models/AuthUser";
31
- export { mongoAuthAdapter } from "./adapters/mongoAuth";
32
32
  export { sqliteAuthAdapter, setSqliteDb, startSqliteCleanup } from "./adapters/sqliteAuth";
33
33
  export { memoryAuthAdapter, clearMemoryStore } from "./adapters/memoryAuth";
34
34
  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,6 +24,9 @@ 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;
21
30
  export declare const setMaxSessions: (n: number) => void;
22
31
  export declare const getMaxSessions: () => number;
23
32
  export declare const setPersistSessionMetadata: (v: boolean) => void;
@@ -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,6 +16,10 @@ 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;
18
23
  // ---------------------------------------------------------------------------
19
24
  // Session policy
20
25
  // ---------------------------------------------------------------------------
@@ -0,0 +1,61 @@
1
+ import type { RouteConfig } from "@hono/zod-openapi";
2
+ import type { ZodType } from "zod";
3
+ /**
4
+ * Registers a Zod schema as a named entry in `components/schemas`.
5
+ *
6
+ * Use this for shared schemas (e.g. shared error types, reusable response shapes)
7
+ * that aren't directly attached to a specific route. Schemas already registered
8
+ * under the same name are silently skipped.
9
+ *
10
+ * @example
11
+ * export const MySchema = registerSchema("MySchema", z.object({ id: z.string() }));
12
+ */
13
+ export declare const registerSchema: <T extends ZodType>(name: string, schema: T) => T;
14
+ /**
15
+ * Registers multiple Zod schemas at once as named entries in `components/schemas`.
16
+ * Object keys become the schema names. Returns the same object so you can
17
+ * destructure or re-export the schemas normally.
18
+ *
19
+ * Schemas already registered (e.g. via a prior `registerSchema` call) are skipped.
20
+ *
21
+ * @example
22
+ * export const { LedgerItem, Product } = registerSchemas({
23
+ * LedgerItem: z.object({ id: z.string(), amount: z.number() }),
24
+ * Product: z.object({ id: z.string(), price: z.number() }),
25
+ * });
26
+ */
27
+ export declare const registerSchemas: <T extends Record<string, ZodType>>(schemas: T) => T;
28
+ /**
29
+ * Auto-registers a module export as a named OpenAPI schema.
30
+ * Used internally by modelSchemas auto-discovery in createApp.
31
+ * Strips a trailing "Schema" suffix from the export name.
32
+ * Skips non-Zod values and already-registered schemas.
33
+ */
34
+ export declare function maybeAutoRegister(exportName: string, value: unknown): void;
35
+ /**
36
+ * Adds an OpenAPI `security` requirement to a route without affecting TypeScript
37
+ * type inference on the handler. Pass each security scheme as a separate object.
38
+ *
39
+ * Use this instead of inlining `security` in `createRoute(...)` — inlining a
40
+ * field typed as `{ [name: string]: string[] }` breaks `c.req.valid()` inference.
41
+ *
42
+ * @example
43
+ * router.openapi(
44
+ * withSecurity(createRoute({ method: "get", path: "/me", ... }), { cookieAuth: [] }, { userToken: [] }),
45
+ * async (c) => { ... }
46
+ * )
47
+ */
48
+ export declare const withSecurity: <T extends RouteConfig>(route: T, ...schemes: Array<Record<string, string[]>>) => T;
49
+ /**
50
+ * Drop-in replacement for `createRoute` from `@hono/zod-openapi`.
51
+ *
52
+ * Automatically registers unnamed request body and response schemas as named
53
+ * OpenAPI components so they appear in `components/schemas` instead of being
54
+ * inlined at every use site. Generated names follow the convention:
55
+ *
56
+ * {Method}{PathSegments}Body — request body
57
+ * {Method}{PathSegments}{StatusCode} — response body
58
+ *
59
+ * Schemas already named via `.openapi("Name")` are never overwritten.
60
+ */
61
+ export declare const createRoute: <T extends RouteConfig>(config: T) => T;
@@ -0,0 +1,147 @@
1
+ import { createRoute as _createRoute } from "@hono/zod-openapi";
2
+ import { getRefId, zodToOpenAPIRegistry } from "@asteasolutions/zod-to-openapi";
3
+ const STATUS_SUFFIX = {
4
+ "200": "Response",
5
+ "201": "Response",
6
+ "204": "Response",
7
+ "400": "BadRequestError",
8
+ "401": "UnauthorizedError",
9
+ "403": "ForbiddenError",
10
+ "404": "NotFoundError",
11
+ "409": "ConflictError",
12
+ "422": "ValidationError",
13
+ "429": "RateLimitError",
14
+ "500": "InternalError",
15
+ "501": "NotImplementedError",
16
+ "503": "UnavailableError",
17
+ };
18
+ const METHOD_VERB = {
19
+ get: "Get",
20
+ post: "Create",
21
+ put: "Replace",
22
+ patch: "Update",
23
+ delete: "Delete",
24
+ };
25
+ /**
26
+ * Converts a route method + path into a PascalCase base name for auto-generated schema names.
27
+ * Examples:
28
+ * POST /ledger-items → CreateLedgerItems
29
+ * GET /ledger-items/{id} → GetLedgerItemsById
30
+ * DELETE /auth/sessions/{sessionId} → DeleteAuthSessionsBySessionId
31
+ */
32
+ function toBaseName(method, path) {
33
+ const m = METHOD_VERB[method.toLowerCase()] ?? (method.charAt(0).toUpperCase() + method.slice(1).toLowerCase());
34
+ const segments = path
35
+ .split("/")
36
+ .filter(Boolean)
37
+ .map((seg) => {
38
+ if (seg.startsWith("{") && seg.endsWith("}")) {
39
+ const param = seg.slice(1, -1);
40
+ return "By" + param.charAt(0).toUpperCase() + param.slice(1);
41
+ }
42
+ // kebab-case and plain segments → PascalCase
43
+ return seg.replace(/-([a-z])/g, (_, c) => c.toUpperCase()).replace(/^[a-z]/, (c) => c.toUpperCase());
44
+ });
45
+ return m + segments.join("");
46
+ }
47
+ function maybeRegister(schema, name) {
48
+ if (!schema || typeof schema !== "object" || !("_def" in schema))
49
+ return;
50
+ if (getRefId(schema))
51
+ return; // already named via .openapi()
52
+ // Write directly to the registry instead of calling schema.openapi(name) — the
53
+ // .openapi() method requires extendZodWithOpenApi() to have been called on the
54
+ // same zod instance that created the schema, which isn't guaranteed in tenant apps.
55
+ zodToOpenAPIRegistry.add(schema, { _internal: { refId: name } });
56
+ }
57
+ /**
58
+ * Registers a Zod schema as a named entry in `components/schemas`.
59
+ *
60
+ * Use this for shared schemas (e.g. shared error types, reusable response shapes)
61
+ * that aren't directly attached to a specific route. Schemas already registered
62
+ * under the same name are silently skipped.
63
+ *
64
+ * @example
65
+ * export const MySchema = registerSchema("MySchema", z.object({ id: z.string() }));
66
+ */
67
+ export const registerSchema = (name, schema) => {
68
+ if (!getRefId(schema)) {
69
+ zodToOpenAPIRegistry.add(schema, { _internal: { refId: name } });
70
+ }
71
+ return schema;
72
+ };
73
+ /**
74
+ * Registers multiple Zod schemas at once as named entries in `components/schemas`.
75
+ * Object keys become the schema names. Returns the same object so you can
76
+ * destructure or re-export the schemas normally.
77
+ *
78
+ * Schemas already registered (e.g. via a prior `registerSchema` call) are skipped.
79
+ *
80
+ * @example
81
+ * export const { LedgerItem, Product } = registerSchemas({
82
+ * LedgerItem: z.object({ id: z.string(), amount: z.number() }),
83
+ * Product: z.object({ id: z.string(), price: z.number() }),
84
+ * });
85
+ */
86
+ export const registerSchemas = (schemas) => {
87
+ for (const [name, schema] of Object.entries(schemas)) {
88
+ if (!getRefId(schema)) {
89
+ zodToOpenAPIRegistry.add(schema, { _internal: { refId: name } });
90
+ }
91
+ }
92
+ return schemas;
93
+ };
94
+ /**
95
+ * Auto-registers a module export as a named OpenAPI schema.
96
+ * Used internally by modelSchemas auto-discovery in createApp.
97
+ * Strips a trailing "Schema" suffix from the export name.
98
+ * Skips non-Zod values and already-registered schemas.
99
+ */
100
+ export function maybeAutoRegister(exportName, value) {
101
+ if (!value || typeof value !== "object" || !("_def" in value))
102
+ return;
103
+ if (getRefId(value))
104
+ return;
105
+ const name = exportName.endsWith("Schema")
106
+ ? exportName.slice(0, -"Schema".length)
107
+ : exportName;
108
+ zodToOpenAPIRegistry.add(value, { _internal: { refId: name } });
109
+ }
110
+ /**
111
+ * Adds an OpenAPI `security` requirement to a route without affecting TypeScript
112
+ * type inference on the handler. Pass each security scheme as a separate object.
113
+ *
114
+ * Use this instead of inlining `security` in `createRoute(...)` — inlining a
115
+ * field typed as `{ [name: string]: string[] }` breaks `c.req.valid()` inference.
116
+ *
117
+ * @example
118
+ * router.openapi(
119
+ * withSecurity(createRoute({ method: "get", path: "/me", ... }), { cookieAuth: [] }, { userToken: [] }),
120
+ * async (c) => { ... }
121
+ * )
122
+ */
123
+ export const withSecurity = (route, ...schemes) => Object.assign(route, { security: schemes });
124
+ /**
125
+ * Drop-in replacement for `createRoute` from `@hono/zod-openapi`.
126
+ *
127
+ * Automatically registers unnamed request body and response schemas as named
128
+ * OpenAPI components so they appear in `components/schemas` instead of being
129
+ * inlined at every use site. Generated names follow the convention:
130
+ *
131
+ * {Method}{PathSegments}Body — request body
132
+ * {Method}{PathSegments}{StatusCode} — response body
133
+ *
134
+ * Schemas already named via `.openapi("Name")` are never overwritten.
135
+ */
136
+ export const createRoute = (config) => {
137
+ const base = toBaseName(config.method, config.path);
138
+ // Auto-name the JSON request body schema if present and unnamed
139
+ const bodySchema = config.request?.body?.content?.["application/json"]?.schema;
140
+ maybeRegister(bodySchema, `${base}Request`);
141
+ // Auto-name each JSON response schema if present and unnamed
142
+ for (const [status, response] of Object.entries(config.responses ?? {})) {
143
+ const resSchema = response?.content?.["application/json"]?.schema;
144
+ maybeRegister(resSchema, `${base}${STATUS_SUFFIX[status] ?? status}`);
145
+ }
146
+ return _createRoute(config);
147
+ };
@@ -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; };
@@ -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
  };