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

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 +68 -9
  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,458 @@
1
+ # WebSocket endpoints
2
+
3
+ `@db3.ai/app/websocket` provides `app.webSockets` and controller-style JSON
4
+ endpoints on the existing HTTP/S server. It uses framework Auth, with a fresh
5
+ RequestContext for each action and server send. No new database tables are needed.
6
+ It depends on Auth, Server and `ws`; the Fastify adapter is optional. The browser
7
+ subpath uses the native WebSocket API and imports no server runtime.
8
+
9
+ ## Define and mount an endpoint
10
+
11
+ ```ts
12
+ import Fastify from 'fastify';
13
+ import { App } from '@db3.ai/app/server';
14
+ import { defineWebSocket } from '@db3.ai/app/websocket';
15
+ import { registerWebSockets } from '@db3.ai/app/websocket/fastify';
16
+
17
+ const application = new App(); // Use your existing configured application.
18
+ const server = Fastify();
19
+ const userEndpoint = defineWebSocket({
20
+ /** Runs after this connection authenticates. */
21
+ async open({ userId, send }) {
22
+ await send({ type: 'hello', userId });
23
+ },
24
+ /** Validates untrusted input and supplies the message action's inferred type. */
25
+ parse(data: unknown): { text: string } {
26
+ if (!data || typeof data !== 'object' || !('text' in data) || typeof data.text !== 'string') throw new Error('Expected text.');
27
+ return { text: data.text };
28
+ },
29
+ /** Handles one validated command at a time for this socket. */
30
+ async message({ send }, data) {
31
+ await send({ type: 'echo', text: data.text });
32
+ },
33
+ });
34
+ registerWebSockets(server, {
35
+ app: application,
36
+ origins: ['https://app.example.com'],
37
+ endpoints: { '/ws/me': userEndpoint },
38
+ });
39
+ server.addHook('onClose', async () => { await application.close(); });
40
+ await server.listen({ host: '127.0.0.1', port: 8001 });
41
+ ```
42
+
43
+ Definitions can live in controller files and be imported into the endpoint map.
44
+ Paths are exact; file discovery, dynamic route parameters and HTTP middleware
45
+ execution are not part of this module. `request.url` exposes query parameters.
46
+ For a plain Node HTTP/S server use `application.webSockets.mount(server, options)`;
47
+ close the WebSocket service **before** awaiting HTTP server shutdown. The Fastify
48
+ adapter does this in `preClose`, and `App.close()` also closes the service before
49
+ clearing application context or closing the database.
50
+
51
+ The [user endpoint example](./examples/userEndpoint.ts) includes command validation
52
+ and private presence access. Tests execute it through the public exports.
53
+
54
+ ## Browser client
55
+
56
+ ```ts
57
+ import { WebSocketClient } from '@db3.ai/app/websocket/client';
58
+
59
+ const url = new URL('/ws/me', window.location.href);
60
+ url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
61
+ const client = new WebSocketClient({
62
+ url: url.href,
63
+ /** Return the current bearer session from the application's existing Auth store. */
64
+ token: () => authStore.token,
65
+ /** Validate your application payload before assigning it to a store. */
66
+ onMessage(data) { handleMessage(data); },
67
+ onState(state) { connectionState.value = state; },
68
+ });
69
+ client.connect();
70
+ client.send({ text: 'Hello' }); // false until authenticated; nothing is queued.
71
+ // On logout/component ownership disposal:
72
+ client.close();
73
+ ```
74
+
75
+ Own the client once per desired endpoint/SPA lifecycle. Imports are safe during
76
+ SSR; the native constructor is resolved only when connecting. Messages remain
77
+ `unknown` until the application validates them. There is no snapshot protocol,
78
+ replay, Pinia dependency or automatic store update. `open` may send ordinary
79
+ application data if desired. When a token changes, close and reconnect explicitly.
80
+
81
+ The client automatically retries transient closure (including 1001 during a
82
+ deployment) with jittered exponential backoff up to 30 seconds. Authentication,
83
+ authorization and protocol errors stop retries (`denied`); call `connect()` after
84
+ repairing credentials. No offline writes are queued or repeated. An HTTP upgrade
85
+ rejection appears as a generic network failure in browsers, so it follows the
86
+ transient retry policy; inspect server/proxy configuration if this persists.
87
+
88
+ ## Authentication and authorization
89
+
90
+ The first frame is `{ "type": "authenticate", "token": "..." }`. The token is
91
+ the existing framework bearer session, held in memory for the connection and
92
+ never placed in URLs or subprotocols. Use **WSS/HTTPS in production**, and do not
93
+ log frames or credentials. Cookie applications supply `resolveToken(request)` at
94
+ mount time and use browser client `auth: 'cookie'`. The resolver reads the existing
95
+ HttpOnly session cookie on the server; it replaces (never falls back to) the frame
96
+ token. No readable cookie or ticket endpoint is needed. Configure exact allowed
97
+ origins; missing/foreign origins are rejected, including for public endpoints.
98
+
99
+ Use `defineWebSocket({ auth: 'public', ...actions })` and client `auth: 'public'`
100
+ for an explicitly anonymous endpoint. Its first frame is `{ "type": "connect" }`.
101
+ Both `context.user` and `context.userId` are null, even when the browser has a
102
+ session cookie. Omitted auth always requires authentication. Public endpoints
103
+ still have origin, heartbeat, payload and connection limits; applications must
104
+ add appropriate abuse controls before opening anonymous writes to the internet.
105
+
106
+ After authentication the server sends `{ "type": "ready" }`. Application frames
107
+ in both directions are `{ "type": "message", "data": ... }`. The library hides
108
+ these envelopes from controller and browser callbacks. Text JSON is supported;
109
+ binary frames and malformed messages close the connection. `send()` returning true
110
+ means accepted into the local transport, not that the browser processed it.
111
+
112
+ Every `open`/`message` action runs with the authenticated user in framework Auth.
113
+ `context.send()` and heartbeat checks also revalidate the session and optional
114
+ `authorize(context)` policy. Authorization must be a pure access decision and
115
+ must not send data. Apps own resource/tenant checks and mutation permissions,
116
+ including read-only impersonation. Fastify HTTP hooks are bypassed by upgrades;
117
+ do not assume existing HTTP billing or tenant middleware protects these actions.
118
+
119
+ Auth currently records session/account activity on authentication, so these fresh
120
+ checks perform database reads and writes. This deliberately prioritizes existing
121
+ Auth semantics over adding a second session cache in this first implementation.
122
+ Permission checks fail closed. Idle revocation is detected on the next heartbeat
123
+ (30 seconds by default), plus query time; a pending check is terminated if it
124
+ has not settled by the following heartbeat. Already-running actions may finish.
125
+ Use `context.signal` to cancel application work and `close({ id, userId, code })`
126
+ to dispose listeners. Close callbacks run without authenticated request context.
127
+ They must settle promptly; shutdown is bounded even if application work hangs.
128
+
129
+ ## One connection, multiple authorized channels
130
+
131
+ Own one `WebSocketClient` per browser tab at app startup and share it across
132
+ features. Register private channel policies on one authenticated endpoint:
133
+
134
+ ```ts
135
+ import { defineChannel, defineWebSocket } from '@db3.ai/app/websocket';
136
+
137
+ const endpoint = defineWebSocket({
138
+ channels: [
139
+ defineChannel('website:{websiteId}:jobs', {
140
+ /** Application helper queries current organization and website membership. */
141
+ authorize: ({ userId, params }) => canAccessWebsite(userId, params.websiteId),
142
+ }),
143
+ ],
144
+ });
145
+ // Mount endpoint at /ws through registerWebSockets using your existing app.
146
+ ```
147
+
148
+ `canAccessWebsite` is your application's access helper, not a framework method.
149
+ The [resource channels example](./examples/resourceChannels.ts) accepts this
150
+ policy as a function and also demonstrates private account notifications.
151
+ Authentication alone never grants website access. Parameters match whole
152
+ colon-delimited segments. Unknown or ambiguously matched channels are denied.
153
+ Every subscription and delivery checks the current session, endpoint policy and
154
+ resource policy. Revoked membership removes the subscription before sending the
155
+ payload. Anonymous endpoints cannot subscribe to these private channels.
156
+
157
+ ```ts
158
+ // After saving the new job state, in the same API process:
159
+ await application.webSockets.channel(`website:${websiteId}:jobs`)
160
+ .publish('completed', { jobId });
161
+ ```
162
+
163
+ ```ts
164
+ const realtime = new WebSocketClient({
165
+ url: websocketUrl, // Absolute ws(s) URL for /ws.
166
+ token: () => authStore.token,
167
+ /** Reload authorized HTTP state after subscribing, including reconnect. */
168
+ onSubscribed: channel => reloadChannelState(channel),
169
+ onChannelDenied: channel => showAccessDenied(channel),
170
+ });
171
+ realtime.connect();
172
+
173
+ const unsubscribe = realtime.channel(`website:${websiteId}:jobs`)
174
+ .on('completed', data => {
175
+ // Payloads are unknown: validate with your application's schema.
176
+ const event = jobCompletedSchema.parse(data);
177
+ refreshJob(event.jobId);
178
+ });
179
+ // Component disposal removes this listener only:
180
+ unsubscribe();
181
+ // App logout disposes the shared connection:
182
+ realtime.close();
183
+ ```
184
+
185
+ The first listener subscribes; removing the final listener unsubscribes. Each
186
+ registration owns its cleanup, including identical callbacks. Connections are
187
+ not shared across separate tabs/devices. A reconnect restores remaining
188
+ subscriptions and reruns access checks. `onSubscribed` is the place to reconcile
189
+ saved state; events have no history or replay guarantee. `onChannelDenied`
190
+ reports rejected subscriptions without closing unrelated channels. To retry a
191
+ denied subscription after permissions change, remove its listeners and add them
192
+ again, or reconnect. On logout, also dispose user-owned feature listeners before
193
+ reusing the client with another account. At most 128 channels may be subscribed
194
+ per connection. Channel and event names are limited to 256 characters.
195
+
196
+ Publishing returns accepted local sends, not browser delivery receipts. Configure authenticated HTTP publication below for separate job workers.
197
+ Public subscription policies are not provided by this API. Existing explicit public endpoints remain available.
198
+
199
+ ## Push from separate workers
200
+
201
+ Use the existing API as an authenticated HTTP receiver. No broker service is
202
+ required. In the API's Fastify registration, add an explicit private route and a
203
+ dedicated random service secret (32–256 URL-safe characters):
204
+
205
+ ```ts
206
+ registerWebSockets(server, {
207
+ app: application,
208
+ origins: ['https://app.example.com'],
209
+ endpoints: { '/ws': endpoint },
210
+ publish: { path: '/_internal/realtime/publish', token: process.env.REALTIME_SECRET! },
211
+ });
212
+ ```
213
+
214
+ Configure the worker App with the receiver's trusted URL and the same secret:
215
+
216
+ ```ts
217
+ const application = new App({
218
+ webSockets: {
219
+ publish: {
220
+ url: 'http://api:8001/_internal/realtime/publish',
221
+ token: process.env.REALTIME_SECRET!,
222
+ },
223
+ },
224
+ });
225
+ // Publish after the authoritative outer transaction commits:
226
+ await application.webSockets.channel(`website:${websiteId}:jobs`)
227
+ .publish('completed', { jobId });
228
+ await application.close();
229
+ ```
230
+
231
+ Mounted APIs deliver directly to local sockets, even if configured with the same
232
+ outbound publishing options as workers. The HTTP receiver also delivers locally
233
+ and never relays recursively. Every send still validates the browser's session
234
+ and channel access. Without outbound configuration, publication is process-local.
235
+
236
+ The Fastify adapter owns HTTP registration; a plain Node host must provide its
237
+ own authenticated receiver. No startup subscription or readiness dependency is
238
+ introduced. Keep the publishing route on a private listener/network, and use
239
+ HTTPS when traffic crosses an untrusted network. The service bearer secret never
240
+ belongs in browser code or URLs. The receiver rejects Origin-bearing browser
241
+ requests and invalid credentials before parsing the JSON body.
242
+
243
+ The receiver validates channel/event names and limits bodies to 64 KiB. Worker
244
+ requests have a five-second deadline (`timeoutMs` can override it), do not follow
245
+ redirects, and do not retry automatically. Non-success responses reject without
246
+ including their response body or credentials in the error. Publishing returns
247
+ accepted local sends, or zero after remote HTTP acceptance; it is not a browser
248
+ delivery receipt.
249
+
250
+ One configured URL reaches one API process. During blue/green replacement, route
251
+ worker requests to the active API and close old sockets so browsers reconnect and
252
+ reload authorized state. A load balancer alone does not broadcast to all replicas.
253
+ Multiple concurrent socket owners require explicit fan-out. Presence and
254
+ lower-level `channels.publish()` memberships remain process-local.
255
+
256
+ Events with no local subscribers are discarded immediately, with no per-user
257
+ authentication or resource queries. They are not retained for later subscribers.
258
+ Workers still make one authenticated HTTP request per publication, even with no
259
+ viewers; this is lightweight receiver work, not a zero-cost path. Subscription
260
+ registries remove empty channels and disconnect listeners when viewers leave.
261
+
262
+ There is no durable replay. A crash between commit and publication, an HTTP
263
+ failure, or a disconnect may lose a signal. Reload saved state after subscription
264
+ and reconnect, and provide explicit refresh. Retrying an ambiguous timeout can
265
+ duplicate events; prefer idempotent state invalidations. Guaranteed delivery
266
+ requires an outbox.
267
+
268
+ For inbox integration, set `inApp.onChanged` to publish a recipient invalidation.
269
+ InApp defers this callback until the surrounding transaction commits and reports
270
+ callback failures through `inApp.onDeliveryError` without undoing saved messages.
271
+ Generic channel publication is not transaction-aware: call it after the outer
272
+ transaction returns.
273
+
274
+ ## Presence and local subscriptions
275
+
276
+ `application.webSockets.channels.join(name, context)` joins an accepted connection
277
+ from a trusted controller action and returns an idempotent leave function. It
278
+ automatically leaves on disconnect. `channels.publish(name, data)` fans out to
279
+ local members and returns the number of accepted sends, not delivery receipts.
280
+ Every send rechecks session and endpoint authorization. This lower-level API has no browser-controlled membership. For browser
281
+ subscriptions use the authorized channel definitions above.
282
+ For private notifications, choose `inbox:${context.userId}` on the server, commit
283
+ the in-app message, then publish an invalidation to that user's channel. The
284
+ browser reloads the authorized HTTP inbox, including after reconnect. InApp has no automatic WebSocket dependency. Configure its `onChanged` callback
285
+ to publish an invalidation after committed service writes.
286
+
287
+ The starter's Live rooms demo implements three endpoint policies: a public lobby,
288
+ signed-in members, and a studio requiring an explicit database grant. Its
289
+ application controller serializes each room's joins and edits, sends saved history
290
+ before live membership, and owns tab-level guest presence and cursor broadcasts.
291
+ The whiteboard stores at most 500 strokes of 200 points, plus 50 recent chat
292
+ messages, in SQL. A refresh reloads that state; no transport replay is promised.
293
+ This demo writer is single-process, not a distributed collaborative document engine.
294
+
295
+ `application.webSockets.presence('/ws/me')` returns `{ userId, connections }[]`
296
+ for authenticated users on that endpoint in this process. Tabs/devices share a
297
+ user entry while retaining separate connection counts. Presence is approximate;
298
+ dead peers disappear after heartbeat detection, not immediately on network loss.
299
+ Presence means connected, not actively looking at the page.
300
+
301
+ Presence is a server API. It is not broadcast automatically because listing other
302
+ users is an application permission decision. A private endpoint can send only the
303
+ current user's entry, as the example does. Shared rooms must authorize membership
304
+ and filter presence by application scope; endpoint paths alone do not create
305
+ tenant isolation, and query strings do not partition this presence list.
306
+
307
+ `onPresence(path, listener)` observes local changes and returns an unsubscribe
308
+ function. For example, an `open` action can push only its account's presence:
309
+
310
+ ```ts
311
+ import type { WebSocketContext } from '@db3.ai/app/websocket';
312
+
313
+ /** Sends local presence changes for this user and removes the listener on disconnect. */
314
+ async function open({ userId, send, signal }: WebSocketContext) {
315
+ /** Re-reads current counts; send rechecks the recipient's session and endpoint policy. */
316
+ function changed() {
317
+ void send({ type: 'presence', users: application.webSockets.presence('/ws/me').filter(item => item.userId === userId) });
318
+ }
319
+ const unsubscribe = application.webSockets.onPresence('/ws/me', changed);
320
+ signal.addEventListener('abort', unsubscribe, { once: true });
321
+ changed();
322
+ }
323
+ ```
324
+
325
+ Presence listeners execute without Auth context and do not supply event history.
326
+ Asynchronous listeners are not serialized. Read current presence when handling
327
+ each signal and keep callbacks short; errors go to the configured `onError`.
328
+
329
+ An `open` action can retain `context.send` in an application-owned subscription
330
+ and remove it on `context.signal` abort or `close`. Every later send rechecks
331
+ access. These callbacks and presence belong to the current process. A separate
332
+ worker cannot reach these retained callbacks or lower-level memberships. Use
333
+ named channel events with the HTTP configuration above for cross-process push.
334
+
335
+ ## Blue/green deployment
336
+
337
+ The Auth session lives in the shared database while sockets live in an API
338
+ process. Route new upgrades to green, then initiate graceful shutdown on blue.
339
+ Blue closes sockets with 1001; clients reconnect to green using the same token.
340
+ Until old connections drain, each process has only its own presence list and
341
+ subscriptions. HTTP publication reaches the API selected by its URL; events sent
342
+ only to green do not reach sockets still on blue.
343
+ Deployments must close old sockets instead of leaving them alive indefinitely.
344
+ There is no guaranteed delivery across disconnects; applications reload needed
345
+ state themselves after reconnecting.
346
+
347
+ The existing reverse proxy must forward WebSocket upgrades on the endpoint path
348
+ and allow idle timeouts longer than the heartbeat interval. No extra public port
349
+ or shared socket/session service is required. This module cannot change proxy
350
+ routing or implement distributed presence for you.
351
+
352
+ ## Limits and verification
353
+
354
+ Defaults: 1000 sockets including pending authentication; 5-second authentication
355
+ deadline; 64 KiB incoming/outgoing frames; 256 KiB outgoing buffer; 16 queued
356
+ incoming actions; 32 concurrent outgoing sends awaiting access checks; 30-second heartbeat; 3-second shutdown deadline. Compression
357
+ is disabled. Slow clients, flooding and oversized payloads are disconnected.
358
+ The `maxPendingSends` limit applies before authentication/resource queries begin,
359
+ so slow access checks cannot build an unlimited per-connection backlog. Overflow
360
+ closes that connection with retryable code 1013; the browser reconnects and the app
361
+ reloads saved state. Pending sends release their payloads when their access checks
362
+ settle; this limit does not cancel database queries already running. Ordinary
363
+ sends are neither throttled nor queued for replay.
364
+ Configure `onError` for server diagnostics; application errors close with 1011
365
+ without sending exception text. Proxy rate limits remain application operations.
366
+
367
+ Run `npm run test:service --workspace @db3.ai/app -- websocket` using the configured
368
+ disposable MariaDB test setup. Tests use real sockets and Auth records, cover
369
+ isolation/revocation, validation, presence, cleanup and reconnect across API
370
+ replacement. Run `npm run check --workspace @db3.ai/app` and
371
+ `npm run framework:package:test` for examples and packed-consumer verification.
372
+
373
+ This module uses the existing-server, heartbeat and bounded transport mechanisms
374
+ described in the [ws documentation](https://github.com/websockets/ws/blob/master/README.md).
375
+
376
+ ## Shared board and durable loading recipe
377
+
378
+ The [WebSocket guide](https://db3.ai/docs/realtime-kanban) composes seven
379
+ copyable application examples with real Auth, SQL, Queue, WebSockets and Pinia:
380
+
381
+ - [LiveBoard](./examples/board/LiveBoard.ts) saves authorized card moves under a
382
+ row lock, rejects stale revisions and emits `changed` after commit.
383
+ - [Public snapshot](./examples/board/boardSnapshot.ts) excludes membership and
384
+ validates the browser projection. [HTTP routes](./examples/board/registerBoardRoutes.ts)
385
+ derive identity from the authenticated session.
386
+ - [Pinia store](./examples/board/createBoardStore.ts) rejects stale responses and
387
+ derives loading from persisted job status, including after a full reload.
388
+ - [Channel bindings](./examples/createChannelSync.ts) share one socket, reload
389
+ after subscription acknowledgements and coalesce changes during an HTTP read.
390
+ - [Browser composition](./examples/board/createBoardClient.ts) owns the store and
391
+ connection at app startup, with explicit logout cleanup.
392
+ - [Summary job](./examples/board/SummarizeBoardJob.ts) executes independently of
393
+ the browser, persists completion/failure and publishes through the HTTP bridge.
394
+
395
+ These are app-owned recipes, not automatic ORM subscriptions or a framework
396
+ Pinia dependency. Install Vue/Pinia in the consuming frontend. Register the
397
+ model with the host's migrations and the job with each worker. The recipe uses
398
+ the database queue driver on the same database so loading and dispatch commit
399
+ atomically. Call its mutation methods outside an outer transaction. It keeps
400
+ one active/latest summary per board, not an unlimited run/idempotency history.
401
+ The sample summary is quick; replace that calculation with the actual work.
402
+ Long external operations still need lease fencing, provider idempotency and
403
+ server-side reconciliation of abandoned runs and failed completion hooks.
404
+
405
+ Run `npm test --workspace @db3.ai/app -- src/websocket/tests/examples/liveBoard.test.ts`
406
+ with disposable MariaDB credentials. It tests real two-user Pinia synchronization,
407
+ conflicts/denial, reload/reconnect, SQL dispatch rollback, worker HTTP publication,
408
+ terminal failure, stale-run fencing and listener cleanup. There is no timed inbox
409
+ or board polling. HTTP publication remains best effort: if it fails while a
410
+ client stays connected, that client needs explicit/focus/reconnect refresh.
411
+ Guaranteed eventual notification requires a durable outbox, not only a socket.
412
+
413
+ ### Presence, Yjs and AI boundaries
414
+
415
+ Endpoint `presence(path)` does not enumerate named-channel subscribers. A shared
416
+ endpoint needs app-owned document-view/leave actions and per-document ephemeral
417
+ presence, including route departure, multiple tabs, access checks and heartbeat
418
+ cleanup. The starter rooms already demonstrate room presence and saved chat.
419
+
420
+ Yjs uses binary updates and a synchronization/awareness protocol. Existing Yjs
421
+ providers do not speak this service's JSON envelopes. Reusing one connection
422
+ requires an adapter, bounded encoding, state-vector recovery, persistence and
423
+ awareness cleanup. No Yjs adapter is shipped or tested here. See
424
+ [Yjs updates](https://docs.yjs.dev/api/document-updates) and its
425
+ [WebSocket provider](https://docs.yjs.dev/ecosystem/connection-provider/y-websocket).
426
+
427
+ `Agent.queue()` executes on the worker independently of a browser, but queued
428
+ runs currently discard the live event sink. There is no durable token log or
429
+ replay cursor. Reconnectable AI viewing needs saved cumulative checkpoints or
430
+ ordered events, authorized recovery, duplicate/gap handling and attempt fencing.
431
+ The guide explains these design boundaries without advertising a replay API.
432
+
433
+
434
+ ## Task tutorials
435
+
436
+ Start with the [main WebSocket guide](https://db3.ai/docs/websocket) for shared
437
+ connection ownership, authorized channels, worker HTTP publishing and recovery.
438
+ Then follow the tutorial for the feature you are building:
439
+
440
+ - [Document presence](https://db3.ai/docs/realtime-presence)
441
+ - [Saved-action alerts](https://db3.ai/docs/realtime-actions)
442
+ - [Shared Kanban and Pinia](https://db3.ai/docs/realtime-kanban)
443
+ - [Durable background-job loading](https://db3.ai/docs/realtime-jobs)
444
+ - [Chat rooms](https://db3.ai/docs/realtime-chat)
445
+ - [Deployment progress](https://db3.ai/docs/realtime-deployments)
446
+ - [Yjs adapter design](https://db3.ai/docs/realtime-yjs)
447
+ - [Reconnectable AI design](https://db3.ai/docs/realtime-ai)
448
+ - [Approval requests](https://db3.ai/docs/realtime-approvals)
449
+ - [Upload processing](https://db3.ai/docs/realtime-uploads)
450
+ - [Inventory updates](https://db3.ai/docs/realtime-inventory)
451
+ - [Permission changes](https://db3.ai/docs/realtime-permissions)
452
+
453
+ The deployment tutorial supplies a tested
454
+ [public projection](./examples/deploymentSnapshot.ts) and
455
+ [authorized environment channel](./examples/deploymentEndpoint.ts). The projection
456
+ rejects stale/wrong-resource responses and requires passed health plus the intended
457
+ active release before displaying live. It validates controller assertions; it does
458
+ not perform infrastructure probes, execute deployments or provide a Cloud API.
@@ -0,0 +1,99 @@
1
+ import type { WebSocketContext, WebSocketChannelDefinition } from './contracts';
2
+
3
+ /** Local fan-out for trusted server memberships and authorized browser subscriptions. */
4
+ export class WebSocketChannels {
5
+ readonly #subscriptions = new Map<string, Map<string, { context: WebSocketContext<any>; definition: WebSocketChannelDefinition; params: Record<string, string>; leave: () => void }>>();
6
+ readonly #counts = new Map<string, number>();
7
+ readonly #channels = new Map<string, Map<string, { context: WebSocketContext<any>; leave: () => void }>>();
8
+
9
+ /** Joins an already-authorized connection and automatically leaves when its signal aborts. */
10
+ join(name: string, context: WebSocketContext<any>): () => void {
11
+ if (context.signal.aborted) return () => {};
12
+ let members = this.#channels.get(name);
13
+ if (!members) { members = new Map(); this.#channels.set(name, members); }
14
+ const existing = members.get(context.id);
15
+ if (existing) return existing.leave;
16
+ /** Removes only this membership and releases its abort listener. */
17
+ const leave = () => {
18
+ if (members.get(context.id)?.context === context) members.delete(context.id);
19
+ if (!members.size && this.#channels.get(name) === members) this.#channels.delete(name);
20
+ context.signal.removeEventListener('abort', leave);
21
+ };
22
+ context.signal.addEventListener('abort', leave, { once: true });
23
+ members.set(context.id, { context, leave });
24
+ return leave;
25
+ }
26
+
27
+ /** Sends JSON to current local members. Returns accepted transport sends, never durable delivery. */
28
+ async publish(name: string, data: unknown): Promise<number> {
29
+ const results = await Promise.all([...this.#channels.get(name)?.values() ?? []].map(member => member.context.send(data)));
30
+ return results.filter(Boolean).length;
31
+ }
32
+
33
+ /**
34
+ * Handles the reserved subscription protocol. Unknown channels and denied access reveal no payload.
35
+ * @returns Whether this was a framework subscription command.
36
+ */
37
+ async handle(definitions: readonly WebSocketChannelDefinition[], context: WebSocketContext<any>, data: unknown): Promise<boolean> {
38
+ if (!data || typeof data !== 'object' || !('type' in data) || !['channel.subscribe', 'channel.unsubscribe'].includes(String(data.type))) return false;
39
+ const name = 'channel' in data ? data.channel : null;
40
+ if (typeof name !== 'string' || name.length > 256 || !/^[a-zA-Z0-9_.-]+(?::[a-zA-Z0-9_.-]+)*$/.test(name)) { context.close(); return true; }
41
+ const existing = this.#subscriptions.get(name)?.get(context.id);
42
+ if (data.type === 'channel.unsubscribe') { existing?.leave(); return true; }
43
+ const matches = definitions.map(definition => ({ definition, params: matchChannel(definition.pattern, name) })).filter(match => match.params !== null);
44
+ const match = matches.length === 1 ? matches[0] : undefined;
45
+ if (!context.user || !context.userId || !match || !await match.definition.authorize({ ...context, userId: context.userId, channel: name, params: match.params! })) {
46
+ existing?.leave();
47
+ await context.send({ type: 'channel.denied', channel: name });
48
+ return true;
49
+ }
50
+ if (context.signal.aborted) return true;
51
+ if (!existing) {
52
+ if ((this.#counts.get(context.id) ?? 0) >= 128) { await context.send({ type: 'channel.denied', channel: name }); return true; }
53
+ let members = this.#subscriptions.get(name);
54
+ if (!members) { members = new Map(); this.#subscriptions.set(name, members); }
55
+ /** Removes this connection's subscription exactly once, including during in-flight authorization. */
56
+ const leave = () => {
57
+ if (!members.delete(context.id)) return;
58
+ if (!members.size) this.#subscriptions.delete(name);
59
+ const count = (this.#counts.get(context.id) ?? 1) - 1;
60
+ if (count) this.#counts.set(context.id, count); else this.#counts.delete(context.id);
61
+ context.signal.removeEventListener('abort', leave);
62
+ };
63
+ members.set(context.id, { context, definition: match.definition, params: match.params!, leave });
64
+ this.#counts.set(context.id, (this.#counts.get(context.id) ?? 0) + 1);
65
+ context.signal.addEventListener('abort', leave, { once: true });
66
+ }
67
+ await context.send({ type: 'channel.subscribed', channel: name });
68
+ return true;
69
+ }
70
+
71
+ /** Publishes a named event only to current authorized resource subscribers in this process. */
72
+ async publishEvent(name: string, event: string, data: unknown): Promise<number> {
73
+ if (!event || event.length > 256) throw new Error('Invalid channel event.');
74
+ const members = this.#subscriptions.get(name);
75
+ const results = await Promise.all([...members?.values() ?? []].map(member => member.context.send({ type: 'channel.event', channel: name, event, data }, async context => {
76
+ if (members?.get(context.id) !== member) return false;
77
+ if (!context.user || !context.userId || !await member.definition.authorize({ ...context, userId: context.userId, channel: name, params: member.params })) { member.leave(); return false; }
78
+ return members?.get(context.id) === member;
79
+ })));
80
+ return results.filter(Boolean).length;
81
+ }
82
+
83
+ /** Drops local memberships during application shutdown. */
84
+ clear(): void { for (const members of this.#subscriptions.values()) for (const member of members.values()) member.leave(); for (const members of this.#channels.values()) for (const member of members.values()) member.leave(); }
85
+ }
86
+
87
+ /** Matches whole colon-delimited segments; parameter values never include separators. */
88
+ function matchChannel(pattern: string, name: string): Record<string, string> | null {
89
+ const expected = pattern.split(':');
90
+ const actual = name.split(':');
91
+ if (expected.length !== actual.length) return null;
92
+ const params: Record<string, string> = Object.create(null);
93
+ for (let index = 0; index < expected.length; index++) {
94
+ const segment = expected[index]!;
95
+ if (/^\{[a-zA-Z][a-zA-Z0-9_]*\}$/.test(segment)) params[segment.slice(1, -1)] = actual[index]!;
96
+ else if (segment !== actual[index]) return null;
97
+ }
98
+ return params;
99
+ }