@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,50 @@
1
+ import type { WebSocketEndpoint } from './WebSocketEndpoint';
2
+ import type { IncomingMessage } from 'node:http';
3
+
4
+ /** Mount configuration for one app-owned HTTP/S server. No additional port is opened. */
5
+ export interface WebSocketOptions {
6
+ /** Optional service-authenticated HTTP publisher registered by the Fastify adapter. */
7
+ publish?: {
8
+ /** Exact private route path on this API; keep it off public reverse-proxy routes. */
9
+ path: string;
10
+ /** Dedicated server-only bearer secret matching worker configuration. */
11
+ token: string;
12
+ };
13
+ /** Exact HTTP(S) origins allowed to upgrade. Missing origins are rejected. */
14
+ origins: readonly string[];
15
+ /** Exact paths to endpoint actions. Query parameters are available on request.url. */
16
+ endpoints: Readonly<Record<string, WebSocketEndpoint<any, any>>>;
17
+ /** Resolves an existing session from the upgrade request, e.g. an HttpOnly cookie. Origin checks still apply. Public endpoints never invoke this. */
18
+ resolveToken?(request: IncomingMessage): string | null | Promise<string | null>;
19
+ /** Maximum simultaneous sockets, including unauthenticated handshakes. Default 1000. */
20
+ maxConnections?: number;
21
+ /** Incoming and outgoing JSON frame limit in bytes. Default 64 KiB. */
22
+ maxPayloadBytes?: number;
23
+ /** Maximum queued outgoing bytes per client. Default 256 KiB. */
24
+ maxBufferedBytes?: number;
25
+ /** Maximum queued incoming actions per client. Default 16. */
26
+ maxPendingMessages?: number;
27
+ /** Maximum concurrent outgoing sends awaiting access checks per client. Overflow closes with retryable 1013. Default 32. */
28
+ maxPendingSends?: number;
29
+ /** Deadline for initial authentication. Default 5000 ms. */
30
+ authTimeoutMs?: number;
31
+ /** Ping and session/policy revalidation interval. Default 30000 ms. */
32
+ heartbeatMs?: number;
33
+ /** Bounded wait for connection cleanup at shutdown. Default 3000 ms. */
34
+ shutdownTimeoutMs?: number;
35
+ /** Server-only error reporting. Never receives authentication frames or raw tokens. */
36
+ onError?(error: unknown): void;
37
+ }
38
+
39
+ /** Validated internal limits shared by the transport and its connections. */
40
+ export interface WebSocketLimits {
41
+ maxConnections: number;
42
+ maxPayloadBytes: number;
43
+ maxBufferedBytes: number;
44
+ maxPendingMessages: number;
45
+ /** Includes authentication and resource authorization, before socket buffering begins. */
46
+ maxPendingSends: number;
47
+ authTimeoutMs: number;
48
+ heartbeatMs: number;
49
+ shutdownTimeoutMs: number;
50
+ }
@@ -0,0 +1,6 @@
1
+ /** Approximate authenticated presence on one endpoint in this API process. */
2
+ export interface WebSocketPresence {
3
+ userId: string;
4
+ /** Multiple tabs/devices for the same account count as separate connections. */
5
+ connections: number;
6
+ }
@@ -0,0 +1,12 @@
1
+ /** Optional worker-to-API HTTP delivery; browser connections remain owned by the API. */
2
+ export interface WebSocketServiceOptions {
3
+ /** Used by publishers without a mounted WebSocket server. Mounted APIs deliver locally. */
4
+ publish?: {
5
+ /** Trusted absolute HTTP(S) endpoint; use HTTPS outside a private application network. */
6
+ url: string;
7
+ /** Server-only bearer secret, at least 32 characters. Never expose it to browsers. */
8
+ token: string;
9
+ /** Bounded connection and response deadline. Defaults to 5000 milliseconds. */
10
+ timeoutMs?: number;
11
+ };
12
+ }
@@ -0,0 +1,6 @@
1
+ export type { WebSocketContext, WebSocketClosed, WebSocketEndpoint } from './WebSocketEndpoint';
2
+ export type { WebSocketOptions, WebSocketLimits } from './WebSocketOptions';
3
+ export type { WebSocketPresence } from './WebSocketPresence';
4
+ export type { ClientSocket, WebSocketClientOptions, WebSocketClientState, WebSocketClientChannel } from './WebSocketClient';
5
+ export type { WebSocketChannelContext, WebSocketChannelDefinition, WebSocketChannelPublisher } from './WebSocketChannel';
6
+ export type { WebSocketServiceOptions } from './WebSocketServiceOptions';
@@ -0,0 +1,14 @@
1
+ import type { WebSocketChannelDefinition } from './contracts/WebSocketChannel';
2
+
3
+ /**
4
+ * Defines a private resource channel with an explicit application access policy.
5
+ * @param pattern - Colon-delimited name with optional {parameter} segments.
6
+ * @param options - Access check using the freshly authenticated user and resource parameters.
7
+ * @returns A definition to include in an endpoint's channels array.
8
+ */
9
+ export function defineChannel(pattern: string, options: Omit<WebSocketChannelDefinition, 'pattern'>): WebSocketChannelDefinition {
10
+ const segments = pattern.split(':');
11
+ const parameters = segments.filter(segment => segment.startsWith('{'));
12
+ if (pattern.length > 256 || !segments.every(segment => /^[a-zA-Z0-9_.-]+$/.test(segment) || /^\{[a-zA-Z][a-zA-Z0-9_]*\}$/.test(segment)) || new Set(parameters).size !== parameters.length || typeof options.authorize !== 'function') throw new Error('Invalid channel definition.');
13
+ return Object.freeze({ pattern, authorize: options.authorize });
14
+ }
@@ -0,0 +1,15 @@
1
+ import type { WebSocketEndpoint } from './contracts';
2
+
3
+ /**
4
+ * Defines controller actions while inferring the validated message type.
5
+ * @param endpoint - Application-owned authorization, validation and lifecycle actions.
6
+ * @returns The same definition for registration against an exact URL path.
7
+ * @example defineWebSocket({ open: async ({ send }) => { await send({ ready: true }); } });
8
+ */
9
+ export function defineWebSocket<TMessage = unknown>(endpoint: WebSocketEndpoint<TMessage, null> & { auth: 'public' }): WebSocketEndpoint<TMessage, null> & { auth: 'public' };
10
+ /** Defines an authenticated endpoint; omitted auth always requires a valid session. */
11
+ export function defineWebSocket<TMessage = unknown>(endpoint: WebSocketEndpoint<TMessage>): WebSocketEndpoint<TMessage>;
12
+ /** Retains controller definitions without wrapping application actions. */
13
+ export function defineWebSocket(endpoint: WebSocketEndpoint<any, any>): WebSocketEndpoint<any, any> {
14
+ return endpoint;
15
+ }
@@ -0,0 +1,97 @@
1
+ import { ActiveRecord, type FieldBuilder } from '@db3.ai/app/db';
2
+ import { app } from '@db3.ai/app/server';
3
+ import { defineChannel, defineWebSocket } from '@db3.ai/app/websocket';
4
+ import { z } from 'zod';
5
+ import { boardSnapshotSchema, type BoardSnapshot } from './boardSnapshot';
6
+ import { SummarizeBoardJob } from './SummarizeBoardJob';
7
+
8
+ /** Application-owned example: a small shared board and its latest durable background activity. */
9
+ export class LiveBoard extends ActiveRecord {
10
+ static override table = 'example_live_boards';
11
+ static override requestFillable = [];
12
+
13
+ /** Defines durable state. Register this model in the host's normal migration workflow. */
14
+ static override fields(field: FieldBuilder) {
15
+ return {
16
+ id: field.ulid(), members: field.json<string[]>(),
17
+ cards: field.json<BoardSnapshot['cards']>(),
18
+ revision: field.integer({ required: true, default: 0 }),
19
+ activity: field.json<BoardSnapshot['activity']>(),
20
+ };
21
+ }
22
+ declare id: string;
23
+ declare members: string[];
24
+ declare cards: BoardSnapshot['cards'];
25
+ declare revision: number;
26
+ declare activity: BoardSnapshot['activity'];
27
+
28
+ /** Checks fresh membership for HTTP reads/writes and each subscription or event delivery. */
29
+ static async canAccess(id: string, userId: string): Promise<boolean> {
30
+ const board = await this.where('id', id).first();
31
+ return board?.members.includes(userId) ?? false;
32
+ }
33
+
34
+ /** Creates channel definitions to merge into the app's one authenticated endpoint. */
35
+ static endpoint() {
36
+ return defineWebSocket({ channels: [defineChannel('board:{boardId}', {
37
+ /** A client-controlled board ID is never proof of access. */
38
+ authorize: ({ userId, params }) => LiveBoard.canAccess(params.boardId!, userId),
39
+ })] });
40
+ }
41
+
42
+ /** Returns an authorized projection; derive userId from the HTTP session, never its body. */
43
+ static async snapshot(id: string, userId: string): Promise<BoardSnapshot> {
44
+ const board = await this.where('id', id).first();
45
+ if (!board?.members.includes(userId)) throw new Error('Board unavailable.');
46
+ return board.snapshot();
47
+ }
48
+
49
+ /** Produces a bounded public projection, excluding membership and private model fields. */
50
+ snapshot(): BoardSnapshot {
51
+ return boardSnapshotSchema.parse({ id: this.id, revision: this.revision, cards: this.cards, activity: this.activity });
52
+ }
53
+
54
+ /** Serializes edits and rejects stale revisions rather than silently overwriting another user's move. */
55
+ static async move(id: string, userId: string, input: unknown): Promise<BoardSnapshot> {
56
+ const command = z.object({ cardId: z.string(), column: z.enum(['todo', 'doing', 'done']), revision: z.number().int().nonnegative() }).strict().parse(input);
57
+ const snapshot = await app().db.transaction(async () => {
58
+ const board = await this.where('id', id).forUpdate().first();
59
+ if (!board?.members.includes(userId)) throw new Error('Board unavailable.');
60
+ if (board.revision !== command.revision) throw new Error('Board changed; reload before saving.');
61
+ if (!board.cards.some(card => card.id === command.cardId)) throw new Error('Card unavailable.');
62
+ board.cards = board.cards.map(card => card.id === command.cardId ? { ...card, column: command.column } : card);
63
+ board.revision++;
64
+ await board.save();
65
+ return board.snapshot();
66
+ });
67
+ await this.publishChanged(id);
68
+ return snapshot;
69
+ }
70
+
71
+ /**
72
+ * Commits loading state and SQL queue dispatch together; duplicate active requests reuse the run.
73
+ * Call outside an outer transaction, with the database queue driver on the same database.
74
+ * A client retains requestId across a lost HTTP response; it is an idempotency key, not authority.
75
+ */
76
+ static async startSummary(id: string, userId: string, requestId: string): Promise<BoardSnapshot> {
77
+ z.string().uuid().parse(requestId);
78
+ const snapshot = await app().db.transaction(async () => {
79
+ const board = await this.where('id', id).forUpdate().first();
80
+ if (!board?.members.includes(userId)) throw new Error('Board unavailable.');
81
+ if (board.activity?.id === requestId || ['queued', 'running'].includes(board.activity?.status ?? '')) return board.snapshot();
82
+ board.activity = { id: requestId, status: 'queued', result: null };
83
+ board.revision++;
84
+ await board.save();
85
+ await app().queue.dispatch(new SummarizeBoardJob({ boardId: id, userId, runId: requestId }), { queue: 'boards', maxTries: 1 });
86
+ return board.snapshot();
87
+ });
88
+ await this.publishChanged(id);
89
+ return snapshot;
90
+ }
91
+
92
+ /** Emits a small post-commit invalidation. Transport failure must not make a saved edit look rolled back. */
93
+ static async publishChanged(id: string): Promise<void> {
94
+ try { await app().webSockets.channel(`board:${id}`).publish('changed', { boardId: id }); }
95
+ catch (error) { app().log.error({ error, boardId: id }, 'Board saved but live notification failed'); }
96
+ }
97
+ }
@@ -0,0 +1,52 @@
1
+ import { QueueableJob } from '@db3.ai/app/queue';
2
+ import { app } from '@db3.ai/app/server';
3
+ import { z } from 'zod';
4
+ import { LiveBoard } from './LiveBoard';
5
+
6
+ /** Immutable identities persisted in the SQL queue; permissions are checked again by the worker. */
7
+ interface SummarizeBoardData extends Record<string, unknown> { boardId: string; userId: string; runId: string }
8
+
9
+ /** Server-owned work whose durable UI state outlives any browser connection. */
10
+ export class SummarizeBoardJob extends QueueableJob<SummarizeBoardData> {
11
+ static readonly jobName = 'boards.summarize.v1';
12
+
13
+ /** Validates new and rehydrated queue payloads. */
14
+ constructor(data: SummarizeBoardData) {
15
+ super(z.object({ boardId: z.string().min(1), userId: z.string().min(1), runId: z.string().uuid() }).parse(data));
16
+ }
17
+
18
+ /**
19
+ * Builds a deterministic summary. Replace computation with your slow server activity.
20
+ * Browser disconnect never cancels this job; a repeated attempt safely replaces the same result.
21
+ */
22
+ async handle(): Promise<void> {
23
+ const snapshot = await app().db.transaction(async () => {
24
+ const board = await LiveBoard.where('id', this.data.boardId).forUpdate().firstOrFail();
25
+ if (board.activity?.id !== this.data.runId || ['completed', 'failed'].includes(board.activity.status)) return null;
26
+ if (!board.members.includes(this.data.userId)) throw new Error('Board access revoked.');
27
+ board.activity = { ...board.activity, status: 'running' };
28
+ board.revision++;
29
+ await board.save();
30
+ return board.snapshot();
31
+ });
32
+ if (!snapshot) return;
33
+ await LiveBoard.publishChanged(this.data.boardId);
34
+ const result = `${snapshot.cards.filter(card => card.column === 'done').length} of ${snapshot.cards.length} cards complete`;
35
+ await this.#finish('completed', result);
36
+ }
37
+
38
+ /** Makes terminal failure visible after Queue persists its failed-job record. */
39
+ override async onFinalFailure(): Promise<void> { await this.#finish('failed', null); }
40
+
41
+ /** Fences an old attempt from overwriting a newer run and publishes only after commit. */
42
+ async #finish(status: 'completed' | 'failed', result: string | null): Promise<void> {
43
+ await app().db.transaction(async () => {
44
+ const board = await LiveBoard.where('id', this.data.boardId).forUpdate().first();
45
+ if (!board || board.activity?.id !== this.data.runId || ['completed', 'failed'].includes(board.activity.status)) return;
46
+ board.activity = { id: this.data.runId, status, result };
47
+ board.revision++;
48
+ await board.save();
49
+ });
50
+ await LiveBoard.publishChanged(this.data.boardId);
51
+ }
52
+ }
@@ -0,0 +1,16 @@
1
+ import { z } from 'zod';
2
+
3
+ /** Browser-safe, bounded public projection; membership and queue internals never leave the server. */
4
+ export const boardSnapshotSchema = z.object({
5
+ id: z.string().min(1),
6
+ revision: z.number().int().nonnegative(),
7
+ cards: z.array(z.object({ id: z.string().min(1), title: z.string().max(120), column: z.enum(['todo', 'doing', 'done']) })).max(100),
8
+ activity: z.object({
9
+ id: z.string().uuid(),
10
+ status: z.enum(['queued', 'running', 'completed', 'failed']),
11
+ result: z.string().nullable(),
12
+ }).nullable(),
13
+ });
14
+
15
+ /** Complete authorized board state fetched after a channel invalidation or reconnect. */
16
+ export type BoardSnapshot = z.infer<typeof boardSnapshotSchema>;
@@ -0,0 +1,43 @@
1
+ import type { Pinia } from 'pinia';
2
+ import type { WebSocketClientOptions } from '@db3.ai/app/websocket/client';
3
+ import { createChannelSync } from '../createChannelSync';
4
+ import { createBoardStore, type BoardApi } from './createBoardStore';
5
+
6
+ /** Host-owned URLs and current bearer identity; create this once at browser application startup. */
7
+ export interface BoardClientOptions {
8
+ baseUrl: string;
9
+ boardId: string;
10
+ pinia: Pinia;
11
+ token(): string | null;
12
+ /** Optional native-compatible socket factory for Node integration tests. */
13
+ createSocket?: WebSocketClientOptions['createSocket'];
14
+ }
15
+
16
+ /**
17
+ * Composes one authenticated HTTP adapter, Pinia store and shared socket for the board recipe.
18
+ * Other features use sync.watch() on this same connection. Route unmount does not close it.
19
+ * Call close() at logout or before replacing the authenticated identity.
20
+ */
21
+ export function createBoardClient(options: BoardClientOptions) {
22
+ const base = options.baseUrl.replace(/\/$/, '');
23
+ /** Rejects unsuccessful HTTP responses before parsing their public snapshot. */
24
+ async function request(method: string, suffix = '', body?: unknown): Promise<unknown> {
25
+ const response = await fetch(`${base}/boards/${encodeURIComponent(options.boardId)}${suffix}`, {
26
+ method, headers: { authorization: `Bearer ${options.token() ?? ''}`, 'content-type': 'application/json' },
27
+ ...(body === undefined ? {} : { body: JSON.stringify(body) }),
28
+ });
29
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
30
+ return response.json();
31
+ }
32
+ const api: BoardApi = { read: () => request('GET'), move: input => request('PATCH', '', input), start: requestId => request('POST', '/summary', { requestId }) };
33
+ const store = createBoardStore(options.boardId, api)(options.pinia);
34
+ const socketUrl = new URL('/ws', base);
35
+ socketUrl.protocol = socketUrl.protocol === 'https:' ? 'wss:' : 'ws:';
36
+ const sync = createChannelSync({ url: socketUrl.href, token: options.token, createSocket: options.createSocket });
37
+ const binding = sync.watch(`board:${options.boardId}`, api.read, store.apply, store.fail);
38
+ sync.client.connect();
39
+ return { store, sync, binding, api,
40
+ /** Stop app-owned listeners and clear private state when identity changes or the app is disposed. */
41
+ close() { sync.close(); store.dispose(); store.$dispose(); },
42
+ };
43
+ }
@@ -0,0 +1,58 @@
1
+ import { defineStore } from 'pinia';
2
+ import { computed, ref } from 'vue';
3
+ import { boardSnapshotSchema, type BoardSnapshot } from './boardSnapshot';
4
+
5
+ /** Authenticated HTTP actions supplied by the host; user identity is never a request-body parameter. */
6
+ export interface BoardApi {
7
+ read(): Promise<unknown>;
8
+ move(input: { cardId: string; column: 'todo' | 'doing' | 'done'; revision: number }): Promise<unknown>;
9
+ start(requestId: string): Promise<unknown>;
10
+ }
11
+
12
+ /**
13
+ * Creates a Pinia store for the selected board. Register once per application, with its HTTP adapter.
14
+ * Install pinia and vue in the consuming browser app; neither is required by the backend runtime.
15
+ */
16
+ export function createBoardStore(boardId: string, api: BoardApi) {
17
+ return defineStore(`board:${boardId}`, () => {
18
+ let active = true;
19
+ const snapshot = ref<BoardSnapshot | null>(null);
20
+ const submitting = ref(false);
21
+ const error = ref<string | null>(null);
22
+ const busy = computed(() => submitting.value || ['queued', 'running'].includes(snapshot.value?.activity?.status ?? ''));
23
+
24
+ /** Validates the projection and ignores old responses arriving after a newer mutation. */
25
+ function apply(value: unknown): void {
26
+ if (!active) return;
27
+ const next = boardSnapshotSchema.parse(value);
28
+ if (next.id !== boardId) throw new Error('Wrong board snapshot.');
29
+ if (!snapshot.value || next.revision >= snapshot.value.revision) snapshot.value = next;
30
+ error.value = null;
31
+ }
32
+
33
+ /** Clears private data on failed authorization; the UI keeps an explicit retry/error state. */
34
+ function fail(_cause: unknown): void { snapshot.value = null; error.value = 'Board unavailable. Reconnect or refresh to try again.'; }
35
+
36
+ /** Applies the HTTP result immediately; a broadcast is only an extra refresh trigger. */
37
+ async function move(cardId: string, column: 'todo' | 'doing' | 'done'): Promise<void> {
38
+ if (!snapshot.value) return;
39
+ try { apply(await api.move({ cardId, column, revision: snapshot.value.revision })); }
40
+ catch (cause) { error.value = 'Save failed or conflicted. Refresh before retrying.'; throw cause; }
41
+ }
42
+
43
+ /**
44
+ * Shows immediate button feedback, then lets durable activity status own loading.
45
+ * Keep requestId across an uncertain response. On reload, GET restores the latest run.
46
+ */
47
+ async function start(requestId: string): Promise<void> {
48
+ if (busy.value || !snapshot.value) return;
49
+ submitting.value = true;
50
+ try { apply(await api.start(requestId)); }
51
+ catch (cause) { error.value = 'Start outcome unknown. Refresh before retrying with the same request ID.'; throw cause; }
52
+ finally { submitting.value = false; }
53
+ }
54
+ /** Fences late mutation responses and removes private state on logout or identity replacement. */
55
+ function dispose(): void { active = false; snapshot.value = null; submitting.value = false; error.value = null; }
56
+ return { snapshot, submitting, error, busy, apply, fail, move, start, dispose };
57
+ });
58
+ }
@@ -0,0 +1,31 @@
1
+ import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
2
+ import type { App } from '@db3.ai/app/server';
3
+ import { z } from 'zod';
4
+ import { LiveBoard } from './LiveBoard';
5
+
6
+ /** Registers the example's bearer-authenticated HTTP API beside the shared WebSocket endpoint. */
7
+ export function registerBoardRoutes(server: FastifyInstance, application: App): void {
8
+ /** Creates a fresh authentication scope and maps expected example errors without disclosing private data. */
9
+ function authorized(action: (id: string, userId: string, body: unknown) => Promise<unknown>) {
10
+ return async (request: FastifyRequest, reply: FastifyReply) => application.requestContext.run(async () => {
11
+ const header = request.headers.authorization ?? '';
12
+ const user = header.startsWith('Bearer ') ? await application.auth.authenticateToken(header.slice(7)) : null;
13
+ if (!user) return reply.code(401).send({ error: 'Sign in required.' });
14
+ try {
15
+ const { id } = z.object({ id: z.string().min(1).max(26) }).parse(request.params);
16
+ return await action(id, String(user.id), request.body);
17
+ } catch (error) {
18
+ if (error instanceof z.ZodError) return reply.code(400).send({ error: 'Invalid board input.' });
19
+ if (error instanceof Error && error.message === 'Board changed; reload before saving.') return reply.code(409).send({ error: error.message });
20
+ if (error instanceof Error && ['Board unavailable.', 'Card unavailable.'].includes(error.message)) return reply.code(404).send({ error: 'Board unavailable.' });
21
+ throw error;
22
+ }
23
+ });
24
+ }
25
+ server.get('/boards/:id', authorized((id, userId) => LiveBoard.snapshot(id, userId)));
26
+ server.patch('/boards/:id', authorized((id, userId, body) => LiveBoard.move(id, userId, body)));
27
+ server.post('/boards/:id/summary', authorized((id, userId, body) => {
28
+ const { requestId } = z.object({ requestId: z.string().uuid() }).strict().parse(body);
29
+ return LiveBoard.startSummary(id, userId, requestId);
30
+ }));
31
+ }
@@ -0,0 +1,66 @@
1
+ import { WebSocketClient, type WebSocketClientOptions } from '@db3.ai/app/websocket/client';
2
+
3
+ /** A snapshot binding owned by one feature; stopping it must not close the shared socket. */
4
+ export interface SnapshotBinding { refresh(): Promise<void>; stop(): void; denied(): void }
5
+
6
+ /**
7
+ * Owns one browser-tab connection and routes subscription acknowledgements to snapshot loaders.
8
+ * Create once at app boot. Snapshot GETs use the app's existing authenticated HTTP client.
9
+ * This is copyable application code, not automatic ORM broadcasting or a framework store API.
10
+ */
11
+ export function createChannelSync(options: Omit<WebSocketClientOptions, 'onSubscribed' | 'onChannelDenied'>) {
12
+ const bindings = new Map<string, Set<SnapshotBinding>>();
13
+ const client = new WebSocketClient({ ...options,
14
+ /** Reconcile only after the server has installed membership, including after reconnect. */
15
+ onSubscribed: channel => { for (const binding of bindings.get(channel) ?? []) void binding.refresh(); },
16
+ /** Clear feature data and fence any old HTTP response when membership is refused. */
17
+ onChannelDenied: channel => { for (const binding of bindings.get(channel) ?? []) binding.denied(); },
18
+ });
19
+ return {
20
+ client,
21
+ /**
22
+ * Binds invalidations to an authoritative snapshot, coalescing concurrent refreshes.
23
+ * A change during a GET schedules another GET, so an older response cannot swallow it.
24
+ * load must validate HTTP status and payload; apply should reject older model revisions.
25
+ */
26
+ watch<T>(channel: string, load: () => Promise<T>, apply: (value: T) => void, fail: (error: unknown) => void): SnapshotBinding {
27
+ let active = true;
28
+ let dirty = false;
29
+ let pending: Promise<void> | undefined;
30
+ let group = bindings.get(channel);
31
+ if (!group) { group = new Set(); bindings.set(channel, group); }
32
+ const binding: SnapshotBinding = {
33
+ /** At most one HTTP read per binding is in flight; errors require explicit refresh or reconnect. */
34
+ refresh() {
35
+ if (!active) return Promise.resolve();
36
+ dirty = true;
37
+ return pending ??= (async () => {
38
+ try {
39
+ while (active && dirty) {
40
+ dirty = false;
41
+ try { const value = await Promise.resolve().then(load); if (active) apply(value); }
42
+ catch (error) { if (active) fail(error); }
43
+ }
44
+ } finally { pending = undefined; }
45
+ })();
46
+ },
47
+ /** Removes listeners and ignores late responses; the shared connection stays alive. */
48
+ stop() {
49
+ active = false;
50
+ unsubscribe();
51
+ group.delete(binding);
52
+ if (!group.size) bindings.delete(channel);
53
+ },
54
+ /** Reports access loss after clearing the binding's ability to apply late responses. */
55
+ denied() { binding.stop(); fail(new Error('Channel access denied.')); },
56
+ };
57
+ const unsubscribe = client.channel(channel).on('changed', () => { void binding.refresh(); });
58
+ group.add(binding);
59
+ // HTTP still provides initial state if the socket is unavailable. The subscription ACK refresh closes the race.
60
+ void binding.refresh();
61
+ return binding;
62
+ },
63
+ /** Dispose once on logout or app teardown, not when an individual route unmounts. */
64
+ close() { for (const group of bindings.values()) for (const binding of group) binding.stop(); client.close(); },
65
+ };
66
+ }
@@ -0,0 +1,13 @@
1
+ import { defineChannel, defineWebSocket } from '@db3.ai/app/websocket';
2
+
3
+ /**
4
+ * Adds an environment-scoped deployment audience to the application's shared endpoint.
5
+ * @param canReadEnvironment - Host policy checking current organization and environment access.
6
+ * @returns An authenticated endpoint; merge its channels if /ws is already registered.
7
+ */
8
+ export function deploymentEndpoint(canReadEnvironment: (userId: string, environmentId: string) => boolean | Promise<boolean>) {
9
+ return defineWebSocket({ channels: [defineChannel('environment:{environmentId}:deployments', {
10
+ /** Rechecks membership on subscription and before every event; read access never grants deploy permission. */
11
+ authorize: ({ userId, params }) => canReadEnvironment(userId, params.environmentId!),
12
+ })] });
13
+ }
@@ -0,0 +1,36 @@
1
+ import { z } from 'zod';
2
+
3
+ /** Public deployment status supplied by the application's deployment controller after persistence. */
4
+ export const deploymentSnapshotSchema = z.object({
5
+ id: z.string().min(1).max(128),
6
+ environmentId: z.string().min(1).max(128),
7
+ revision: z.number().int().nonnegative(),
8
+ releaseId: z.string().min(1).max(128),
9
+ activeReleaseId: z.string().min(1).max(128).nullable(),
10
+ phase: z.enum(['queued', 'building', 'migrating', 'starting', 'checking', 'switching', 'live', 'failed', 'rolled-back', 'cancelled']),
11
+ health: z.enum(['unknown', 'checking', 'passed', 'failed']),
12
+ /** A safe application message, never raw build output, environment values or provider errors. */
13
+ message: z.string().max(500),
14
+ }).strict().superRefine((snapshot, context) => {
15
+ if (snapshot.phase === 'live' && (snapshot.health !== 'passed' || snapshot.activeReleaseId !== snapshot.releaseId)) {
16
+ context.addIssue({ code: 'custom', message: 'Live requires passed health and the intended active release.' });
17
+ }
18
+ });
19
+
20
+ /** Validated browser projection; it neither executes a deployment nor measures infrastructure health. */
21
+ export type DeploymentSnapshot = z.infer<typeof deploymentSnapshotSchema>;
22
+
23
+ /**
24
+ * Validates a snapshot for the selected environment/run and rejects older HTTP responses.
25
+ * The server increments revision across the entire run, including retries and rollback.
26
+ * @param input - Authorized HTTP response, treated as unknown until validation.
27
+ * @param expected - Route-selected resource identities, independently authorized by the server.
28
+ * @param current - Last accepted snapshot, or null after a fresh page load.
29
+ * @returns The newest validated snapshot for this exact deployment.
30
+ */
31
+ export function readDeploymentSnapshot(input: unknown, expected: { id: string; environmentId: string }, current: DeploymentSnapshot | null): DeploymentSnapshot {
32
+ const next = deploymentSnapshotSchema.parse(input);
33
+ if (next.id !== expected.id || next.environmentId !== expected.environmentId) throw new Error('Wrong deployment snapshot.');
34
+ if (current && (current.id !== expected.id || current.environmentId !== expected.environmentId)) throw new Error('Clear the previous deployment before changing resources.');
35
+ return current && next.revision <= current.revision ? current : next;
36
+ }
@@ -0,0 +1,18 @@
1
+ import { defineChannel, defineWebSocket, type WebSocketChannelContext } from '@db3.ai/app/websocket';
2
+
3
+ /**
4
+ * Defines one authenticated endpoint for website jobs and private account notifications.
5
+ * @param canAccessWebsite - App-owned policy checking current organization membership and website access.
6
+ * @returns An endpoint to mount at /ws using the existing HTTP server and session.
7
+ */
8
+ export function resourceChannels(canAccessWebsite: (context: WebSocketChannelContext) => boolean | Promise<boolean>) {
9
+ return defineWebSocket({
10
+ channels: [
11
+ defineChannel('website:{websiteId}:jobs', { authorize: canAccessWebsite }),
12
+ defineChannel('user:{userId}:notifications', {
13
+ /** Only the authenticated account may subscribe to its notification events. */
14
+ authorize: ({ userId, params }) => userId === params.userId,
15
+ }),
16
+ ],
17
+ });
18
+ }
@@ -0,0 +1,27 @@
1
+ import type { App } from '@db3.ai/app/server';
2
+ import { defineWebSocket } from '@db3.ai/app/websocket';
3
+
4
+ /**
5
+ * Creates controller-style actions for the current user. No snapshots or persistence are implied.
6
+ * @param application - The same app used to mount the endpoints.
7
+ * @returns Copyable authenticated endpoint definition.
8
+ */
9
+ export function userEndpoint(application: App) {
10
+ return defineWebSocket({
11
+ /** Sends only this connection's identity after framework authentication. */
12
+ async open({ userId, send }) {
13
+ await send({ type: 'hello', userId });
14
+ },
15
+ /** Validates an application command before the message action runs. */
16
+ parse(data: unknown): { type: 'ping' | 'my-presence' } {
17
+ if (!data || typeof data !== 'object' || !('type' in data) || !['ping', 'my-presence'].includes(String(data.type))) throw new Error('Unknown command.');
18
+ return data as { type: 'ping' | 'my-presence' };
19
+ },
20
+ /** Handles commands; presence disclosure is limited to the authenticated account. */
21
+ async message({ userId, send }, command) {
22
+ await send(command.type === 'ping'
23
+ ? { type: 'pong' }
24
+ : { type: 'my-presence', users: application.webSockets.presence('/ws/me').filter(user => user.userId === userId) });
25
+ },
26
+ });
27
+ }
@@ -0,0 +1,39 @@
1
+ import { createHash, timingSafeEqual } from 'node:crypto';
2
+ import type { FastifyInstance } from 'fastify';
3
+ import type { App } from '../server/App';
4
+ import type { WebSocketOptions } from './contracts';
5
+
6
+ /**
7
+ * Registers authenticated controller endpoints and closes upgraded sockets before Fastify drains HTTP.
8
+ * @param server - Fastify server before listen(). HTTP route hooks do not authorize upgrades.
9
+ * @param options - Application, exact origins, endpoints and optional limits.
10
+ */
11
+ export function registerWebSockets(server: FastifyInstance, options: WebSocketOptions & { app: App }): void {
12
+ options.app.webSockets.mount(server.server, options);
13
+ if (options.publish) {
14
+ const { path, token } = options.publish;
15
+ if (!/^\/[A-Za-z0-9/_-]+$/.test(path) || path.startsWith('//')) throw new Error('Invalid WebSocket publishing path.');
16
+ if (!/^[A-Za-z0-9_-]{32,256}$/.test(token)) throw new Error('WebSocket publish token must contain 32 to 256 URL-safe characters.');
17
+ const expected = createHash('sha256').update(`Bearer ${token}`).digest();
18
+ server.post<{ Body: { channel: string; event: string; data?: unknown } }>(path, {
19
+ bodyLimit: 65536,
20
+ /** Service credentials are checked before parsing a body; browser/session credentials never authorize publication. */
21
+ onRequest: async (request, reply) => {
22
+ const supplied = createHash('sha256').update(request.headers.authorization ?? '').digest();
23
+ if (request.headers.origin !== undefined || !timingSafeEqual(expected, supplied)) return reply.code(401).send({ error: 'Unauthorized' });
24
+ },
25
+ schema: { body: {
26
+ type: 'object', required: ['channel', 'event'], additionalProperties: false,
27
+ properties: {
28
+ channel: { type: 'string', maxLength: 256, pattern: '^[a-zA-Z0-9_.-]+(:[a-zA-Z0-9_.-]+)*$' },
29
+ event: { type: 'string', minLength: 1, maxLength: 256 }, data: {},
30
+ },
31
+ } },
32
+ }, async (request, reply) => {
33
+ // Deliver locally, never call the outbound HTTP publisher or relay recursively.
34
+ await options.app.requestContext.run(() => options.app.webSockets.channels.publishEvent(request.body.channel, request.body.event, request.body.data));
35
+ return reply.code(204).send();
36
+ });
37
+ }
38
+ server.addHook('preClose', async () => { await options.app.webSockets.close(); });
39
+ }
@@ -0,0 +1,5 @@
1
+ export { WebSockets } from './WebSockets';
2
+ export { WebSocketChannels } from './WebSocketChannels';
3
+ export { defineWebSocket } from './defineWebSocket';
4
+ export type * from './contracts';
5
+ export { defineChannel } from './defineChannel';