@db3.ai/app 0.1.0-beta.1 → 0.1.0

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 (653) hide show
  1. package/README.md +75 -17
  2. package/agent-instructions.md +76 -0
  3. package/dist/ai/AIErrors.d.ts +7 -1
  4. package/dist/ai/AIErrors.js +8 -1
  5. package/dist/ai/AIFailover.d.ts +10 -0
  6. package/dist/ai/AIFailover.js +3 -2
  7. package/dist/ai/AIProviderAdmission.d.ts +101 -0
  8. package/dist/ai/AIProviderAdmission.js +336 -0
  9. package/dist/ai/AIRateLimiter.d.ts +11 -2
  10. package/dist/ai/AIRateLimiter.js +125 -10
  11. package/dist/ai/AIRequestTrackingError.d.ts +22 -0
  12. package/dist/ai/AIRequestTrackingError.js +33 -0
  13. package/dist/ai/Agent.d.ts +7 -7
  14. package/dist/ai/Agent.js +84 -56
  15. package/dist/ai/Ai.d.ts +11 -3
  16. package/dist/ai/Ai.js +94 -44
  17. package/dist/ai/OpenAIProviderError.js +1 -1
  18. package/dist/ai/ProviderSseObserver.d.ts +19 -0
  19. package/dist/ai/ProviderSseObserver.js +98 -0
  20. package/dist/ai/aiServiceTier.d.ts +5 -0
  21. package/dist/ai/aiServiceTier.js +14 -0
  22. package/dist/ai/chunkEmbeddingText.d.ts +14 -0
  23. package/dist/ai/chunkEmbeddingText.js +147 -0
  24. package/dist/ai/contracts/AI.d.ts +16 -0
  25. package/dist/ai/contracts/AIRequestErrorDiagnostics.d.ts +12 -0
  26. package/dist/ai/contracts/AIRequestErrorDiagnostics.js +1 -0
  27. package/dist/ai/contracts/AIServiceTier.d.ts +2 -0
  28. package/dist/ai/contracts/AIServiceTier.js +1 -0
  29. package/dist/ai/contracts/Agent.d.ts +3 -0
  30. package/dist/ai/contracts/EmbeddingTextChunk.d.ts +25 -0
  31. package/dist/ai/contracts/EmbeddingTextChunk.js +1 -0
  32. package/dist/ai/contracts/Pricing.d.ts +7 -1
  33. package/dist/ai/contracts/Providers.d.ts +2 -0
  34. package/dist/ai/contracts/index.d.ts +2 -0
  35. package/dist/ai/contracts/index.js +1 -0
  36. package/dist/ai/index.d.ts +8 -2
  37. package/dist/ai/index.js +5 -2
  38. package/dist/ai/{Models.d.ts → modelPricing.d.ts} +2 -2
  39. package/dist/ai/{Models.js → modelPricing.js} +38 -4
  40. package/dist/apps/AppInstallation.d.ts +21 -0
  41. package/dist/apps/AppInstallation.js +16 -0
  42. package/dist/apps/AppRouteError.d.ts +6 -0
  43. package/dist/apps/AppRouteError.js +10 -0
  44. package/dist/apps/AppService.d.ts +24 -0
  45. package/dist/apps/AppService.js +21 -0
  46. package/dist/apps/Apps.d.ts +59 -0
  47. package/dist/apps/Apps.js +566 -0
  48. package/dist/apps/appsPlugin.d.ts +3 -0
  49. package/dist/apps/appsPlugin.js +36 -0
  50. package/dist/apps/commands/disableApp.d.ts +2 -0
  51. package/dist/apps/commands/disableApp.js +5 -0
  52. package/dist/apps/commands/enableApp.d.ts +2 -0
  53. package/dist/apps/commands/enableApp.js +5 -0
  54. package/dist/apps/commands/index.d.ts +10 -0
  55. package/dist/apps/commands/index.js +27 -0
  56. package/dist/apps/commands/installApp.d.ts +2 -0
  57. package/dist/apps/commands/installApp.js +5 -0
  58. package/dist/apps/commands/listApps.d.ts +2 -0
  59. package/dist/apps/commands/listApps.js +5 -0
  60. package/dist/apps/commands/makeAppMigration.d.ts +2 -0
  61. package/dist/apps/commands/makeAppMigration.js +9 -0
  62. package/dist/apps/commands/rollbackApp.d.ts +2 -0
  63. package/dist/apps/commands/rollbackApp.js +5 -0
  64. package/dist/apps/commands/uninstallApp.d.ts +2 -0
  65. package/dist/apps/commands/uninstallApp.js +5 -0
  66. package/dist/apps/contracts/AppAction.d.ts +2 -0
  67. package/dist/apps/contracts/AppAction.js +1 -0
  68. package/dist/apps/contracts/AppDefinition.d.ts +45 -0
  69. package/dist/apps/contracts/AppDefinition.js +1 -0
  70. package/dist/apps/contracts/AppDescription.d.ts +32 -0
  71. package/dist/apps/contracts/AppDescription.js +1 -0
  72. package/dist/apps/contracts/AppManifest.d.ts +30 -0
  73. package/dist/apps/contracts/AppManifest.js +1 -0
  74. package/dist/apps/contracts/AppNavigation.d.ts +9 -0
  75. package/dist/apps/contracts/AppNavigation.js +1 -0
  76. package/dist/apps/contracts/AppNavigationBadge.d.ts +6 -0
  77. package/dist/apps/contracts/AppNavigationBadge.js +1 -0
  78. package/dist/apps/contracts/AppNavigationContext.d.ts +8 -0
  79. package/dist/apps/contracts/AppNavigationContext.js +1 -0
  80. package/dist/apps/contracts/AppNavigationResult.d.ts +12 -0
  81. package/dist/apps/contracts/AppNavigationResult.js +1 -0
  82. package/dist/apps/contracts/AppRoute.d.ts +19 -0
  83. package/dist/apps/contracts/AppRoute.js +1 -0
  84. package/dist/apps/contracts/DiscoveredApp.d.ts +10 -0
  85. package/dist/apps/contracts/DiscoveredApp.js +1 -0
  86. package/dist/apps/contracts/InstalledApps.d.ts +3 -0
  87. package/dist/apps/contracts/InstalledApps.js +1 -0
  88. package/dist/apps/contracts/index.d.ts +11 -0
  89. package/dist/apps/contracts/index.js +1 -0
  90. package/dist/apps/defineApp.d.ts +7 -0
  91. package/dist/apps/defineApp.js +8 -0
  92. package/dist/apps/discoverApps.d.ts +3 -0
  93. package/dist/apps/discoverApps.js +62 -0
  94. package/dist/apps/fastify.d.ts +1 -0
  95. package/dist/apps/fastify.js +1 -0
  96. package/dist/apps/index.d.ts +7 -0
  97. package/dist/apps/index.js +7 -0
  98. package/dist/apps/normalizeAppNavigation.d.ts +3 -0
  99. package/dist/apps/normalizeAppNavigation.js +30 -0
  100. package/dist/apps/registerAppRoutes.d.ts +6 -0
  101. package/dist/apps/registerAppRoutes.js +52 -0
  102. package/dist/apps/validateAppDefinitions.d.ts +3 -0
  103. package/dist/apps/validateAppDefinitions.js +65 -0
  104. package/dist/apps/vite.d.ts +1 -0
  105. package/dist/apps/vite.js +1 -0
  106. package/dist/apps/writeAppTypes.d.ts +2 -0
  107. package/dist/apps/writeAppTypes.js +13 -0
  108. package/dist/auth/Auth.d.ts +18 -0
  109. package/dist/auth/Auth.js +89 -13
  110. package/dist/auth/PasswordLoginAttempt.d.ts +50 -0
  111. package/dist/auth/PasswordLoginAttempt.js +60 -0
  112. package/dist/auth/PasswordSuspendedError.d.ts +10 -0
  113. package/dist/auth/PasswordSuspendedError.js +13 -0
  114. package/dist/auth/contracts/PasswordAuthProviderOptions.d.ts +26 -0
  115. package/dist/auth/contracts/PasswordAuthProviderOptions.js +1 -0
  116. package/dist/auth/contracts/PasswordSuspension.d.ts +7 -0
  117. package/dist/auth/contracts/PasswordSuspension.js +1 -0
  118. package/dist/auth/contracts/PasswordSuspensionOptions.d.ts +10 -0
  119. package/dist/auth/contracts/PasswordSuspensionOptions.js +1 -0
  120. package/dist/auth/contracts/index.d.ts +3 -0
  121. package/dist/auth/contracts/index.js +3 -0
  122. package/dist/auth/index.d.ts +2 -0
  123. package/dist/auth/index.js +2 -0
  124. package/dist/auth/providers/PasswordAuthProvider.d.ts +19 -23
  125. package/dist/auth/providers/PasswordAuthProvider.js +96 -8
  126. package/dist/auth/providers/resolveAuthProviders.js +1 -0
  127. package/dist/cli/Cli.d.ts +2 -0
  128. package/dist/cli/Cli.js +8 -0
  129. package/dist/cli/commands/repl.js +2 -1
  130. package/dist/cli/contracts/CliCommand.d.ts +2 -0
  131. package/dist/cli/contracts/CliCommandInfo.d.ts +12 -0
  132. package/dist/cli/contracts/CliCommandInfo.js +1 -0
  133. package/dist/cli/contracts/index.d.ts +1 -0
  134. package/dist/cli/index.d.ts +1 -0
  135. package/dist/cli/index.js +1 -0
  136. package/dist/cli/listDb3Commands.d.ts +8 -0
  137. package/dist/cli/listDb3Commands.js +10 -0
  138. package/dist/cli/runDb3.d.ts +3 -6
  139. package/dist/cli/runDb3.js +5 -59
  140. package/dist/cli/withDb3Cli.d.ts +4 -0
  141. package/dist/cli/withDb3Cli.js +60 -0
  142. package/dist/db/ActiveQueryBuilder.d.ts +8 -0
  143. package/dist/db/ActiveQueryBuilder.js +11 -0
  144. package/dist/db/ActiveRecord.d.ts +6 -0
  145. package/dist/db/ActiveRecord.js +8 -0
  146. package/dist/db/connection.js +14 -2
  147. package/dist/db/fields/TimestampField.d.ts +1 -1
  148. package/dist/db/fields/TimestampField.js +7 -12
  149. package/dist/db/migrations/DatabaseMigrationManager.d.ts +3 -1
  150. package/dist/db/migrations/DatabaseMigrationManager.js +9 -3
  151. package/dist/db/migrations/contracts/DatabaseMigration.d.ts +7 -0
  152. package/dist/db/migrations/index.d.ts +1 -1
  153. package/dist/db/migrations/index.js +1 -1
  154. package/dist/db/migrations/{KnexMigrationRenderer.d.ts → renderKnexMigration.d.ts} +4 -3
  155. package/dist/db/migrations/{KnexMigrationRenderer.js → renderKnexMigration.js} +7 -6
  156. package/dist/flows/FlowCompiler.js +1 -1
  157. package/dist/flows/Flows.js +1 -1
  158. package/dist/flows/index.d.ts +1 -1
  159. package/dist/flows/index.js +1 -1
  160. package/dist/health/Health.d.ts +41 -0
  161. package/dist/health/Health.js +153 -0
  162. package/dist/health/contracts/Health.d.ts +36 -0
  163. package/dist/health/contracts/Health.js +1 -0
  164. package/dist/health/contracts/HealthHttp.d.ts +15 -0
  165. package/dist/health/contracts/HealthHttp.js +1 -0
  166. package/dist/health/contracts/index.d.ts +2 -0
  167. package/dist/health/contracts/index.js +1 -0
  168. package/dist/health/databaseHealthCheck.d.ts +15 -0
  169. package/dist/health/databaseHealthCheck.js +18 -0
  170. package/dist/health/fastify.d.ts +13 -0
  171. package/dist/health/fastify.js +44 -0
  172. package/dist/health/index.d.ts +3 -0
  173. package/dist/health/index.js +2 -0
  174. package/dist/in-app/InApp.d.ts +33 -0
  175. package/dist/in-app/InApp.js +191 -0
  176. package/dist/in-app/InAppError.d.ts +6 -0
  177. package/dist/in-app/InAppError.js +10 -0
  178. package/dist/in-app/InAppRecord.d.ts +36 -0
  179. package/dist/in-app/InAppRecord.js +24 -0
  180. package/dist/in-app/contracts/InAppInbox.d.ts +45 -0
  181. package/dist/in-app/contracts/InAppInbox.js +1 -0
  182. package/dist/in-app/contracts/InAppMessage.d.ts +22 -0
  183. package/dist/in-app/contracts/InAppMessage.js +1 -0
  184. package/dist/in-app/contracts/InAppScope.d.ts +17 -0
  185. package/dist/in-app/contracts/InAppScope.js +1 -0
  186. package/dist/in-app/contracts/index.d.ts +3 -0
  187. package/dist/in-app/contracts/index.js +3 -0
  188. package/dist/in-app/index.d.ts +4 -0
  189. package/dist/in-app/index.js +4 -0
  190. package/dist/in-app/validation.d.ts +21 -0
  191. package/dist/in-app/validation.js +50 -0
  192. package/dist/index.d.ts +2 -14
  193. package/dist/index.js +2 -5
  194. package/dist/logging/DevelopmentConsole.d.ts +42 -0
  195. package/dist/logging/DevelopmentConsole.js +162 -0
  196. package/dist/logging/Log.d.ts +3 -1
  197. package/dist/logging/Log.js +5 -2
  198. package/dist/logging/PrettyLogFormatter.d.ts +16 -0
  199. package/dist/logging/PrettyLogFormatter.js +151 -0
  200. package/dist/logging/acquireDevelopmentConsole.d.ts +6 -0
  201. package/dist/logging/acquireDevelopmentConsole.js +21 -0
  202. package/dist/logging/contracts/DevelopmentConsoleOptions.d.ts +16 -0
  203. package/dist/logging/contracts/DevelopmentConsoleOptions.js +1 -0
  204. package/dist/logging/contracts/EmailLogOptions.d.ts +8 -0
  205. package/dist/logging/contracts/EmailLogOptions.js +1 -0
  206. package/dist/logging/contracts/HttpExchangeMonitorOptions.d.ts +12 -0
  207. package/dist/logging/contracts/HttpExchangeMonitorOptions.js +1 -0
  208. package/dist/logging/contracts/LogTransportOptions.d.ts +19 -0
  209. package/dist/logging/contracts/LogTransportOptions.js +1 -0
  210. package/dist/logging/contracts/LoggingOptions.d.ts +12 -1
  211. package/dist/logging/contracts/index.d.ts +4 -0
  212. package/dist/logging/contracts/index.js +4 -0
  213. package/dist/logging/drivers/PinoLoggerDriver.d.ts +3 -1
  214. package/dist/logging/drivers/PinoLoggerDriver.js +75 -16
  215. package/dist/logging/formatLogEmail.d.ts +4 -0
  216. package/dist/logging/formatLogEmail.js +21 -0
  217. package/dist/logging/highlightQueueMessage.d.ts +11 -0
  218. package/dist/logging/highlightQueueMessage.js +36 -0
  219. package/dist/logging/httpBodyCapture.d.ts +24 -0
  220. package/dist/logging/httpBodyCapture.js +321 -0
  221. package/dist/logging/index.d.ts +2 -0
  222. package/dist/logging/index.js +2 -0
  223. package/dist/logging/redactLogEmail.d.ts +2 -0
  224. package/dist/logging/redactLogEmail.js +12 -0
  225. package/dist/logging/registerHttpExchangeMonitor.d.ts +10 -0
  226. package/dist/logging/registerHttpExchangeMonitor.js +43 -0
  227. package/dist/logging/transports/EmailLogTransport.d.ts +18 -0
  228. package/dist/logging/transports/EmailLogTransport.js +47 -0
  229. package/dist/mail/Mail.d.ts +2 -0
  230. package/dist/mail/Mail.js +3 -0
  231. package/dist/mail/MailDeliveryError.d.ts +9 -0
  232. package/dist/mail/MailDeliveryError.js +16 -0
  233. package/dist/mail/index.d.ts +1 -0
  234. package/dist/mail/index.js +1 -0
  235. package/dist/mail/transports/MailgunTransport.js +2 -0
  236. package/dist/mail/transports/ResendTransport.d.ts +3 -2
  237. package/dist/mail/transports/ResendTransport.js +22 -14
  238. package/dist/media/MediaManager.d.ts +4 -2
  239. package/dist/media/MediaManager.js +40 -8
  240. package/dist/network/BlockedUrlError.d.ts +16 -0
  241. package/dist/network/BlockedUrlError.js +19 -0
  242. package/dist/network/assertPublicUrl.d.ts +22 -0
  243. package/dist/network/assertPublicUrl.js +67 -0
  244. package/dist/network/contracts/GuardedFetch.d.ts +35 -0
  245. package/dist/network/contracts/GuardedFetch.js +1 -0
  246. package/dist/network/contracts/PublicUrlOptions.d.ts +17 -0
  247. package/dist/network/contracts/PublicUrlOptions.js +1 -0
  248. package/dist/network/contracts/index.d.ts +2 -0
  249. package/dist/network/contracts/index.js +2 -0
  250. package/dist/network/guardedFetch.d.ts +29 -0
  251. package/dist/network/guardedFetch.js +133 -0
  252. package/dist/network/index.d.ts +6 -0
  253. package/dist/network/index.js +6 -0
  254. package/dist/network/playwright/GuardedBrowserProxy.d.ts +32 -0
  255. package/dist/network/playwright/GuardedBrowserProxy.js +172 -0
  256. package/dist/network/playwright/contracts/GuardedBrowserContextOptions.d.ts +37 -0
  257. package/dist/network/playwright/contracts/GuardedBrowserContextOptions.js +1 -0
  258. package/dist/network/playwright/contracts/index.d.ts +1 -0
  259. package/dist/network/playwright/contracts/index.js +1 -0
  260. package/dist/network/playwright/index.d.ts +2 -0
  261. package/dist/network/playwright/index.js +2 -0
  262. package/dist/network/playwright/newGuardedBrowserContext.d.ts +21 -0
  263. package/dist/network/playwright/newGuardedBrowserContext.js +86 -0
  264. package/dist/network/privateNetworkAddresses.d.ts +44 -0
  265. package/dist/network/privateNetworkAddresses.js +157 -0
  266. package/dist/network/publicAddressLookup.d.ts +26 -0
  267. package/dist/network/publicAddressLookup.js +42 -0
  268. package/dist/notifications/Notifications.d.ts +27 -0
  269. package/dist/notifications/Notifications.js +99 -0
  270. package/dist/notifications/contracts/Notification.d.ts +56 -0
  271. package/dist/notifications/contracts/Notification.js +1 -0
  272. package/dist/notifications/contracts/NotificationDelivery.d.ts +11 -0
  273. package/dist/notifications/contracts/NotificationDelivery.js +1 -0
  274. package/dist/notifications/contracts/index.d.ts +2 -0
  275. package/dist/notifications/contracts/index.js +2 -0
  276. package/dist/notifications/index.d.ts +2 -0
  277. package/dist/notifications/index.js +2 -0
  278. package/dist/queue/JobRunAttempt.d.ts +16 -0
  279. package/dist/queue/JobRunAttempt.js +17 -0
  280. package/dist/queue/JobRunEvent.d.ts +15 -0
  281. package/dist/queue/JobRunEvent.js +16 -0
  282. package/dist/queue/JobRunStoppedError.d.ts +6 -0
  283. package/dist/queue/JobRunStoppedError.js +10 -0
  284. package/dist/queue/Queue.d.ts +19 -4
  285. package/dist/queue/Queue.js +122 -9
  286. package/dist/queue/QueueTerminalError.d.ts +9 -0
  287. package/dist/queue/QueueTerminalError.js +12 -0
  288. package/dist/queue/QueueWorker.d.ts +12 -5
  289. package/dist/queue/QueueWorker.js +94 -9
  290. package/dist/queue/QueueWorkerReporter.d.ts +17 -0
  291. package/dist/queue/QueueWorkerReporter.js +71 -0
  292. package/dist/queue/QueueableJob.d.ts +2 -2
  293. package/dist/queue/RunnableJob.d.ts +78 -0
  294. package/dist/queue/RunnableJob.js +284 -0
  295. package/dist/queue/console.d.ts +8 -0
  296. package/dist/queue/console.js +53 -19
  297. package/dist/queue/contracts/QueueDriver.d.ts +8 -0
  298. package/dist/queue/contracts/QueueJobRunner.d.ts +4 -0
  299. package/dist/queue/contracts/QueueJobRunner.js +1 -0
  300. package/dist/queue/contracts/QueuePayload.d.ts +1 -1
  301. package/dist/queue/contracts/QueueSelection.d.ts +11 -0
  302. package/dist/queue/contracts/QueueSelection.js +1 -0
  303. package/dist/queue/contracts/QueueService.d.ts +18 -4
  304. package/dist/queue/contracts/QueueWorkerLifecycle.d.ts +2 -2
  305. package/dist/queue/contracts/QueueWorkerSnapshot.d.ts +14 -0
  306. package/dist/queue/contracts/QueueWorkerSnapshot.js +1 -0
  307. package/dist/queue/contracts/QueueableJob.d.ts +7 -3
  308. package/dist/queue/contracts/QueueableJobContract.d.ts +12 -0
  309. package/dist/queue/contracts/QueueableJobContract.js +1 -0
  310. package/dist/queue/contracts/index.d.ts +4 -0
  311. package/dist/queue/contracts/index.js +2 -0
  312. package/dist/queue/drivers/DatabaseQueueDriver.d.ts +4 -0
  313. package/dist/queue/drivers/DatabaseQueueDriver.js +27 -3
  314. package/dist/queue/drivers/RedisQueueDriver.d.ts +7 -0
  315. package/dist/queue/drivers/RedisQueueDriver.js +37 -0
  316. package/dist/queue/index.d.ts +5 -0
  317. package/dist/queue/index.js +5 -0
  318. package/dist/queue/jobEnvelopeHasPrefix.d.ts +3 -0
  319. package/dist/queue/jobEnvelopeHasPrefix.js +4 -0
  320. package/dist/scheduler/ScheduledEvent.d.ts +8 -4
  321. package/dist/scheduler/ScheduledEvent.js +17 -3
  322. package/dist/scheduler/ScheduledJob.d.ts +1 -1
  323. package/dist/scheduler/Scheduler.d.ts +5 -0
  324. package/dist/scheduler/Scheduler.js +92 -28
  325. package/dist/scheduler/SchedulerCheckpointRecord.d.ts +16 -0
  326. package/dist/scheduler/SchedulerCheckpointRecord.js +53 -0
  327. package/dist/scheduler/SchedulerWorker.d.ts +3 -5
  328. package/dist/scheduler/SchedulerWorker.js +58 -14
  329. package/dist/scheduler/console.js +3 -1
  330. package/dist/scheduler/contracts/Schedule.d.ts +19 -0
  331. package/dist/scheduler/contracts/Schedule.js +1 -0
  332. package/dist/scheduler/contracts/Scheduler.d.ts +18 -6
  333. package/dist/scheduler/contracts/SchedulerCheckpoint.d.ts +23 -0
  334. package/dist/scheduler/contracts/SchedulerCheckpoint.js +1 -0
  335. package/dist/scheduler/contracts/SchedulerConsole.d.ts +5 -0
  336. package/dist/scheduler/contracts/SchedulerWorker.d.ts +6 -1
  337. package/dist/scheduler/contracts/index.d.ts +2 -0
  338. package/dist/scheduler/contracts/index.js +2 -0
  339. package/dist/scheduler/index.d.ts +1 -0
  340. package/dist/scheduler/index.js +1 -0
  341. package/dist/server/App.d.ts +36 -4
  342. package/dist/server/App.js +57 -7
  343. package/dist/server/contracts/HttpErrorHandlerOptions.d.ts +29 -0
  344. package/dist/server/contracts/HttpErrorHandlerOptions.js +1 -0
  345. package/dist/server/contracts/index.d.ts +1 -0
  346. package/dist/server/contracts/index.js +1 -0
  347. package/dist/server/index.d.ts +4 -0
  348. package/dist/server/index.js +4 -0
  349. package/dist/server/publicServerErrorMessage.d.ts +11 -0
  350. package/dist/server/publicServerErrorMessage.js +16 -0
  351. package/dist/server/registerBrowserJsonFormatting.d.ts +16 -0
  352. package/dist/server/registerBrowserJsonFormatting.js +100 -0
  353. package/dist/server/registerHttpErrorHandler.d.ts +20 -0
  354. package/dist/server/registerHttpErrorHandler.js +93 -0
  355. package/dist/ssr/vite/contracts/ViteSsrOptions.d.ts +2 -0
  356. package/dist/ssr/vite/fastifyViteSsr.js +4 -3
  357. package/dist/storage/drivers/FlystorageDisk.js +2 -2
  358. package/dist/storage/index.d.ts +2 -2
  359. package/dist/storage/index.js +2 -2
  360. package/dist/websocket/WebSocketChannels.d.ts +18 -0
  361. package/dist/websocket/WebSocketChannels.js +129 -0
  362. package/dist/websocket/WebSocketConnection.d.ts +25 -0
  363. package/dist/websocket/WebSocketConnection.js +244 -0
  364. package/dist/websocket/WebSockets.d.ts +41 -0
  365. package/dist/websocket/WebSockets.js +194 -0
  366. package/dist/websocket/client/WebSocketClient.d.ts +21 -0
  367. package/dist/websocket/client/WebSocketClient.js +230 -0
  368. package/dist/websocket/client/index.d.ts +3 -0
  369. package/dist/websocket/client/index.js +1 -0
  370. package/dist/websocket/contracts/WebSocketChannel.d.ts +20 -0
  371. package/dist/websocket/contracts/WebSocketChannel.js +1 -0
  372. package/dist/websocket/contracts/WebSocketClient.d.ts +45 -0
  373. package/dist/websocket/contracts/WebSocketClient.js +1 -0
  374. package/dist/websocket/contracts/WebSocketEndpoint.d.ts +43 -0
  375. package/dist/websocket/contracts/WebSocketEndpoint.js +1 -0
  376. package/dist/websocket/contracts/WebSocketOptions.d.ts +48 -0
  377. package/dist/websocket/contracts/WebSocketOptions.js +1 -0
  378. package/dist/websocket/contracts/WebSocketPresence.d.ts +6 -0
  379. package/dist/websocket/contracts/WebSocketPresence.js +1 -0
  380. package/dist/websocket/contracts/WebSocketServiceOptions.d.ts +12 -0
  381. package/dist/websocket/contracts/WebSocketServiceOptions.js +1 -0
  382. package/dist/websocket/contracts/index.d.ts +6 -0
  383. package/dist/websocket/contracts/index.js +1 -0
  384. package/dist/websocket/defineChannel.d.ts +8 -0
  385. package/dist/websocket/defineChannel.js +13 -0
  386. package/dist/websocket/defineWebSocket.d.ts +14 -0
  387. package/dist/websocket/defineWebSocket.js +4 -0
  388. package/dist/websocket/fastify.d.ts +11 -0
  389. package/dist/websocket/fastify.js +38 -0
  390. package/dist/websocket/index.d.ts +5 -0
  391. package/dist/websocket/index.js +4 -0
  392. package/package.json +108 -7
  393. package/src/ai/AIErrors.ts +10 -0
  394. package/src/ai/AIFailover.ts +3 -2
  395. package/src/ai/AIProviderAdmission.ts +329 -0
  396. package/src/ai/AIRateLimiter.ts +134 -10
  397. package/src/ai/AIRequestTrackingError.ts +37 -0
  398. package/src/ai/Agent.ts +85 -71
  399. package/src/ai/Ai.ts +104 -48
  400. package/src/ai/OpenAIProviderError.ts +1 -1
  401. package/src/ai/ProviderSseObserver.ts +89 -0
  402. package/src/ai/README.md +216 -11
  403. package/src/ai/aiServiceTier.ts +14 -0
  404. package/src/ai/chunkEmbeddingText.ts +137 -0
  405. package/src/ai/contracts/AI.ts +17 -0
  406. package/src/ai/contracts/AIRequestErrorDiagnostics.ts +12 -0
  407. package/src/ai/contracts/AIServiceTier.ts +2 -0
  408. package/src/ai/contracts/Agent.ts +3 -0
  409. package/src/ai/contracts/EmbeddingTextChunk.ts +26 -0
  410. package/src/ai/contracts/Pricing.ts +7 -1
  411. package/src/ai/contracts/Providers.ts +2 -0
  412. package/src/ai/contracts/index.ts +2 -0
  413. package/src/ai/examples/prepareDocumentEmbeddings.ts +20 -0
  414. package/src/ai/index.ts +9 -2
  415. package/src/ai/{Models.ts → modelPricing.ts} +37 -4
  416. package/src/apps/AppInstallation.ts +25 -0
  417. package/src/apps/AppRouteError.ts +8 -0
  418. package/src/apps/AppService.ts +30 -0
  419. package/src/apps/Apps.ts +508 -0
  420. package/src/apps/README.md +354 -0
  421. package/src/apps/appsPlugin.ts +34 -0
  422. package/src/apps/commands/disableApp.ts +6 -0
  423. package/src/apps/commands/enableApp.ts +6 -0
  424. package/src/apps/commands/index.ts +29 -0
  425. package/src/apps/commands/installApp.ts +6 -0
  426. package/src/apps/commands/listApps.ts +6 -0
  427. package/src/apps/commands/makeAppMigration.ts +9 -0
  428. package/src/apps/commands/rollbackApp.ts +6 -0
  429. package/src/apps/commands/uninstallApp.ts +6 -0
  430. package/src/apps/contracts/AppAction.ts +2 -0
  431. package/src/apps/contracts/AppDefinition.ts +49 -0
  432. package/src/apps/contracts/AppDescription.ts +27 -0
  433. package/src/apps/contracts/AppManifest.ts +32 -0
  434. package/src/apps/contracts/AppNavigation.ts +10 -0
  435. package/src/apps/contracts/AppNavigationBadge.ts +6 -0
  436. package/src/apps/contracts/AppNavigationContext.ts +6 -0
  437. package/src/apps/contracts/AppNavigationResult.ts +9 -0
  438. package/src/apps/contracts/AppRoute.ts +18 -0
  439. package/src/apps/contracts/DiscoveredApp.ts +11 -0
  440. package/src/apps/contracts/InstalledApps.ts +2 -0
  441. package/src/apps/contracts/index.ts +11 -0
  442. package/src/apps/defineApp.ts +10 -0
  443. package/src/apps/discoverApps.ts +56 -0
  444. package/src/apps/fastify.ts +1 -0
  445. package/src/apps/index.ts +7 -0
  446. package/src/apps/normalizeAppNavigation.ts +29 -0
  447. package/src/apps/registerAppRoutes.ts +48 -0
  448. package/src/apps/validateAppDefinitions.ts +52 -0
  449. package/src/apps/vite.ts +1 -0
  450. package/src/apps/writeAppTypes.ts +14 -0
  451. package/src/auth/Auth.ts +89 -23
  452. package/src/auth/PasswordLoginAttempt.ts +72 -0
  453. package/src/auth/PasswordSuspendedError.ts +13 -0
  454. package/src/auth/README.md +94 -4
  455. package/src/auth/contracts/PasswordAuthProviderOptions.ts +30 -0
  456. package/src/auth/contracts/PasswordSuspension.ts +7 -0
  457. package/src/auth/contracts/PasswordSuspensionOptions.ts +10 -0
  458. package/src/auth/contracts/index.ts +3 -0
  459. package/src/auth/examples/runPasswordAuth.ts +6 -6
  460. package/src/auth/index.ts +2 -0
  461. package/src/auth/providers/PasswordAuthProvider.ts +96 -30
  462. package/src/auth/providers/resolveAuthProviders.ts +1 -0
  463. package/src/cli/Cli.ts +9 -0
  464. package/src/cli/README.md +19 -0
  465. package/src/cli/commands/repl.ts +2 -1
  466. package/src/cli/contracts/CliCommand.ts +2 -0
  467. package/src/cli/contracts/CliCommandInfo.ts +13 -0
  468. package/src/cli/contracts/index.ts +1 -0
  469. package/src/cli/index.ts +1 -0
  470. package/src/cli/listDb3Commands.ts +12 -0
  471. package/src/cli/runDb3.ts +5 -51
  472. package/src/cli/withDb3Cli.ts +55 -0
  473. package/src/db/ActiveQueryBuilder.ts +12 -0
  474. package/src/db/ActiveRecord.ts +9 -0
  475. package/src/db/README.md +45 -1
  476. package/src/db/connection.ts +12 -1
  477. package/src/db/examples/FieldNote.ts +7 -13
  478. package/src/db/examples/KnowledgeNote.ts +7 -13
  479. package/src/db/examples/runNoteMigrations.ts +11 -11
  480. package/src/db/fields/TimestampField.ts +8 -17
  481. package/src/db/migrations/DatabaseMigrationManager.ts +12 -4
  482. package/src/db/migrations/contracts/DatabaseMigration.ts +9 -0
  483. package/src/db/migrations/index.ts +1 -1
  484. package/src/db/migrations/{KnexMigrationRenderer.ts → renderKnexMigration.ts} +8 -14
  485. package/src/flows/FlowCompiler.ts +1 -1
  486. package/src/flows/Flows.ts +1 -1
  487. package/src/flows/index.ts +1 -1
  488. package/src/health/Health.ts +163 -0
  489. package/src/health/README.md +46 -0
  490. package/src/health/contracts/Health.ts +47 -0
  491. package/src/health/contracts/HealthHttp.ts +19 -0
  492. package/src/health/contracts/index.ts +2 -0
  493. package/src/health/databaseHealthCheck.ts +21 -0
  494. package/src/health/fastify.ts +49 -0
  495. package/src/health/index.ts +3 -0
  496. package/src/in-app/InApp.ts +173 -0
  497. package/src/in-app/InAppError.ts +8 -0
  498. package/src/in-app/InAppRecord.ts +41 -0
  499. package/src/in-app/README.md +193 -0
  500. package/src/in-app/contracts/InAppInbox.ts +51 -0
  501. package/src/in-app/contracts/InAppMessage.ts +19 -0
  502. package/src/in-app/contracts/InAppScope.ts +12 -0
  503. package/src/in-app/contracts/index.ts +3 -0
  504. package/src/in-app/examples/sendReportReady.ts +14 -0
  505. package/src/in-app/index.ts +4 -0
  506. package/src/in-app/validation.ts +49 -0
  507. package/src/index.ts +2 -22
  508. package/src/logging/DevelopmentConsole.ts +154 -0
  509. package/src/logging/Log.ts +5 -2
  510. package/src/logging/PrettyLogFormatter.ts +165 -0
  511. package/src/logging/README.md +52 -109
  512. package/src/logging/acquireDevelopmentConsole.ts +19 -0
  513. package/src/logging/contracts/DevelopmentConsoleOptions.ts +13 -0
  514. package/src/logging/contracts/EmailLogOptions.ts +9 -0
  515. package/src/logging/contracts/HttpExchangeMonitorOptions.ts +13 -0
  516. package/src/logging/contracts/LogTransportOptions.ts +10 -0
  517. package/src/logging/contracts/LoggingOptions.ts +12 -1
  518. package/src/logging/contracts/index.ts +4 -0
  519. package/src/logging/development/README.md +88 -0
  520. package/src/logging/drivers/PinoLoggerDriver.ts +72 -20
  521. package/src/logging/examples/runDevelopmentLogs.ts +36 -0
  522. package/src/logging/formatLogEmail.ts +24 -0
  523. package/src/logging/highlightQueueMessage.ts +38 -0
  524. package/src/logging/httpBodyCapture.ts +393 -0
  525. package/src/logging/index.ts +2 -0
  526. package/src/logging/redactLogEmail.ts +12 -0
  527. package/src/logging/registerHttpExchangeMonitor.ts +45 -0
  528. package/src/logging/transports/EmailLogTransport.ts +46 -0
  529. package/src/mail/Mail.ts +5 -0
  530. package/src/mail/MailDeliveryError.ts +8 -0
  531. package/src/mail/README.md +11 -1
  532. package/src/mail/index.ts +1 -0
  533. package/src/mail/transports/MailgunTransport.ts +2 -0
  534. package/src/mail/transports/ResendTransport.ts +24 -22
  535. package/src/media/MediaManager.ts +31 -10
  536. package/src/media/README.md +4 -0
  537. package/src/media/examples/runPrivateFiles.ts +2 -2
  538. package/src/network/BlockedUrlError.ts +19 -0
  539. package/src/network/README.md +133 -0
  540. package/src/network/assertPublicUrl.ts +77 -0
  541. package/src/network/contracts/GuardedFetch.ts +40 -0
  542. package/src/network/contracts/PublicUrlOptions.ts +17 -0
  543. package/src/network/contracts/index.ts +2 -0
  544. package/src/network/examples/runGuardedFetch.ts +35 -0
  545. package/src/network/guardedFetch.ts +150 -0
  546. package/src/network/index.ts +6 -0
  547. package/src/network/playwright/GuardedBrowserProxy.ts +172 -0
  548. package/src/network/playwright/contracts/GuardedBrowserContextOptions.ts +43 -0
  549. package/src/network/playwright/contracts/index.ts +1 -0
  550. package/src/network/playwright/index.ts +2 -0
  551. package/src/network/playwright/newGuardedBrowserContext.ts +74 -0
  552. package/src/network/privateNetworkAddresses.ts +166 -0
  553. package/src/network/publicAddressLookup.ts +49 -0
  554. package/src/notifications/Notifications.ts +105 -0
  555. package/src/notifications/README.md +59 -0
  556. package/src/notifications/contracts/Notification.ts +65 -0
  557. package/src/notifications/contracts/NotificationDelivery.ts +11 -0
  558. package/src/notifications/contracts/index.ts +2 -0
  559. package/src/notifications/examples/ReportReadyNotification.ts +31 -0
  560. package/src/notifications/index.ts +2 -0
  561. package/src/queue/JobRunAttempt.ts +17 -0
  562. package/src/queue/JobRunEvent.ts +16 -0
  563. package/src/queue/JobRunStoppedError.ts +8 -0
  564. package/src/queue/Queue.ts +112 -11
  565. package/src/queue/QueueTerminalError.ts +12 -0
  566. package/src/queue/QueueWorker.ts +86 -7
  567. package/src/queue/QueueWorkerReporter.ts +59 -0
  568. package/src/queue/QueueableJob.ts +2 -2
  569. package/src/queue/README.md +142 -1
  570. package/src/queue/RunnableJob.ts +272 -0
  571. package/src/queue/console.ts +54 -21
  572. package/src/queue/contracts/QueueDriver.ts +9 -0
  573. package/src/queue/contracts/QueueJobRunner.ts +4 -0
  574. package/src/queue/contracts/QueuePayload.ts +1 -1
  575. package/src/queue/contracts/QueueSelection.ts +11 -0
  576. package/src/queue/contracts/QueueService.ts +19 -4
  577. package/src/queue/contracts/QueueWorkerLifecycle.ts +2 -2
  578. package/src/queue/contracts/QueueWorkerSnapshot.ts +15 -0
  579. package/src/queue/contracts/QueueableJob.ts +7 -3
  580. package/src/queue/contracts/QueueableJobContract.ts +13 -0
  581. package/src/queue/contracts/index.ts +6 -0
  582. package/src/queue/drivers/DatabaseQueueDriver.ts +25 -3
  583. package/src/queue/drivers/RedisQueueDriver.ts +36 -0
  584. package/src/queue/examples/runQueueSelection.ts +34 -0
  585. package/src/queue/index.ts +7 -0
  586. package/src/queue/jobEnvelopeHasPrefix.ts +6 -0
  587. package/src/scheduler/README.md +50 -9
  588. package/src/scheduler/ScheduledEvent.ts +20 -5
  589. package/src/scheduler/ScheduledJob.ts +1 -1
  590. package/src/scheduler/Scheduler.ts +73 -27
  591. package/src/scheduler/SchedulerCheckpointRecord.ts +51 -0
  592. package/src/scheduler/SchedulerWorker.ts +54 -19
  593. package/src/scheduler/console.ts +3 -1
  594. package/src/scheduler/contracts/Schedule.ts +22 -0
  595. package/src/scheduler/contracts/Scheduler.ts +21 -6
  596. package/src/scheduler/contracts/SchedulerCheckpoint.ts +23 -0
  597. package/src/scheduler/contracts/SchedulerConsole.ts +5 -0
  598. package/src/scheduler/contracts/SchedulerWorker.ts +6 -1
  599. package/src/scheduler/contracts/index.ts +2 -0
  600. package/src/scheduler/examples/runSchedulerWindow.ts +54 -0
  601. package/src/scheduler/index.ts +1 -0
  602. package/src/server/App.ts +73 -10
  603. package/src/server/README.md +95 -2
  604. package/src/server/contracts/HttpErrorHandlerOptions.ts +29 -0
  605. package/src/server/contracts/index.ts +1 -0
  606. package/src/server/examples/createFirstServer.ts +5 -3
  607. package/src/server/index.ts +4 -0
  608. package/src/server/publicServerErrorMessage.ts +17 -0
  609. package/src/server/registerBrowserJsonFormatting.ts +94 -0
  610. package/src/server/registerHttpErrorHandler.ts +96 -0
  611. package/src/ssr/README.md +6 -0
  612. package/src/ssr/vite/contracts/ViteSsrOptions.ts +2 -0
  613. package/src/ssr/vite/fastifyViteSsr.ts +4 -3
  614. package/src/storage/drivers/FlystorageDisk.ts +2 -2
  615. package/src/storage/index.ts +2 -2
  616. package/src/websocket/README.md +458 -0
  617. package/src/websocket/WebSocketChannels.ts +99 -0
  618. package/src/websocket/WebSocketConnection.ts +162 -0
  619. package/src/websocket/WebSockets.ts +157 -0
  620. package/src/websocket/client/WebSocketClient.ts +188 -0
  621. package/src/websocket/client/index.ts +3 -0
  622. package/src/websocket/contracts/WebSocketChannel.ts +23 -0
  623. package/src/websocket/contracts/WebSocketClient.ts +48 -0
  624. package/src/websocket/contracts/WebSocketEndpoint.ts +46 -0
  625. package/src/websocket/contracts/WebSocketOptions.ts +50 -0
  626. package/src/websocket/contracts/WebSocketPresence.ts +6 -0
  627. package/src/websocket/contracts/WebSocketServiceOptions.ts +12 -0
  628. package/src/websocket/contracts/index.ts +6 -0
  629. package/src/websocket/defineChannel.ts +14 -0
  630. package/src/websocket/defineWebSocket.ts +15 -0
  631. package/src/websocket/examples/board/LiveBoard.ts +97 -0
  632. package/src/websocket/examples/board/SummarizeBoardJob.ts +52 -0
  633. package/src/websocket/examples/board/boardSnapshot.ts +16 -0
  634. package/src/websocket/examples/board/createBoardClient.ts +43 -0
  635. package/src/websocket/examples/board/createBoardStore.ts +58 -0
  636. package/src/websocket/examples/board/registerBoardRoutes.ts +31 -0
  637. package/src/websocket/examples/createChannelSync.ts +66 -0
  638. package/src/websocket/examples/deploymentEndpoint.ts +13 -0
  639. package/src/websocket/examples/deploymentSnapshot.ts +36 -0
  640. package/src/websocket/examples/resourceChannels.ts +18 -0
  641. package/src/websocket/examples/userEndpoint.ts +27 -0
  642. package/src/websocket/fastify.ts +39 -0
  643. package/src/websocket/index.ts +5 -0
  644. package/templates/AGENTS.md +33 -2
  645. /package/dist/flows/{FlowValueValidator.d.ts → flowValueValidator.d.ts} +0 -0
  646. /package/dist/flows/{FlowValueValidator.js → flowValueValidator.js} +0 -0
  647. /package/dist/storage/{mime.d.ts → mimeTypeFromPath.d.ts} +0 -0
  648. /package/dist/storage/{mime.js → mimeTypeFromPath.js} +0 -0
  649. /package/dist/storage/{contents.d.ts → storageContentsToBuffer.d.ts} +0 -0
  650. /package/dist/storage/{contents.js → storageContentsToBuffer.js} +0 -0
  651. /package/src/flows/{FlowValueValidator.ts → flowValueValidator.ts} +0 -0
  652. /package/src/storage/{mime.ts → mimeTypeFromPath.ts} +0 -0
  653. /package/src/storage/{contents.ts → storageContentsToBuffer.ts} +0 -0
