@lastshotlabs/bunshot 0.0.21 → 0.0.27

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 (185) hide show
  1. package/README.md +3035 -1249
  2. package/dist/adapters/localStorage.d.ts +6 -0
  3. package/dist/adapters/localStorage.js +59 -0
  4. package/dist/adapters/memoryAuth.d.ts +13 -0
  5. package/dist/adapters/memoryAuth.js +261 -2
  6. package/dist/adapters/memoryStorage.d.ts +3 -0
  7. package/dist/adapters/memoryStorage.js +44 -0
  8. package/dist/adapters/mongoAuth.js +217 -1
  9. package/dist/adapters/s3Storage.d.ts +14 -0
  10. package/dist/adapters/s3Storage.js +126 -0
  11. package/dist/adapters/sqliteAuth.d.ts +30 -0
  12. package/dist/adapters/sqliteAuth.js +352 -2
  13. package/dist/app.d.ts +203 -3
  14. package/dist/app.js +352 -48
  15. package/dist/cli.js +118 -38
  16. package/dist/index.d.ts +69 -8
  17. package/dist/index.js +46 -5
  18. package/dist/lib/HttpError.d.ts +7 -1
  19. package/dist/lib/HttpError.js +10 -1
  20. package/dist/lib/appConfig.d.ts +157 -0
  21. package/dist/lib/appConfig.js +54 -0
  22. package/dist/lib/auditLog.d.ts +58 -0
  23. package/dist/lib/auditLog.js +218 -0
  24. package/dist/lib/authAdapter.d.ts +140 -1
  25. package/dist/lib/authRateLimit.js +36 -0
  26. package/dist/lib/breachedPassword.d.ts +13 -0
  27. package/dist/lib/breachedPassword.js +48 -0
  28. package/dist/lib/captcha.d.ts +25 -0
  29. package/dist/lib/captcha.js +37 -0
  30. package/dist/lib/constants.d.ts +4 -0
  31. package/dist/lib/constants.js +4 -0
  32. package/dist/lib/context.d.ts +24 -1
  33. package/dist/lib/context.js +17 -3
  34. package/dist/lib/createRoute.d.ts +28 -2
  35. package/dist/lib/createRoute.js +54 -3
  36. package/dist/lib/credentialStuffing.d.ts +31 -0
  37. package/dist/lib/credentialStuffing.js +77 -0
  38. package/dist/lib/deletionCancelToken.d.ts +12 -0
  39. package/dist/lib/deletionCancelToken.js +88 -0
  40. package/dist/lib/emailVerification.d.ts +6 -0
  41. package/dist/lib/emailVerification.js +46 -3
  42. package/dist/lib/groups.d.ts +113 -0
  43. package/dist/lib/groups.js +133 -0
  44. package/dist/lib/idempotency.d.ts +22 -0
  45. package/dist/lib/idempotency.js +182 -0
  46. package/dist/lib/jwks.d.ts +25 -0
  47. package/dist/lib/jwks.js +51 -0
  48. package/dist/lib/jwt.d.ts +15 -2
  49. package/dist/lib/jwt.js +92 -5
  50. package/dist/lib/logger.d.ts +2 -0
  51. package/dist/lib/logger.js +6 -0
  52. package/dist/lib/m2m.d.ts +29 -0
  53. package/dist/lib/m2m.js +48 -0
  54. package/dist/lib/metrics.d.ts +14 -0
  55. package/dist/lib/metrics.js +158 -0
  56. package/dist/lib/mfaChallenge.d.ts +14 -1
  57. package/dist/lib/mfaChallenge.js +111 -6
  58. package/dist/lib/mongo.js +1 -1
  59. package/dist/lib/oauthCode.js +23 -18
  60. package/dist/lib/pagination.d.ts +119 -0
  61. package/dist/lib/pagination.js +166 -0
  62. package/dist/lib/resetPassword.js +3 -1
  63. package/dist/lib/saml.d.ts +25 -0
  64. package/dist/lib/saml.js +64 -0
  65. package/dist/lib/scim.d.ts +44 -0
  66. package/dist/lib/scim.js +54 -0
  67. package/dist/lib/securityEvents.d.ts +28 -0
  68. package/dist/lib/securityEvents.js +26 -0
  69. package/dist/lib/session.d.ts +14 -0
  70. package/dist/lib/session.js +121 -5
  71. package/dist/lib/signing.d.ts +52 -0
  72. package/dist/lib/signing.js +183 -0
  73. package/dist/lib/storageAdapter.d.ts +30 -0
  74. package/dist/lib/storageAdapter.js +1 -0
  75. package/dist/lib/stripUnreferencedSchemas.d.ts +11 -0
  76. package/dist/lib/stripUnreferencedSchemas.js +79 -0
  77. package/dist/lib/suspension.d.ts +13 -0
  78. package/dist/lib/suspension.js +23 -0
  79. package/dist/lib/tenant.js +2 -2
  80. package/dist/lib/upload.d.ts +39 -0
  81. package/dist/lib/upload.js +112 -0
  82. package/dist/lib/uploadRegistry.d.ts +18 -0
  83. package/dist/lib/uploadRegistry.js +83 -0
  84. package/dist/lib/validate.js +2 -2
  85. package/dist/lib/ws.d.ts +1 -0
  86. package/dist/lib/ws.js +28 -0
  87. package/dist/lib/wsHeartbeat.d.ts +12 -0
  88. package/dist/lib/wsHeartbeat.js +57 -0
  89. package/dist/lib/wsMessages.d.ts +40 -0
  90. package/dist/lib/wsMessages.js +330 -0
  91. package/dist/lib/wsPresence.d.ts +25 -0
  92. package/dist/lib/wsPresence.js +99 -0
  93. package/dist/middleware/auditLog.d.ts +22 -0
  94. package/dist/middleware/auditLog.js +39 -0
  95. package/dist/middleware/bearerAuth.js +1 -1
  96. package/dist/middleware/cacheResponse.js +5 -1
  97. package/dist/middleware/captcha.d.ts +10 -0
  98. package/dist/middleware/captcha.js +36 -0
  99. package/dist/middleware/csrf.js +18 -4
  100. package/dist/middleware/errorHandler.js +4 -1
  101. package/dist/middleware/identify.js +89 -14
  102. package/dist/middleware/metrics.d.ts +9 -0
  103. package/dist/middleware/metrics.js +26 -0
  104. package/dist/middleware/requestId.d.ts +3 -0
  105. package/dist/middleware/requestId.js +7 -0
  106. package/dist/middleware/requestLogger.d.ts +38 -0
  107. package/dist/middleware/requestLogger.js +68 -0
  108. package/dist/middleware/requestSigning.d.ts +20 -0
  109. package/dist/middleware/requestSigning.js +100 -0
  110. package/dist/middleware/requireMfaSetup.d.ts +16 -0
  111. package/dist/middleware/requireMfaSetup.js +37 -0
  112. package/dist/middleware/requireRole.d.ts +9 -3
  113. package/dist/middleware/requireRole.js +23 -36
  114. package/dist/middleware/requireScope.d.ts +10 -0
  115. package/dist/middleware/requireScope.js +25 -0
  116. package/dist/middleware/requireStepUp.d.ts +18 -0
  117. package/dist/middleware/requireStepUp.js +29 -0
  118. package/dist/middleware/scimAuth.d.ts +8 -0
  119. package/dist/middleware/scimAuth.js +29 -0
  120. package/dist/middleware/upload.d.ts +5 -0
  121. package/dist/middleware/upload.js +27 -0
  122. package/dist/middleware/webhookAuth.d.ts +30 -0
  123. package/dist/middleware/webhookAuth.js +58 -0
  124. package/dist/models/AuditLog.d.ts +30 -0
  125. package/dist/models/AuditLog.js +39 -0
  126. package/dist/models/AuthUser.d.ts +7 -0
  127. package/dist/models/AuthUser.js +7 -0
  128. package/dist/models/Group.d.ts +21 -0
  129. package/dist/models/Group.js +28 -0
  130. package/dist/models/GroupMembership.d.ts +21 -0
  131. package/dist/models/GroupMembership.js +25 -0
  132. package/dist/models/M2MClient.d.ts +18 -0
  133. package/dist/models/M2MClient.js +18 -0
  134. package/dist/routes/auth.d.ts +3 -2
  135. package/dist/routes/auth.js +238 -21
  136. package/dist/routes/groups.d.ts +21 -0
  137. package/dist/routes/groups.js +346 -0
  138. package/dist/routes/jobs.js +66 -46
  139. package/dist/routes/m2m.d.ts +2 -0
  140. package/dist/routes/m2m.js +72 -0
  141. package/dist/routes/metrics.d.ts +8 -0
  142. package/dist/routes/metrics.js +55 -0
  143. package/dist/routes/mfa.js +13 -1
  144. package/dist/routes/oauth.js +6 -0
  145. package/dist/routes/oidc.d.ts +2 -0
  146. package/dist/routes/oidc.js +29 -0
  147. package/dist/routes/passkey.d.ts +1 -0
  148. package/dist/routes/passkey.js +157 -0
  149. package/dist/routes/saml.d.ts +2 -0
  150. package/dist/routes/saml.js +86 -0
  151. package/dist/routes/scim.d.ts +2 -0
  152. package/dist/routes/scim.js +255 -0
  153. package/dist/routes/uploads.d.ts +14 -0
  154. package/dist/routes/uploads.js +227 -0
  155. package/dist/server.d.ts +26 -0
  156. package/dist/server.js +46 -3
  157. package/dist/services/auth.d.ts +2 -0
  158. package/dist/services/auth.js +101 -22
  159. package/dist/services/mfa.js +2 -2
  160. package/dist/ws/index.js +5 -1
  161. package/docs/sections/auth-flow/full.md +203 -47
  162. package/docs/sections/auth-flow/overview.md +2 -2
  163. package/docs/sections/auth-security-examples/full.md +388 -0
  164. package/docs/sections/authentication/full.md +130 -0
  165. package/docs/sections/authentication/overview.md +5 -0
  166. package/docs/sections/cli/full.md +13 -1
  167. package/docs/sections/configuration/full.md +17 -0
  168. package/docs/sections/configuration/overview.md +1 -0
  169. package/docs/sections/exports/full.md +34 -3
  170. package/docs/sections/logging/full.md +83 -0
  171. package/docs/sections/metrics/full.md +131 -0
  172. package/docs/sections/oauth/full.md +189 -189
  173. package/docs/sections/oauth/overview.md +1 -1
  174. package/docs/sections/pagination/full.md +93 -0
  175. package/docs/sections/passkey-login/full.md +90 -0
  176. package/docs/sections/passkey-login/overview.md +1 -0
  177. package/docs/sections/roles/full.md +224 -135
  178. package/docs/sections/roles/overview.md +3 -1
  179. package/docs/sections/signing/full.md +203 -0
  180. package/docs/sections/uploads/full.md +208 -0
  181. package/docs/sections/versioning/full.md +85 -0
  182. package/docs/sections/webhook-auth/full.md +100 -0
  183. package/docs/sections/websocket/full.md +95 -0
  184. package/docs/sections/websocket-rooms/full.md +6 -1
  185. package/package.json +18 -5
