@lastshotlabs/bunshot 0.0.13 → 0.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/README.md +2510 -1747
  2. package/dist/adapters/memoryAuth.d.ts +4 -0
  3. package/dist/adapters/memoryAuth.js +131 -2
  4. package/dist/adapters/mongoAuth.js +56 -0
  5. package/dist/adapters/sqliteAuth.d.ts +6 -0
  6. package/dist/adapters/sqliteAuth.js +137 -2
  7. package/dist/app.d.ts +77 -2
  8. package/dist/app.js +29 -4
  9. package/dist/entrypoints/queue.d.ts +2 -2
  10. package/dist/entrypoints/queue.js +1 -1
  11. package/dist/index.d.ts +14 -5
  12. package/dist/index.js +9 -3
  13. package/dist/lib/appConfig.d.ts +46 -0
  14. package/dist/lib/appConfig.js +20 -0
  15. package/dist/lib/authAdapter.d.ts +30 -0
  16. package/dist/lib/constants.d.ts +2 -0
  17. package/dist/lib/constants.js +2 -0
  18. package/dist/lib/context.d.ts +2 -0
  19. package/dist/lib/createDtoMapper.d.ts +33 -0
  20. package/dist/lib/createDtoMapper.js +69 -0
  21. package/dist/lib/jwt.d.ts +1 -1
  22. package/dist/lib/jwt.js +2 -2
  23. package/dist/lib/mfaChallenge.d.ts +20 -0
  24. package/dist/lib/mfaChallenge.js +184 -0
  25. package/dist/lib/queue.d.ts +33 -0
  26. package/dist/lib/queue.js +98 -0
  27. package/dist/lib/roles.d.ts +4 -0
  28. package/dist/lib/roles.js +27 -0
  29. package/dist/lib/session.d.ts +12 -0
  30. package/dist/lib/session.js +163 -5
  31. package/dist/lib/tenant.d.ts +15 -0
  32. package/dist/lib/tenant.js +65 -0
  33. package/dist/lib/zodToMongoose.d.ts +38 -0
  34. package/dist/lib/zodToMongoose.js +84 -0
  35. package/dist/middleware/cacheResponse.js +4 -1
  36. package/dist/middleware/rateLimit.d.ts +2 -1
  37. package/dist/middleware/rateLimit.js +5 -2
  38. package/dist/middleware/requireRole.d.ts +14 -3
  39. package/dist/middleware/requireRole.js +46 -6
  40. package/dist/middleware/tenant.d.ts +5 -0
  41. package/dist/middleware/tenant.js +116 -0
  42. package/dist/models/AuthUser.d.ts +8 -0
  43. package/dist/models/AuthUser.js +8 -0
  44. package/dist/models/TenantRole.d.ts +15 -0
  45. package/dist/models/TenantRole.js +23 -0
  46. package/dist/routes/auth.d.ts +5 -3
  47. package/dist/routes/auth.js +153 -22
  48. package/dist/routes/jobs.d.ts +2 -0
  49. package/dist/routes/jobs.js +270 -0
  50. package/dist/routes/mfa.d.ts +1 -0
  51. package/dist/routes/mfa.js +409 -0
  52. package/dist/routes/oauth.js +107 -16
  53. package/dist/server.js +9 -0
  54. package/dist/services/auth.d.ts +17 -5
  55. package/dist/services/auth.js +95 -17
  56. package/dist/services/mfa.d.ts +37 -0
  57. package/dist/services/mfa.js +276 -0
  58. package/docs/sections/adding-middleware/full.md +35 -0
  59. package/docs/sections/adding-models/full.md +125 -0
  60. package/docs/sections/adding-models/overview.md +13 -0
  61. package/docs/sections/adding-routes/full.md +182 -0
  62. package/docs/sections/adding-routes/overview.md +23 -0
  63. package/docs/sections/auth-flow/full.md +456 -0
  64. package/docs/sections/auth-flow/overview.md +10 -0
  65. package/docs/sections/cli/full.md +30 -0
  66. package/docs/sections/configuration/full.md +135 -0
  67. package/docs/sections/configuration/overview.md +17 -0
  68. package/docs/sections/configuration-example/full.md +99 -0
  69. package/docs/sections/configuration-example/overview.md +30 -0
  70. package/docs/sections/documentation/full.md +171 -0
  71. package/docs/sections/environment-variables/full.md +55 -0
  72. package/docs/sections/exports/full.md +83 -0
  73. package/docs/sections/extending-context/full.md +59 -0
  74. package/docs/sections/header.md +3 -0
  75. package/docs/sections/installation/full.md +6 -0
  76. package/docs/sections/jobs/full.md +140 -0
  77. package/docs/sections/jobs/overview.md +15 -0
  78. package/docs/sections/mongodb-connections/full.md +45 -0
  79. package/docs/sections/mongodb-connections/overview.md +7 -0
  80. package/docs/sections/multi-tenancy/full.md +62 -0
  81. package/docs/sections/multi-tenancy/overview.md +15 -0
  82. package/docs/sections/oauth/full.md +119 -0
  83. package/docs/sections/oauth/overview.md +16 -0
  84. package/docs/sections/package-development/full.md +7 -0
  85. package/docs/sections/peer-dependencies/full.md +43 -0
  86. package/docs/sections/quick-start/full.md +43 -0
  87. package/docs/sections/response-caching/full.md +115 -0
  88. package/docs/sections/response-caching/overview.md +13 -0
  89. package/docs/sections/roles/full.md +136 -0
  90. package/docs/sections/roles/overview.md +12 -0
  91. package/docs/sections/running-without-redis/full.md +16 -0
  92. package/docs/sections/running-without-redis-or-mongodb/full.md +60 -0
  93. package/docs/sections/stack/full.md +10 -0
  94. package/docs/sections/websocket/full.md +100 -0
  95. package/docs/sections/websocket/overview.md +5 -0
  96. package/docs/sections/websocket-rooms/full.md +97 -0
  97. package/docs/sections/websocket-rooms/overview.md +5 -0
  98. package/package.json +19 -10
