@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,30 @@
1
+ import type { AuthProvider } from '../AuthProvider';
2
+ import type { UserIdentity, UserIdentityModel } from '../UserIdentity';
3
+ import type { PasswordHash } from '../passwordHash';
4
+ import type { PasswordSuspensionOptions } from './PasswordSuspensionOptions';
5
+
6
+ /**
7
+ * Construction options for the password auth provider.
8
+ */
9
+ export interface PasswordAuthProviderOptions<TIdentity extends UserIdentity = UserIdentity> {
10
+ /**
11
+ * User identity model configured for the host app.
12
+ */
13
+ identityModel: UserIdentityModel<TIdentity>;
14
+
15
+ /**
16
+ * Provider-link model configured for the auth service.
17
+ */
18
+ providerModel?: typeof AuthProvider;
19
+
20
+ /**
21
+ * Password hashing service used to verify stored hashes.
22
+ */
23
+ passwordHash?: PasswordHash;
24
+
25
+ /**
26
+ * Failed-password suspension by normalized identity, including unknown identities.
27
+ * Enabled after 20 attempts by default; `false` disables it.
28
+ */
29
+ suspension?: PasswordSuspensionOptions | false;
30
+ }
@@ -0,0 +1,7 @@
1
+ /** Identifies a newly committed suspension so an application can enqueue recovery notices. */
2
+ export interface PasswordSuspension {
3
+ /** Attempt-state record, equally available for registered and unknown identities. */
4
+ attemptId: string;
5
+ /** Unique suspension occurrence; changes after recovery and a later suspension. */
6
+ suspensionId: string;
7
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Persistent suspension policy for submitted password identities.
3
+ *
4
+ * Known and unknown emails share the same threshold. Only completed password
5
+ * recovery clears suspension; this policy deliberately has no timed unlock.
6
+ */
7
+ export interface PasswordSuspensionOptions {
8
+ /** Consecutive failures before recovery is required. Defaults to 20. */
9
+ maxFailedAttempts?: number;
10
+ }
@@ -1 +1,4 @@
1
1
  export * from './AuthProviderDriver';
2
+ export * from './PasswordSuspensionOptions';
3
+ export * from './PasswordSuspension';
4
+ export * from './PasswordAuthProviderOptions';
@@ -1,5 +1,5 @@
1
1
  import { pathToFileURL } from 'node:url';
2
- import { AuthProvider, AuthToken, PasswordResetToken, UserIdentity, PASSWORD_AUTH_PROVIDER } from '@db3.ai/app/auth';
2
+ import { AuthProvider, PasswordLoginAttempt, AuthToken, PasswordResetToken, UserIdentity, PASSWORD_AUTH_PROVIDER } from '@db3.ai/app/auth';
3
3
  import { createGeneratedTestDatabase } from '@db3.ai/app/db/test/db';
4
4
  import { App } from '@db3.ai/app/server';
5
5
 
@@ -17,7 +17,7 @@ export async function runPasswordAuth() {
17
17
  const application = new App({ db: database.db, config: { auth: { providers: { password: true } } } });
18
18
  try {
19
19
  // Lab-only schema setup. Use committed migrations in an application.
20
- await application.db.install(UserIdentity, AuthProvider, AuthToken, PasswordResetToken);
20
+ await application.db.install(UserIdentity, AuthProvider, PasswordLoginAttempt, AuthToken, PasswordResetToken);
21
21
  const credentials = { name: 'Ada', email: 'ada@example.test', password: 'example-only-password-123' };
22
22
  const issued = await application.auth.registerWithPassword(credentials, { expiresInMs: 60 * 60 * 1000 });
23
23
  const wrongPasswordRejected = await application.auth.issueTokenForProvider(PASSWORD_AUTH_PROVIDER, { ...credentials, password: 'wrong-password' }) === null;
@@ -27,14 +27,14 @@ export async function runPasswordAuth() {
27
27
  return user?.id === issued.user.id;
28
28
  });
29
29
  const reset = await application.auth.createPasswordResetToken(issued.user);
30
+ const allDevicesRevoked = await application.auth.revokeAllTokens(issued.user) === 2;
31
+ const freshSession = await application.auth.createToken(issued.user);
30
32
  const replacement = { token: reset.token, password: 'replacement-example-password-456' };
31
33
  const passwordChanged = Boolean(await application.auth.resetPassword(replacement));
32
34
  const usedResetRejected = await application.auth.resetPassword(replacement) === null;
33
35
  const oldPasswordRejected = await application.auth.issueTokenForProvider(PASSWORD_AUTH_PROVIDER, credentials) === null;
34
- const sessions = await application.auth.tokensFor(issued.user);
35
- for (const session of sessions) await application.auth.revokeToken(issued.user, session.id!);
36
- const revokedTokenRejected = await application.requestContext.run(async () => await application.auth.authenticateToken(issued.token) === null);
37
- return { authenticated, signedIn: Boolean(signedIn), wrongPasswordRejected, passwordChanged, usedResetRejected, oldPasswordRejected, revokedTokenRejected };
36
+ const revokedTokenRejected = await application.requestContext.run(async () => await application.auth.authenticateToken(freshSession.token) === null);
37
+ return { authenticated, signedIn: Boolean(signedIn), wrongPasswordRejected, allDevicesRevoked, passwordChanged, usedResetRejected, oldPasswordRejected, revokedTokenRejected };
38
38
  } finally {
39
39
  try { await application.close(); } finally { await database.destroy(); }
40
40
  }
package/src/auth/index.ts CHANGED
@@ -2,6 +2,8 @@ export * from './Auth';
2
2
  export * from './AuthProvider';
3
3
  export * from './AuthToken';
4
4
  export * from './contracts';
5
+ export * from './PasswordSuspendedError';
6
+ export * from './PasswordLoginAttempt';
5
7
  export * from './PasswordResetToken';
6
8
  export * from './passwordHash';
7
9
  export * from './providers';
@@ -1,28 +1,15 @@
1
+ import { randomBytes, randomUUID } from 'node:crypto';
1
2
  import { AuthProvider, PASSWORD_AUTH_PROVIDER } from '../AuthProvider';
2
3
  import type * as auth from '../contracts';
3
4
  import type { PasswordCredentials } from '../Auth';
4
5
  import type { UserIdentity, UserIdentityModel } from '../UserIdentity';
5
6
  import { defaultPasswordHash, type PasswordHash } from '../passwordHash';
7
+ import { PasswordSuspendedError } from '../PasswordSuspendedError';
8
+ import { PasswordLoginAttempt } from '../PasswordLoginAttempt';
6
9
 
7
- /**
8
- * Construction options for the password auth provider.
9
- */
10
- export interface PasswordAuthProviderOptions<TIdentity extends UserIdentity = UserIdentity> {
11
- /**
12
- * User identity model configured for the host app.
13
- */
14
- identityModel: UserIdentityModel<TIdentity>;
15
-
16
- /**
17
- * Provider-link model configured for the auth service.
18
- */
19
- providerModel?: typeof AuthProvider;
20
-
21
- /**
22
- * Password hashing service used to verify stored hashes.
23
- */
24
- passwordHash?: PasswordHash;
25
- }
10
+ const DEFAULT_MAX_FAILED_ATTEMPTS = 20;
11
+ /** Valid default-cost scrypt value used only for unknown-identity timing parity. */
12
+ const DUMMY_PASSWORD_HASH = 'scrypt$1$16384$8$1$64$0123456789abcdef0123456789abcdef$' + '00'.repeat(64);
26
13
 
27
14
  /**
28
15
  * Provider driver for first-party email and password authentication.
@@ -36,37 +23,102 @@ export class PasswordAuthProvider<TIdentity extends UserIdentity = UserIdentity>
36
23
  private readonly IdentityModel: UserIdentityModel<TIdentity>;
37
24
  private readonly ProviderModel: typeof AuthProvider;
38
25
  private readonly passwordHash: PasswordHash;
26
+ readonly #suspension: Required<auth.PasswordSuspensionOptions> | null;
27
+ #dummyHash: Promise<string> | undefined;
39
28
 
40
29
  /**
41
30
  * Creates a password auth provider.
42
31
  *
43
32
  * @param options - Password provider dependencies configured at app startup.
44
33
  */
45
- constructor(options: PasswordAuthProviderOptions<TIdentity>) {
34
+ constructor(options: auth.PasswordAuthProviderOptions<TIdentity>) {
46
35
  this.IdentityModel = options.identityModel;
47
36
  this.ProviderModel = options.providerModel ?? AuthProvider;
48
37
  this.passwordHash = options.passwordHash ?? defaultPasswordHash;
38
+ this.#suspension = resolveSuspension(options.suspension);
49
39
  }
50
40
 
51
41
  /**
52
- * Verifies submitted password credentials against the stored provider row.
42
+ * Verifies credentials under the identity's durable attempt lock.
53
43
  *
54
- * @param input - Submitted password credential payload.
55
- * @returns Normalized password provider profile, or null when verification fails.
44
+ * Unknown identities receive the same failed-attempt accounting and password
45
+ * hashing work as existing accounts. Suspension persists until verified
46
+ * password recovery; a correct password cannot clear a suspended identity.
47
+ *
48
+ * @param input - Submitted password credentials.
49
+ * @returns Verified provider profile, or null for an invalid password.
50
+ * @throws {PasswordSuspendedError} When this identity needs password recovery.
56
51
  */
57
52
  async verify(input: PasswordCredentials): Promise<auth.AuthProviderProfile | null> {
58
53
  if (!isPasswordCredentials(input)) return null;
59
-
60
54
  const identity = normalizeIdentity(input[this.IdentityModel.identityField]);
55
+ if (!identity || identity.length > 255) return null;
56
+
57
+ if (!this.#suspension) return this.#verifyPassword(identity, input.password);
58
+
59
+ // Return the refusal from the transaction so the threshold update commits
60
+ // before the public error is thrown.
61
+ const result = await PasswordLoginAttempt.withIdentityLock(identity, async attempt => {
62
+ if (attempt.suspendedAt) return new PasswordSuspendedError();
63
+ const profile = await this.#verifyPassword(identity, input.password);
64
+ if (profile) {
65
+ attempt.failedAttempts = 0;
66
+ } else {
67
+ attempt.failedAttempts += 1;
68
+ if (attempt.failedAttempts >= this.#suspension!.maxFailedAttempts) {
69
+ attempt.suspendedAt = new Date();
70
+ attempt.suspensionId = randomUUID();
71
+ }
72
+ }
73
+ await attempt.save();
74
+ return attempt.suspendedAt ? new PasswordSuspendedError({ attemptId: String(attempt.id), suspensionId: attempt.suspensionId! }) : profile;
75
+ });
76
+
77
+ if (result instanceof PasswordSuspendedError) throw result;
78
+ return result;
79
+ }
61
80
 
62
- if (!identity) return null;
63
-
81
+ /**
82
+ * Performs equivalent hashing work for known and unknown identities.
83
+ * @param identity - Normalized submitted identity.
84
+ * @param password - Submitted plaintext password, never persisted.
85
+ * @returns Profile only when an existing password credential matches.
86
+ */
87
+ async #verifyPassword(identity: string, password: string): Promise<auth.AuthProviderProfile | null> {
64
88
  const provider = await this.findProvider(identity);
89
+ // SQL collations may match distinct identities (for example accented text).
90
+ // Only our exact normalized key may reach the credential verification path.
91
+ if (!provider || normalizeIdentity(provider.providerUserId) !== identity) {
92
+ const dummy = this.passwordHash === defaultPasswordHash
93
+ ? DUMMY_PASSWORD_HASH
94
+ : await (this.#dummyHash ??= this.passwordHash.hash(randomBytes(32).toString('hex')).catch(error => {
95
+ this.#dummyHash = undefined;
96
+ throw error;
97
+ }));
98
+ await this.passwordHash.verify(password, dummy);
99
+ return null;
100
+ }
101
+ return await provider.verifyPassword(password, this.passwordHash) ? profileFromProvider(provider) : null;
102
+ }
65
103
 