@@ -0,0 +1,162 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import type { IncomingMessage } from 'node:http';
3
+ import { WebSocket, type RawData } from 'ws';
4
+ import type { App } from '../server/App';
5
+ import type { UserIdentity } from '../auth';
6
+ import type * as socket from './contracts';
7
+
8
+ /** One bounded JSON connection with fresh request authentication for application work. */
9
+ export class WebSocketConnection {
10
+ readonly id = randomUUID();
11
+ readonly #abort = new AbortController();
12
+ #token = '';
13
+ #userId = '';
14
+ #ready = false;
15
+ #pending = 0;
16
+ /** Bounds retained payloads and concurrent access checks before transport buffering. */
17
+ #pendingSends = 0;
18
+ #chain: Promise<void> = Promise.resolve();
19
+ #alive = true;
20
+ #checking = false;
21
+ #timer: ReturnType<typeof setTimeout>;
22
+ #heartbeat: ReturnType<typeof setInterval>;
23
+ #finished: Promise<void>;
24
+ #finish!: () => void;
25
+
26
+ /** Attaches listeners synchronously so early frames cannot bypass authentication. */
27
+ constructor(readonly ws: WebSocket, readonly request: IncomingMessage, readonly app: App, readonly endpoint: socket.WebSocketEndpoint<any, any>, readonly limits: socket.WebSocketLimits, readonly report: (error: unknown) => void, readonly presenceChanged: () => void, readonly resolveToken?: socket.WebSocketOptions['resolveToken']) {
28
+ this.#finished = new Promise(resolve => { this.#finish = resolve; });
29
+ this.#abort.signal.addEventListener('abort', () => { if (this.#ready) this.presenceChanged(); }, { once: true });
30
+ this.#timer = setTimeout(() => this.stop(4401), limits.authTimeoutMs);
31
+ this.#heartbeat = setInterval(() => void this.#tick(), limits.heartbeatMs);
32
+ this.#timer.unref();
33
+ this.#heartbeat.unref();
34
+ ws.on('pong', () => { this.#alive = true; });
35
+ ws.on('error', () => this.stop(1011));
36
+ ws.on('message', (data, binary) => this.#receive(data, binary));
37
+ ws.once('close', code => {
38
+ this.#abort.abort();
39
+ this.#token = '';
40
+ clearTimeout(this.#timer);
41
+ clearInterval(this.#heartbeat);
42
+ // Wait for open/message work before cleanup, including an open action that closed itself.
43
+ void this.#chain.then(async () => {
44
+ if (this.#ready) await this.endpoint.close?.({ id: this.id, userId: this.#userId || null, code });
45
+ }).catch(this.report).finally(this.#finish);
46
+ });
47
+ }
48
+
49
+ /** Stops delivery immediately and bounds the close handshake for unresponsive peers. */
50
+ stop(code = 1000): void {
51
+ if (this.#abort.signal.aborted) return;
52
+ this.#abort.abort();
53
+ clearTimeout(this.#timer);
54
+ clearInterval(this.#heartbeat);
55
+ this.ws.close(code);
56
+ this.#timer = setTimeout(() => this.ws.terminate(), this.limits.shutdownTimeoutMs);
57
+ this.#timer.unref();
58
+ }
59
+
60
+ /** Resolves when the socket and application cleanup are finished. */
61
+ finished(): Promise<void> { return this.#finished; }
62
+
63
+ /** Returns local presence only after authorization and until shutdown/disconnection. */
64
+ get userId(): string | null { return this.#ready && !this.#abort.signal.aborted ? this.#userId : null; }
65
+
66
+ /** Queues bounded text messages; the first frame is reserved for authentication. */
67
+ #receive(data: RawData, binary: boolean): void {
68
+ if (this.#abort.signal.aborted) return;
69
+ if (binary) { this.stop(1003); return; }
70
+ if (++this.#pending > this.limits.maxPendingMessages) { this.stop(1008); return; }
71
+ const text = data.toString();
72
+ this.#chain = this.#chain.then(async () => {
73
+ if (this.#abort.signal.aborted) return;
74
+ let frame: any;
75
+ try { frame = JSON.parse(text); } catch { this.stop(1007); return; }
76
+ if (!this.#ready) {
77
+ if (this.endpoint.auth === 'public') {
78
+ if (frame?.type !== 'connect') { this.stop(1008); return; }
79
+ } else {
80
+ if (frame?.type !== 'authenticate') { this.stop(4401); return; }
81
+ const token = this.resolveToken ? await this.resolveToken(this.request) : frame.token;
82
+ if (typeof token !== 'string' || !token || token.length > 4096) { this.stop(4401); return; }
83
+ this.#token = token;
84
+ }
85
+ await this.#authorized(async context => {
86
+ this.#ready = true;
87
+ clearTimeout(this.#timer);
88
+ this.#write({ type: 'ready' });
89
+ this.presenceChanged();
90
+ await this.endpoint.open?.(context);
91
+ });
92
+ return;
93
+ }
94
+ if (frame?.type !== 'message' || !Object.hasOwn(frame, 'data')) { this.stop(1008); return; }
95
+ await this.#authorized(async context => {
96
+ if (await this.app.webSockets.channels.handle(this.endpoint.channels ?? [], context, frame.data)) return;
97
+ let data: unknown;
98
+ try { data = this.endpoint.parse ? await this.endpoint.parse(frame.data) : frame.data; } catch { this.stop(1008); return; }
99
+ if (!this.#abort.signal.aborted) await this.endpoint.message?.(context, data);
100
+ });
101
+ }).catch(error => { this.report(error); this.stop(1011); }).finally(() => { this.#pending--; });
102
+ }
103
+
104
+ /** Revalidates sessions and application access without retaining request-scoped Auth memoization. */
105
+ async #authorized(action: (context: socket.WebSocketContext<any>) => Promise<void>): Promise<boolean> {
106
+ if (this.#abort.signal.aborted) return false;
107
+ return this.app.requestContext.run(async () => {
108
+ const publicEndpoint = this.endpoint.auth === 'public';
109
+ const user = publicEndpoint ? null : await this.app.auth.authenticateToken(this.#token);
110
+ if (this.#abort.signal.aborted) return false;
111
+ if (!publicEndpoint && !user) { this.stop(4401); return false; }
112
+ const userId = user ? String(user.get((user.constructor as typeof UserIdentity).primaryKey)) : null;
113
+ if (this.#userId && this.#userId !== userId) { this.stop(4401); return false; }
114
+ this.#userId = userId ?? '';
115
+ const context: socket.WebSocketContext<any> = {
116
+ id: this.id, userId, user, request: this.request, signal: this.#abort.signal,
117
+ send: (data, authorize) => this.#send(data, authorize), close: () => this.stop(),
118
+ };
119
+ if (this.endpoint.authorize && !await this.endpoint.authorize(context)) { this.stop(4403); return false; }
120
+ if (this.#abort.signal.aborted) return false;
121
+ await action(context);
122
+ return true;
123
+ });
124
+ }
125
+
126
+ /** Bounds in-flight sends before fresh access checks; overload disconnects without queuing or replay. */
127
+ async #send(data: unknown, authorize?: Parameters<socket.WebSocketContext<any>['send']>[1]): Promise<boolean> {
128
+ if (this.#abort.signal.aborted) return false;
129
+ if (this.#pendingSends >= this.limits.maxPendingSends) { this.stop(1013); return false; }
130
+ this.#pendingSends++;
131
+ try {
132
+ let sent = false;
133
+ await this.#authorized(async context => {
134
+ if (!authorize || await authorize(context)) sent = this.#write({ type: 'message', data });
135
+ });
136
+ return sent;
137
+ } catch (error) { this.report(error); this.stop(1011); return false; }
138
+ finally { this.#pendingSends--; }
139
+ }
140
+
141
+ /** Enforces frame and buffered-byte limits before handing data to the socket. */
142
+ #write(frame: unknown): boolean {
143
+ if (this.#abort.signal.aborted || this.ws.readyState !== WebSocket.OPEN) return false;
144
+ const value = JSON.stringify(frame);
145
+ const bytes = Buffer.byteLength(value);
146
+ if (bytes > this.limits.maxPayloadBytes || this.ws.bufferedAmount + bytes > this.limits.maxBufferedBytes) { this.stop(1009); return false; }
147
+ this.ws.send(value, error => { if (error) this.stop(1011); });
148
+ return true;
149
+ }
150
+
151
+ /** Detects dead peers and revokes idle authenticated sockets as well as active ones. */
152
+ async #tick(): Promise<void> {
153
+ if (this.#abort.signal.aborted) return;
154
+ if (!this.#alive || this.#checking) { this.stop(1011); return; }
155
+ this.#alive = false;
156
+ this.ws.ping();
157
+ if (!this.#ready) return;
158
+ this.#checking = true;
159
+ try { await this.#authorized(async () => {}); } catch (error) { this.report(error); this.stop(1011); }
160
+ finally { this.#checking = false; }
161
+ }
162
+ }
@@ -0,0 +1,157 @@
1
+ import type { Server, IncomingMessage } from 'node:http';
2
+ import type { Duplex } from 'node:stream';
3
+ import { WebSocketServer } from 'ws';
4
+ import type { App } from '../server/App';
5
+ import { WebSocketConnection } from './WebSocketConnection';
6
+ import { WebSocketChannels } from './WebSocketChannels';
7
+ import type * as socket from './contracts';
8
+
9
+ /** App-owned WebSocket endpoints on an existing Node HTTP/S listener. */
10
+ export class WebSockets {
11
+ /** Process-local, server-authorized channels. Each delivery rechecks the connection's policy. */
12
+ readonly channels = new WebSocketChannels();
13
+ #close: (() => Promise<void>) | undefined;
14
+ readonly #connections = new Map<WebSocketConnection, string>();
15
+ readonly #presenceListeners = new Map<string, Set<() => void | Promise<void>>>();
16
+ #closed = false;
17
+
18
+ /** Configures worker HTTP delivery without opening a connection or starting a polling loop. */
19
+ constructor(readonly app: App, readonly options: socket.WebSocketServiceOptions = {}) {
20
+ if (options.publish) {
21
+ const { url, token, timeoutMs = 5000 } = options.publish;
22
+ const target = new URL(url);
23
+ if (!['http:', 'https:'].includes(target.protocol) || target.username || target.password || target.hash || target.search) throw new Error('Use a trusted HTTP(S) publishing URL without credentials, query or fragment.');
24
+ if (!/^[A-Za-z0-9_-]{32,256}$/.test(token)) throw new Error('WebSocket publish token must contain 32 to 256 URL-safe characters.');
25
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0) throw new Error('Invalid WebSocket publish timeout.');
26
+ }
27
+ }
28
+
29
+ /** Returns a publisher for a concrete authorized resource channel. No client subscription is created. */
30
+ channel(name: string): socket.WebSocketChannelPublisher {
31
+ if (name.length > 256 || !/^[a-zA-Z0-9_.-]+(?::[a-zA-Z0-9_.-]+)*$/.test(name)) throw new Error('Invalid channel name.');
32
+ return { publish: async (event, data) => {
33
+ if (!event || event.length > 256) throw new Error('Invalid channel event.');
34
+ if (this.#closed) throw new Error('WebSocket service is closed.');
35
+ const message = JSON.stringify({ channel: name, event, data });
36
+ if (Buffer.byteLength(message) > 65536) throw new Error('WebSocket event exceeds 64 KiB.');
37
+ if (!this.#close && this.options.publish) {
38
+ const { url, token, timeoutMs = 5000 } = this.options.publish;
39
+ const response = await fetch(url, {
40
+ method: 'POST', redirect: 'error', signal: AbortSignal.timeout(timeoutMs),
41
+ headers: { authorization: `Bearer ${token}`, 'content-type': 'application/json' }, body: message,
42
+ });
43
+ try {
44
+ if (!response.ok) throw new Error(`WebSocket publishing failed (${response.status}).`);
45
+ // HTTP acceptance is not a browser delivery receipt or a remote connection count.
46
+ return 0;
47
+ } finally { await response.body?.cancel(); }
48
+ }
49
+ return this.channels.publishEvent(name, event, data);
50
+ } };
51
+ }
52
+
53
+ /**
54
+ * Mounts exact-path endpoint actions on one existing HTTP/S server.
55
+ * @param server - Host-owned listener; the service never opens or closes its port.
56
+ * @param options - Allowed origins, controller definitions and resource limits.
57
+ */
58
+ mount(server: Server, options: socket.WebSocketOptions): void {
59
+ if (this.#close) throw new Error('WebSockets are already mounted.');
60
+ this.#closed = false;
61
+ const origins = new Set(options.origins);
62
+ if (!origins.size || [...origins].some(origin => { try { const url = new URL(origin); return !['http:', 'https:'].includes(url.protocol) || url.origin !== origin; } catch { return true; } })) throw new Error('WebSockets require exact HTTP(S) origins.');
63
+ const endpoints = new Map(Object.entries(options.endpoints));
64
+ for (const [path, endpoint] of endpoints) {
65
+ if (!path.startsWith('/') || path.startsWith('//') || /[?#\s]/.test(path) || !endpoint || typeof endpoint !== 'object') throw new Error('Invalid WebSocket endpoint.');
66
+ }
67
+ const limits: socket.WebSocketLimits = { maxConnections: 1000, maxPayloadBytes: 65536, maxBufferedBytes: 262144, maxPendingMessages: 16, maxPendingSends: 32, authTimeoutMs: 5000, heartbeatMs: 30000, shutdownTimeoutMs: 3000 };
68
+ for (const key of Object.keys(limits) as (keyof socket.WebSocketLimits)[]) {
69
+ const value = options[key] ?? limits[key];
70
+ if (!Number.isSafeInteger(value) || value <= 0) throw new Error(`Invalid WebSocket limit: ${key}.`);
71
+ limits[key] = value;
72
+ }
73
+ const connections = this.#connections;
74
+ const sockets = new WebSocketServer({ noServer: true, maxPayload: limits.maxPayloadBytes, perMessageDeflate: false });
75
+ /** Contains reporting failures and keeps credentials out of transport diagnostics. */
76
+ const report = (error: unknown): void => { try { options.onError?.(error); } catch { /* Reporting cannot break cleanup. */ } };
77
+ let closing: Promise<void> | undefined;
78
+ /** Handles only owned paths; other upgrade listeners retain ownership of their paths. */
79
+ const upgrade = (request: IncomingMessage, stream: Duplex, head: Buffer): void => {
80
+ let pathname: string;
81
+ try { pathname = new URL(request.url ?? '/', 'http://localhost').pathname; } catch { stream.destroy(); return; }
82
+ const endpoint = endpoints.get(pathname);
83
+ if (!endpoint) {
84
+ if (server.listenerCount('upgrade') === 1) stream.end('HTTP/1.1 404 Not Found\r\nConnection: close\r\nContent-Length: 0\r\n\r\n');
85
+ return;
86
+ }
87
+ if (closing || !request.headers.origin || !origins.has(request.headers.origin) || connections.size >= limits.maxConnections) {
88
+ stream.end('HTTP/1.1 403 Forbidden\r\nConnection: close\r\nContent-Length: 0\r\n\r\n');
89
+ return;
90
+ }
91
+ sockets.handleUpgrade(request, stream, head, ws => {
92
+ const connection = new WebSocketConnection(ws, request, this.app, endpoint, limits, report, () => {
93
+ for (const listener of this.#presenceListeners.get(pathname) ?? []) {
94
+ try { void Promise.resolve(this.app.requestContext.run(listener)).catch(report); } catch (error) { report(error); }
95
+ }
96
+ }, options.resolveToken);
97
+ connections.set(connection, pathname);
98
+ void connection.finished().then(() => connections.delete(connection));
99
+ });
100
+ };
101
+ server.on('upgrade', upgrade);
102
+ this.#close = () => closing ??= (async () => {
103
+ const current = [...connections.keys()];
104
+ for (const connection of current) connection.stop(1001);
105
+ let timer: ReturnType<typeof setTimeout> | undefined;
106
+ try {
107
+ await Promise.race([Promise.all(current.map(connection => connection.finished())), new Promise<void>(resolve => { timer = setTimeout(resolve, limits.shutdownTimeoutMs); })]);
108
+ } finally {
109
+ clearTimeout(timer);
110
+ for (const connection of current) connection.ws.terminate();
111
+ connections.clear();
112
+ this.#presenceListeners.clear();
113
+ this.channels.clear();
114
+ server.off('upgrade', upgrade);
115
+ await new Promise<void>(resolve => sockets.close(() => resolve()));
116
+ this.#close = undefined;
117
+ }
118
+ })();
119
+ }
120
+
121
+ /** Stops accepting upgrades and bounds socket/application cleanup; safe to call repeatedly. */
122
+ async close(): Promise<void> { this.#closed = true; await this.#close?.(); }
123
+
124
+ /**
125
+ * Lists distinct authenticated users connected to an exact endpoint in this process.
126
+ * Application code must authorize disclosure; this is never exposed automatically.
127
+ * Presence is approximate and excludes the other instance during blue/green overlap.
128
+ * @param path - Registered endpoint path, excluding query parameters.
129
+ * @returns Current user IDs and connection counts; no session tokens or profile data.
130
+ */
131
+ presence(path: string): socket.WebSocketPresence[] {
132
+ const users = new Map<string, number>();
133
+ for (const [connection, endpoint] of this.#connections) {
134
+ const id = connection.userId;
135
+ if (endpoint === path && id) users.set(id, (users.get(id) ?? 0) + 1);
136
+ }
137
+ return [...users].map(([userId, connections]) => ({ userId, connections }));
138
+ }
139
+
140
+ /**
141
+ * Subscribes to local presence changes on one exact endpoint, without exposing them to browsers.
142
+ * Call presence(path) inside the listener to read current users. The listener has no Auth context
143
+ * and does not serialize asynchronous listeners; connection.send() reauthenticates its recipient.
144
+ * @param path - Exact endpoint whose authenticated connections should be observed.
145
+ * @param listener - Application callback for connect, disconnect or revocation changes.
146
+ * @returns Idempotent unsubscribe callback; register it on the owning connection's abort signal.
147
+ */
148
+ onPresence(path: string, listener: () => void | Promise<void>): () => void {
149
+ let listeners = this.#presenceListeners.get(path);
150
+ if (!listeners) { listeners = new Set(); this.#presenceListeners.set(path, listeners); }
151
+ listeners.add(listener);
152
+ return () => {
153
+ listeners.delete(listener);
154
+ if (!listeners.size && this.#presenceListeners.get(path) === listeners) this.#presenceListeners.delete(path);
155
+ };
156
+ }
157
+ }
@@ -0,0 +1,188 @@
1
+ import type { ClientSocket, WebSocketClientOptions, WebSocketClientState, WebSocketClientChannel } from '../contracts/WebSocketClient';
2
+
3
+ /** Browser-safe JSON transport with generation fencing, bounded reconnect and no offline message queue. */
4
+ export class WebSocketClient {
5
+ #subscriptionQueue: string[] = [];
6
+ #subscribing: string | undefined;
7
+ readonly #channels = new Map<string, Map<string, Set<(data: unknown) => void>>>();
8
+ #state: WebSocketClientState = 'closed';
9
+ #socket: ClientSocket | undefined;
10
+ #generation = 0;
11
+ #attempt = 0;
12
+ #stopped = true;
13
+ #retry: ReturnType<typeof setTimeout> | undefined;
14
+ #deadline: ReturnType<typeof setTimeout> | undefined;
15
+ readonly #options: Required<Pick<WebSocketClientOptions, 'maxReconnectDelayMs' | 'connectTimeoutMs' | 'maxBufferedBytes'>> & WebSocketClientOptions;
16
+
17
+ /** Validates configuration without opening a connection, allowing safe SSR imports. */
18
+ constructor(options: WebSocketClientOptions) {
19
+ const url = new URL(options.url);
20
+ if (!['ws:', 'wss:'].includes(url.protocol) || url.username || url.password || url.hash) throw new Error('Use an absolute ws(s) URL without credentials or fragment.');
21
+ this.#options = { maxReconnectDelayMs: 30000, connectTimeoutMs: 10000, maxBufferedBytes: 65536, ...options };
22
+ for (const key of ['maxReconnectDelayMs', 'connectTimeoutMs', 'maxBufferedBytes'] as const) {
23
+ if (!Number.isSafeInteger(this.#options[key]) || this.#options[key] <= 0) throw new Error(`Invalid WebSocket client option: ${key}.`);
24
+ }
25
+ }
26
+
27
+ /** Current transport state; connected is set only after the server accepts the endpoint handshake. */
28
+ get state(): WebSocketClientState { return this.#state; }
29
+
30
+ /** Starts once, or explicitly retries after credentials/policy have been repaired. */
31
+ connect(): void {
32
+ if (!this.#stopped) return;
33
+ this.#stopped = false;
34
+ this.#attempt = 0;
35
+ void this.#open();
36
+ }
37
+
38
+ /** Closes the socket, cancels reconnect and fences late token/network results. */
39
+ close(): void {
40
+ this.#stopped = true;
41
+ this.#generation++;
42
+ clearTimeout(this.#retry);
43
+ clearTimeout(this.#deadline);
44
+ this.#socket?.close(1000);
45
+ this.#socket = undefined;
46
+ this.#subscriptionQueue = [];
47
+ this.#subscribing = undefined;
48
+ this.#setState('closed');
49
+ }
50
+
51
+ /** Sends JSON only while connected; rejected messages are never queued or replayed. */
52
+ send(data: unknown): boolean {
53
+ if (this.#state !== 'connected' || this.#socket?.readyState !== 1 || data === undefined) return false;
54
+ const frame = JSON.stringify({ type: 'message', data });
55
+ if (new TextEncoder().encode(frame).length + this.#socket.bufferedAmount > this.#options.maxBufferedBytes) return false;
56
+ this.#socket.send(frame);
57
+ return true;
58
+ }
59
+
60
+ /**
61
+ * Returns a logical channel sharing this client's physical connection.
62
+ * The first listener subscribes; removing the final listener unsubscribes.
63
+ * @param name - Concrete resource channel; the server must independently authorize it.
64
+ */
65
+ channel(name: string): WebSocketClientChannel {
66
+ if (name.length > 256 || !/^[a-zA-Z0-9_.-]+(?::[a-zA-Z0-9_.-]+)*$/.test(name)) throw new Error('Invalid channel name.');
67
+ return { on: (event, listener) => {
68
+ if (!event || event.length > 256) throw new Error('Invalid channel event.');
69
+ let events = this.#channels.get(name);
70
+ const subscribe = !events;
71
+ if (!events) { events = new Map(); this.#channels.set(name, events); }
72
+ let listeners = events.get(event);
73
+ if (!listeners) { listeners = new Set(); events.set(event, listeners); }
74
+ // Each registration owns its own cleanup, even when callbacks are identical.
75
+ const callback = (data: unknown) => listener(data);
76
+ listeners.add(callback);
77
+ if (subscribe && this.#state === 'connected') { this.#subscriptionQueue.push(name); this.#subscribeNext(); }
78
+ return () => {
79
+ if (!listeners.delete(callback)) return;
80
+ if (!listeners.size) events.delete(event);
81
+ if (!events.size) {
82
+ this.#channels.delete(name);
83
+ this.#subscriptionQueue = this.#subscriptionQueue.filter(channel => channel !== name);
84
+ this.send({ type: 'channel.unsubscribe', channel: name });
85
+ }
86
+ };
87
+ } };
88
+ }
89
+
90
+ /** Serializes subscriptions so a reconnect cannot flood the server's bounded action queue. */
91
+ #subscribeNext(): void {
92
+ if (this.#subscribing || this.#state !== 'connected') return;
93
+ const channel = this.#subscriptionQueue.shift();
94
+ if (!channel) return;
95
+ this.#subscribing = channel;
96
+ if (!this.send({ type: 'channel.subscribe', channel })) { this.#subscribing = undefined; this.#deny(); }
97
+ }
98
+
99
+ /** Dispatches reserved channel envelopes without exposing them as ordinary application messages. */
100
+ #message(data: any): void {
101
+ if (data && typeof data.channel === 'string' && typeof data.type === 'string' && data.type.startsWith('channel.')) {
102
+ if (['channel.subscribed', 'channel.denied'].includes(data.type) && this.#subscribing === data.channel) { this.#subscribing = undefined; this.#subscribeNext(); }
103
+ const events = this.#channels.get(data.channel);
104
+ if (!events) return;
105
+ if (data.type === 'channel.subscribed') this.#options.onSubscribed?.(data.channel);
106
+ else if (data.type === 'channel.denied') this.#options.onChannelDenied?.(data.channel);
107
+ else if (data.type === 'channel.event' && typeof data.event === 'string') {
108
+ for (const listener of [...events.get(data.event) ?? []]) listener(data.data);
109
+ }
110
+ return;
111
+ }
112
+ this.#options.onMessage?.(data);
113
+ }
114
+
115
+ /** Creates one attempt and ignores callbacks from replaced connections. */
116
+ async #open(): Promise<void> {
117
+ const generation = ++this.#generation;
118
+ this.#setState(this.#attempt ? 'reconnecting' : 'connecting');
119
+ this.#deadline = setTimeout(() => {
120
+ if (generation !== this.#generation || this.#stopped) return;
121
+ this.#generation++;
122
+ this.#socket?.close();
123
+ this.#retryLater();
124
+ }, this.#options.connectTimeoutMs);
125
+ try {
126
+ const auth = this.#options.auth ?? 'required';
127
+ const token = auth === 'required' ? await this.#options.token?.() : null;
128
+ if (generation !== this.#generation || this.#stopped) return;
129
+ if (auth === 'required' && !token) { this.#deny(); return; }
130
+ const factory = this.#options.createSocket ?? nativeSocket;
131
+ const ws = factory(this.#options.url);
132
+ this.#socket = ws;
133
+ ws.onopen = () => { if (generation === this.#generation && !this.#stopped) ws.send(JSON.stringify(auth === 'public' ? { type: 'connect' } : { type: 'authenticate', token })); };
134
+ ws.onmessage = event => {
135
+ if (generation !== this.#generation || this.#stopped) return;
136
+ let frame: any;
137
+ try { frame = JSON.parse(String(event.data)); } catch { this.#deny(); return; }
138
+ if (frame?.type === 'ready' && this.#state !== 'connected') {
139
+ clearTimeout(this.#deadline);
140
+ this.#attempt = 0;
141
+ this.#subscriptionQueue = [...this.#channels.keys()];
142
+ this.#subscribing = undefined;
143
+ this.#setState('connected');
144
+ this.#subscribeNext();
145
+ } else if (frame?.type === 'message' && this.#state === 'connected') this.#message(frame.data);
146
+ else this.#deny();
147
+ };
148
+ ws.onerror = () => { /* Native sockets report terminal failures through close. */ };
149
+ ws.onclose = event => {
150
+ if (generation !== this.#generation || this.#stopped) return;
151
+ this.#generation++;
152
+ clearTimeout(this.#deadline);
153
+ if ([4401, 4403, 1003, 1007, 1008, 1009].includes(event.code)) this.#deny();
154
+ else this.#retryLater();
155
+ };
156
+ } catch {
157
+ if (generation !== this.#generation || this.#stopped) return;
158
+ clearTimeout(this.#deadline);
159
+ this.#retryLater();
160
+ }
161
+ }
162
+
163
+ /** Stops retries on an authentication, policy or protocol failure until explicitly connected again. */
164
+ #deny(): void { this.close(); this.#setState('denied'); }
165
+
166
+ /** Retries transient failures with exponential backoff and jitter after blue/green shutdown. */
167
+ #retryLater(): void {
168
+ if (this.#stopped) return;
169
+ if (this.#options.reconnect === false) { this.close(); return; }
170
+ this.#setState('reconnecting');
171
+ const ceiling = Math.min(this.#options.maxReconnectDelayMs, 500 * 2 ** Math.min(this.#attempt++, 16));
172
+ this.#retry = setTimeout(() => void this.#open(), Math.max(1, Math.floor(ceiling * (0.5 + Math.random() * 0.5))));
173
+ }
174
+
175
+ /** Notifies state consumers only when the lifecycle actually changes. */
176
+ #setState(state: WebSocketClientState): void {
177
+ if (this.#state === state) return;
178
+ this.#state = state;
179
+ this.#options.onState?.(state);
180
+ }
181
+ }
182
+
183
+ /** Resolves the native browser constructor only when connecting, keeping imports SSR-safe. */
184
+ function nativeSocket(url: string): ClientSocket {
185
+ const Constructor = (globalThis as unknown as { WebSocket: new (url: string) => ClientSocket }).WebSocket;
186
+ if (!Constructor) throw new Error('This runtime needs a WebSocket factory.');
187
+ return new Constructor(url);
188
+ }
@@ -0,0 +1,3 @@
1
+ export { WebSocketClient } from './WebSocketClient';
2
+ export type { ClientSocket, WebSocketClientOptions, WebSocketClientState } from '../contracts/WebSocketClient';
3
+ export type { WebSocketClientChannel } from '../contracts/WebSocketClient';
@@ -0,0 +1,23 @@
1
+ import type { WebSocketContext } from './WebSocketEndpoint';
2
+
3
+ /** Current authenticated context and parameters extracted from a channel name. */
4
+ export type WebSocketChannelContext = Omit<WebSocketContext, 'send' | 'close'> & {
5
+ /** Concrete requested channel, never trusted as proof of access. */
6
+ readonly channel: string;
7
+ /** Colon-delimited parameters matched by the server's definition. */
8
+ readonly params: Readonly<Record<string, string>>;
9
+ };
10
+
11
+ /** Application policy for a named resource channel. All subscriptions require authorization. */
12
+ export interface WebSocketChannelDefinition {
13
+ /** Pattern such as website:{websiteId}:jobs. */
14
+ readonly pattern: string;
15
+ /** Pure access check, evaluated on subscription and before every delivery. */
16
+ authorize(context: WebSocketChannelContext): boolean | Promise<boolean>;
17
+ }
18
+
19
+ /** Server publisher for a concrete channel, including configured worker-to-API HTTP delivery. */
20
+ export interface WebSocketChannelPublisher {
21
+ /** Returns accepted local sends, or zero after remote HTTP acceptance. Never a browser delivery receipt. */
22
+ publish(event: string, data: unknown): Promise<number>;
23
+ }
@@ -0,0 +1,48 @@
1
+ /** Browser-visible lifecycle; connected means accepted under the endpoint's authentication policy. */
2
+ export type WebSocketClientState = 'closed' | 'connecting' | 'connected' | 'reconnecting' | 'denied';
3
+
4
+ /** Minimal native browser socket surface, also usable by Node integration clients. */
5
+ export interface ClientSocket {
6
+ readonly readyState: number;
7
+ readonly bufferedAmount: number;
8
+ onopen: ((event: any) => void) | null;
9
+ /** Native implementations have different event types; the client reads only data. */
10
+ onmessage: ((event: any) => void) | null;
11
+ /** Native implementations have different event types; the client reads only code. */
12
+ onclose: ((event: any) => void) | null;
13
+ onerror: ((event: any) => void) | null;
14
+ send(data: string): void;
15
+ close(code?: number): void;
16
+ }
17
+
18
+ /** Options for the small JSON client. Incoming application data remains untrusted/unknown. */
19
+ export interface WebSocketClientOptions {
20
+ /** Absolute ws(s) URL. Production applications must use wss. */
21
+ url: string;
22
+ /** Reads the existing bearer session afresh on every connection attempt. Never put it in the URL. */
23
+ token?(): string | null | Promise<string | null>;
24
+ /** Must match the endpoint: required (default), cookie (server token resolver), or public. */
25
+ auth?: 'required' | 'cookie' | 'public';
26
+ onMessage?(data: unknown): void;
27
+ /** Called after each successful subscription, including reconnect; reload durable state here. */
28
+ onSubscribed?(channel: string): void;
29
+ /** Called when a requested channel is unknown, ambiguous, denied or exceeds the subscription limit. */
30
+ onChannelDenied?(channel: string): void;
31
+ onState?(state: WebSocketClientState): void;
32
+ /** Defaults to true; authentication/policy denial and protocol failures never auto-retry. */
33
+ reconnect?: boolean;
34
+ /** Maximum delay between reconnect attempts. Default 30000 ms. */
35
+ maxReconnectDelayMs?: number;
36
+ /** Deadline including token acquisition, socket opening and server authentication. Default 10000 ms. */
37
+ connectTimeoutMs?: number;
38
+ /** Outgoing frame/queue limit. Default 64 KiB. */
39
+ maxBufferedBytes?: number;
40
+ /** Optional native-compatible factory for non-browser runtimes. */
41
+ createSocket?(url: string): ClientSocket;
42
+ }
43
+
44
+ /** Browser-side view of one logical channel on a shared connection. */
45
+ export interface WebSocketClientChannel {
46
+ /** Adds a listener and subscribes on first use. Returns idempotent listener cleanup. Payloads require app validation. */
47
+ on(event: string, listener: (data: unknown) => void): () => void;
48
+ }
@@ -0,0 +1,46 @@
1
+ import type { WebSocketChannelDefinition } from './WebSocketChannel';
2
+ import type { IncomingMessage } from 'node:http';
3
+ import type { UserIdentity } from '../../auth';
4
+
5
+ /** An accepted connection. Authenticated sends recheck the session; all sends recheck endpoint policy. */
6
+ export interface WebSocketContext<TUser extends UserIdentity | null = UserIdentity> {
7
+ /** Stable connection identity for application-owned local subscriptions. */
8
+ readonly id: string;
9
+ /** Authenticated account ID, derived from framework Auth rather than client data. */
10
+ readonly userId: TUser extends UserIdentity ? string : null;
11
+ /** Fresh account model for this action. */
12
+ readonly user: TUser;
13
+ /** Original upgrade request; HTTP route hooks do not run for this transport. */
14
+ readonly request: IncomingMessage;
15
+ /** Aborted as soon as the connection closes or shutdown starts. */
16
+ readonly signal: AbortSignal;
17
+ /** Sends JSON after fresh authentication and endpoint policy checks. Optional resource guard uses that same fresh context; false means closed or denied. */
18
+ send(data: unknown, authorize?: (context: Omit<WebSocketContext<TUser>, 'send' | 'close'>) => boolean | Promise<boolean>): Promise<boolean>;
19
+ /** Ends the connection normally. */
20
+ close(): void;
21
+ }
22
+
23
+ /** Cleanup metadata; close handlers run without authenticated request context. */
24
+ export interface WebSocketClosed {
25
+ id: string;
26
+ userId: string | null;
27
+ code: number;
28
+ }
29
+
30
+ /** Controller-style JSON endpoint. Authentication is required unless public access is explicit. */
31
+ export interface WebSocketEndpoint<TMessage = unknown, TUser extends UserIdentity | null = UserIdentity> {
32
+ /** Authentication is required by default. Public endpoints explicitly opt out and always have null identity. */
33
+ auth?: TUser extends UserIdentity ? 'required' : 'public';
34
+ /** Additional resource/tenant authorization, rerun before each action and send. */
35
+ authorize?(context: Omit<WebSocketContext<TUser>, 'send' | 'close'>): boolean | Promise<boolean>;
36
+ /** Named channel policies on this shared endpoint. Anonymous endpoints cannot subscribe to private channels. */
37
+ channels?: readonly WebSocketChannelDefinition[];
38
+ /** Called once after authentication. May send initial application data. */
39
+ open?(context: WebSocketContext<TUser>): void | Promise<void>;
40
+ /** Validates untrusted JSON. Throwing closes with 1008; omitted messages remain unknown. */
41
+ parse?(data: unknown): TMessage | Promise<TMessage>;
42
+ /** Called sequentially for validated application messages in a fresh Auth context. */
43
+ message?(context: WebSocketContext<TUser>, data: TMessage): void | Promise<void>;
44
+ /** Releases application subscriptions. Must settle promptly and must not send data. */
45
+ close?(context: WebSocketClosed): void | Promise<void>;
46
+ }