@@ -0,0 +1,30 @@
1
+ ## CLI — Scaffold a New Project
2
+
3
+ ```bash
4
+ bunx @lastshotlabs/bunshot "My App"
5
+ ```
6
+
7
+ You can also pass a custom directory name:
8
+
9
+ ```bash
10
+ bunx @lastshotlabs/bunshot "My App" my-app-dir
11
+ ```
12
+
13
+ This creates a ready-to-run project with:
14
+
15
+ ```
16
+ my-app/
17
+ src/
18
+ index.ts # entry point
19
+ config/index.ts # centralized app configuration
20
+ lib/constants.ts # app name, version, roles
21
+ routes/ # add your route files here
22
+ workers/ # BullMQ workers (auto-discovered)
23
+ middleware/ # custom middleware
24
+ models/ # data models
25
+ services/ # business logic
26
+ tsconfig.json # pre-configured with path aliases
27
+ .env # environment variables template
28
+ ```
29
+
30
+ Path aliases like `@config/*`, `@lib/*`, `@middleware/*`, `@models/*`, `@routes/*`, `@services/*`, and `@workers/*` are set up automatically in `tsconfig.json`.
@@ -0,0 +1,135 @@
1
+ ## Configuration
2
+
3
+ ```ts
4
+ await createServer({
5
+ // Required
6
+ routesDir: import.meta.dir + "/routes",
7
+
8
+ // Shared schemas (imported before routes; see "Shared schemas across routes" above)
9
+ modelSchemas: import.meta.dir + "/schemas", // string shorthand — registration: "auto"
10
+ // modelSchemas: [dir + "/schemas", dir + "/models"], // multiple dirs
11
+ // modelSchemas: { paths: dir + "/schemas", registration: "explicit" }, // full object
12
+
13
+ // App metadata (shown in root endpoint + OpenAPI docs)
14
+ app: {
15
+ name: "My App", // default: "Bun Core API"
16
+ version: "1.0.0", // default: "1.0.0"
17
+ },
18
+
19
+ // Auth, roles, and OAuth
20
+ auth: {
21
+ enabled: true, // default: true — set false to disable /auth/* routes
22
+ adapter: pgAuthAdapter, // custom adapter — overrides db.auth (use for Postgres etc.)
23
+ roles: ["admin", "editor", "user"], // valid roles — required to use requireRole
24
+ defaultRole: "user", // assigned to every new user on /auth/register
25
+ primaryField: "email", // default: "email" — use "username" or "phone" to change the login identifier
26
+ emailVerification: { // optional — only active when primaryField is "email"
27
+ required: true, // default: false (soft gate) — set true to block login until verified
28
+ tokenExpiry: 60 * 60, // default: 86400 (24 hours) — token TTL in seconds
29
+ onSend: async (email, token) => { // called after registration and resend — use any email provider
30
+ await resend.emails.send({ to: email, subject: "Verify your email", text: `Token: ${token}` });
31
+ },
32
+ },
33
+ passwordReset: { // optional — only active when primaryField is "email"
34
+ tokenExpiry: 60 * 60, // default: 3600 (1 hour) — token TTL in seconds
35
+ onSend: async (email, token) => { // called by POST /auth/forgot-password — use any email provider
36
+ await resend.emails.send({ to: email, subject: "Reset your password", text: `Token: ${token}` });
37
+ },
38
+ },
39
+ rateLimit: { // optional — built-in auth endpoint rate limiting
40
+ login: { windowMs: 15 * 60 * 1000, max: 10 }, // default: 10 failures / 15 min
41
+ register: { windowMs: 60 * 60 * 1000, max: 5 }, // default: 5 attempts / hour (per IP)
42
+ verifyEmail: { windowMs: 15 * 60 * 1000, max: 10 }, // default: 10 attempts / 15 min (per IP)
43
+ resendVerification: { windowMs: 60 * 60 * 1000, max: 3 }, // default: 3 attempts / hour (per user)
44
+ forgotPassword: { windowMs: 15 * 60 * 1000, max: 5 }, // default: 5 attempts / 15 min (per IP)
45
+ resetPassword: { windowMs: 15 * 60 * 1000, max: 10 }, // default: 10 attempts / 15 min (per IP)
46
+ store: "redis", // default: "redis" when Redis is enabled, else "memory"
47
+ },
48
+ sessionPolicy: { // optional — session concurrency and metadata
49
+ maxSessions: 6, // default: 6 — max simultaneous sessions per user; oldest evicted when exceeded
50
+ persistSessionMetadata: true, // default: true — keep IP/UA/timestamp row after session expires (for device detection)
51
+ includeInactiveSessions: false, // default: false — include expired/deleted sessions in GET /auth/sessions
52
+ trackLastActive: false, // default: false — update lastActiveAt on every auth'd request (adds one DB write)
53
+ },
54
+ oauth: {
55
+ providers: { google: { ... }, apple: { ... } }, // omit a provider to disable it
56
+ postRedirect: "/dashboard", // default: "/"
57
+ },
58
+ refreshTokens: { // optional — short-lived access + long-lived refresh tokens
59
+ accessTokenExpiry: 900, // default: 900 (15 min)
60
+ refreshTokenExpiry: 2_592_000, // default: 2_592_000 (30 days)
61
+ rotationGraceSeconds: 30, // default: 30 — old token still works briefly after rotation
62
+ },
63
+ mfa: { // optional — TOTP/MFA support (requires otpauth peer dep)
64
+ issuer: "My App", // shown in authenticator apps (default: app name)
65
+ recoveryCodes: 10, // default: 10
66
+ challengeTtlSeconds: 300, // default: 300 (5 min)
67
+ emailOtp: { // optional — email OTP as alternative MFA method
68
+ onSend: async (email, code) => {}, // called to deliver the OTP code — use any email provider
69
+ codeLength: 6, // default: 6
70
+ },
71
+ },
72
+ accountDeletion: { // optional — enables DELETE /auth/me
73
+ onBeforeDelete: async (userId) => {}, // throw to abort
74
+ onAfterDelete: async (userId) => {}, // cleanup callback
75
+ },
76
+ },
77
+
78
+ // Multi-tenancy
79
+ tenancy: {
80
+ resolution: "header", // "header" | "subdomain" | "path"
81
+ headerName: "x-tenant-id", // header name (when resolution is "header")
82
+ onResolve: async (tenantId) => ({}), // validate/load tenant — return null to reject
83
+ cacheTtlMs: 60_000, // LRU cache TTL (default: 60s, 0 to disable)
84
+ cacheMaxSize: 500, // max cached entries (default: 500)
85
+ exemptPaths: [], // extra paths that skip tenant resolution
86
+ rejectionStatus: 403, // 403 (default) or 404
87
+ },
88
+
89
+ // Job status endpoint
90
+ jobs: {
91
+ statusEndpoint: true, // default: false
92
+ auth: "userAuth", // "userAuth" | "none" | MiddlewareHandler[]
93
+ roles: ["admin"], // require roles (works with userAuth)
94
+ allowedQueues: ["export"], // whitelist — empty = nothing exposed
95
+ scopeToUser: false, // when true with userAuth, users see only their own jobs
96
+ },
97
+
98
+ // Security
99
+ security: {
100
+ cors: ["https://myapp.com"], // default: "*"
101
+ rateLimit: { windowMs: 60_000, max: 100 }, // default: 100 req/min
102
+ bearerAuth: true, // default: true — set false to disable, or { bypass: ["/my-public-route"] }
103
+ botProtection: {
104
+ fingerprintRateLimit: true, // rate-limit by HTTP fingerprint (IP-rotation resistant). default: false
105
+ blockList: ["198.51.100.0/24"], // IPv4 CIDRs or exact IPs to block with 403. default: []
106
+ },
107
+ },
108
+
109
+ // Extra middleware injected after identify, before route matching
110
+ middleware: [],
111
+
112
+ // Connections & store routing (all optional — shown with defaults)
113
+ db: {
114
+ mongo: "single", // "single" | "separate" | false
115
+ redis: true, // false to skip auto-connect
116
+ sqlite: undefined, // absolute path to .db file — required when any store is "sqlite"
117
+ auth: "mongo", // "mongo" | "sqlite" | "memory" — which built-in auth adapter to use
118
+ sessions: "redis", // "redis" | "mongo" | "sqlite" | "memory"
119
+ oauthState: "redis", // default: follows sessions
120
+ cache: "redis", // global default for cacheResponse (overridable per-route)
121
+ },
122
+
123
+ // Server
124
+ port: 3000, // default: process.env.PORT ?? 3000
125
+ workersDir: import.meta.dir + "/workers", // auto-imports all .ts files after server starts
126
+ enableWorkers: true, // default: true — set false to disable auto-loading
127
+
128
+ // WebSocket (see WebSocket section for full examples)
129
+ ws: {
130
+ handler: { ... }, // override open/message/close/drain handlers
131
+ upgradeHandler: async (req, server) => { ... }, // replace default cookie-JWT upgrade logic
132
+ onRoomSubscribe(ws, room) { return true; }, // gate room subscriptions; can be async
133
+ },
134
+ });
135
+ ```
@@ -0,0 +1,17 @@
1
+ ## Configuration
2
+
3
+ `createServer` / `createApp` accept a config object with these top-level keys:
4
+
5
+ | Key | Purpose |
6
+ |-----|---------|
7
+ | `routesDir` | **(required)** Path to auto-discovered route files |
8
+ | `app` | App name and version (shown in docs) |
9
+ | `auth` | Roles, OAuth, email verification, MFA, refresh tokens, rate limiting, account deletion |
10
+ | `db` | Connection and store routing — mongo, redis, sqlite, sessions, cache, auth adapter |
11
+ | `security` | CORS, bearer auth, rate limiting, bot protection |
12
+ | `tenancy` | Multi-tenant resolution (header/subdomain/path) |
13
+ | `jobs` | Job status REST endpoint config |
14
+ | `ws` | WebSocket handler and upgrade overrides |
15
+ | `middleware` | Extra global middleware array |
16
+ | `modelSchemas` | Schema auto-discovery paths |
17
+ | `port`, `workersDir`, `enableWorkers` | Server options |
@@ -0,0 +1,99 @@
1
+ ## Full Configuration Example
2
+
3
+ For production apps, break config into its own file. Here's a real-world setup with MongoDB, Redis, OAuth, and email verification:
4
+
5
+ ```ts
6
+ // src/config/index.ts
7
+ import path from "path";
8
+ import {
9
+ type CreateServerConfig,
10
+ type AppMeta,
11
+ type AuthConfig,
12
+ type DbConfig,
13
+ type SecurityConfig,
14
+ type ModelSchemasConfig,
15
+ } from "@lastshotlabs/bunshot";
16
+
17
+ const app: AppMeta = {
18
+ name: "My App",
19
+ version: "1.0.0",
20
+ };
21
+
22
+ const db: DbConfig = {
23
+ mongo: "single", // "single" | "separate" | false
24
+ redis: true, // false to skip Redis
25
+ sessions: "redis", // "redis" | "mongo" | "sqlite" | "memory"
26
+ cache: "memory", // default store for cacheResponse
27
+ auth: "mongo", // "mongo" | "sqlite" | "memory"
28
+ oauthState: "memory", // where to store OAuth state tokens
29
+ };
30
+
31
+ const auth: AuthConfig = {
32
+ roles: ["admin", "user"],
33
+ defaultRole: "user",
34
+ primaryField: "email",
35
+ rateLimit: { store: "redis" },
36
+ emailVerification: {
37
+ required: true,
38
+ tokenExpiry: 60 * 60, // 1 hour
39
+ onSend: async (email, token) => {
40
+ // send verification email using any provider (Resend, SES, etc.)
41
+ },
42
+ },
43
+ oauth: {
44
+ postRedirect: "http://localhost:5175/oauth/callback",
45
+ providers: {
46
+ google: {
47
+ clientId: process.env.GOOGLE_CLIENT_ID!,
48
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
49
+ redirectUri: `http://localhost:${process.env.PORT ?? 3000}/auth/google/callback`,
50
+ },
51
+ },
52
+ },
53
+ };
54
+
55
+ const security: SecurityConfig = {
56
+ bearerAuth: true,
57
+ cors: ["*", "http://localhost:5173"],
58
+ botProtection: { fingerprintRateLimit: true },
59
+ };
60
+
61
+ const modelSchemas: ModelSchemasConfig = {
62
+ registration: "auto",
63
+ paths: [path.join(import.meta.dir, "../schemas/*.ts")],
64
+ };
65
+
66
+ export const appConfig: CreateServerConfig = {
67
+ app,
68
+ routesDir: path.join(import.meta.dir, "../routes"),
69
+ workersDir: path.join(import.meta.dir, "../workers"),
70
+ port: process.env.PORT ? parseInt(process.env.PORT) : 3000,
71
+ db,
72
+ auth,
73
+ security,
74
+ modelSchemas,
75
+ middleware: [/* your global middleware here */],
76
+ };
77
+ ```
78
+
79
+ Every field above is optional except `routesDir`. See the [Configuration](#configuration) section for the full reference.
80
+
81
+ ### Built-in endpoints
82
+
83
+ | Endpoint | Description |
84
+ |---|---|
85
+ | `POST /auth/register` | Create account, returns JWT |
86
+ | `POST /auth/login` | Login, returns JWT (includes `emailVerified` when verification is configured) |
87
+ | `POST /auth/logout` | Invalidates the current session only |
88
+ | `GET /auth/me` | Returns current user's `userId`, `email`, `emailVerified`, and `googleLinked` (requires login) |
89
+ | `POST /auth/set-password` | Set or update password (requires login) |
90
+ | `GET /auth/sessions` | List active sessions with metadata — IP, user-agent, timestamps (requires login) |
91
+ | `DELETE /auth/sessions/:sessionId` | Revoke a specific session by ID (requires login) |
92
+ | `POST /auth/verify-email` | Verify email with token (when `emailVerification` is configured) |
93
+ | `POST /auth/resend-verification` | Resend verification email (requires credentials, when `emailVerification` is configured) |
94
+ | `POST /auth/forgot-password` | Request a password reset email (when `passwordReset` is configured) |
95
+ | `POST /auth/reset-password` | Reset password using a token from the reset email (when `passwordReset` is configured) |
96
+ | `GET /health` | Health check |
97
+ | `GET /docs` | Scalar API docs UI |
98
+ | `GET /openapi.json` | OpenAPI spec |
99
+ | `WS /ws` | WebSocket endpoint (cookie-JWT auth) |
@@ -0,0 +1,30 @@
1
+ ## Full Configuration Example
2
+
3
+ For production apps, break config into its own file with MongoDB, Redis, OAuth, and email verification. See the [Configuration](#configuration) section for the full reference.
4
+
5
+ ```ts
6
+ // src/config/index.ts
7
+ import { type CreateServerConfig } from "@lastshotlabs/bunshot";
8
+
9
+ export const appConfig: CreateServerConfig = {
10
+ app: { name: "My App", version: "1.0.0" },
11
+ routesDir: import.meta.dir + "/routes",
12
+ workersDir: import.meta.dir + "/workers",
13
+ db: { mongo: "single", redis: true, sessions: "redis", cache: "memory", auth: "mongo" },
14
+ auth: { roles: ["admin", "user"], defaultRole: "user", primaryField: "email" },
15
+ security: { bearerAuth: true, cors: ["*"] },
16
+ };
17
+ ```
18
+
19
+ ### Built-in endpoints
20
+
21
+ | Endpoint | Description |
22
+ |---|---|
23
+ | `POST /auth/register` | Create account, returns JWT |
24
+ | `POST /auth/login` | Login, returns JWT |
25
+ | `POST /auth/logout` | Invalidates the current session |
26
+ | `GET /auth/me` | Current user profile |
27
+ | `GET /health` | Health check |
28
+ | `GET /docs` | Scalar API docs UI |
29
+ | `GET /openapi.json` | OpenAPI spec |
30
+ | `WS /ws` | WebSocket endpoint |
@@ -0,0 +1,171 @@
1
+ ## Documentation Generation
2
+
3
+ Bunshot ships its documentation as modular markdown sections that you can pull into your own project's README.
4
+
5
+ ### Setup
6
+
7
+ Create a `docs/` directory in your project with a config and build script:
8
+
9
+ ```
10
+ my-app/
11
+ docs/
12
+ readme.config.json
13
+ build-readme.ts
14
+ sections/
15
+ intro/
16
+ full.md
17
+ my-api/
18
+ full.md
19
+ overview.md
20
+ ```
21
+
22
+ ### Config — `docs/readme.config.json`
23
+
24
+ ```json
25
+ {
26
+ "output": "../README.md",
27
+ "separator": "---",
28
+ "sections": [
29
+ { "topic": "intro", "default": "full", "separator": false },
30
+ { "topic": "my-api", "default": "full" },
31
+ { "topic": "bunshot-auth", "file": "@lastshotlabs/bunshot/docs/auth-flow/overview.md" },
32
+ { "topic": "bunshot-config", "file": "@lastshotlabs/bunshot/docs/configuration/full.md" }
33
+ ],
34
+ "profiles": {
35
+ "short": {
36
+ "my-api": "overview"
37
+ }
38
+ }
39
+ }
40
+ ```
41
+
42
+ **Section entries:**
43
+
44
+ | Field | Description |
45
+ |-------|-------------|
46
+ | `topic` | Section identifier. Maps to `sections/{topic}/` directory when no `file` is specified. |
47
+ | `default` | Variant to use: `"full"` or `"overview"`. Falls back to `"full"` if the requested variant doesn't exist. |
48
+ | `file` | Explicit file path. Supports relative paths (`sections/header.md`) and package paths (`@lastshotlabs/bunshot/docs/auth-flow/overview.md`). |
49
+ | `separator` | `true`/`false` — whether to insert `---` before this section. Defaults to `true` (except the first section). |
50
+
51
+ **Profiles** override specific sections' variants. Only list sections you want to change:
52
+
53
+ ```json
54
+ "profiles": {
55
+ "short": { "my-api": "overview", "bunshot-auth": "overview" }
56
+ }
57
+ ```
58
+
59
+ ### Build script — `docs/build-readme.ts`
60
+
61
+ Copy this into your project:
62
+
63
+ ```ts
64
+ const configPath = import.meta.dir + "/readme.config.json";
65
+ const config = await Bun.file(configPath).json();
66
+ const profile = Bun.argv[2];
67
+ const overrides: Record<string, string> = profile
68
+ ? config.profiles?.[profile] ?? {}
69
+ : {};
70
+ const separator: string = config.separator ?? "---";
71
+
72
+ if (profile && !config.profiles?.[profile]) {
73
+ console.error(`Unknown profile: "${profile}". Available: ${Object.keys(config.profiles ?? {}).join(", ")}`);
74
+ process.exit(1);
75
+ }
76
+
77
+ function resolveFilePath(file: string): string {
78
+ if (file.startsWith("./") || file.startsWith("/") || file.startsWith("../")) {
79
+ return import.meta.dir + "/" + file;
80
+ }
81
+ if (file.includes("/") && !file.startsWith("sections")) {
82
+ const resolved = import.meta.resolve(file);
83
+ return resolved.replace(/^file:\/\/\//, "");
84
+ }
85
+ return import.meta.dir + "/" + file;
86
+ }
87
+
88
+ const parts: string[] = [
89
+ "<!-- AUTO-GENERATED — edit docs/sections/, not this file. Run: bun run readme -->",
90
+ ];
91
+
92
+ for (let i = 0; i < config.sections.length; i++) {
93
+ const section = config.sections[i];
94
+
95
+ let filePath: string;
96
+ if (section.file) {
97
+ filePath = resolveFilePath(section.file);
98
+ } else {
99
+ const variant = overrides[section.topic] ?? section.default ?? "full";
100
+ const candidate = `${import.meta.dir}/sections/${section.topic}/${variant}.md`;
101
+ filePath = (await Bun.file(candidate).exists())
102
+ ? candidate
103
+ : `${import.meta.dir}/sections/${section.topic}/full.md`;
104
+ }
105
+
106
+ const content = (await Bun.file(filePath).text()).replace(/\r\n/g, "\n");
107
+
108
+ const useSeparator = section.separator !== undefined ? section.separator : i > 0;
109
+ if (useSeparator) parts.push(separator);
110
+
111
+ parts.push(content.trimEnd());
112
+ }
113
+
114
+ const outputPath = import.meta.dir + "/" + (config.output ?? "../README.md");
115
+ await Bun.write(outputPath, parts.join("\n\n") + "\n");
116
+ console.log(
117
+ `README.md compiled (${config.sections.length} sections${profile ? `, profile: ${profile}` : ""})`
118
+ );
119
+ ```
120
+
121
+ ### Add to package.json
122
+
123
+ ```json
124
+ "scripts": {
125
+ "readme": "bun docs/build-readme.ts",
126
+ "readme:short": "bun docs/build-readme.ts short"
127
+ }
128
+ ```
129
+
130
+ ### Available bunshot sections
131
+
132
+ Pull any of these into your project's README via `"file": "@lastshotlabs/bunshot/docs/{section}/{variant}.md"`:
133
+
134
+ | Section | Variants |
135
+ |---------|----------|
136
+ | `quick-start` | `full` |
137
+ | `stack` | `full` |
138
+ | `cli` | `full` |
139
+ | `installation` | `full` |
140
+ | `configuration-example` | `full`, `overview` |
141
+ | `adding-routes` | `full`, `overview` |
142
+ | `mongodb-connections` | `full`, `overview` |
143
+ | `adding-models` | `full`, `overview` |
144
+ | `jobs` | `full`, `overview` |
145
+ | `websocket` | `full`, `overview` |
146
+ | `websocket-rooms` | `full`, `overview` |
147
+ | `adding-middleware` | `full` |
148
+ | `response-caching` | `full`, `overview` |
149
+ | `extending-context` | `full` |
150
+ | `configuration` | `full`, `overview` |
151
+ | `running-without-redis` | `full` |
152
+ | `running-without-redis-or-mongodb` | `full` |
153
+ | `auth-flow` | `full`, `overview` |
154
+ | `roles` | `full`, `overview` |
155
+ | `multi-tenancy` | `full`, `overview` |
156
+ | `oauth` | `full`, `overview` |
157
+ | `peer-dependencies` | `full` |
158
+ | `environment-variables` | `full` |
159
+ | `exports` | `full` |
160
+
161
+ ### Writing your own sections
162
+
163
+ Each section file is self-contained markdown starting with a `## Heading`. Create `docs/sections/{topic}/full.md` and optionally `overview.md`:
164
+
165
+ ```markdown
166
+ ## My Feature
167
+
168
+ Description and code examples here...
169
+ ```
170
+
171
+ The `---` separators between sections are inserted by the build script — don't include them in section files.
@@ -0,0 +1,55 @@
1
+ ## Environment Variables
2
+
3
+ ```env
4
+ NODE_ENV=development
5
+ PORT=...
6
+
7
+ # MongoDB (single connection — used by connectMongo())
8
+ MONGO_USER_DEV=...
9
+ MONGO_PW_DEV=...
10
+ MONGO_HOST_DEV=...
11
+ MONGO_DB_DEV=...
12
+ MONGO_USER_PROD=...
13
+ MONGO_PW_PROD=...
14
+ MONGO_HOST_PROD=...
15
+ MONGO_DB_PROD=...
16
+
17
+ # MongoDB auth connection (separate server — used by connectAuthMongo())
18
+ # Only needed when running auth on a different cluster from app data
19
+ MONGO_AUTH_USER_DEV=...
20
+ MONGO_AUTH_PW_DEV=...
21
+ MONGO_AUTH_HOST_DEV=...
22
+ MONGO_AUTH_DB_DEV=...
23
+ MONGO_AUTH_USER_PROD=...
24
+ MONGO_AUTH_PW_PROD=...
25
+ MONGO_AUTH_HOST_PROD=...
26
+ MONGO_AUTH_DB_PROD=...
27
+
28
+ # Redis
29
+ REDIS_HOST_DEV=host:port
30
+ REDIS_USER_DEV=...
31
+ REDIS_PW_DEV=...
32
+ REDIS_HOST_PROD=host:port
33
+ REDIS_USER_PROD=...
34
+ REDIS_PW_PROD=...
35
+
36
+ # JWT
37
+ JWT_SECRET_DEV=...
38
+ JWT_SECRET_PROD=...
39
+
40
+ # Bearer API key (required on every non-bypassed request)
41
+ BEARER_TOKEN_DEV=...
42
+ BEARER_TOKEN_PROD=...
43
+
44
+ # Logging (optional — defaults to on in dev)
45
+ LOGGING_VERBOSE=true
46
+
47
+ # OAuth (only needed if using oauthProviders)
48
+ GOOGLE_CLIENT_ID=...
49
+ GOOGLE_CLIENT_SECRET=...
50
+
51
+ APPLE_CLIENT_ID=...
52
+ APPLE_TEAM_ID=...
53
+ APPLE_KEY_ID=...
54
+ APPLE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n..."
55
+ ```
@@ -0,0 +1,83 @@
1
+ ## Exports
2
+
3
+ ```ts
4
+ import {
5
+ // Server factory
6
+ createServer, createApp,
7
+
8
+ // DB
9
+ connectMongo, connectAuthMongo, connectAppMongo, disconnectMongo,
10
+ authConnection, appConnection, mongoose,
11
+ connectRedis, disconnectRedis, getRedis,
12
+
13
+ // Jobs
14
+ createQueue, createWorker,
15
+ type Job,
16
+
17
+ // WebSocket
18
+ websocket, createWsUpgradeHandler, publish,
19
+ subscribe, unsubscribe, getSubscriptions, handleRoomActions,
20
+ getRooms, getRoomSubscribers,
21
+
22
+ // Auth utilities
23
+ signToken, verifyToken,
24
+ createSession, getSession, deleteSession, getUserSessions, getActiveSessionCount,
25
+ evictOldestSession, updateSessionLastActive, setSessionStore, deleteUserSessions,
26
+ setRefreshToken, getSessionByRefreshToken, rotateRefreshToken, // refresh token management
27
+ createVerificationToken, getVerificationToken, deleteVerificationToken, // email verification tokens
28
+ createResetToken, consumeResetToken, setPasswordResetStore, // password reset tokens
29
+ createMfaChallenge, consumeMfaChallenge, replaceMfaChallengeOtp, setMfaChallengeStore, // MFA challenge tokens
30
+ bustAuthLimit, trackAttempt, isLimited, // auth rate limiting — use in custom routes or admin unlocks
31
+ buildFingerprint, // HTTP fingerprint hash (IP-independent) — use in custom bot detection logic
32
+ sqliteAuthAdapter, setSqliteDb, startSqliteCleanup, // SQLite backend (persisted)
33
+ memoryAuthAdapter, clearMemoryStore, // in-memory backend (ephemeral)
34
+ setUserRoles, addUserRole, removeUserRole, // app-wide role management
35
+ getTenantRoles, setTenantRoles, addTenantRole, removeTenantRole, // tenant-scoped role management
36
+ type AuthAdapter, type OAuthProfile, type OAuthProviderConfig, type MfaChallengeData,
37
+ type AuthRateLimitConfig, type BotProtectionConfig, type BotProtectionOptions,
38
+ type LimitOpts, type RateLimitOptions,
39
+ type SessionMetadata, type SessionInfo, type RefreshResult,
40
+
41
+ // Tenancy
42
+ createTenant, deleteTenant, getTenant, listTenants, // tenant provisioning (MongoDB)
43
+ invalidateTenantCache, // invalidate LRU cache entry
44
+ type TenantInfo, type CreateTenantOptions,
45
+ type TenancyConfig, type TenantConfig,
46
+
47
+ // Middleware
48
+ bearerAuth, identify, userAuth, rateLimit,
49
+ botProtection, // CIDR blocklist + per-route bot protection
50
+ requireRole, // role-based access control (tenant-aware)
51
+ requireVerifiedEmail, // blocks unverified email addresses
52
+ cacheResponse, bustCache, bustCachePattern, setCacheStore, // response caching (tenant-namespaced)
53
+
54
+ // Utilities
55
+ HttpError, log, validate, createRouter, createRoute,
56
+ registerSchema, registerSchemas, // named OpenAPI schema registration
57
+ zodToMongoose, // Zod → Mongoose schema conversion
58
+ createDtoMapper, // DB document → API DTO mapper factory
59
+ type ZodToMongooseConfig, type ZodToMongooseRefConfig, type DtoMapperConfig,
60
+ getAppRoles, // returns the valid roles list configured at startup
61
+
62
+ // Constants
63
+ COOKIE_TOKEN, HEADER_USER_TOKEN,
64
+ COOKIE_REFRESH_TOKEN, HEADER_REFRESH_TOKEN, // refresh token cookie/header names
65
+
66
+ // Types
67
+ type AppEnv, type AppVariables,
68
+ type CreateServerConfig, type CreateAppConfig, type ModelSchemasConfig,
69
+ type DbConfig, type AppMeta, type AuthConfig, type OAuthConfig, type SecurityConfig,
70
+ type PrimaryField, type EmailVerificationConfig, type PasswordResetConfig,
71
+ type RefreshTokenConfig, type MfaConfig, type MfaEmailOtpConfig, type JobsConfig,
72
+ type AccountDeletionConfig,
73
+ type SocketData, type WsConfig,
74
+ } from "@lastshotlabs/bunshot";
75
+
76
+ // Jobs (separate entrypoint)
77
+ import {
78
+ createQueue, createWorker,
79
+ createCronWorker, cleanupStaleSchedulers, getRegisteredCronNames,
80
+ createDLQHandler,
81
+ type Job,
82
+ } from "@lastshotlabs/bunshot/queue";
83
+ ```