66
- if (!provider) return null;
67
- if (!await provider.verifyPassword(input.password, this.passwordHash)) return null;
68
-
69
- return profileFromProvider(provider);
104
+ /**
105
+ * Replaces a verified owner's password and clears suspension atomically.
106
+ * Called by Auth only after a usable, identity-matched recovery token is locked.
107
+ * @param user - Account whose email recovery proof was verified.
108
+ * @param password - Replacement plaintext password.
109
+ * @returns Saved password provider.
110
+ */
111
+ async recoverPassword(user: TIdentity, password: string): Promise<AuthProvider> {
112
+ const identity = this.userIdentityValue(user);
113
+ if (!identity) throw new Error('Cannot recover a password without an identity.');
114
+ return PasswordLoginAttempt.withIdentityLock(identity, async attempt => {
115
+ const provider = await this.setPassword(user, password);
116
+ attempt.failedAttempts = 0;
117
+ attempt.suspendedAt = null;
118
+ attempt.suspensionId = null;
119
+ await attempt.save();
120
+ return provider;
121
+ });
70
122
  }
71
123
 
72
124
  /**
@@ -254,3 +306,17 @@ function profileFromProvider(provider: AuthProvider): auth.AuthProviderProfile {
254
306
  profile: provider.profile,
255
307
  };
256
308
  }
309
+
310
+ /**
311
+ * Resolves the failed-attempt threshold and rejects invalid startup config.
312
+ * @param options - Identity suspension policy, or false to disable accounting.
313
+ * @returns Validated threshold, or null when disabled.
314
+ */
315
+ function resolveSuspension(options: auth.PasswordSuspensionOptions | false | undefined): Required<auth.PasswordSuspensionOptions> | null {
316
+ if (options === false) return null;
317
+ const maxFailedAttempts = options?.maxFailedAttempts ?? DEFAULT_MAX_FAILED_ATTEMPTS;
318
+ if (!Number.isSafeInteger(maxFailedAttempts) || maxFailedAttempts < 1 || maxFailedAttempts > 1_000_000) {
319
+ throw new Error('Password suspension maxFailedAttempts must be an integer between 1 and 1000000.');
320
+ }
321
+ return { maxFailedAttempts };
322
+ }
@@ -81,6 +81,7 @@ function resolveAuthProvider<TIdentity extends UserIdentity>(
81
81
  identityModel: factoryOptions.identityModel,
82
82
  providerModel: factoryOptions.providerModel,
83
83
  passwordHash: factoryOptions.passwordHash,
84
+ suspension: options.suspension as auth.PasswordSuspensionOptions | false | undefined,
84
85
  }) as auth.AuthProviderDriver<unknown, TIdentity>;
