@lastshotlabs/bunshot 0.0.4 → 0.0.6

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 CHANGED
@@ -15,6 +15,39 @@ A personal Bun + Hono API framework. Install it in any app and get auth, session
15
15
 
16
16
  ---
17
17
 
18
+ ## CLI — Scaffold a New Project
19
+
20
+ ```bash
21
+ bunx @lastshotlabs/bunshot "My App"
22
+ ```
23
+
24
+ You can also pass a custom directory name:
25
+
26
+ ```bash
27
+ bunx @lastshotlabs/bunshot "My App" my-app-dir
28
+ ```
29
+
30
+ This creates a ready-to-run project with:
31
+
32
+ ```
33
+ my-app/
34
+ src/
35
+ index.ts # entry point
36
+ config/index.ts # centralized app configuration
37
+ lib/constants.ts # app name, version, roles
38
+ routes/ # add your route files here
39
+ workers/ # BullMQ workers (auto-discovered)
40
+ middleware/ # custom middleware
41
+ models/ # data models
42
+ services/ # business logic
43
+ tsconfig.json # pre-configured with path aliases
44
+ .env # environment variables template
45
+ ```
46
+
47
+ Path aliases like `@config/*`, `@lib/*`, `@middleware/*`, `@models/*`, `@routes/*`, `@services/*`, and `@workers/*` are set up automatically in `tsconfig.json`.
48
+
49
+ ---
50
+
18
51
  ## Installation
19
52
 
20
53
  ```bash
@@ -32,15 +65,13 @@ bun add @lastshotlabs/bunshot
32
65
  ```ts
33
66
  // src/index.ts
34
67
  import { createServer } from "@lastshotlabs/bunshot";
68
+ import { appConfig } from "@config/index";
35
69
 
36
- await createServer({
37
- routesDir: import.meta.dir + "/routes",
38
- workersDir: import.meta.dir + "/workers",
39
- app: { name: "My App", version: "1.0.0" },
40
- // db: { mongo: "single", redis: true } — defaults, connects automatically
41
- });
70
+ await createServer(appConfig);
42
71
  ```
43
72
 
73
+ All configuration lives in `src/config/index.ts` — see the CLI-generated scaffold for the full setup.
74
+
44
75
  That's it. Your app gets:
45
76
 
46
77
  | Endpoint | Description |
@@ -686,6 +717,7 @@ await createServer({
686
717
  primaryField: "email", // default: "email" — use "username" or "phone" to change the login identifier
687
718
  emailVerification: { // optional — only active when primaryField is "email"
688
719
  required: true, // default: false (soft gate) — set true to block login until verified
720
+ tokenExpiry: 60 * 60, // default: 86400 (24 hours) — token TTL in seconds
689
721
  onSend: async (email, token) => { // called after registration and resend — use any email provider
690
722
  await resend.emails.send({ to: email, subject: "Verify your email", text: `Token: ${token}` });
691
723
  },
@@ -14,7 +14,7 @@ export declare const memoryGetCache: (key: string) => string | null;
14
14
  export declare const memorySetCache: (key: string, value: string, ttlSeconds?: number) => void;
15
15
  export declare const memoryDelCache: (key: string) => void;
16
16
  export declare const memoryDelCachePattern: (pattern: string) => void;
17
- export declare const memoryCreateVerificationToken: (token: string, userId: string, email: string) => void;
17
+ export declare const memoryCreateVerificationToken: (token: string, userId: string, email: string, ttlSeconds: number) => void;
18
18
  export declare const memoryGetVerificationToken: (token: string) => {
19
19
  userId: string;
20
20
  email: string;
@@ -14,7 +14,7 @@ export declare const sqliteGetCache: (key: string) => string | null;
14
14
  export declare const sqliteSetCache: (key: string, value: string, ttlSeconds?: number) => void;
15
15
  export declare const sqliteDelCache: (key: string) => void;
16
16
  export declare const sqliteDelCachePattern: (pattern: string) => void;
17
- export declare const sqliteCreateVerificationToken: (token: string, userId: string, email: string) => void;
17
+ export declare const sqliteCreateVerificationToken: (token: string, userId: string, email: string, ttlSeconds: number) => void;
18
18
  export declare const sqliteGetVerificationToken: (token: string) => {
19
19
  userId: string;
20
20
  email: string;
package/dist/cli.js CHANGED
@@ -1,34 +1,66 @@
1
1
  #!/usr/bin/env bun
2
2
  // @bun
3
- var R=import.meta.require;import{existsSync as V,mkdirSync as E,writeFileSync as J,readSync as v,rmSync as I}from"fs";import{join as z}from"path";import{spawnSync as Q}from"child_process";function W($){process.stdout.write($);let L=Buffer.alloc(1024),_=v(0,L,0,L.length,null);return L.subarray(0,_).toString().trim().replace(/\r/g,"")}var X=process.argv[2],q=process.argv[3],K=X||W("App name: ");if(!K)console.error("App name is required."),process.exit(1);var M=K.toLowerCase().replace(/\s+/g,"-").replace(/[^a-z0-9-]/g,""),A=q||(X?M:W(`Directory (${M}): `))||M,B=z(process.cwd(),A),G=z(B,"src"),b=z(G,"routes"),C=z(G,"workers"),x=z(G,"queues"),F=z(G,"ws"),P=z(G,"services");if(V(B))console.error(`Directory "${A}" already exists.`),process.exit(1);var T=`import { createServer, type CreateServerConfig } from "@lastshotlabs/bunshot";
3
+ var D=import.meta.require;import{existsSync as N,mkdirSync as X,writeFileSync as q,readSync as b,rmSync as h}from"fs";import{join as B}from"path";import{spawnSync as M}from"child_process";function w(z){process.stdout.write(z);let H=Buffer.alloc(1024),Q=b(0,H,0,H.length,null);return H.subarray(0,Q).toString().trim().replace(/\r/g,"")}function E(z,H,Q=0){let J=Q;function Z($=!1){if(!$)process.stdout.write(`\x1B[${H.length}A`);for(let A=0;A<H.length;A++){let V=A===J,R=V?"\x1B[36m>\x1B[0m":" ",g=V?`\x1B[1m${H[A]}\x1B[0m`:`\x1B[2m${H[A]}\x1B[0m`;process.stdout.write(`\x1B[2K ${R} ${g}
4
+ `)}}if(!process.stdin.isTTY){console.log(z),H.forEach((V,R)=>console.log(` ${R+1}) ${V}`));let $=w(` Choose [${Q+1}]: `);if(!$)return Q;let A=parseInt($);if(A>=1&&A<=H.length)return A-1;return Q}console.log(z),process.stdout.write("\x1B[?25l"),Z(!0),process.stdin.setRawMode(!0);let _=Buffer.alloc(16);try{while(!0){let $=b(0,_,0,_.length,null),A=_.subarray(0,$).toString();if(A==="\r"||A===`
5
+ `)break;else if(A==="\x1B[A"||A==="\x1BOA")J=(J-1+H.length)%H.length,Z();else if(A==="\x1B[B"||A==="\x1BOB")J=(J+1)%H.length,Z();else if(A==="\x03")process.stdout.write(`\x1B[?25h
6
+ `),process.stdin.setRawMode(!1),process.exit(0);else{let V=parseInt(A);if(V>=1&&V<=H.length){J=V-1,Z();break}}}}finally{process.stdin.setRawMode(!1),process.stdout.write("\x1B[?25h")}return J}var f=process.argv[2],m=process.argv[3],O=f||w("App name: ");if(!O)console.error("App name is required."),process.exit(1);var I=O.toLowerCase().replace(/\s+/g,"-").replace(/[^a-z0-9-]/g,""),G=m||(f?I:w(`Directory (${I}): `))||I,K=!1,W=!1,P="mongo",v="redis",T="redis",F="redis";console.log("");var x=E("Database setup:",["Full stack (MongoDB + Redis \u2014 production ready)","SQLite (single file, no external services)","Memory (ephemeral, great for prototyping/tests)","Custom (choose each store individually)"]);if(x===0)K=E("MongoDB connection mode:",["Single (auth + app data share one connection)","Separate (auth on its own cluster)"])===0?"single":"separate",W=!0,P="mongo",v="redis",T="redis",F="redis";else if(x===1)K=!1,W=!1,P="sqlite",v="sqlite",T="sqlite",F="sqlite";else if(x===2)K=!1,W=!1,P="memory",v="memory",T="memory",F="memory";else{console.log(`
7
+ Configure each store:
8
+ `);let z=E("MongoDB:",["Single (one connection for auth + app data)","Separate (auth on its own cluster)","None (no MongoDB)"]);if(z===0)K="single";else if(z===1)K="separate";else K=!1;W=E("Redis:",["Yes","No"])===0;let Q=[],J=[];if(W)Q.push("redis"),J.push("Redis");if(K)Q.push("mongo"),J.push("MongoDB");Q.push("sqlite","memory"),J.push("SQLite","Memory");let Z=[],_=[];if(K)Z.push("mongo"),_.push("MongoDB");Z.push("sqlite","memory"),_.push("SQLite","Memory");let $=E("Auth store:",_);P=Z[$];let A=E("Sessions store:",J);v=Q[A];let V=E("Cache store:",J);T=Q[V];let R=E("OAuth state store:",J);F=Q[R]}var S=P==="sqlite"||v==="sqlite"||T==="sqlite"||F==="sqlite",U=B(process.cwd(),G),Y=B(U,"src"),k=B(Y,"config"),j=B(Y,"lib"),p=B(Y,"routes"),l=B(Y,"workers"),u=B(Y,"queues"),d=B(Y,"ws"),c=B(Y,"services"),a=B(Y,"middleware"),r=B(Y,"models");if(N(U))console.error(`Directory "${G}" already exists.`),process.exit(1);function n(){let z=[];if(K)z.push(` mongo: "${K}",`);else z.push(" mongo: false,");if(z.push(` redis: ${W},`),z.push(` auth: "${P}",`),z.push(` sessions: "${v}",`),z.push(` oauthState: "${F}",`),z.push(` cache: "${T}",`),S)z.push(' sqlite: path.join(import.meta.dir, "../../data.db"),');return`{
9
+ ${z.join(`
10
+ `)}
11
+ }`}var s=`export const APP_NAME = "${O}";
12
+ export const APP_VERSION = "1.0.0";
13
+
14
+ export const USER_ROLES = {
15
+ ADMIN: "admin",
16
+ USER: "user",
17
+ };
18
+ `,t=`import path from "path";
19
+ import {
20
+ type AppMeta,
21
+ type AuthConfig,
22
+ type CreateServerConfig,
23
+ type DbConfig,
24
+ type SecurityConfig,
25
+ } from "@lastshotlabs/bunshot";
26
+ import { APP_NAME, APP_VERSION, USER_ROLES } from "./lib/constants";
27
+
28
+ export const app: AppMeta = {
29
+ name: APP_NAME,
30
+ version: APP_VERSION,
31
+ };
32
+
33
+ export const routesDir = path.join(import.meta.dir, "../routes");
34
+
35
+ export const workersDir = path.join(import.meta.dir, "../workers");
36
+
37
+ export const port = process.env.PORT ? parseInt(process.env.PORT) : 3000;
4
38
 
5
- const roles = {
6
- admin: "admin",
7
- user: "user",
39
+ export const db: DbConfig = ${n()};
40
+
41
+ export const auth: AuthConfig = {
42
+ roles: Object.values(USER_ROLES),
43
+ defaultRole: USER_ROLES.USER,
44
+ };
45
+
46
+ export const security: SecurityConfig = {
47
+ cors: ["*"],
8
48
  };
9
49
 
10
- const config: CreateServerConfig = {
11
- routesDir: import.meta.dir + "/routes",
12
- workersDir: import.meta.dir + "/workers",
13
- app: {
14
- name: "${K}",
15
- version: "1.0.0",
16
- },
17
- auth: {
18
- roles: Object.values(roles),
19
- defaultRole: roles.user,
20
- },
21
- security: {
22
- cors: ["*"],
23
- },
24
- db: {
25
- mongo: "single",
26
- },
27
- port: process.env.PORT ? parseInt(process.env.PORT) : 3000,
50
+ export const appConfig: CreateServerConfig = {
51
+ app,
52
+ routesDir,
53
+ workersDir,
54
+ port,
55
+ db,
56
+ auth,
57
+ security,
28
58
  };
59
+ `,i=`import { createServer } from "@lastshotlabs/bunshot";
60
+ import { appConfig } from "@config/index";
29
61
 
30
- await createServer(config);
31
- `,h=`# ${K}
62
+ await createServer(appConfig);
63
+ `,o=`# ${O}
32
64
 
33
65
  Built with [@lastshotlabs/bunshot](https://github.com/Last-Shot-Labs/bunshot).
34
66
 
@@ -50,9 +82,14 @@ bun dev
50
82
 
51
83
  \`\`\`
52
84
  src/
53
- index.ts # server entry point
54
- routes/ # file-based routing (each file = a router)
55
- workers/ # BullMQ workers (auto-imported on start)
85
+ index.ts # server entry point
86
+ config/index.ts # centralized app configuration
87
+ lib/constants.ts # app name, version, roles
88
+ routes/ # file-based routing (each file = a router)
89
+ workers/ # BullMQ workers (auto-imported on start)
90
+ middleware/ # custom middleware
91
+ models/ # data models
92
+ services/ # business logic
56
93
  \`\`\`
57
94
 
58
95
  ## Adding routes
@@ -68,7 +105,7 @@ export const router = createRouter();
68
105
 
69
106
  router.get("/products", (c) => c.json({ products: [] }));
70
107
  \`\`\`
71
-
108
+ ${K?`
72
109
  ## Adding models