@@ -81,6 +81,101 @@ await createServer({
81
81
 
82
82
  You can supply any subset of `open`, `message`, `close`, `drain` — unset handlers fall back to the defaults.
83
83
 
84
+ ### Heartbeat / Ping-Pong Keepalive
85
+
86
+ Opt-in via `ws.heartbeat`. Detects and closes stale connections via ping/pong:
87
+
88
+ ```ts
89
+ await createServer({
90
+ ws: {
91
+ heartbeat: true, // 30s interval, 10s timeout
92
+ // or fine-tune:
93
+ heartbeat: { intervalMs: 15_000, timeoutMs: 5_000 },
94
+ },
95
+ });
96
+ ```
97
+
98
+ Every `intervalMs`, the server pings all connected sockets. If a socket hasn't responded within `timeoutMs`, it's closed with code `1001` ("Heartbeat timeout"). `stopHeartbeat()` is called automatically on SIGTERM/SIGINT and is also exported for custom shutdown logic.
99
+
100
+ ### Presence Tracking
101
+
102
+ Opt-in via `ws.presence`. Tracks which authenticated users are online in each room (multi-tab aware):
103
+
104
+ ```ts
105
+ await createServer({
106
+ ws: {
107
+ presence: true,
108
+ },
109
+ });
110
+ ```
111
+
112
+ When a user's first socket subscribes to a room, all room members receive `{ event: "presence_join", room, userId }`. When the user's last socket leaves (unsubscribe or disconnect), `{ event: "presence_leave", room, userId }` is broadcast.
113
+
114
+ **Query presence:**
115
+
116
+ ```ts
117
+ import { getRoomPresence, getUserPresence } from "@lastshotlabs/bunshot";
118
+
119
+ getRoomPresence("chat:general"); // ["user1", "user2"] — deduplicated userIds
120
+ getUserPresence("user1"); // ["chat:general", "chat:vip"] — rooms where user is present
121
+ ```
122
+
123
+ ### Message Persistence
124
+
125
+ Opt-in via `ws.persistence`. Persists messages for late joiners. Rooms must be individually opted in via `configureRoom()`:
126
+
127
+ ```ts
128
+ import { configureRoom, persistMessage, getMessageHistory } from "@lastshotlabs/bunshot";
129
+
130
+ await createServer({
131
+ ws: {
132
+ persistence: {
133
+ store: "memory", // "redis" | "mongo" | "sqlite" | "memory"
134
+ defaults: { maxCount: 100, ttlSeconds: 86_400 }, // 24h
135
+ },
136
+ },
137
+ });
138
+
139
+ // Opt rooms in
140
+ configureRoom("chat:general", { persist: true });
141
+ configureRoom("chat:vip", { persist: true, maxCount: 50, ttlSeconds: 3600 });
142
+
143
+ // Persist from your custom message handler
144
+ await createServer({
145
+ ws: {
146
+ handler: {
147
+ async message(ws, message) {
148
+ const data = JSON.parse(message as string);
149
+ if (data.type === "chat") {
150
+ await persistMessage(data.room, {
151
+ senderId: ws.data.userId,
152
+ payload: data,
153
+ });
154
+ }
155
+ },
156
+ },
157
+ },
158
+ });
159
+
160
+ // Retrieve history (cursor-based pagination)
161
+ const history = await getMessageHistory("chat:general", { limit: 50 });
162
+ const older = await getMessageHistory("chat:general", { limit: 50, before: history[0].id });
163
+ ```
164
+
165
+ Messages for non-configured rooms are silently ignored. Store errors are caught and logged (non-blocking) — message delivery is never interrupted by a persistence failure.
166
+
167
+ ### Room Name Validation
168
+
169
+ Room names sent by clients via `subscribe` and `unsubscribe` actions are validated before any action is taken. Invalid room names are silently dropped — no error is sent to the client.
170
+
171
+ **Valid room name rules:**
172
+ - Characters: `a–z`, `A–Z`, `0–9`, `_`, `:`, `.`, `/`, `-`
173
+ - Length: 1–128 characters
174
+
175
+ Examples of valid names: `chat:general`, `user:123`, `tenant/room-1`, `notifications`
176
+
177
+ Room names containing characters outside this set (e.g. spaces, `@`, `#`, or Unicode) are silently ignored. This prevents injection of arbitrary pub/sub topic names from untrusted client input.
178
+
84
179
  ### Overriding the upgrade / auth handler
85
180
 
86
181
  Replace the default cookie-JWT handshake entirely via `ws.upgradeHandler`. You must call `server.upgrade()` yourself and include `rooms: new Set()` in data:
@@ -13,6 +13,11 @@ Rooms are built on Bun's native pub/sub. `createServer` always intercepts room a
13
13
  | `getRooms()` | Returns `string[]` of all rooms with at least one active subscriber |
14
14
  | `getRoomSubscribers(room)` | Returns `string[]` of socket IDs currently subscribed to `room` |
15
15
  | `handleRoomActions(ws, message, onSubscribe?)` | Parses and dispatches subscribe/unsubscribe actions. Returns `true` if the message was a room action (consumed), `false` otherwise. Pass an optional async guard as the third argument. |
16
+ | `getRoomPresence(room)` | Returns deduplicated `string[]` of userIds present in room (requires `ws.presence` enabled) |
17
+ | `getUserPresence(userId)` | Returns `string[]` of rooms where user is present (requires `ws.presence` enabled) |
18
+ | `persistMessage(room, data)` | Store a message for a room (requires `ws.persistence` + `configureRoom`). Returns `StoredMessage \| null` |
19
+ | `getMessageHistory(room, opts?)` | Cursor-based retrieval. `opts`: `{ limit?, before?, after? }` (cursors are message IDs) |
20
+ | `configureRoom(room, opts)` | Opt a room into persistence: `{ persist: true, maxCount?, ttlSeconds? }` |
16
21
 
17
22
  ### Client → server: join or leave a room
18
23
 
@@ -94,4 +99,4 @@ await createServer({
94
99
  },
95
100
  },
96
101
  });