85
86
 
86
87
  case GOOGLE_AUTH_PROVIDER:
package/src/cli/Cli.ts CHANGED
@@ -34,6 +34,15 @@ export class Cli {
34
34
  }
35
35
  }
36
36
 
37
+ /** Returns detached metadata without booting the app or exposing executable handlers. */
38
+ commands(): cli.CliCommandInfo[] {
39
+ return [...this.#commands.values()].map(command => ({
40
+ name: command.name, description: command.description,
41
+ arguments: (command.arguments ?? []).map(argument => ({ ...argument })),
42
+ interactive: command.interactive ?? false,
43
+ }));
44
+ }
45
+
37
46
  /**
38
47
  * Runs one invocation, printing help without app startup and closing a created app on failure too.
39
48
  * @param argv - Arguments after the executable name.
package/src/cli/README.md CHANGED
@@ -162,3 +162,22 @@ failure cleanup and the real executable's TypeScript configuration loading.
162
162
  Database command tests live under the DB service and use disposable MariaDB
163
163
  databases. Release verification installs the packed framework into a generated
164
164
  app, then generates, applies and checks actual migrations through `db3`.
165
+
166
+ ## Visual command discovery
167
+
168
+ `listDb3Commands({ directory })` returns serializable `CliCommandInfo` records
169
+ for the same built-in and app-registered commands as `runDb3`. Each record has
170
+ `name`, `description`, ordered `arguments`, and `interactive`. Discovery imports
171
+ registration without calling `createApp`, validates duplicate/invalid definitions,
172
+ and releases its scoped TypeScript loader. Invalid configuration rejects the
173
+ promise. `new Cli(config).commands()` exposes the same metadata for an already
174
+ registered runner, with detached argument declarations.
175
+
176
+ Set `interactive: true` on a custom `CliCommand` when it requires a terminal
177
+ session. The built-in REPL does this. Visual one-shot runners must exclude these
178
+ commands from execution. Registration and discovery do not grant HTTP access.
179
+
180
+ The separate `@db3.ai/dev` panel uses discovery for its Commands tab and runs the
181
+ ordinary CLI in isolated local processes. It captures the same output and exit
182
+ status without adding endpoints to consuming applications. Structured service
183
+ results remain available by calling the underlying action directly.
@@ -10,7 +10,8 @@ import type { CliCommand, CliOptions } from '../contracts';
10
10
  */