73
110
 
74
111
  \`\`\`ts
@@ -82,14 +119,21 @@ const ProductSchema = new mongoose.Schema({
82
119
 
83
120
  export const Product = appConnection.model("Product", ProductSchema);
84
121
  \`\`\`
85
-
122
+ `:""}
86
123
  ## Environment variables
87
124
 
88
- See \`.env\` \u2014 fill in MongoDB, Redis, JWT, Bearer token, and OAuth provider values before running.
89
- `,w=`NODE_ENV=development
90
- PORT=3000
91
-
92
- # MongoDB (single connection)
125
+ See \`.env\` \u2014 fill in the values before running.
126
+ `;function e(){let z=["NODE_ENV=development","PORT=3000"];if(K==="single")z.push(`
127
+ # MongoDB
128
+ MONGO_USER_DEV=
129
+ MONGO_PW_DEV=
130
+ MONGO_HOST_DEV=
131
+ MONGO_DB_DEV=
132
+ MONGO_USER_PROD=
133
+ MONGO_PW_PROD=
134
+ MONGO_HOST_PROD=
135
+ MONGO_DB_PROD=`);else if(K==="separate")z.push(`
136
+ # MongoDB (app data)
93
137
  MONGO_USER_DEV=
94
138
  MONGO_PW_DEV=
95
139
  MONGO_HOST_DEV=
@@ -99,7 +143,7 @@ MONGO_PW_PROD=
99
143
  MONGO_HOST_PROD=
100
144
  MONGO_DB_PROD=
101
145
 
102
- # MongoDB auth connection (only needed if mongo: "separate")
146
+ # MongoDB (auth \u2014 separate cluster)
103
147
  MONGO_AUTH_USER_DEV=
104
148
  MONGO_AUTH_PW_DEV=
105
149
  MONGO_AUTH_HOST_DEV=
@@ -107,16 +151,14 @@ MONGO_AUTH_DB_DEV=
107
151
  MONGO_AUTH_USER_PROD=
108
152
  MONGO_AUTH_PW_PROD=
109
153
  MONGO_AUTH_HOST_PROD=
110
- MONGO_AUTH_DB_PROD=
111
-
154
+ MONGO_AUTH_DB_PROD=`);if(W)z.push(`
112
155
  # Redis
113
156
  REDIS_HOST_DEV=
114
157
  REDIS_USER_DEV=
115
158
  REDIS_PW_DEV=
116
159
  REDIS_HOST_PROD=
117
160
  REDIS_USER_PROD=
118
- REDIS_PW_PROD=
119
-
161
+ REDIS_PW_PROD=`);return z.push(`
120
162
  # JWT
121
163
  JWT_SECRET_DEV=
122
164
  JWT_SECRET_PROD=
@@ -135,15 +177,17 @@ APPLE_CLIENT_ID=
135
177
  APPLE_TEAM_ID=
136
178
  APPLE_KEY_ID=
137
179
  APPLE_PRIVATE_KEY=
138
- APPLE_REDIRECT_URI=
139
- `;console.log(`
140
- @lastshotlabs/bunshot \u2014 creating ${A}
141
- `);E(B,{recursive:!0});console.log(" Running bun init...");Q("bun",["init","-y"],{cwd:B,stdio:"inherit"});var U=z(B,"index.ts");if(V(U))I(U);var Y=z(B,"package.json"),H=JSON.parse(R("fs").readFileSync(Y,"utf-8"));H.module="src/index.ts";H.scripts={dev:"bun --watch src/index.ts",start:"bun src/index.ts"};H.dependencies={...H.dependencies,"@lastshotlabs/bunshot":"*"};J(Y,JSON.stringify(H,null,2)+`
142
- `,"utf-8");var Z=z(B,"tsconfig.json"),O=JSON.parse(R("fs").readFileSync(Z,"utf-8"));O.compilerOptions={...O.compilerOptions,paths:{"@lib/*":["./src/lib/*"],"@middleware/*":["./src/middleware/*"],"@models/*":["./src/models/*"],"@queues/*":["./src/queues/*"],"@routes/*":["./src/routes/*"],"@scripts/*":["./src/scripts/*"],"@services/*":["./src/services/*"],"@workers/*":["./src/workers/*"],"@queues":["./src/queues/index.ts"],"@jobs":["./src/queues/jobs.ts"],"@ws":["./src/ws/index.ts"]}};J(Z,JSON.stringify(O,null,2)+`
143
- `,"utf-8");E(b,{recursive:!0});E(C,{recursive:!0});E(x,{recursive:!0});E(F,{recursive:!0});E(P,{recursive:!0});J(z(G,"index.ts"),T,"utf-8");J(z(B,".env"),w,"utf-8");J(z(B,"README.md"),h,"utf-8");console.log(" Created:");console.log(` + ${A}/src/index.ts`);console.log(` + ${A}/src/routes/`);console.log(` + ${A}/src/workers/`);console.log(` + ${A}/src/queues/`);console.log(` + ${A}/src/ws/`);console.log(` + ${A}/src/services/`);console.log(` + ${A}/.env`);console.log(` + ${A}/README.md`);console.log(`
144
- Initializing git...`);var N=Q("git",["init"],{cwd:B,stdio:"inherit"});if(N.status!==0)console.error(" git init failed \u2014 skipping.");console.log(`
145
- Installing dependencies...`);var u=Q("bun",["install"],{cwd:B,stdio:"inherit"});if(u.status!==0)console.error(`
180
+ APPLE_REDIRECT_URI=`),z.join(`
181
+ `)+`
182
+ `}console.log(`
183
+ @lastshotlabs/bunshot \u2014 creating ${G}
184
+ `);X(U,{recursive:!0});console.log(" Running bun init...");M("bun",["init","-y"],{cwd:U,stdio:"inherit"});var C=B(U,"index.ts");if(N(C))h(C);var y=B(U,"package.json"),L=JSON.parse(D("fs").readFileSync(y,"utf-8"));L.module="src/index.ts";L.scripts={dev:"bun --watch src/index.ts",start:"bun src/index.ts"};L.dependencies={...L.dependencies,"@lastshotlabs/bunshot":"*"};q(y,JSON.stringify(L,null,2)+`
185
+ `,"utf-8");var zz=B(U,"tsconfig.json"),Az={compilerOptions:{lib:["ESNext"],target:"ESNext",module:"Preserve",moduleDetection:"force",jsx:"react-jsx",allowJs:!0,moduleResolution:"bundler",allowImportingTsExtensions:!0,verbatimModuleSyntax:!0,noEmit:!0,strict:!0,skipLibCheck:!0,noFallthroughCasesInSwitch:!0,noUncheckedIndexedAccess:!0,noImplicitOverride:!0,noUnusedLocals:!1,noUnusedParameters:!1,noPropertyAccessFromIndexSignature:!1,paths:{"@lib/*":["./src/lib/*"],"@middleware/*":["./src/middleware/*"],"@models/*":["./src/models/*"],"@queues/*":["./src/queues/*"],"@routes/*":["./src/routes/*"],"@scripts/*":["./src/scripts/*"],"@services/*":["./src/services/*"],"@workers/*":["./src/workers/*"],"@service-facades/*":["./src/service-facades/*"],"@config/*":["./src/config/*"],"@constants/*":["./src/lib/constants/*"]}}};q(zz,JSON.stringify(Az,null,2)+`
186
+ `,"utf-8");X(k,{recursive:!0});X(j,{recursive:!0});X(p,{recursive:!0});X(l,{recursive:!0});X(u,{recursive:!0});X(d,{recursive:!0});X(c,{recursive:!0});X(a,{recursive:!0});X(r,{recursive:!0});q(B(j,"constants.ts"),s,"utf-8");q(B(k,"index.ts"),t,"utf-8");q(B(Y,"index.ts"),i,"utf-8");q(B(U,".env"),e(),"utf-8");q(B(U,"README.md"),o,"utf-8");console.log(" Created:");console.log(` + ${G}/src/index.ts`);console.log(` + ${G}/src/config/index.ts`);console.log(` + ${G}/src/lib/constants.ts`);console.log(` + ${G}/src/routes/`);console.log(` + ${G}/src/workers/`);console.log(` + ${G}/src/queues/`);console.log(` + ${G}/src/ws/`);console.log(` + ${G}/src/services/`);console.log(` + ${G}/src/middleware/`);console.log(` + ${G}/src/models/`);console.log(` + ${G}/.env`);console.log(` + ${G}/README.md`);console.log(`
187
+ DB config:`);console.log(` mongo: ${K||"none"} | redis: ${W}`);console.log(` auth: ${P} | sessions: ${v} | cache: ${T} | oauthState: ${F}`);console.log(`
188
+ Initializing git...`);var Bz=M("git",["init"],{cwd:U,stdio:"inherit"});if(Bz.status!==0)console.error(" git init failed \u2014 skipping.");console.log(`
189
+ Installing dependencies...`);var Gz=M("bun",["install"],{cwd:U,stdio:"inherit"});if(Gz.status!==0)console.error(`
146
190
  bun install failed. Run it manually inside the directory.`),process.exit(1);console.log(`
147
191
  Done! Next steps:
148
- `);console.log(` cd ${A}`);console.log(" # fill in .env");console.log(` bun dev
192
+ `);console.log(` cd ${G}`);console.log(" # fill in .env");console.log(` bun dev
149
193
  `);
package/dist/index.js CHANGED
@@ -1,2 +1,27 @@
1
1
  // @bun
2
- import{createApp as v}from"./app";import{createServer as B}from"./server";import{getAppRoles as D}from"./lib/appConfig";import{HttpError as G}from"./lib/HttpError";import{COOKIE_TOKEN as I,HEADER_USER_TOKEN as J}from"./lib/constants";import{createRouter as L}from"./lib/context";import{signToken as N,verifyToken as O}from"./lib/jwt";import{log as Q}from"./lib/logger";import{connectMongo as T,connectAuthMongo as W,connectAppMongo as X,disconnectMongo as Y,authConnection as Z,appConnection as _,mongoose as $}from"./lib/mongo";import{connectRedis as y,disconnectRedis as E,getRedis as R}from"./lib/redis";import{createQueue as V,createWorker as c}from"./lib/queue";import{createSession as g,getSession as q,deleteSession as b,setSessionStore as w}from"./lib/session";import{createVerificationToken as h,getVerificationToken as n,deleteVerificationToken as p}from"./lib/emailVerification";import{bustAuthLimit as o,trackAttempt as m,isLimited as l}from"./lib/authRateLimit";import{validate as a}from"./lib/validate";import{bearerAuth as s}from"./middleware/bearerAuth";import{botProtection as r}from"./middleware/botProtection";import{identify as jj}from"./middleware/identify";import{rateLimit as vj}from"./middleware/rateLimit";import{userAuth as Bj}from"./middleware/userAuth";import{requireRole as Dj}from"./middleware/requireRole";import{requireVerifiedEmail as Gj}from"./middleware/requireVerifiedEmail";import{cacheResponse as Ij,bustCache as Jj,bustCachePattern as Kj,setCacheStore as Lj}from"./middleware/cacheResponse";import{buildFingerprint as Nj}from"./lib/fingerprint";import{AuthUser as Pj}from"./models/AuthUser";import{mongoAuthAdapter as Sj}from"./adapters/mongoAuth";import{sqliteAuthAdapter as Wj,setSqliteDb as Xj,startSqliteCleanup as Yj}from"./adapters/sqliteAuth";import{memoryAuthAdapter as _j,clearMemoryStore as $j}from"./adapters/memoryAuth";import{setUserRoles as yj,addUserRole as Ej,removeUserRole as Rj}from"./lib/roles";import{websocket as Vj,createWsUpgradeHandler as cj}from"./ws/index";import{publish as gj,subscribe as qj,unsubscribe as bj,getSubscriptions as wj,handleRoomActions as Aj,getRooms as hj,getRoomSubscribers as nj}from"./lib/ws";export{Vj as websocket,O as verifyToken,a as validate,Bj as userAuth,bj as unsubscribe,m as trackAttempt,qj as subscribe,Yj as startSqliteCleanup,Wj as sqliteAuthAdapter,N as signToken,yj as setUserRoles,Xj as setSqliteDb,w as setSessionStore,Lj as setCacheStore,Gj as requireVerifiedEmail,Dj as requireRole,Rj as removeUserRole,vj as rateLimit,gj as publish,$ as mongoose,Sj as mongoAuthAdapter,_j as memoryAuthAdapter,Q as log,l as isLimited,jj as identify,Aj as handleRoomActions,n as getVerificationToken,wj as getSubscriptions,q as getSession,hj as getRooms,nj as getRoomSubscribers,R as getRedis,D as getAppRoles,E as disconnectRedis,Y as disconnectMongo,p as deleteVerificationToken,b as deleteSession,cj as createWsUpgradeHandler,c as createWorker,h as createVerificationToken,g as createSession,B as createServer,L as createRouter,V as createQueue,v as createApp,y as connectRedis,T as connectMongo,W as connectAuthMongo,X as connectAppMongo,$j as clearMemoryStore,Ij as cacheResponse,Kj as bustCachePattern,Jj as bustCache,o as bustAuthLimit,Nj as buildFingerprint,r as botProtection,s as bearerAuth,Z as authConnection,_ as appConnection,Ej as addUserRole,G as HttpError,J as HEADER_USER_TOKEN,I as COOKIE_TOKEN,Pj as AuthUser};
2
+ var UZ=Object.defineProperty;var LZ=(Q)=>Q;function DZ(Q,J){this[Q]=LZ.bind(null,J)}var RQ=(Q,J)=>{for(var Z in J)UZ(Q,Z,{get:J[Z],enumerable:!0,configurable:!0,set:DZ.bind(J,Z)})};var t=(Q,J)=>()=>(Q&&(J=Q(Q=0)),J);var N;var k=t(()=>{N=class N extends Error{status;constructor(Q,J){super(J);this.status=Q}}});var wJ,wZ,H=(...Q)=>{if(wZ)console.log(...Q)};var d=t(()=>{wJ=process.env.LOGGING_VERBOSE,wZ=wJ!==void 0?wJ==="true":!0});var WQ={};RQ(WQ,{getRedisConnectionOptions:()=>y,getRedis:()=>E,disconnectRedis:()=>SJ,connectRedis:()=>BQ});import SZ from"ioredis";var YQ=!1,y=()=>{let Q=YQ?process.env.REDIS_HOST_PROD:process.env.REDIS_HOST_DEV;if(!Q)throw Error(`Missing env var: ${YQ?"REDIS_HOST_PROD":"REDIS_HOST_DEV"}`);let[J,Z]=Q.split(":");if(!J||!Z)throw Error(`Invalid Redis host format \u2014 expected "host:port", got "${Q}"`);let j=YQ?process.env.REDIS_USER_PROD:process.env.REDIS_USER_DEV,$=YQ?process.env.REDIS_PW_PROD:process.env.REDIS_PW_DEV;return{host:J,port:Number(Z),...j&&{username:j},...$&&{password:$}}},A=null,_Z=()=>{let Q=new SZ(y());return Q.on("error",(J)=>H(`[redis] error: ${J.message}`)),Q},BQ=()=>{if(A)return Promise.resolve();return A=_Z(),new Promise((Q,J)=>{A.once("ready",()=>{H(`[redis] connected to ${y().host}:${y().port} as ${y().username||"default user"}`),Q()}),A.once("error",J)})},SJ=async()=>{if(!A)return;await A.quit(),A=null,H("[redis] disconnected")},E=()=>{if(!A)throw Error("Redis not connected \u2014 call connectRedis() first");return A};var R=t(()=>{d()});var iQ={};RQ(iQ,{startSqliteCleanup:()=>yJ,sqliteStoreOAuthState:()=>gQ,sqliteSetCache:()=>cQ,sqliteGetVerificationToken:()=>dQ,sqliteGetSession:()=>fQ,sqliteGetCache:()=>pQ,sqliteDeleteVerificationToken:()=>aQ,sqliteDeleteSession:()=>mQ,sqliteDelCachePattern:()=>uQ,sqliteDelCache:()=>nQ,sqliteCreateVerificationToken:()=>lQ,sqliteCreateSession:()=>yQ,sqliteConsumeOAuthState:()=>hQ,sqliteAuthAdapter:()=>kJ,setSqliteDb:()=>bJ,isSqliteReady:()=>i});import{Database as cZ}from"bun:sqlite";function W(){if(!g)throw Error("SQLite not initialized \u2014 call setSqliteDb(path) before using sqliteAuthAdapter or sessionStore: 'sqlite'");return g}function nZ(Q){Q.run(`CREATE TABLE IF NOT EXISTS users (
3
+ id TEXT PRIMARY KEY,
4
+ email TEXT UNIQUE,
5
+ passwordHash TEXT,
6
+ providerIds TEXT NOT NULL DEFAULT '[]',
7
+ roles TEXT NOT NULL DEFAULT '[]',
8
+ emailVerified INTEGER NOT NULL DEFAULT 0
9
+ )`);try{Q.run("ALTER TABLE users ADD COLUMN emailVerified INTEGER NOT NULL DEFAULT 0")}catch{}Q.run(`CREATE TABLE IF NOT EXISTS sessions (
10
+ userId TEXT PRIMARY KEY,
11
+ token TEXT NOT NULL,
12
+ expiresAt INTEGER NOT NULL
13
+ )`),Q.run(`CREATE TABLE IF NOT EXISTS oauth_states (
14
+ state TEXT PRIMARY KEY,
15
+ codeVerifier TEXT,
16
+ linkUserId TEXT,
17
+ expiresAt INTEGER NOT NULL
18
+ )`),Q.run(`CREATE TABLE IF NOT EXISTS cache_entries (
19
+ key TEXT PRIMARY KEY,
20
+ value TEXT NOT NULL,
21
+ expiresAt INTEGER -- NULL = indefinite
22
+ )`),Q.run(`CREATE TABLE IF NOT EXISTS email_verifications (
23
+ token TEXT PRIMARY KEY,
24
+ userId TEXT NOT NULL,
25
+ email TEXT NOT NULL,
26
+ expiresAt INTEGER NOT NULL
27
+ )`)}var g=null,bJ=(Q)=>{g=new cZ(Q,{create:!0}),g.run("PRAGMA journal_mode = WAL"),g.run("PRAGMA foreign_keys = ON"),nZ(g)},kJ,uZ=604800000,yQ=(Q,J)=>{let Z=Date.now()+uZ;W().run("INSERT INTO sessions (userId, token, expiresAt) VALUES (?, ?, ?) ON CONFLICT(userId) DO UPDATE SET token = excluded.token, expiresAt = excluded.expiresAt",[Q,J,Z])},fQ=(Q)=>{return W().query("SELECT token FROM sessions WHERE userId = ? AND expiresAt > ?").get(Q,Date.now())?.token??null},mQ=(Q)=>{W().run("DELETE FROM sessions WHERE userId = ?",[Q])},lZ=300000,gQ=(Q,J,Z)=>{let j=Date.now()+lZ;W().run("INSERT INTO oauth_states (state, codeVerifier, linkUserId, expiresAt) VALUES (?, ?, ?, ?)",[Q,J??null,Z??null,j])},hQ=(Q)=>{let J=W().query("DELETE FROM oauth_states WHERE state = ? AND expiresAt > ? RETURNING codeVerifier, linkUserId").get(Q,Date.now());if(!J)return null;return{codeVerifier:J.codeVerifier??void 0,linkUserId:J.linkUserId??void 0}},i=()=>g!==null,pQ=(Q)=>{return W().query("SELECT value FROM cache_entries WHERE key = ? AND (expiresAt IS NULL OR expiresAt > ?)").get(Q,Date.now())?.value??null},cQ=(Q,J,Z)=>{let j=Z?Date.now()+Z*1000:null;W().run("INSERT INTO cache_entries (key, value, expiresAt) VALUES (?, ?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value, expiresAt = excluded.expiresAt",[Q,J,j])},nQ=(Q)=>{W().run("DELETE FROM cache_entries WHERE key = ?",[Q])},uQ=(Q)=>{let J=Q.replace(/%/g,"\\%").replace(/_/g,"\\_").replace(/\*/g,"%");W().run("DELETE FROM cache_entries WHERE key LIKE ? ESCAPE '\\'",[J])},lQ=(Q,J,Z,j)=>{let $=Date.now()+j*1000;W().run("INSERT INTO email_verifications (token, userId, email, expiresAt) VALUES (?, ?, ?, ?)",[Q,J,Z,$])},dQ=(Q)=>{return W().query("SELECT userId, email FROM email_verifications WHERE token = ? AND expiresAt > ?").get(Q,Date.now())??null},aQ=(Q)=>{W().run("DELETE FROM email_verifications WHERE token = ?",[Q])},yJ=(Q=3600000)=>{return setInterval(()=>{let J=W(),Z=Date.now();J.run("DELETE FROM sessions WHERE expiresAt <= ?",[Z]),J.run("DELETE FROM oauth_states WHERE expiresAt <= ?",[Z]),J.run("DELETE FROM cache_entries WHERE expiresAt IS NOT NULL AND expiresAt <= ?",[Z]),J.run("DELETE FROM email_verifications WHERE expiresAt <= ?",[Z])},Q)};var f=t(()=>{k();kJ={async findByEmail(Q){return W().query("SELECT id, passwordHash FROM users WHERE email = ?").get(Q)??null},async create(Q,J){let Z=crypto.randomUUID();try{return W().run("INSERT INTO users (id, email, passwordHash) VALUES (?, ?, ?)",[Z,Q,J]),{id:Z}}catch(j){if(j?.code==="SQLITE_CONSTRAINT_UNIQUE")throw new N(409,"Email already registered");throw j}},async setPassword(Q,J){W().run("UPDATE users SET passwordHash = ? WHERE id = ?",[J,Q])},async findOrCreateByProvider(Q,J,Z){let j=`${Q}:${J}`,$=W(),z=$.query("SELECT u.id FROM users u, json_each(u.providerIds) p WHERE p.value = ?").get(j);if(z)return{id:z.id,created:!1};if(Z.email){if($.query("SELECT id FROM users WHERE email = ?").get(Z.email))throw new N(409,"An account with this email already exists. Sign in with your credentials, then link Google from your account settings.")}let X=crypto.randomUUID();return $.run("INSERT INTO users (id, email, providerIds) VALUES (?, ?, ?)",[X,Z.email??null,JSON.stringify([j])]),{id:X,created:!0}},async linkProvider(Q,J,Z){let j=`${J}:${Z}`,$=W(),z=$.query("SELECT id, providerIds FROM users WHERE id = ?").get(Q);if(!z)throw new N(404,"User not found");let X=JSON.parse(z.providerIds);if(!X.includes(j))$.run("UPDATE users SET providerIds = ? WHERE id = ?",[JSON.stringify([...X,j]),Q])},async getRoles(Q){let J=W().query("SELECT roles FROM users WHERE id = ?").get(Q);return J?JSON.parse(J.roles):[]},async setRoles(Q,J){W().run("UPDATE users SET roles = ? WHERE id = ?",[JSON.stringify(J),Q])},async addRole(Q,J){let Z=W(),j=Z.query("SELECT roles FROM users WHERE id = ?").get(Q);if(!j)return;let $=JSON.parse(j.roles);if(!$.includes(J))Z.run("UPDATE users SET roles = ? WHERE id = ?",[JSON.stringify([...$,J]),Q])},async removeRole(Q,J){let Z=W(),j=Z.query("SELECT roles FROM users WHERE id = ?").get(Q);if(!j)return;let $=JSON.parse(j.roles);Z.run("UPDATE users SET roles = ? WHERE id = ?",[JSON.stringify($.filter((z)=>z!==J)),Q])},async getUser(Q){let J=W().query("SELECT email, providerIds, emailVerified FROM users WHERE id = ?").get(Q);if(!J)return null;return{email:J.email??void 0,providerIds:JSON.parse(J.providerIds),emailVerified:J.emailVerified===1}},async unlinkProvider(Q,J){let Z=W(),j=Z.query("SELECT providerIds FROM users WHERE id = ?").get(Q);if(!j)throw new N(404,"User not found");let $=JSON.parse(j.providerIds);Z.run("UPDATE users SET providerIds = ? WHERE id = ?",[JSON.stringify($.filter((z)=>!z.startsWith(`${J}:`))),Q])},async findByIdentifier(Q){return W().query("SELECT id, passwordHash FROM users WHERE email = ?").get(Q)??null},async setEmailVerified(Q,J){W().run("UPDATE users SET emailVerified = ? WHERE id = ?",[J?1:0,Q])},async getEmailVerified(Q){return W().query("SELECT emailVerified FROM users WHERE id = ?").get(Q)?.emailVerified===1}}});var FZ={};RQ(FZ,{botProtection:()=>WZ});function BZ(Q){let J=Q.split(".").map(Number);return(J[0]<<24|J[1]<<16|J[2]<<8|J[3])>>>0}function q0(Q,J){let Z=Q.indexOf("/"),j=Z===-1?Q:Q.slice(0,Z),$=Z===-1?32:parseInt(Q.slice(Z+1),10),z=$===0?0:-1<<32-$>>>0;return(BZ(j)&z)===(BZ(J)&z)}function L0(Q){if(Q.startsWith("::ffff:"))return Q.slice(7);return Q}function D0(Q,J){let Z=L0(Q),j=U0.test(Z);for(let $ of J)if(j){if(q0($,Z))return!0}else if($===Z)return!0;return!1}var U0,WZ=({blockList:Q=[]})=>{if(Q.length===0)return(J,Z)=>Z();return async(J,Z)=>{let $=(J.req.header("x-forwarded-for")??"").split(",")[0]?.trim()??"unknown";if($!=="unknown"&&D0($,Q))return J.json({error:"Forbidden"},403);await Z()}};var YJ=t(()=>{U0=/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/});k();import{OpenAPIHono as R0}from"@hono/zod-openapi";import{cors as C0}from"hono/cors";import{logger as v0}from"hono/logger";import{secureHeaders as w0}from"hono/secure-headers";import{Scalar as S0}from"@scalar/hono-api-reference";var VJ="Core API",xJ=[],KJ=null,RZ="email",PJ=null,qJ=(Q)=>{VJ=Q},O=()=>VJ,UJ=(Q)=>{xJ=Q},CZ=()=>xJ,LJ=(Q)=>{KJ=Q},DJ=()=>KJ,RJ=(Q)=>{RZ=Q};var CJ=(Q)=>{PJ=Q};var vZ=86400,vJ=()=>PJ?.tokenExpiry??vZ;var FQ=new Map,_J={async get(Q){let J=FQ.get(Q);if(!J)return null;if(J.resetAt<=Date.now())return FQ.delete(Q),null;return J},async set(Q,J){FQ.set(Q,J)},async delete(Q){FQ.delete(Q)}},AZ={async get(Q){let{getRedis:J}=await Promise.resolve().then(() => (R(),WQ)),Z=await J().get(`rl:${O()}:${Q}`);if(!Z)return null;let j=JSON.parse(Z);if(j.resetAt<=Date.now())return null;return j},async set(Q,J,Z){let{getRedis:j}=await Promise.resolve().then(() => (R(),WQ));await j().set(`rl:${O()}:${Q}`,JSON.stringify(J),"PX",Z)},async delete(Q){let{getRedis:J}=await Promise.resolve().then(() => (R(),WQ));await J().del(`rl:${O()}:${Q}`)}},a=_J,AJ=(Q)=>{a=Q==="redis"?AZ:_J},IZ=async(Q,J)=>{let Z=await a.get(Q);if(!Z)return!1;return Z.count>=J.max},GQ=async(Q,J)=>{let Z=Date.now(),j=await a.get(Q);if(!j)return await a.set(Q,{count:1,resetAt:Z+J.windowMs},J.windowMs),1>=J.max;let $={count:j.count+1,resetAt:j.resetAt},z=Math.max(1,j.resetAt-Z);return await a.set(Q,$,z),$.count>=J.max},bZ=async(Q)=>{await a.delete(Q)};var kZ=["sec-fetch-site","sec-fetch-mode","sec-fetch-dest","sec-ch-ua","sec-ch-ua-mobile","sec-ch-ua-platform","origin","referer","x-requested-with"],yZ=new TextEncoder;async function CQ(Q){let J=(X)=>Q.headers.get(X)??"",Z=kZ.map((X)=>Q.headers.has(X)?"1":"0").join(""),j=[J("user-agent"),J("accept"),J("accept-language"),J("accept-encoding"),J("connection"),Z].join("|"),$=await crypto.subtle.digest("SHA-256",yZ.encode(j)),z=new Uint8Array($).slice(0,6);return Array.from(z).map((X)=>X.toString(16).padStart(2,"0")).join("")}var vQ=({windowMs:Q,max:J,fingerprintLimit:Z=!1})=>{let j={windowMs:Q,max:J};return async($,z)=>{let F=($.req.header("x-forwarded-for")??"").split(",")[0]?.trim()||"unknown";if(await GQ(`ip:${F}`,j))return $.json({error:"Too Many Requests"},429);if(Z){let B=await CQ($.req.raw);if(await GQ(`fp:${B}`,j))return $.json({error:"Too Many Requests"},429)}await z()}};var fZ=process.env.BEARER_TOKEN_DEV,wQ=async(Q,J)=>{let Z=Q.req.header("Authorization"),j=Z?.startsWith("Bearer ")?Z.slice(7):null;if(!j||j!==fZ)return Q.json({error:"Unauthorized"},401);await J()};import{getCookie as rZ}from"hono/cookie";import{SignJWT as mZ,jwtVerify as gZ}from"jose";var hZ=!1,IJ=new TextEncoder().encode(hZ?process.env.JWT_SECRET_PROD:process.env.JWT_SECRET_DEV),SQ=async(Q)=>new mZ({sub:Q}).setProtectedHeader({alg:"HS256"}).setExpirationTime("7d").sign(IJ),r=async(Q)=>{let{payload:J}=await gZ(Q,IJ);return J};R();d();import _Q from"mongoose";var L=!1;function AQ(Q,J,Z,j){let[$,z]=Z.split("?");return`mongodb+srv://${Q}:${J}@${$.replace(/\/$/,"")}/${j}${z?`?${z}`:""}`}var m=_Q.createConnection(),V=_Q.createConnection(),IQ=async()=>{let Q=L?process.env.MONGO_AUTH_USER_PROD:process.env.MONGO_AUTH_USER_DEV,J=L?process.env.MONGO_AUTH_PW_PROD:process.env.MONGO_AUTH_PW_DEV,Z=L?process.env.MONGO_AUTH_HOST_PROD:process.env.MONGO_AUTH_HOST_DEV,j=L?process.env.MONGO_AUTH_DB_PROD:process.env.MONGO_AUTH_DB_DEV,$=AQ(Q,J,Z,j);await m.openUri($),H(`[mongo] auth connected to ${Z} as ${Q}`)},bQ=async()=>{let Q=L?process.env.MONGO_USER_PROD:process.env.MONGO_USER_DEV,J=L?process.env.MONGO_PW_PROD:process.env.MONGO_PW_DEV,Z=L?process.env.MONGO_HOST_PROD:process.env.MONGO_HOST_DEV,j=L?process.env.MONGO_DB_PROD:process.env.MONGO_DB_DEV,$=AQ(Q,J,Z,j);await V.openUri($),H(`[mongo] app connected to ${Z} as ${Q}`)},kQ=async()=>{let Q=L?process.env.MONGO_USER_PROD:process.env.MONGO_USER_DEV,J=L?process.env.MONGO_PW_PROD:process.env.MONGO_PW_DEV,Z=L?process.env.MONGO_HOST_PROD:process.env.MONGO_HOST_DEV,j=L?process.env.MONGO_DB_PROD:process.env.MONGO_DB_DEV,$=AQ(Q,J,Z,j);await Promise.all([m.openUri($),V.openUri($)]),H(`[mongo] connected to ${Z} as ${Q}`)},pZ=async()=>{await Promise.all([m.readyState!==0?m.close():Promise.resolve(),V.readyState!==0?V.close():Promise.resolve()]),H("[mongo] disconnected")};f();import{Schema as oZ}from"mongoose";k();var K=new Map,h=new Map,NQ=new Map,e=new Map,p=new Map,QQ=new Map,dZ=()=>{K.clear(),h.clear(),NQ.clear(),e.clear(),p.clear(),QQ.clear()},oQ={async findByEmail(Q){let J=h.get(Q.toLowerCase());if(!J)return null;let Z=K.get(J);if(!Z||!Z.passwordHash)return null;return{id:Z.id,passwordHash:Z.passwordHash}},async create(Q,J){let Z=Q.toLowerCase();if(h.has(Z))throw new N(409,"Email already registered");let j=crypto.randomUUID(),$={id:j,email:Z,passwordHash:J,providerIds:[],roles:[],emailVerified:!1};return K.set(j,$),h.set(Z,j),{id:j}},async setPassword(Q,J){let Z=K.get(Q);if(!Z)return;Z.passwordHash=J},async findOrCreateByProvider(Q,J,Z){let j=`${Q}:${J}`;for(let F of K.values())if(F.providerIds.includes(j))return{id:F.id,created:!1};if(Z.email){if(h.get(Z.email.toLowerCase()))throw new N(409,"An account with this email already exists. Sign in with your credentials, then link Google from your account settings.")}let $=crypto.randomUUID(),z=Z.email?Z.email.toLowerCase():null,X={id:$,email:z,passwordHash:null,providerIds:[j],roles:[],emailVerified:!1};if(K.set($,X),z)h.set(z,$);return{id:$,created:!0}},async linkProvider(Q,J,Z){let j=K.get(Q);if(!j)throw new N(404,"User not found");let $=`${J}:${Z}`;if(!j.providerIds.includes($))j.providerIds.push($)},async getRoles(Q){return K.get(Q)?.roles??[]},async setRoles(Q,J){let Z=K.get(Q);if(!Z)return;Z.roles=[...J]},async addRole(Q,J){let Z=K.get(Q);if(!Z)return;if(!Z.roles.includes(J))Z.roles.push(J)},async removeRole(Q,J){let Z=K.get(Q);if(!Z)return;Z.roles=Z.roles.filter((j)=>j!==J)},async getUser(Q){let J=K.get(Q);if(!J)return null;return{email:J.email??void 0,providerIds:[...J.providerIds],emailVerified:J.emailVerified}},async unlinkProvider(Q,J){let Z=K.get(Q);if(!Z)throw new N(404,"User not found");Z.providerIds=Z.providerIds.filter((j)=>!j.startsWith(`${J}:`))},async findByIdentifier(Q){let J=h.get(Q.toLowerCase());if(!J)return null;let Z=K.get(J);if(!Z||!Z.passwordHash)return null;return{id:Z.id,passwordHash:Z.passwordHash}},async setEmailVerified(Q,J){let Z=K.get(Q);if(Z)Z.emailVerified=J},async getEmailVerified(Q){return K.get(Q)?.emailVerified??!1}},aZ=604800000,fJ=(Q,J)=>{NQ.set(Q,{token:J,expiresAt:Date.now()+aZ})},mJ=(Q)=>{let J=NQ.get(Q);if(!J||J.expiresAt<=Date.now())return null;return J.token},gJ=(Q)=>{NQ.delete(Q)},iZ=300000,hJ=(Q,J,Z)=>{e.set(Q,{codeVerifier:J,linkUserId:Z,expiresAt:Date.now()+iZ})},pJ=(Q)=>{let J=e.get(Q);if(!J||J.expiresAt<=Date.now())return e.delete(Q),null;return e.delete(Q),{codeVerifier:J.codeVerifier,linkUserId:J.linkUserId}},cJ=(Q)=>{let J=p.get(Q);if(!J)return null;if(J.expiresAt!==void 0&&J.expiresAt<=Date.now())return p.delete(Q),null;return J.value},nJ=(Q,J,Z)=>{let j=Z?Date.now()+Z*1000:void 0;p.set(Q,{value:J,expiresAt:j})},uJ=(Q)=>{p.delete(Q)},lJ=(Q)=>{let J=new RegExp("^"+Q.replace(/[.+^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*")+"$");for(let Z of p.keys())if(J.test(Z))p.delete(Z)},dJ=(Q,J,Z,j)=>{QQ.set(Q,{userId:J,email:Z,expiresAt:Date.now()+j*1000})},aJ=(Q)=>{let J=QQ.get(Q);if(!J||J.expiresAt<=Date.now())return QQ.delete(Q),null;return{userId:J.userId,email:J.email}},iJ=(Q)=>{QQ.delete(Q)};var sZ=new oZ({userId:{type:String,required:!0,unique:!0},token:{type:String,required:!0},expiresAt:{type:Date,required:!0,index:{expireAfterSeconds:0}}},{collection:"sessions"});function sQ(){return V.models.Session??V.model("Session",sZ)}var v="redis",tQ=(Q)=>{v=Q},oJ=604800,rQ=async(Q,J)=>{if(v==="memory"){fJ(Q,J);return}if(v==="sqlite"){yQ(Q,J);return}if(v==="mongo"){let Z=new Date(Date.now()+oJ*1000);await sQ().updateOne({userId:Q},{$set:{token:J,expiresAt:Z}},{upsert:!0});return}await E().set(`session:${O()}:${Q}`,J,"EX",oJ)},JQ=async(Q)=>{if(v==="memory")return mJ(Q);if(v==="sqlite")return fQ(Q);if(v==="mongo"){let J=await sQ().findOne({userId:Q,expiresAt:{$gt:new Date}},"token").lean();return J?J.token:null}return E().get(`session:${O()}:${Q}`)},tZ=async(Q)=>{if(v==="memory"){gJ(Q);return}if(v==="sqlite"){mQ(Q);return}if(v==="mongo"){await sQ().deleteOne({userId:Q});return}await E().del(`session:${O()}:${Q}`)};var c="token",ZQ="x-user-token";d();var eQ=async(Q,J)=>{Q.set("authUserId",null),Q.set("roles",null);let Z=rZ(Q,c)??Q.req.header(ZQ)??null;if(H(`[identify] token=${Z?"present":"absent"}`),Z)try{let j=await r(Z),$=await JQ(j.sub);if(H(`[identify] token for authUserId=${j.sub} verified, checking session...`),$===Z)Q.set("authUserId",j.sub),H(`[identify] authUserId=${j.sub}`);else H("[identify] token/session mismatch \u2014 unauthenticated")}catch{H("[identify] invalid token \u2014 unauthenticated")}else H("[identify] no token \u2014 unauthenticated");await J()};R();f();import{Schema as eZ}from"mongoose";var Q0=new eZ({token:{type:String,required:!0,unique:!0},userId:{type:String,required:!0},email:{type:String,required:!0},expiresAt:{type:Date,required:!0,index:{expireAfterSeconds:0}}},{collection:"email_verifications"});function QJ(){return V.models.EmailVerification??V.model("EmailVerification",Q0)}var w="redis",sJ=(Q)=>{w=Q},J0=async(Q,J)=>{let Z=crypto.randomUUID(),j=vJ();if(w==="memory")return dJ(Z,Q,J,j),Z;if(w==="sqlite")return lQ(Z,Q,J,j),Z;if(w==="mongo")return await QJ().create({token:Z,userId:Q,email:J,expiresAt:new Date(Date.now()+j*1000)}),Z;return await E().set(`verify:${O()}:${Z}`,JSON.stringify({userId:Q,email:J}),"EX",j),Z},Z0=async(Q)=>{if(w==="memory")return aJ(Q);if(w==="sqlite")return dQ(Q);if(w==="mongo"){let Z=await QJ().findOne({token:Q,expiresAt:{$gt:new Date}}).lean();if(!Z)return null;return{userId:Z.userId,email:Z.email}}let J=await E().get(`verify:${O()}:${Q}`);if(!J)return null;return JSON.parse(J)},j0=async(Q)=>{if(w==="memory"){iJ(Q);return}if(w==="sqlite"){aQ(Q);return}if(w==="mongo"){await QJ().deleteOne({token:Q});return}await E().del(`verify:${O()}:${Q}`)};var JJ=null,tJ=(Q)=>{JJ=Q},U=()=>{if(!JJ)throw Error("No auth adapter set \u2014 pass authAdapter to createApp/createServer, or call setAuthAdapter()");return JJ};import $0 from"mongoose";var rJ=new $0.Schema({email:{type:String,unique:!0,sparse:!0,lowercase:!0},password:{type:String},providerIds:[{type:String}],roles:[{type:String}],emailVerified:{type:Boolean,default:!1}},{timestamps:!0});rJ.index({providerIds:1});var M=m.model("AuthUser",rJ);k();var ZJ={async findByEmail(Q){let J=await M.findOne({email:Q});if(!J)return null;return{id:String(J._id),passwordHash:J.password}},async create(Q,J){try{let Z=await M.create({email:Q,password:J});return{id:String(Z._id)}}catch(Z){if(Z?.code===11000)throw new N(409,"Email already registered");throw Z}},async setPassword(Q,J){await M.findByIdAndUpdate(Q,{password:J})},async findOrCreateByProvider(Q,J,Z){let j=`${Q}:${J}`,$=await M.findOne({providerIds:j});if($)return{id:String($._id),created:!1};if(Z.email){if(await M.findOne({email:Z.email}))throw new N(409,"An account with this email already exists. Sign in with your credentials, then link Google from your account settings.")}return $=await M.create({email:Z.email,providerIds:[j]}),{id:String($._id),created:!0}},async linkProvider(Q,J,Z){let j=`${J}:${Z}`,$=await M.findById(Q);if(!$)throw new N(404,"User not found");if(!$.providerIds.includes(j))$.providerIds=[...$.providerIds,j],await $.save()},async getRoles(Q){return(await M.findById(Q,"roles").lean())?.roles??[]},async setRoles(Q,J){await M.findByIdAndUpdate(Q,{roles:J})},async addRole(Q,J){await M.findByIdAndUpdate(Q,{$addToSet:{roles:J}})},async removeRole(Q,J){await M.findByIdAndUpdate(Q,{$pull:{roles:J}})},async getUser(Q){let J=await M.findById(Q,"email providerIds emailVerified").lean();if(!J)return null;return{email:J.email,providerIds:J.providerIds,emailVerified:J.emailVerified??!1}},async unlinkProvider(Q,J){let Z=await M.findById(Q);if(!Z)throw new N(404,"User not found");Z.providerIds=Z.providerIds.filter((j)=>!j.startsWith(`${J}:`)),await Z.save()},async findByIdentifier(Q){let J=await M.findOne({email:Q});if(!J)return null;return{id:String(J._id),passwordHash:J.password}},async setEmailVerified(Q,J){await M.findByIdAndUpdate(Q,{emailVerified:J})},async getEmailVerified(Q){return(await M.findById(Q,"emailVerified").lean())?.emailVerified??!1}};R();import{Google as z0,Apple as X0,generateState as jQ,generateCodeVerifier as jJ}from"arctic";f();import{Schema as Y0}from"mongoose";var u={},QZ=(Q)=>{if(Q.google){let{clientId:J,clientSecret:Z,redirectUri:j}=Q.google;u.google=new z0(J,Z,j)}if(Q.apple){let{clientId:J,teamId:Z,keyId:j,privateKey:$,redirectUri:z}=Q.apple;u.apple=new X0(J,Z,j,new TextEncoder().encode($),z)}},EQ=()=>{if(!u.google)throw Error("Google OAuth not configured");return u.google},HQ=()=>{if(!u.apple)throw Error("Apple OAuth not configured");return u.apple},JZ=()=>Object.entries(u).filter(([,Q])=>Q!=null).map(([Q])=>Q),B0=new Y0({state:{type:String,required:!0,unique:!0},codeVerifier:{type:String},linkUserId:{type:String},expiresAt:{type:Date,required:!0,index:{expireAfterSeconds:0}}},{collection:"oauth_states"});function ZZ(){return V.models.OAuthState??V.model("OAuthState",B0)}var n="redis",jZ=(Q)=>{n=Q},eJ=300,$Q=async(Q,J,Z)=>{if(n==="memory"){hJ(Q,J,Z);return}if(n==="sqlite"){gQ(Q,J,Z);return}if(n==="mongo"){let j=new Date(Date.now()+eJ*1000);await ZZ().create({state:Q,codeVerifier:J,linkUserId:Z,expiresAt:j});return}await E().set(`oauth:${O()}:state:${Q}`,JSON.stringify({codeVerifier:J,linkUserId:Z}),"EX",eJ)},$J=async(Q)=>{if(n==="memory")return pJ(Q);if(n==="sqlite")return hQ(Q);if(n==="mongo"){let j=await ZZ().findOneAndDelete({state:Q,expiresAt:{$gt:new Date}}).lean();return j?{codeVerifier:j.codeVerifier,linkUserId:j.linkUserId}:null}let J=`oauth:${O()}:state:${Q}`,Z=await E().get(J);if(!Z)return null;return await E().del(J),JSON.parse(Z)};import{OpenAPIHono as W0}from"@hono/zod-openapi";var F0=(Q,J)=>{if(!Q.success){let Z=Q.error.issues.map((j)=>j.message).join(", ");return J.json({error:Z},400)}},zJ=()=>new W0({defaultHook:F0});import{setCookie as G0}from"hono/cookie";import{decodeIdToken as N0}from"arctic";k();var zQ=async(Q,J)=>{if(!Q.get("authUserId"))return Q.json({error:"Unauthorized"},401);await J()};var E0=!1,H0={httpOnly:!0,secure:E0,sameSite:"Lax",path:"/",maxAge:604800},$Z=async(Q,J,Z,j,$)=>{let z=U();if(!z.findOrCreateByProvider)return Q.json({error:"Auth adapter does not support social login"},500);let X;try{X=await z.findOrCreateByProvider(J,Z,j)}catch(B){let G=B instanceof N?B.message:"Authentication failed",P=$.includes("?")?"&":"?";return Q.redirect(`${$}${P}error=${encodeURIComponent(G)}`)}if(X.created){let B=DJ();if(B&&z.setRoles)await z.setRoles(X.id,[B])}let F=await SQ(X.id);await rQ(X.id,F),G0(Q,c,F,H0);try{let B=new URL($);if(B.searchParams.set("token",F),j.email)B.searchParams.set("user",j.email);return Q.redirect(B.toString())}catch{let B=$.includes("?")?"&":"?",G=j.email?`&user=${encodeURIComponent(j.email)}`:"";return Q.redirect(`${$}${B}token=${F}${G}`)}},zZ=(Q,J)=>{let Z=zJ();if(Q.includes("google"))Z.get("/auth/google",async(j)=>{let $=jQ(),z=jJ();await $Q($,z);let X=EQ().createAuthorizationURL($,z,["openid","profile","email"]);return j.redirect(X.toString())}),Z.get("/auth/google/callback",async(j)=>{let{code:$,state:z}=j.req.query();if(!$||!z)return j.json({error:"Invalid callback"},400);let X=await $J(z);if(!X?.codeVerifier)return j.json({error:"Invalid or expired state"},400);let F=await EQ().validateAuthorizationCode($,X.codeVerifier),B=await fetch("https://openidconnect.googleapis.com/v1/userinfo",{headers:{Authorization:`Bearer ${F.accessToken()}`}}).then((G)=>G.json());if(X.linkUserId){let G=U();if(!G.linkProvider)return j.json({error:"Auth adapter does not support linkProvider"},500);await G.linkProvider(X.linkUserId,"google",B.sub);let P=J.includes("?")?"&":"?";return j.redirect(`${J}${P}linked=google`)}return $Z(j,"google",B.sub,{email:B.email,name:B.name,avatarUrl:B.picture},J)}),Z.get("/auth/google/link",zQ,async(j)=>{let $=jQ(),z=jJ();await $Q($,z,j.get("authUserId"));let X=EQ().createAuthorizationURL($,z,["openid","profile","email"]);return j.redirect(X.toString())}),Z.delete("/auth/google/link",zQ,async(j)=>{let $=U();if(!$.unlinkProvider)return j.json({error:"Auth adapter does not support unlinkProvider"},500);return await $.unlinkProvider(j.get("authUserId"),"google"),j.body(null,204)});if(Q.includes("apple"))Z.get("/auth/apple",async(j)=>{let $=jQ();await $Q($);let z=HQ().createAuthorizationURL($,["name","email"]);return j.redirect(z.toString())}),Z.post("/auth/apple/callback",async(j)=>{let $=await j.req.formData(),z=$.get("code"),X=$.get("state");if(!z||!X)return j.json({error:"Invalid callback"},400);let F=await $J(X);if(!F)return j.json({error:"Invalid or expired state"},400);let B=await HQ().validateAuthorizationCode(z),G=N0(B.idToken());if(F.linkUserId){let b=U();if(!b.linkProvider)return j.json({error:"Auth adapter does not support linkProvider"},500);await b.linkProvider(F.linkUserId,"apple",G.sub);let D=J.includes("?")?"&":"?";return j.redirect(`${J}${D}linked=apple`)}let P=$.get("user"),q=P?JSON.parse(P):{},S=q.name?`${q.name.firstName??""} ${q.name.lastName??""}`.trim()||void 0:void 0;return $Z(j,"apple",G.sub,{email:G.email,name:S},J)}),Z.get("/auth/apple/link",zQ,async(j)=>{let $=jQ();await $Q($,void 0,j.get("authUserId"));let z=HQ().createAuthorizationURL($,["name","email"]);return j.redirect(z.toString())});return Z};R();R();f();import{Schema as M0}from"mongoose";var O0=new M0({key:{type:String,required:!0,unique:!0},value:{type:String,required:!0},expiresAt:{type:Date,index:{expireAfterSeconds:0}}},{collection:"cache_entries"});function TQ(){return V.models.CacheEntry??V.model("CacheEntry",O0)}function VQ(){return V.readyState===1}function XZ(){try{return E(),!0}catch{return!1}}var YZ="redis",XJ=(Q)=>{YZ=Q};async function T0(Q,J){if(Q==="memory")return cJ(J);if(Q==="sqlite"){if(!i())throw Error('cacheResponse: store is "sqlite" but SQLite is not initialized. Call setSqliteDb(path) or pass sqliteDb to createServer.');return pQ(J)}if(Q==="mongo"){if(!VQ())throw Error('cacheResponse: store is "mongo" but appConnection is not connected. Ensure connectMongo() or connectAppMongo() is called before handling requests.');let Z=await TQ().findOne({key:J},"value").lean();return Z?Z.value:null}return E().get(J)}async function V0(Q,J,Z,j){if(Q==="memory"){nJ(J,Z,j);return}if(Q==="sqlite"){if(!i())throw Error('cacheResponse: store is "sqlite" but SQLite is not initialized. Call setSqliteDb(path) or pass sqliteDb to createServer.');cQ(J,Z,j);return}if(Q==="mongo"){if(!VQ())throw Error('cacheResponse: store is "mongo" but appConnection is not connected. Ensure connectMongo() or connectAppMongo() is called before handling requests.');let $=j?new Date(Date.now()+j*1000):void 0;await TQ().updateOne({key:J},{$set:{value:Z,...$?{expiresAt:$}:{}}},{upsert:!0});return}if(j)await E().setex(J,j,Z);else await E().set(J,Z)}async function MQ(Q,J){if(Q==="memory"){uJ(J);return}if(Q==="sqlite"){if(!i())return;nQ(J);return}if(Q==="mongo"){if(!VQ())return;await TQ().deleteOne({key:J});return}if(!XZ())return;await E().del(J)}async function OQ(Q,J){if(Q==="memory"){lJ(J);return}if(Q==="sqlite"){if(!i())return;uQ(J);return}if(Q==="mongo"){if(!VQ())return;let $=new RegExp("^"+J.replace(/\*/g,".*")+"$");await TQ().deleteMany({key:$});return}if(!XZ())return;let Z=E(),j="0";do{let[$,z]=await Z.scan(j,"MATCH",J,"COUNT",100);if(j=$,z.length>0)await Z.del(...z)}while(j!=="0")}var x0=async(Q)=>{let J=`cache:${O()}:${Q}`;await Promise.all([MQ("redis",J),MQ("mongo",J),MQ("sqlite",J),MQ("memory",J)])},K0=async(Q)=>{let J=`cache:${O()}:${Q}`;await Promise.all([OQ("redis",J),OQ("mongo",J),OQ("sqlite",J),OQ("memory",J)])},P0=({ttl:Q,key:J,store:Z=YZ})=>{return async(j,$)=>{let z=O(),X=typeof J==="function"?J(j):J,F=`cache:${z}:${X}`,B=await T0(Z,F);if(B){let{status:P,headers:q,body:S}=JSON.parse(B);return new Response(S,{status:P,headers:{...q,"x-cache":"HIT"}})}await $();let G=j.res;if(G.status>=200&&G.status<300){let P=await G.text(),q={};G.headers.forEach((S,b)=>{q[b]=S}),await V0(Z,F,JSON.stringify({status:G.status,headers:q,body:P}),Q),j.res=new Response(P,{status:G.status,headers:{...q,"x-cache":"MISS"}})}}};var BJ=async(Q)=>{let{routesDir:J,app:Z={},auth:j={},security:$={},middleware:z=[],db:X={}}=Q,F=Z.name??"Bun Core API",B=Z.version??"1.0.0",G=$.cors??"*",P=$.rateLimit??{windowMs:60000,max:100},q=$.botProtection??{},S=$.bearerAuth!==!1,b=typeof $.bearerAuth==="object"&&$.bearerAuth!==null?$.bearerAuth.bypass??[]:[],D=j.enabled!==!1,x=j.adapter,_=j.oauth?.providers,xQ=j.oauth?.postRedirect??"/",OZ=j.roles??[],KQ=j.defaultRole,NJ=j.primaryField??"email",EJ=j.emailVerification,HJ=j.rateLimit,{sqlite:PQ,mongo:XQ="single",redis:qQ=!0}=X,MJ=qQ?"redis":PQ?"sqlite":XQ!==!1?"mongo":"memory",s=X.sessions??MJ,OJ=X.oauthState??s,TZ=X.cache??MJ,UQ=X.auth??(XQ!==!1?"mongo":s);if(PQ||s==="sqlite"||OJ==="sqlite"||UQ==="sqlite"){let{setSqliteDb:Y}=await Promise.resolve().then(() => (f(),iQ));Y(PQ??"./data.db")}if(tQ(s),jZ(OJ),XJ(TZ),XQ==="single")await kQ();else if(XQ==="separate")await Promise.all([IQ(),bQ()]);if(qQ)await BQ();let l;if(x)l=x;else if(UQ==="sqlite"){let{sqliteAuthAdapter:Y}=await Promise.resolve().then(() => (f(),iQ));l=Y}else if(UQ==="memory")l=oQ;else l=ZJ;if(tJ(l),UJ(OZ),LJ(KQ??null),RJ(NJ),CJ(EJ??null),sJ(s),AJ(HJ?.store??(qQ?"redis":"memory")),KQ&&!l.setRoles)throw Error(`createApp: "defaultRole" is set to "${KQ}" but the auth adapter does not implement setRoles. Add setRoles to your adapter or remove defaultRole.`);if(_)QZ(_);let LQ=JZ(),VZ=LQ.flatMap((Y)=>[`/auth/${Y}`,`/auth/${Y}/callback`,`/auth/${Y}/link`]),xZ=[...["/docs","/openapi.json","/sw.js","/health","/"],...VZ,...b],T=new R0;if(T.use(v0()),T.use(w0()),T.use(C0({origin:G,allowHeaders:["Content-Type","Authorization",ZQ],exposeHeaders:["x-cache"],credentials:!0})),(q.blockList?.length??0)>0){let{botProtection:Y}=await Promise.resolve().then(() => (YJ(),FZ));T.use(Y({blockList:q.blockList}))}if(T.use(vQ({...P,fingerprintLimit:q.fingerprintRateLimit??!1})),S)T.use(async(Y,C)=>{let qZ=Y.req.path;if(xZ.includes(qZ))return C();return wQ(Y,C)});T.use(eQ);for(let Y of z)T.use(Y);qJ(F);let DQ=import.meta.dir+"/routes",KZ=new Bun.Glob("*.ts");for await(let Y of KZ.scan({cwd:DQ})){if(Y==="auth.ts")continue;if(Y==="oauth.ts")continue;let C=await import(`${DQ}/${Y}`);if(C.router)T.route("/",C.router)}if(D){let{createAuthRouter:Y}=await import(`${DQ}/auth`);T.route("/",Y({primaryField:NJ,emailVerification:EJ,rateLimit:HJ}))}if(LQ.length>0)T.route("/",zZ(LQ,xQ));let PZ=new Bun.Glob("**/*.ts"),TJ=[];for await(let Y of PZ.scan({cwd:J}))TJ.push(Y);return(await Promise.all(TJ.map(async(Y)=>({file:Y,mod:await import(`${J}/${Y}`)})))).sort((Y,C)=>(Y.mod.priority??1/0)-(C.mod.priority??1/0)).forEach(({mod:Y})=>{if(Y.router)T.route("/",Y.router)}),T.onError((Y,C)=>{if(Y instanceof N)return C.json({error:Y.message},Y.status);return console.error(Y),C.json({error:"Internal Server Error"},500)}),T.notFound((Y)=>Y.json({error:"Not Found"},404)),T.doc("/openapi.json",{openapi:"3.0.0",info:{title:F,version:B}}),T.get("/docs",S0({url:"/openapi.json"})),T.get("/sw.js",(Y)=>Y.body("",200,{"Content-Type":"application/javascript"})),T};var WJ=(Q)=>async(J)=>{let Z=null;try{let $=J.headers.get("cookie")?.match(new RegExp(`(?:^|;\\s*)${c}=([^;]+)`))?.[1]??null;if($){let z=await r($);if(await JQ(z.sub)===$)Z=z.sub}}catch{}return Q.upgrade(J,{data:{id:crypto.randomUUID(),userId:Z,rooms:new Set}})?void 0:Response.json({error:"Upgrade failed"},{status:400})},o={open(Q){console.log(`[ws] connected: ${Q.data.id}`),Q.send(JSON.stringify({event:"connected",id:Q.data.id}))},message(Q,J){Q.send(J)},close(Q){console.log(`[ws] disconnected: ${Q.data.id}`)}};var GZ=null,NZ=(Q)=>{GZ=Q},_0=(Q,J)=>{GZ?.publish(Q,JSON.stringify(J))},I=new Map,A0=()=>[...I.keys()],I0=(Q)=>[...I.get(Q)??[]],FJ=async(Q,J,Z)=>{try{let j=JSON.parse(typeof J==="string"?J:Buffer.from(J).toString());if(j.action==="subscribe"&&typeof j.room==="string"){if(Z&&!await Z(Q,j.room))Q.send(JSON.stringify({event:"subscribe_denied",room:j.room}));else EZ(Q,j.room),Q.send(JSON.stringify({event:"subscribed",room:j.room}));return!0}if(j.action==="unsubscribe"&&typeof j.room==="string")return HZ(Q,j.room),Q.send(JSON.stringify({event:"unsubscribed",room:j.room})),!0}catch{}return!1},EZ=(Q,J)=>{if(Q.subscribe(J),Q.data.rooms.add(J),!I.has(J))I.set(J,new Set);I.get(J).add(Q.data.id)},HZ=(Q,J)=>{Q.unsubscribe(J),Q.data.rooms.delete(J);let Z=I.get(J);if(Z){if(Z.delete(Q.data.id),Z.size===0)I.delete(J)}},b0=(Q)=>[...Q.data.rooms],MZ=(Q,J)=>{for(let Z of J){let j=I.get(Z);if(j){if(j.delete(Q),j.size===0)I.delete(Z)}}};d();var k0=async(Q)=>{let J=await BJ(Q),Z=Number(process.env.PORT??Q.port??3000),{workersDir:j,enableWorkers:$=!0,ws:z={}}=Q,{handler:X,upgradeHandler:F,onRoomSubscribe:B}=z,G=o.open,P=o.message,q=o.close,S=o.drain,b={open:X?.open??G,async message(x,_){if(!await FJ(x,_,B))(X?.message??P)(x,_)},close(x,_,xQ){MZ(x.data.id,x.data.rooms),x.data.rooms.clear(),(X?.close??q)(x,_,xQ)},drain:X?.drain??S},D;if(D=Bun.serve({port:Z,routes:{"/ws":(x)=>F?F(x,D):WJ(D)(x)},fetch:J.fetch,websocket:b,error(x){return console.error(x),Response.json({error:"Internal Server Error"},{status:500})}}),NZ(D),$&&j){let x=new Bun.Glob("**/*.ts");for await(let _ of x.scan({cwd:j}))await import(`${j}/${_}`)}return H(`[server] running at http://localhost:${D.port}`),H(`[server] API docs at http://localhost:${D.port}/docs`),D};k();d();R();R();import{Queue as y0,Worker as f0}from"bullmq";var m0=(Q,J)=>new y0(Q,{connection:y(),...J}),g0=(Q,J,Z)=>new f0(Q,J,{connection:y(),...Z});k();import{z as h0}from"zod";var p0=async(Q,J)=>{try{let Z=await J.json();return Q.parse(Z)}catch(Z){if(Z instanceof h0.ZodError)throw new N(400,Z.issues.map((j)=>j.message).join(", "));throw Z}};YJ();var c0=(...Q)=>async(J,Z)=>{let j=J.get("authUserId");if(!j)return J.json({error:"Unauthorized"},401);let $=J.get("roles");if($===null){let X=U();if(!X.getRoles)throw Error("requireRole used but auth adapter does not implement getRoles");$=await X.getRoles(j),J.set("roles",$)}if(!Q.some((X)=>$.includes(X)))return J.json({error:"Forbidden"},403);await Z()};var n0=async(Q,J)=>{let Z=Q.get("authUserId");if(!Z)return Q.json({error:"Unauthorized"},401);let j=U();if(!j.getEmailVerified)throw Error("requireVerifiedEmail used but auth adapter does not implement getEmailVerified");if(!await j.getEmailVerified(Z))return Q.json({error:"Email not verified"},403);await J()};f();var GJ=(Q)=>{throw Error(`Auth adapter does not implement ${Q} \u2014 add it to your adapter to manage roles`)},u0=async(Q,J)=>{let Z=U();if(!Z.setRoles)GJ("setRoles");await Z.setRoles(Q,J)},l0=async(Q,J)=>{let Z=U();if(!Z.addRole)GJ("addRole");await Z.addRole(Q,J)},d0=async(Q,J)=>{let Z=U();if(!Z.removeRole)GJ("removeRole");await Z.removeRole(Q,J)};export{o as websocket,r as verifyToken,p0 as validate,zQ as userAuth,HZ as unsubscribe,GQ as trackAttempt,EZ as subscribe,yJ as startSqliteCleanup,kJ as sqliteAuthAdapter,SQ as signToken,u0 as setUserRoles,bJ as setSqliteDb,tQ as setSessionStore,XJ as setCacheStore,n0 as requireVerifiedEmail,c0 as requireRole,d0 as removeUserRole,vQ as rateLimit,_0 as publish,_Q as mongoose,ZJ as mongoAuthAdapter,oQ as memoryAuthAdapter,H as log,IZ as isLimited,eQ as identify,FJ as handleRoomActions,Z0 as getVerificationToken,b0 as getSubscriptions,JQ as getSession,A0 as getRooms,I0 as getRoomSubscribers,E as getRedis,CZ as getAppRoles,SJ as disconnectRedis,pZ as disconnectMongo,j0 as deleteVerificationToken,tZ as deleteSession,WJ as createWsUpgradeHandler,g0 as createWorker,J0 as createVerificationToken,rQ as createSession,k0 as createServer,zJ as createRouter,m0 as createQueue,BJ as createApp,BQ as connectRedis,kQ as connectMongo,IQ as connectAuthMongo,bQ as connectAppMongo,dZ as clearMemoryStore,P0 as cacheResponse,K0 as bustCachePattern,x0 as bustCache,bZ as bustAuthLimit,CQ as buildFingerprint,WZ as botProtection,wQ as bearerAuth,m as authConnection,V as appConnection,l0 as addUserRole,N as HttpError,ZQ as HEADER_USER_TOKEN,c as COOKIE_TOKEN,M as AuthUser};
@@ -2,6 +2,8 @@ export type PrimaryField = "email" | "username" | "phone";
2
2
  export interface EmailVerificationConfig {
3
3
  /** Block login until email is verified. Defaults to false (soft gate — emailVerified returned in login response). */
4
4
  required?: boolean;
5
+ /** Token time-to-live in seconds. Defaults to 86 400 (24 hours). */
6
+ tokenExpiry?: number;
5
7
  /** Called after registration with the identifier and verification token. Use to send the email. */
6
8
  onSend: (email: string, token: string) => Promise<void>;
7
9
  }
@@ -15,3 +17,4 @@ export declare const setPrimaryField: (field: PrimaryField) => void;
15
17
  export declare const getPrimaryField: () => PrimaryField;
16
18
  export declare const setEmailVerificationConfig: (config: EmailVerificationConfig | null) => void;
17
19
  export declare const getEmailVerificationConfig: () => EmailVerificationConfig | null;
20
+ export declare const getTokenExpiry: () => number;
@@ -4,7 +4,7 @@ type CacheStore = "redis" | "mongo" | "sqlite" | "memory";
4
4
  export declare const setCacheStore: (store: CacheStore) => void;
5
5
  export declare const bustCache: (key: string) => Promise<void>;
6
6
  export declare const bustCachePattern: (pattern: string) => Promise<void>;
7
- type KeyFn = (c: Parameters<MiddlewareHandler<AppEnv>>[0]) => string;
7
+ type KeyFn = (c: Parameters<MiddlewareHandler<any>>[0]) => string;
8
8
  interface CacheOptions {
9
9
  ttl?: number;
10
10
  key: string | KeyFn;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lastshotlabs/bunshot",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
4
4
  "description": "Batteries-included Bun + Hono API framework — auth, sessions, rate limiting, WebSocket, queues, and OpenAPI docs out of the box",
5
5
  "repository": {
6
6
  "type": "git",
@@ -31,7 +31,7 @@
31
31
  "dist"
32
32
  ],
33
33
  "scripts": {
34
- "build": "bun build src/index.ts --outdir dist --minify --target bun --external '*' && bun build src/cli.ts --outdir dist --minify --target bun --external '*' && tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json",
34
+ "build": "bun build src/index.ts --outdir dist --minify --target bun --external @hono/zod-openapi --external @scalar/hono-api-reference --external arctic --external bullmq --external hono --external ioredis --external jose --external mongoose --external zod && bun build src/cli.ts --outdir dist --minify --target bun --external @hono/zod-openapi --external @scalar/hono-api-reference --external arctic --external bullmq --external hono --external ioredis --external jose --external mongoose --external zod && tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json",
35
35
  "prepublishOnly": "bun run build",
36
36
  "release": "npm version patch && npm publish",
37
37
  "dev": "bun --watch src/index.ts",