97
- ```
102
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lastshotlabs/bunshot",
3
- "version": "0.0.21",
3
+ "version": "0.0.27",
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",
@@ -52,8 +52,8 @@
52
52
  "start": "bun src/index.ts",
53
53
  "readme": "bun docs/build-readme.ts",
54
54
  "readme:npm": "bun docs/build-readme.ts npm",
55
- "test": "bun test tests/unit tests/integration && bun test tests/isolated",
56
- "test:isolated": "bun test tests/isolated",
55
+ "test": "bun test tests/unit tests/integration && bun test tests/isolated/optional-deps.test.ts && bun test tests/isolated/jwt-secret.test.ts && bun test tests/isolated/queue.test.ts tests/isolated/jobs-router.test.ts && bun test tests/isolated/queued-deletion.test.ts",
56
+ "test:isolated": "bun test tests/isolated/optional-deps.test.ts && bun test tests/isolated/jwt-secret.test.ts && bun test tests/isolated/queue.test.ts tests/isolated/jobs-router.test.ts && bun test tests/isolated/queued-deletion.test.ts",
57
57
  "test:coverage": "bun test --coverage tests/unit tests/integration",
58
58
  "test:docker:up": "docker compose -f docker-compose.test.yml up -d --wait",
59
59
  "test:docker:down": "docker compose -f docker-compose.test.yml down",
@@ -66,7 +66,8 @@
66
66
  "@hono/zod-openapi": "1.2.2",
67
67
  "@scalar/hono-api-reference": "0.10.0",
68
68
  "arctic": "^3.7.0",
69
- "jose": "6.2.0"
69
+ "jose": "6.2.0",
70
+ "samlify": "^2.8"
70
71
  },
71
72
  "peerDependencies": {
72
73
  "hono": ">=4.12 <5",
@@ -75,7 +76,10 @@
75
76
  "ioredis": ">=5.0 <6",
76
77
  "bullmq": ">=5.0 <6",
77
78
  "otpauth": ">=9.0 <10",
78
- "@simplewebauthn/server": ">=10.0.0"
79
+ "@simplewebauthn/server": ">=10.0.0",
80
+ "@aws-sdk/client-s3": ">=3.0",
81
+ "@aws-sdk/s3-request-presigner": ">=3.0",
82
+ "@aws-sdk/lib-storage": ">=3.0"
79
83
  },
80
84
  "peerDependenciesMeta": {
81
85
  "mongoose": {
@@ -92,6 +96,15 @@
92
96
  },
93
97
  "@simplewebauthn/server": {
94
98
  "optional": true
99
+ },
100
+ "@aws-sdk/client-s3": {
101
+ "optional": true
102
+ },
103
+ "@aws-sdk/s3-request-presigner": {
104
+ "optional": true
105
+ },
106
+ "@aws-sdk/lib-storage": {
107
+ "optional": true
95
108
  }
96
109
  },
97
110
  "devDependencies": {