11
11
  export function createReplCommand(loadModels: () => Promise<Record<string, unknown>>, options: CliOptions): CliCommand {
12
12
  return {
13
- name: 'repl', description: 'Explore the application and its models in an interactive terminal.',
13
+ name: 'repl',
14
+ interactive: true, description: 'Explore the application and its models in an interactive terminal.',
14
15
  /** Boots the app without an HTTP listener and keeps it alive until REPL exit. */
15
16
  async run(context) {
16
17
  const application = await context.app();
@@ -28,6 +28,8 @@ export interface CliCommand {
28
28
  description: string;
29
29
  /** Positional argument declarations used for validation and help. */
30
30
  arguments?: readonly CliArgument[];
31
+ /** Requires an interactive terminal session; visual one-shot runners must exclude it. */
32
+ interactive?: boolean;
31
33
  /** Executes the operation; return a nonzero status for a reported failure. */
32
34
  run(context: CliCommandContext): number | void | Promise<number | void>;
33
35
  }
@@ -0,0 +1,13 @@
1
+ import type { CliArgument } from './CliCommand';
2
+
3
+ /** Serializable command metadata for explorers; never includes executable handlers or configuration. */
4
+ export interface CliCommandInfo {
5
+ /** Unique registered command name. */
6
+ name: string;
7
+ /** Human-readable purpose shown in help and visual explorers. */
8
+ description: string;
9
+ /** Ordered positional inputs accepted by the CLI. */
10
+ arguments: readonly CliArgument[];
11
+ /** Interactive commands require a terminal session instead of a one-shot form. */
12
+ interactive: boolean;
13
+ }
@@ -3,3 +3,4 @@ export type * from './CliConfig';
3
3
  export type * from './CliOptions';
4
4
  export type * from './CommandDefinition';
5
5
  export type * from './ReplOptions';
6
+ export type * from './CliCommandInfo';
package/src/cli/index.ts CHANGED
@@ -3,3 +3,4 @@ export * from './Cli';
3
3
  export * from './runDb3';
4
4
  export * from './defineCommand';
5
5
  export * from './runRepl';
6
+ export * from './listDb3Commands';
@@ -0,0 +1,12 @@
1
+ import { withDb3Cli } from './withDb3Cli';
2
+ import type * as cli from './contracts';
3
+
4
+ /**
5
+ * Discovers registered and built-in commands without creating the application.
6
+ * Configuration imports must remain free of startup side effects, as for CLI help.
7
+ * @param options - App root containing server/cli.config.ts, .mjs or .js.
8
+ * @returns Serializable command descriptions, including terminal-only capabilities.
9
+ */
10
+ export async function listDb3Commands(options: Pick<cli.CliOptions, 'directory'> = {}): Promise<cli.CliCommandInfo[]> {
11
+ return withDb3Cli(options, async runner => runner.commands());
12
+ }
package/src/cli/runDb3.ts CHANGED
@@ -1,63 +1,17 @@
1
- import { access } from 'node:fs/promises';
2
- import { randomUUID } from 'node:crypto';
3
- import { resolve } from 'node:path';
4
- import { pathToFileURL } from 'node:url';
5
- import { register, type ScopedImport } from 'tsx/esm/api';
6
- import { Cli } from './Cli';
7
- import { createReplCommand } from './commands/repl';
1
+ import { withDb3Cli } from './withDb3Cli';
8
2
  import type * as cli from './contracts';
9
3
 
10
4
  /**
11
- * Loads app registration and runs the framework CLI from source or an installed package.
12
- *
13
- * TypeScript config loading supports the same extensionless imports as the starter.
14
- * No HTTP listener is started. Help and queue:make-job do not create the app.
5
+ * Runs registered framework commands with app-owned startup and cleanup.
15
6
  * @param argv - Arguments after db3.
16
- * @param options - Application root and optional output adapters.
17
- * @returns Exit status to assign to process.exitCode.
7
+ * @param options - Application root and terminal adapters.
8
+ * @returns Process-compatible exit status without exiting the process.
18
9
  */
19
10
  export async function runDb3(argv: readonly string[], options: cli.CliOptions = {}): Promise<number> {
20
- const loader = register({ namespace: `db3-cli-${randomUUID()}` });
21
11
  try {
22
- const { queueCommands } = await import('../queue/commands');
23
- const directory = resolve(options.directory ?? process.cwd());
24
- const configModule = await loadAppModule(directory, 'server/cli.config', loader.import);
25
- const config: cli.CliConfig = configModule ? configModule.default : { commands: [] };
26
- // Validate app config before combining it with commands that need no app bootstrap.
27
- if (!config || !Array.isArray(config.commands)) throw new Error('CLI configuration must export a commands array.');
28
- const repl = createReplCommand(async () => loadReplModels(directory, loader.import), options);
29
- return await new Cli({ ...config, commands: [repl, ...queueCommands, ...config.commands] }, { ...options, directory }).run(argv);
12
+ return await withDb3Cli(options, runner => runner.run(argv));
30
13
  } catch (error) {
31
14
  (options.writeError ?? console.error)(error instanceof Error ? error.message : 'Unable to load CLI configuration.');
32
15
  return 1;
33
- } finally {
34
- await loader.unregister();
35
16
  }
36
17
  }
37
-
38
- /** Loads an optional conventional app module without hiding errors from its imports. */
39
- async function loadAppModule(directory: string, path: string, importModule: ScopedImport): Promise<Record<string, any> | undefined> {
40
- for (const extension of ['ts', 'mjs', 'js']) {
41
- const file = resolve(directory, `${path}.${extension}`);
42
- try { await access(file); } catch (error) {
43
- if ((error as NodeJS.ErrnoException).code === 'ENOENT') continue;
44
- throw error;
45
- }
46
- return importModule(pathToFileURL(file).href, import.meta.url);
47
- }
48
- return undefined;
49
- }
50
-
51
- /** Exposes named model constructors from the same registry used by app migrations. */
52
- async function loadReplModels(directory: string, importModule: ScopedImport): Promise<Record<string, unknown>> {
53
- const module = await loadAppModule(directory, 'server/database/models', importModule);
54
- if (!module) return {};
55
- if (!Array.isArray(module.models)) throw new Error('server/database/models must export a models array for the REPL.');
56
- const models: Record<string, unknown> = Object.create(null);
57
- for (const model of module.models) {
58
- if (typeof model !== 'function' || !/^[A-Za-z_$][\w$]*$/.test(model.name)) throw new Error('REPL models must be named constructors.');
59
- if (['app', 'models', '__proto__', 'constructor', 'prototype'].includes(model.name) || Object.hasOwn(models, model.name)) throw new Error(`Duplicate or reserved REPL model name: ${model.name}`);
60
- models[model.name] = model;
61
- }
62
- return models;
63
- }
@@ -0,0 +1,55 @@
1
+ import { access } from 'node:fs/promises';
2
+ import { randomUUID } from 'node:crypto';
3
+ import { resolve } from 'node:path';
4
+ import { pathToFileURL } from 'node:url';
5
+ import { register, type ScopedImport } from 'tsx/esm/api';
6
+ import { Cli } from './Cli';
7
+ import { createReplCommand } from './commands/repl';
8
+ import type * as cli from './contracts';
9
+
10
+ // Cache the import before scoped loaders alter hooks; avoid the CLI/Queue barrel cycle.
11
+ let queueModule: Promise<typeof import('../queue/commands')> | undefined;
12
+
13
+ /** Loads one app's command registry and releases its TypeScript loader after use. */
14
+ export async function withDb3Cli<T>(options: cli.CliOptions, use: (runner: Cli) => Promise<T>): Promise<T> {
15
+ const { queueCommands } = await (queueModule ??= import('../queue/commands'));
16
+ const loader = register({ namespace: `db3-cli-${randomUUID()}` });
17
+ try {
18
+ const directory = resolve(options.directory ?? process.cwd());
19
+ const configModule = await loadAppModule(directory, 'server/cli.config', loader.import);
20
+ const config: cli.CliConfig = configModule ? configModule.default : { commands: [] };
21
+ // Validate app config before combining it with commands that need no app bootstrap.
22
+ if (!config || !Array.isArray(config.commands)) throw new Error('CLI configuration must export a commands array.');
23
+ const repl = createReplCommand(async () => loadReplModels(directory, loader.import), options);
24
+ return await use(new Cli({ ...config, commands: [repl, ...queueCommands, ...config.commands] }, { ...options, directory }));
25
+ } finally {
26
+ await loader.unregister();
27
+ }
28
+ }
29
+
30
+ /** Loads an optional conventional app module without hiding errors from its imports. */
31
+ async function loadAppModule(directory: string, path: string, importModule: ScopedImport): Promise<Record<string, any> | undefined> {
32
+ for (const extension of ['ts', 'mjs', 'js']) {
33
+ const file = resolve(directory, `${path}.${extension}`);
34
+ try { await access(file); } catch (error) {
35
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') continue;
36
+ throw error;
37
+ }
38
+ return importModule(pathToFileURL(file).href, import.meta.url);
39
+ }
40
+ return undefined;
41
+ }
42
+
43
+ /** Exposes named model constructors from the same registry used by app migrations. */
44
+ async function loadReplModels(directory: string, importModule: ScopedImport): Promise<Record<string, unknown>> {
45
+ const module = await loadAppModule(directory, 'server/database/models', importModule);
46
+ if (!module) return {};
47
+ if (!Array.isArray(module.models)) throw new Error('server/database/models must export a models array for the REPL.');
48
+ const models: Record<string, unknown> = Object.create(null);
49
+ for (const model of module.models) {
50
+ if (typeof model !== 'function' || !/^[A-Za-z_$][\w$]*$/.test(model.name)) throw new Error('REPL models must be named constructors.');
51
+ if (['app', 'models', '__proto__', 'constructor', 'prototype'].includes(model.name) || Object.hasOwn(models, model.name)) throw new Error(`Duplicate or reserved REPL model name: ${model.name}`);
52
+ models[model.name] = model;
53
+ }
54
+ return models;
55
+ }
@@ -317,6 +317,18 @@ export class ActiveQueryBuilder<TRecord extends ActiveRecord> {
317
317
  return this;
318
318
  }
319
319
 
320
+ /**
321
+ * Adds SELECT ... FOR UPDATE while retaining typed ActiveRecord results.
322
+ * Execute inside a transaction; locks are released on commit or rollback.
323
+ * This delegates to Knex and does not start or finish a transaction.
324
+ * @returns This query for further filtering or execution.
325
+ * @example await app().db.transaction(async () => { const user = await User.where('id', id).forUpdate().first(); });
326
+ */
327
+ forUpdate(): this {
328
+ this.qb.forUpdate();
329
+ return this;
330
+ }
331
+
320
332
  /**
321
333
  * Limits the number of returned records.
322
334
  */
@@ -397,6 +397,15 @@ export abstract class ActiveRecord {
397
397
  ?? defaultDb();
398
398
  }
399
399
 
400
+ /**
401
+ * Returns the explicit transaction/database scope, without resolving model or app defaults.
402
+ * Infrastructure adapters use this to participate in an application's atomic outbox writes.
403
+ * @returns Current scoped connection, or undefined outside withDb().
404
+ */
405
+ static getScopedDb(): Knex | undefined {
406
+ return scopedDb.getStore();
407
+ }
408
+
400
409
  /**
401
410
  * Runs work with a temporary database connection for all ActiveRecord statics.
402
411
  */
package/src/db/README.md CHANGED
@@ -7,7 +7,7 @@ data.
7
7
 
8
8
  ## Define an inferred model
9
9
 
10
- Use `ActiveRecord.define()` to declare fields once and infer model properties
10
+ Prefer `ActiveRecord.define()` for new models. Declare fields once and infer model properties
11
11
  and constructor/`create()` inputs. Extend the returned class with ordinary
12
12
  instance and static methods. The [user example](examples/DefinedUser.ts) is a
13
13
  complete model with an inferred email property and a domain method.
@@ -506,3 +506,47 @@ class:
506
506
  Prefer normalising invalid input into a stable internal value and reporting
507
507
  problems through validation errors. Throw from parsing only for programmer
508
508
  errors or truly unrecoverable input.
509
+
510
+ ## Lock a row during a transaction
511
+
512
+ Use `forUpdate()` when a read and subsequent update must exclude competing
513
+ writers. It delegates to Knex's `forUpdate()` while keeping typed model results:
514
+
515
+ ```ts
516
+ await app().db.transaction(async () => {
517
+ const user = await User.where('id', userId).forUpdate().firstOrFail();
518
+ // Read and update protected state here, then save the model.
519
+ await user.save();
520
+ });
521
+ ```
522
+
523
+ Create the query inside the transaction. `forUpdate()` adds the SQL lock clause;
524
+ it does not open a transaction itself. The database releases locks on commit or
525
+ rollback, including rollback when the callback throws. A terminated database
526
+ connection also rolls back its transaction. Without an explicit transaction,
527
+ autocommitted queries do not keep a useful lock across separate operations.
528
+ Keep transactions short and avoid email, network requests or user input while
529
+ holding locks. Ordinary consistent reads can still read committed state; locks
530
+ coordinate competing writes and locking reads, not every reader. Actual lock
531
+ scope and supported options depend on the database engine and indexes.
532
+
533
+ `ActiveRecord.getScopedDb()` exposes only the explicit `withDb` connection, or
534
+ `undefined`. It is an infrastructure-adapter seam for transaction participation;
535
+ normal model/application code should continue using model APIs and `getDb()`.
536
+
537
+ ## UTC timestamp contract
538
+
539
+ `TimestampField` uses Date values in memory and ISO UTC strings in JSON. ISO/SQL
540
+ datetimes without an offset are interpreted as UTC, independent of the Node
541
+ process timezone. Invalid/locale date strings normalize to null and required
542
+ field validation still applies. Explicit offsets are honored. This does not
543
+ interpret a user's local scheduling form; resolve its timezone before assigning.
544
+
545
+ The default `db()` connection forces MySQL/MariaDB driver conversion to UTC and
546
+ initializes every SQL session to UTC, including DATABASE_URL connections.
547
+ PostgreSQL sessions use UTC and their per-client timestamp-without-timezone
548
+ parser follows the same UTC convention. Timestamp fields write UTC calendar
549
+ components to PostgreSQL timestamp columns, avoiding local Date serialization. Custom/injected Knex connections remain
550
+ caller-owned: configure both driver conversion and the SQL session for UTC.
551
+ Changing that contract does not repair historical incorrectly encoded values;
552
+ inspect and migrate those separately before adopting it in an existing app.
@@ -1,5 +1,6 @@
1
1
  import 'dotenv/config';
2
2
  import knex, { type Knex } from 'knex';
3
+ import { utcDate } from '@db3.ai/pure/dates';
3
4
  import {
4
5
  rememberDatabaseDialect,
5
6
  resolveDatabaseDialect,
@@ -25,9 +26,14 @@ function requiredEnv(name: string): string {
25
26
  * Builds a Knex connection value from the current process environment.
26
27
  */
27
28
  function databaseConnection(dialect: DatabaseDialect): Knex.Config['connection'] {
28
- if (process.env.DATABASE_URL) return process.env.DATABASE_URL;
29
+ if (process.env.DATABASE_URL) {
30
+ const url = new URL(process.env.DATABASE_URL);
31
+ if (dialect.name !== 'postgres') url.searchParams.set('timezone', 'Z');
32
+ return url.toString();
33
+ }
29
34
 
30
35
  return {
36
+ ...(dialect.name !== 'postgres' ? { timezone: 'Z' } : {}),
31
37
  host: process.env.DB_HOST || '127.0.0.1',
32
38
  port: Number(process.env.DB_PORT || dialect.defaultPort),
33
39
  user: process.env.DB_USER || 'root',
@@ -47,6 +53,11 @@ export function db(): Knex {
47
53
  client: dialect.knexClient,
48
54
  connection: databaseConnection(dialect),
49
55
  pool: {
56
+ /** Initializes SQL functions and TIMESTAMP conversion in UTC on every pooled connection. */
57
+ afterCreate(connection: { setTypeParser?: (oid: number, parser: (value: string) => Date | null) => void; query: (sql: string, callback: (error: Error | null) => void) => void }, done: (error: Error | null, connection: unknown) => void) {
58
+ if (dialect.name === 'postgres') connection.setTypeParser?.(1114, utcDate);
59
+ connection.query(dialect.name === 'postgres' ? "SET TIME ZONE 'UTC'" : "SET time_zone = '+00:00'", error => done(error, connection));
60
+ },
50
61
  min: Number(process.env.DB_POOL_MIN || 0),
51
62
  max: Number(process.env.DB_POOL_MAX || 10),
52
63
  },