@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.
- package/README.md +241 -7
- package/dist/adapters/memoryAuth.d.ts +5 -0
- package/dist/adapters/memoryAuth.js +23 -0
- package/dist/adapters/sqliteAuth.d.ts +5 -0
- package/dist/adapters/sqliteAuth.js +18 -0
- package/dist/app.d.ts +48 -2
- package/dist/app.js +72 -5
- package/dist/entrypoints/mongo.d.ts +3 -0
- package/dist/entrypoints/mongo.js +3 -0
- package/dist/entrypoints/queue.d.ts +2 -0
- package/dist/entrypoints/queue.js +1 -0
- package/dist/entrypoints/redis.d.ts +1 -0
- package/dist/entrypoints/redis.js +1 -0
- package/dist/index.d.ts +5 -7
- package/dist/index.js +5 -5
- package/dist/lib/appConfig.d.ts +9 -0
- package/dist/lib/appConfig.js +5 -0
- package/dist/lib/createRoute.d.ts +61 -0
- package/dist/lib/createRoute.js +147 -0
- package/dist/lib/emailVerification.js +11 -10
- package/dist/lib/mongo.d.ts +9 -4
- package/dist/lib/mongo.js +61 -10
- package/dist/lib/oauth.js +11 -10
- package/dist/lib/queue.d.ts +3 -4
- package/dist/lib/queue.js +18 -3
- package/dist/lib/redis.d.ts +3 -8
- package/dist/lib/redis.js +19 -8
- package/dist/lib/resetPassword.d.ts +12 -0
- package/dist/lib/resetPassword.js +95 -0
- package/dist/lib/session.js +12 -12
- package/dist/middleware/cacheResponse.js +10 -9
- package/dist/models/AuthUser.d.ts +14 -106
- package/dist/models/AuthUser.js +31 -14
- package/dist/routes/auth.d.ts +3 -2
- package/dist/routes/auth.js +176 -59
- package/dist/services/auth.d.ts +8 -1
- package/dist/services/auth.js +5 -3
- package/package.json +38 -8
|
@@ -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 {
|
|
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 {
|
|
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";
|
package/dist/lib/appConfig.d.ts
CHANGED
|
@@ -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;
|
package/dist/lib/appConfig.js
CHANGED
|
@@ -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
|
-
|
|
15
|
-
appConnection.
|
|
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/mongo.d.ts
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
|
-
import
|
|
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:
|
|
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:
|
|
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 {
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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
|
-
|
|
58
|
-
|
|
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
|
-
|
|
69
|
-
|
|
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
|
-
|
|
40
|
-
appConnection.
|
|
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; };
|
package/dist/lib/queue.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { Queue, Worker } from "bullmq";
|
|
2
|
-
|
|
3
|
-
export declare const
|
|
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
|
-
|
|
4
|
-
|
|
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
|
+
};
|
package/dist/lib/redis.d.ts
CHANGED
|
@@ -1,14 +1,9 @@
|
|
|
1
|
-
import
|
|
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: () =>
|
|
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
|
-
|
|
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", () => {
|
|
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
|
};
|