@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.
- package/README.md +75 -17
- package/agent-instructions.md +76 -0
- package/dist/ai/AIErrors.d.ts +7 -1
- package/dist/ai/AIErrors.js +8 -1
- package/dist/ai/AIFailover.d.ts +10 -0
- package/dist/ai/AIFailover.js +3 -2
- package/dist/ai/AIProviderAdmission.d.ts +101 -0
- package/dist/ai/AIProviderAdmission.js +336 -0
- package/dist/ai/AIRateLimiter.d.ts +11 -2
- package/dist/ai/AIRateLimiter.js +125 -10
- package/dist/ai/AIRequestTrackingError.d.ts +22 -0
- package/dist/ai/AIRequestTrackingError.js +33 -0
- package/dist/ai/Agent.d.ts +7 -7
- package/dist/ai/Agent.js +84 -56
- package/dist/ai/Ai.d.ts +11 -3
- package/dist/ai/Ai.js +94 -44
- package/dist/ai/OpenAIProviderError.js +1 -1
- package/dist/ai/ProviderSseObserver.d.ts +19 -0
- package/dist/ai/ProviderSseObserver.js +98 -0
- package/dist/ai/aiServiceTier.d.ts +5 -0
- package/dist/ai/aiServiceTier.js +14 -0
- package/dist/ai/chunkEmbeddingText.d.ts +14 -0
- package/dist/ai/chunkEmbeddingText.js +147 -0
- package/dist/ai/contracts/AI.d.ts +16 -0
- package/dist/ai/contracts/AIRequestErrorDiagnostics.d.ts +12 -0
- package/dist/ai/contracts/AIRequestErrorDiagnostics.js +1 -0
- package/dist/ai/contracts/AIServiceTier.d.ts +2 -0
- package/dist/ai/contracts/AIServiceTier.js +1 -0
- package/dist/ai/contracts/Agent.d.ts +3 -0
- package/dist/ai/contracts/EmbeddingTextChunk.d.ts +25 -0
- package/dist/ai/contracts/EmbeddingTextChunk.js +1 -0
- package/dist/ai/contracts/Pricing.d.ts +7 -1
- package/dist/ai/contracts/Providers.d.ts +2 -0
- package/dist/ai/contracts/index.d.ts +2 -0
- package/dist/ai/contracts/index.js +1 -0
- package/dist/ai/index.d.ts +8 -2
- package/dist/ai/index.js +5 -2
- package/dist/ai/{Models.d.ts → modelPricing.d.ts} +2 -2
- package/dist/ai/{Models.js → modelPricing.js} +38 -4
- package/dist/apps/AppInstallation.d.ts +21 -0
- package/dist/apps/AppInstallation.js +16 -0
- package/dist/apps/AppRouteError.d.ts +6 -0
- package/dist/apps/AppRouteError.js +10 -0
- package/dist/apps/AppService.d.ts +24 -0
- package/dist/apps/AppService.js +21 -0
- package/dist/apps/Apps.d.ts +59 -0
- package/dist/apps/Apps.js +566 -0
- package/dist/apps/appsPlugin.d.ts +3 -0
- package/dist/apps/appsPlugin.js +36 -0
- package/dist/apps/commands/disableApp.d.ts +2 -0
- package/dist/apps/commands/disableApp.js +5 -0
- package/dist/apps/commands/enableApp.d.ts +2 -0
- package/dist/apps/commands/enableApp.js +5 -0
- package/dist/apps/commands/index.d.ts +10 -0
- package/dist/apps/commands/index.js +27 -0
- package/dist/apps/commands/installApp.d.ts +2 -0
- package/dist/apps/commands/installApp.js +5 -0
- package/dist/apps/commands/listApps.d.ts +2 -0
- package/dist/apps/commands/listApps.js +5 -0
- package/dist/apps/commands/makeAppMigration.d.ts +2 -0
- package/dist/apps/commands/makeAppMigration.js +9 -0
- package/dist/apps/commands/rollbackApp.d.ts +2 -0
- package/dist/apps/commands/rollbackApp.js +5 -0
- package/dist/apps/commands/uninstallApp.d.ts +2 -0
- package/dist/apps/commands/uninstallApp.js +5 -0
- package/dist/apps/contracts/AppAction.d.ts +2 -0
- package/dist/apps/contracts/AppAction.js +1 -0
- package/dist/apps/contracts/AppDefinition.d.ts +45 -0
- package/dist/apps/contracts/AppDefinition.js +1 -0
- package/dist/apps/contracts/AppDescription.d.ts +32 -0
- package/dist/apps/contracts/AppDescription.js +1 -0
- package/dist/apps/contracts/AppManifest.d.ts +30 -0
- package/dist/apps/contracts/AppManifest.js +1 -0
- package/dist/apps/contracts/AppNavigation.d.ts +9 -0
- package/dist/apps/contracts/AppNavigation.js +1 -0
- package/dist/apps/contracts/AppNavigationBadge.d.ts +6 -0
- package/dist/apps/contracts/AppNavigationBadge.js +1 -0
- package/dist/apps/contracts/AppNavigationContext.d.ts +8 -0
- package/dist/apps/contracts/AppNavigationContext.js +1 -0
- package/dist/apps/contracts/AppNavigationResult.d.ts +12 -0
- package/dist/apps/contracts/AppNavigationResult.js +1 -0
- package/dist/apps/contracts/AppRoute.d.ts +19 -0
- package/dist/apps/contracts/AppRoute.js +1 -0
- package/dist/apps/contracts/DiscoveredApp.d.ts +10 -0
- package/dist/apps/contracts/DiscoveredApp.js +1 -0
- package/dist/apps/contracts/InstalledApps.d.ts +3 -0
- package/dist/apps/contracts/InstalledApps.js +1 -0
- package/dist/apps/contracts/index.d.ts +11 -0
- package/dist/apps/contracts/index.js +1 -0
- package/dist/apps/defineApp.d.ts +7 -0
- package/dist/apps/defineApp.js +8 -0
- package/dist/apps/discoverApps.d.ts +3 -0
- package/dist/apps/discoverApps.js +62 -0
- package/dist/apps/fastify.d.ts +1 -0
- package/dist/apps/fastify.js +1 -0
- package/dist/apps/index.d.ts +7 -0
- package/dist/apps/index.js +7 -0
- package/dist/apps/normalizeAppNavigation.d.ts +3 -0
- package/dist/apps/normalizeAppNavigation.js +30 -0
- package/dist/apps/registerAppRoutes.d.ts +6 -0
- package/dist/apps/registerAppRoutes.js +52 -0
- package/dist/apps/validateAppDefinitions.d.ts +3 -0
- package/dist/apps/validateAppDefinitions.js +65 -0
- package/dist/apps/vite.d.ts +1 -0
- package/dist/apps/vite.js +1 -0
- package/dist/apps/writeAppTypes.d.ts +2 -0
- package/dist/apps/writeAppTypes.js +13 -0
- package/dist/auth/Auth.d.ts +18 -0
- package/dist/auth/Auth.js +89 -13
- package/dist/auth/PasswordLoginAttempt.d.ts +50 -0
- package/dist/auth/PasswordLoginAttempt.js +60 -0
- package/dist/auth/PasswordSuspendedError.d.ts +10 -0
- package/dist/auth/PasswordSuspendedError.js +13 -0
- package/dist/auth/contracts/PasswordAuthProviderOptions.d.ts +26 -0
- package/dist/auth/contracts/PasswordAuthProviderOptions.js +1 -0
- package/dist/auth/contracts/PasswordSuspension.d.ts +7 -0
- package/dist/auth/contracts/PasswordSuspension.js +1 -0
- package/dist/auth/contracts/PasswordSuspensionOptions.d.ts +10 -0
- package/dist/auth/contracts/PasswordSuspensionOptions.js +1 -0
- package/dist/auth/contracts/index.d.ts +3 -0
- package/dist/auth/contracts/index.js +3 -0
- package/dist/auth/index.d.ts +2 -0
- package/dist/auth/index.js +2 -0
- package/dist/auth/providers/PasswordAuthProvider.d.ts +19 -23
- package/dist/auth/providers/PasswordAuthProvider.js +96 -8
- package/dist/auth/providers/resolveAuthProviders.js +1 -0
- package/dist/cli/Cli.d.ts +2 -0
- package/dist/cli/Cli.js +8 -0
- package/dist/cli/commands/repl.js +2 -1
- package/dist/cli/contracts/CliCommand.d.ts +2 -0
- package/dist/cli/contracts/CliCommandInfo.d.ts +12 -0
- package/dist/cli/contracts/CliCommandInfo.js +1 -0
- package/dist/cli/contracts/index.d.ts +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +1 -0
- package/dist/cli/listDb3Commands.d.ts +8 -0
- package/dist/cli/listDb3Commands.js +10 -0
- package/dist/cli/runDb3.d.ts +3 -6
- package/dist/cli/runDb3.js +5 -59
- package/dist/cli/withDb3Cli.d.ts +4 -0
- package/dist/cli/withDb3Cli.js +60 -0
- package/dist/db/ActiveQueryBuilder.d.ts +8 -0
- package/dist/db/ActiveQueryBuilder.js +11 -0
- package/dist/db/ActiveRecord.d.ts +6 -0
- package/dist/db/ActiveRecord.js +8 -0
- package/dist/db/connection.js +14 -2
- package/dist/db/fields/TimestampField.d.ts +1 -1
- package/dist/db/fields/TimestampField.js +7 -12
- package/dist/db/migrations/DatabaseMigrationManager.d.ts +3 -1
- package/dist/db/migrations/DatabaseMigrationManager.js +9 -3
- package/dist/db/migrations/contracts/DatabaseMigration.d.ts +7 -0
- package/dist/db/migrations/index.d.ts +1 -1
- package/dist/db/migrations/index.js +1 -1
- package/dist/db/migrations/{KnexMigrationRenderer.d.ts → renderKnexMigration.d.ts} +4 -3
- package/dist/db/migrations/{KnexMigrationRenderer.js → renderKnexMigration.js} +7 -6
- package/dist/flows/FlowCompiler.js +1 -1
- package/dist/flows/Flows.js +1 -1
- package/dist/flows/index.d.ts +1 -1
- package/dist/flows/index.js +1 -1
- package/dist/health/Health.d.ts +41 -0
- package/dist/health/Health.js +153 -0
- package/dist/health/contracts/Health.d.ts +36 -0
- package/dist/health/contracts/Health.js +1 -0
- package/dist/health/contracts/HealthHttp.d.ts +15 -0
- package/dist/health/contracts/HealthHttp.js +1 -0
- package/dist/health/contracts/index.d.ts +2 -0
- package/dist/health/contracts/index.js +1 -0
- package/dist/health/databaseHealthCheck.d.ts +15 -0
- package/dist/health/databaseHealthCheck.js +18 -0
- package/dist/health/fastify.d.ts +13 -0
- package/dist/health/fastify.js +44 -0
- package/dist/health/index.d.ts +3 -0
- package/dist/health/index.js +2 -0
- package/dist/in-app/InApp.d.ts +33 -0
- package/dist/in-app/InApp.js +191 -0
- package/dist/in-app/InAppError.d.ts +6 -0
- package/dist/in-app/InAppError.js +10 -0
- package/dist/in-app/InAppRecord.d.ts +36 -0
- package/dist/in-app/InAppRecord.js +24 -0
- package/dist/in-app/contracts/InAppInbox.d.ts +45 -0
- package/dist/in-app/contracts/InAppInbox.js +1 -0
- package/dist/in-app/contracts/InAppMessage.d.ts +22 -0
- package/dist/in-app/contracts/InAppMessage.js +1 -0
- package/dist/in-app/contracts/InAppScope.d.ts +17 -0
- package/dist/in-app/contracts/InAppScope.js +1 -0
- package/dist/in-app/contracts/index.d.ts +3 -0
- package/dist/in-app/contracts/index.js +3 -0
- package/dist/in-app/index.d.ts +4 -0
- package/dist/in-app/index.js +4 -0
- package/dist/in-app/validation.d.ts +21 -0
- package/dist/in-app/validation.js +50 -0
- package/dist/index.d.ts +2 -14
- package/dist/index.js +2 -5
- package/dist/logging/DevelopmentConsole.d.ts +42 -0
- package/dist/logging/DevelopmentConsole.js +162 -0
- package/dist/logging/Log.d.ts +3 -1
- package/dist/logging/Log.js +5 -2
- package/dist/logging/PrettyLogFormatter.d.ts +16 -0
- package/dist/logging/PrettyLogFormatter.js +151 -0
- package/dist/logging/acquireDevelopmentConsole.d.ts +6 -0
- package/dist/logging/acquireDevelopmentConsole.js +21 -0
- package/dist/logging/contracts/DevelopmentConsoleOptions.d.ts +16 -0
- package/dist/logging/contracts/DevelopmentConsoleOptions.js +1 -0
- package/dist/logging/contracts/EmailLogOptions.d.ts +8 -0
- package/dist/logging/contracts/EmailLogOptions.js +1 -0
- package/dist/logging/contracts/HttpExchangeMonitorOptions.d.ts +12 -0
- package/dist/logging/contracts/HttpExchangeMonitorOptions.js +1 -0
- package/dist/logging/contracts/LogTransportOptions.d.ts +19 -0
- package/dist/logging/contracts/LogTransportOptions.js +1 -0
- package/dist/logging/contracts/LoggingOptions.d.ts +12 -1
- package/dist/logging/contracts/index.d.ts +4 -0
- package/dist/logging/contracts/index.js +4 -0
- package/dist/logging/drivers/PinoLoggerDriver.d.ts +3 -1
- package/dist/logging/drivers/PinoLoggerDriver.js +75 -16
- package/dist/logging/formatLogEmail.d.ts +4 -0
- package/dist/logging/formatLogEmail.js +21 -0
- package/dist/logging/highlightQueueMessage.d.ts +11 -0
- package/dist/logging/highlightQueueMessage.js +36 -0
- package/dist/logging/httpBodyCapture.d.ts +24 -0
- package/dist/logging/httpBodyCapture.js +321 -0
- package/dist/logging/index.d.ts +2 -0
- package/dist/logging/index.js +2 -0
- package/dist/logging/redactLogEmail.d.ts +2 -0
- package/dist/logging/redactLogEmail.js +12 -0
- package/dist/logging/registerHttpExchangeMonitor.d.ts +10 -0
- package/dist/logging/registerHttpExchangeMonitor.js +43 -0
- package/dist/logging/transports/EmailLogTransport.d.ts +18 -0
- package/dist/logging/transports/EmailLogTransport.js +47 -0
- package/dist/mail/Mail.d.ts +2 -0
- package/dist/mail/Mail.js +3 -0
- package/dist/mail/MailDeliveryError.d.ts +9 -0
- package/dist/mail/MailDeliveryError.js +16 -0
- package/dist/mail/index.d.ts +1 -0
- package/dist/mail/index.js +1 -0
- package/dist/mail/transports/MailgunTransport.js +2 -0
- package/dist/mail/transports/ResendTransport.d.ts +3 -2
- package/dist/mail/transports/ResendTransport.js +22 -14
- package/dist/media/MediaManager.d.ts +4 -2
- package/dist/media/MediaManager.js +40 -8
- package/dist/network/BlockedUrlError.d.ts +16 -0
- package/dist/network/BlockedUrlError.js +19 -0
- package/dist/network/assertPublicUrl.d.ts +22 -0
- package/dist/network/assertPublicUrl.js +67 -0
- package/dist/network/contracts/GuardedFetch.d.ts +35 -0
- package/dist/network/contracts/GuardedFetch.js +1 -0
- package/dist/network/contracts/PublicUrlOptions.d.ts +17 -0
- package/dist/network/contracts/PublicUrlOptions.js +1 -0
- package/dist/network/contracts/index.d.ts +2 -0
- package/dist/network/contracts/index.js +2 -0
- package/dist/network/guardedFetch.d.ts +29 -0
- package/dist/network/guardedFetch.js +133 -0
- package/dist/network/index.d.ts +6 -0
- package/dist/network/index.js +6 -0
- package/dist/network/playwright/GuardedBrowserProxy.d.ts +32 -0
- package/dist/network/playwright/GuardedBrowserProxy.js +172 -0
- package/dist/network/playwright/contracts/GuardedBrowserContextOptions.d.ts +37 -0
- package/dist/network/playwright/contracts/GuardedBrowserContextOptions.js +1 -0
- package/dist/network/playwright/contracts/index.d.ts +1 -0
- package/dist/network/playwright/contracts/index.js +1 -0
- package/dist/network/playwright/index.d.ts +2 -0
- package/dist/network/playwright/index.js +2 -0
- package/dist/network/playwright/newGuardedBrowserContext.d.ts +21 -0
- package/dist/network/playwright/newGuardedBrowserContext.js +86 -0
- package/dist/network/privateNetworkAddresses.d.ts +44 -0
- package/dist/network/privateNetworkAddresses.js +157 -0
- package/dist/network/publicAddressLookup.d.ts +26 -0
- package/dist/network/publicAddressLookup.js +42 -0
- package/dist/notifications/Notifications.d.ts +27 -0
- package/dist/notifications/Notifications.js +99 -0
- package/dist/notifications/contracts/Notification.d.ts +56 -0
- package/dist/notifications/contracts/Notification.js +1 -0
- package/dist/notifications/contracts/NotificationDelivery.d.ts +11 -0
- package/dist/notifications/contracts/NotificationDelivery.js +1 -0
- package/dist/notifications/contracts/index.d.ts +2 -0
- package/dist/notifications/contracts/index.js +2 -0
- package/dist/notifications/index.d.ts +2 -0
- package/dist/notifications/index.js +2 -0
- package/dist/queue/JobRunAttempt.d.ts +16 -0
- package/dist/queue/JobRunAttempt.js +17 -0
- package/dist/queue/JobRunEvent.d.ts +15 -0
- package/dist/queue/JobRunEvent.js +16 -0
- package/dist/queue/JobRunStoppedError.d.ts +6 -0
- package/dist/queue/JobRunStoppedError.js +10 -0
- package/dist/queue/Queue.d.ts +19 -4
- package/dist/queue/Queue.js +122 -9
- package/dist/queue/QueueTerminalError.d.ts +9 -0
- package/dist/queue/QueueTerminalError.js +12 -0
- package/dist/queue/QueueWorker.d.ts +12 -5
- package/dist/queue/QueueWorker.js +94 -9
- package/dist/queue/QueueWorkerReporter.d.ts +17 -0
- package/dist/queue/QueueWorkerReporter.js +71 -0
- package/dist/queue/QueueableJob.d.ts +2 -2
- package/dist/queue/RunnableJob.d.ts +78 -0
- package/dist/queue/RunnableJob.js +284 -0
- package/dist/queue/console.d.ts +8 -0
- package/dist/queue/console.js +53 -19
- package/dist/queue/contracts/QueueDriver.d.ts +8 -0
- package/dist/queue/contracts/QueueJobRunner.d.ts +4 -0
- package/dist/queue/contracts/QueueJobRunner.js +1 -0
- package/dist/queue/contracts/QueuePayload.d.ts +1 -1
- package/dist/queue/contracts/QueueSelection.d.ts +11 -0
- package/dist/queue/contracts/QueueSelection.js +1 -0
- package/dist/queue/contracts/QueueService.d.ts +18 -4
- package/dist/queue/contracts/QueueWorkerLifecycle.d.ts +2 -2
- package/dist/queue/contracts/QueueWorkerSnapshot.d.ts +14 -0
- package/dist/queue/contracts/QueueWorkerSnapshot.js +1 -0
- package/dist/queue/contracts/QueueableJob.d.ts +7 -3
- package/dist/queue/contracts/QueueableJobContract.d.ts +12 -0
- package/dist/queue/contracts/QueueableJobContract.js +1 -0
- package/dist/queue/contracts/index.d.ts +4 -0
- package/dist/queue/contracts/index.js +2 -0
- package/dist/queue/drivers/DatabaseQueueDriver.d.ts +4 -0
- package/dist/queue/drivers/DatabaseQueueDriver.js +27 -3
- package/dist/queue/drivers/RedisQueueDriver.d.ts +7 -0
- package/dist/queue/drivers/RedisQueueDriver.js +37 -0
- package/dist/queue/index.d.ts +5 -0
- package/dist/queue/index.js +5 -0
- package/dist/queue/jobEnvelopeHasPrefix.d.ts +3 -0
- package/dist/queue/jobEnvelopeHasPrefix.js +4 -0
- package/dist/scheduler/ScheduledEvent.d.ts +8 -4
- package/dist/scheduler/ScheduledEvent.js +17 -3
- package/dist/scheduler/ScheduledJob.d.ts +1 -1
- package/dist/scheduler/Scheduler.d.ts +5 -0
- package/dist/scheduler/Scheduler.js +92 -28
- package/dist/scheduler/SchedulerCheckpointRecord.d.ts +16 -0
- package/dist/scheduler/SchedulerCheckpointRecord.js +53 -0
- package/dist/scheduler/SchedulerWorker.d.ts +3 -5
- package/dist/scheduler/SchedulerWorker.js +58 -14
- package/dist/scheduler/console.js +3 -1
- package/dist/scheduler/contracts/Schedule.d.ts +19 -0
- package/dist/scheduler/contracts/Schedule.js +1 -0
- package/dist/scheduler/contracts/Scheduler.d.ts +18 -6
- package/dist/scheduler/contracts/SchedulerCheckpoint.d.ts +23 -0
- package/dist/scheduler/contracts/SchedulerCheckpoint.js +1 -0
- package/dist/scheduler/contracts/SchedulerConsole.d.ts +5 -0
- package/dist/scheduler/contracts/SchedulerWorker.d.ts +6 -1
- package/dist/scheduler/contracts/index.d.ts +2 -0
- package/dist/scheduler/contracts/index.js +2 -0
- package/dist/scheduler/index.d.ts +1 -0
- package/dist/scheduler/index.js +1 -0
- package/dist/server/App.d.ts +36 -4
- package/dist/server/App.js +57 -7
- package/dist/server/contracts/HttpErrorHandlerOptions.d.ts +29 -0
- package/dist/server/contracts/HttpErrorHandlerOptions.js +1 -0
- package/dist/server/contracts/index.d.ts +1 -0
- package/dist/server/contracts/index.js +1 -0
- package/dist/server/index.d.ts +4 -0
- package/dist/server/index.js +4 -0
- package/dist/server/publicServerErrorMessage.d.ts +11 -0
- package/dist/server/publicServerErrorMessage.js +16 -0
- package/dist/server/registerBrowserJsonFormatting.d.ts +16 -0
- package/dist/server/registerBrowserJsonFormatting.js +100 -0
- package/dist/server/registerHttpErrorHandler.d.ts +20 -0
- package/dist/server/registerHttpErrorHandler.js +93 -0
- package/dist/ssr/vite/contracts/ViteSsrOptions.d.ts +2 -0
- package/dist/ssr/vite/fastifyViteSsr.js +4 -3
- package/dist/storage/drivers/FlystorageDisk.js +2 -2
- package/dist/storage/index.d.ts +2 -2
- package/dist/storage/index.js +2 -2
- package/dist/websocket/WebSocketChannels.d.ts +18 -0
- package/dist/websocket/WebSocketChannels.js +129 -0
- package/dist/websocket/WebSocketConnection.d.ts +25 -0
- package/dist/websocket/WebSocketConnection.js +244 -0
- package/dist/websocket/WebSockets.d.ts +41 -0
- package/dist/websocket/WebSockets.js +194 -0
- package/dist/websocket/client/WebSocketClient.d.ts +21 -0
- package/dist/websocket/client/WebSocketClient.js +230 -0
- package/dist/websocket/client/index.d.ts +3 -0
- package/dist/websocket/client/index.js +1 -0
- package/dist/websocket/contracts/WebSocketChannel.d.ts +20 -0
- package/dist/websocket/contracts/WebSocketChannel.js +1 -0
- package/dist/websocket/contracts/WebSocketClient.d.ts +45 -0
- package/dist/websocket/contracts/WebSocketClient.js +1 -0
- package/dist/websocket/contracts/WebSocketEndpoint.d.ts +43 -0
- package/dist/websocket/contracts/WebSocketEndpoint.js +1 -0
- package/dist/websocket/contracts/WebSocketOptions.d.ts +48 -0
- package/dist/websocket/contracts/WebSocketOptions.js +1 -0
- package/dist/websocket/contracts/WebSocketPresence.d.ts +6 -0
- package/dist/websocket/contracts/WebSocketPresence.js +1 -0
- package/dist/websocket/contracts/WebSocketServiceOptions.d.ts +12 -0
- package/dist/websocket/contracts/WebSocketServiceOptions.js +1 -0
- package/dist/websocket/contracts/index.d.ts +6 -0
- package/dist/websocket/contracts/index.js +1 -0
- package/dist/websocket/defineChannel.d.ts +8 -0
- package/dist/websocket/defineChannel.js +13 -0
- package/dist/websocket/defineWebSocket.d.ts +14 -0
- package/dist/websocket/defineWebSocket.js +4 -0
- package/dist/websocket/fastify.d.ts +11 -0
- package/dist/websocket/fastify.js +38 -0
- package/dist/websocket/index.d.ts +5 -0
- package/dist/websocket/index.js +4 -0
- package/package.json +108 -7
- package/src/ai/AIErrors.ts +10 -0
- package/src/ai/AIFailover.ts +3 -2
- package/src/ai/AIProviderAdmission.ts +329 -0
- package/src/ai/AIRateLimiter.ts +134 -10
- package/src/ai/AIRequestTrackingError.ts +37 -0
- package/src/ai/Agent.ts +85 -71
- package/src/ai/Ai.ts +104 -48
- package/src/ai/OpenAIProviderError.ts +1 -1
- package/src/ai/ProviderSseObserver.ts +89 -0
- package/src/ai/README.md +216 -11
- package/src/ai/aiServiceTier.ts +14 -0
- package/src/ai/chunkEmbeddingText.ts +137 -0
- package/src/ai/contracts/AI.ts +17 -0
- package/src/ai/contracts/AIRequestErrorDiagnostics.ts +12 -0
- package/src/ai/contracts/AIServiceTier.ts +2 -0
- package/src/ai/contracts/Agent.ts +3 -0
- package/src/ai/contracts/EmbeddingTextChunk.ts +26 -0
- package/src/ai/contracts/Pricing.ts +7 -1
- package/src/ai/contracts/Providers.ts +2 -0
- package/src/ai/contracts/index.ts +2 -0
- package/src/ai/examples/prepareDocumentEmbeddings.ts +20 -0
- package/src/ai/index.ts +9 -2
- package/src/ai/{Models.ts → modelPricing.ts} +37 -4
- package/src/apps/AppInstallation.ts +25 -0
- package/src/apps/AppRouteError.ts +8 -0
- package/src/apps/AppService.ts +30 -0
- package/src/apps/Apps.ts +508 -0
- package/src/apps/README.md +354 -0
- package/src/apps/appsPlugin.ts +34 -0
- package/src/apps/commands/disableApp.ts +6 -0
- package/src/apps/commands/enableApp.ts +6 -0
- package/src/apps/commands/index.ts +29 -0
- package/src/apps/commands/installApp.ts +6 -0
- package/src/apps/commands/listApps.ts +6 -0
- package/src/apps/commands/makeAppMigration.ts +9 -0
- package/src/apps/commands/rollbackApp.ts +6 -0
- package/src/apps/commands/uninstallApp.ts +6 -0
- package/src/apps/contracts/AppAction.ts +2 -0
- package/src/apps/contracts/AppDefinition.ts +49 -0
- package/src/apps/contracts/AppDescription.ts +27 -0
- package/src/apps/contracts/AppManifest.ts +32 -0
- package/src/apps/contracts/AppNavigation.ts +10 -0
- package/src/apps/contracts/AppNavigationBadge.ts +6 -0
- package/src/apps/contracts/AppNavigationContext.ts +6 -0
- package/src/apps/contracts/AppNavigationResult.ts +9 -0
- package/src/apps/contracts/AppRoute.ts +18 -0
- package/src/apps/contracts/DiscoveredApp.ts +11 -0
- package/src/apps/contracts/InstalledApps.ts +2 -0
- package/src/apps/contracts/index.ts +11 -0
- package/src/apps/defineApp.ts +10 -0
- package/src/apps/discoverApps.ts +56 -0
- package/src/apps/fastify.ts +1 -0
- package/src/apps/index.ts +7 -0
- package/src/apps/normalizeAppNavigation.ts +29 -0
- package/src/apps/registerAppRoutes.ts +48 -0
- package/src/apps/validateAppDefinitions.ts +52 -0
- package/src/apps/vite.ts +1 -0
- package/src/apps/writeAppTypes.ts +14 -0
- package/src/auth/Auth.ts +89 -23
- package/src/auth/PasswordLoginAttempt.ts +72 -0
- package/src/auth/PasswordSuspendedError.ts +13 -0
- package/src/auth/README.md +94 -4
- package/src/auth/contracts/PasswordAuthProviderOptions.ts +30 -0
- package/src/auth/contracts/PasswordSuspension.ts +7 -0
- package/src/auth/contracts/PasswordSuspensionOptions.ts +10 -0
- package/src/auth/contracts/index.ts +3 -0
- package/src/auth/examples/runPasswordAuth.ts +6 -6
- package/src/auth/index.ts +2 -0
- package/src/auth/providers/PasswordAuthProvider.ts +96 -30
- package/src/auth/providers/resolveAuthProviders.ts +1 -0
- package/src/cli/Cli.ts +9 -0
- package/src/cli/README.md +19 -0
- package/src/cli/commands/repl.ts +2 -1
- package/src/cli/contracts/CliCommand.ts +2 -0
- package/src/cli/contracts/CliCommandInfo.ts +13 -0
- package/src/cli/contracts/index.ts +1 -0
- package/src/cli/index.ts +1 -0
- package/src/cli/listDb3Commands.ts +12 -0
- package/src/cli/runDb3.ts +5 -51
- package/src/cli/withDb3Cli.ts +55 -0
- package/src/db/ActiveQueryBuilder.ts +12 -0
- package/src/db/ActiveRecord.ts +9 -0
- package/src/db/README.md +45 -1
- package/src/db/connection.ts +12 -1
- package/src/db/examples/FieldNote.ts +7 -13
- package/src/db/examples/KnowledgeNote.ts +7 -13
- package/src/db/examples/runNoteMigrations.ts +11 -11
- package/src/db/fields/TimestampField.ts +8 -17
- package/src/db/migrations/DatabaseMigrationManager.ts +12 -4
- package/src/db/migrations/contracts/DatabaseMigration.ts +9 -0
- package/src/db/migrations/index.ts +1 -1
- package/src/db/migrations/{KnexMigrationRenderer.ts → renderKnexMigration.ts} +8 -14
- package/src/flows/FlowCompiler.ts +1 -1
- package/src/flows/Flows.ts +1 -1
- package/src/flows/index.ts +1 -1
- package/src/health/Health.ts +163 -0
- package/src/health/README.md +46 -0
- package/src/health/contracts/Health.ts +47 -0
- package/src/health/contracts/HealthHttp.ts +19 -0
- package/src/health/contracts/index.ts +2 -0
- package/src/health/databaseHealthCheck.ts +21 -0
- package/src/health/fastify.ts +49 -0
- package/src/health/index.ts +3 -0
- package/src/in-app/InApp.ts +173 -0
- package/src/in-app/InAppError.ts +8 -0
- package/src/in-app/InAppRecord.ts +41 -0
- package/src/in-app/README.md +193 -0
- package/src/in-app/contracts/InAppInbox.ts +51 -0
- package/src/in-app/contracts/InAppMessage.ts +19 -0
- package/src/in-app/contracts/InAppScope.ts +12 -0
- package/src/in-app/contracts/index.ts +3 -0
- package/src/in-app/examples/sendReportReady.ts +14 -0
- package/src/in-app/index.ts +4 -0
- package/src/in-app/validation.ts +49 -0
- package/src/index.ts +2 -22
- package/src/logging/DevelopmentConsole.ts +154 -0
- package/src/logging/Log.ts +5 -2
- package/src/logging/PrettyLogFormatter.ts +165 -0
- package/src/logging/README.md +52 -109
- package/src/logging/acquireDevelopmentConsole.ts +19 -0
- package/src/logging/contracts/DevelopmentConsoleOptions.ts +13 -0
- package/src/logging/contracts/EmailLogOptions.ts +9 -0
- package/src/logging/contracts/HttpExchangeMonitorOptions.ts +13 -0
- package/src/logging/contracts/LogTransportOptions.ts +10 -0
- package/src/logging/contracts/LoggingOptions.ts +12 -1
- package/src/logging/contracts/index.ts +4 -0
- package/src/logging/development/README.md +88 -0
- package/src/logging/drivers/PinoLoggerDriver.ts +72 -20
- package/src/logging/examples/runDevelopmentLogs.ts +36 -0
- package/src/logging/formatLogEmail.ts +24 -0
- package/src/logging/highlightQueueMessage.ts +38 -0
- package/src/logging/httpBodyCapture.ts +393 -0
- package/src/logging/index.ts +2 -0
- package/src/logging/redactLogEmail.ts +12 -0
- package/src/logging/registerHttpExchangeMonitor.ts +45 -0
- package/src/logging/transports/EmailLogTransport.ts +46 -0
- package/src/mail/Mail.ts +5 -0
- package/src/mail/MailDeliveryError.ts +8 -0
- package/src/mail/README.md +11 -1
- package/src/mail/index.ts +1 -0
- package/src/mail/transports/MailgunTransport.ts +2 -0
- package/src/mail/transports/ResendTransport.ts +24 -22
- package/src/media/MediaManager.ts +31 -10
- package/src/media/README.md +4 -0
- package/src/media/examples/runPrivateFiles.ts +2 -2
- package/src/network/BlockedUrlError.ts +19 -0
- package/src/network/README.md +133 -0
- package/src/network/assertPublicUrl.ts +77 -0
- package/src/network/contracts/GuardedFetch.ts +40 -0
- package/src/network/contracts/PublicUrlOptions.ts +17 -0
- package/src/network/contracts/index.ts +2 -0
- package/src/network/examples/runGuardedFetch.ts +35 -0
- package/src/network/guardedFetch.ts +150 -0
- package/src/network/index.ts +6 -0
- package/src/network/playwright/GuardedBrowserProxy.ts +172 -0
- package/src/network/playwright/contracts/GuardedBrowserContextOptions.ts +43 -0
- package/src/network/playwright/contracts/index.ts +1 -0
- package/src/network/playwright/index.ts +2 -0
- package/src/network/playwright/newGuardedBrowserContext.ts +74 -0
- package/src/network/privateNetworkAddresses.ts +166 -0
- package/src/network/publicAddressLookup.ts +49 -0
- package/src/notifications/Notifications.ts +105 -0
- package/src/notifications/README.md +59 -0
- package/src/notifications/contracts/Notification.ts +65 -0
- package/src/notifications/contracts/NotificationDelivery.ts +11 -0
- package/src/notifications/contracts/index.ts +2 -0
- package/src/notifications/examples/ReportReadyNotification.ts +31 -0
- package/src/notifications/index.ts +2 -0
- package/src/queue/JobRunAttempt.ts +17 -0
- package/src/queue/JobRunEvent.ts +16 -0
- package/src/queue/JobRunStoppedError.ts +8 -0
- package/src/queue/Queue.ts +112 -11
- package/src/queue/QueueTerminalError.ts +12 -0
- package/src/queue/QueueWorker.ts +86 -7
- package/src/queue/QueueWorkerReporter.ts +59 -0
- package/src/queue/QueueableJob.ts +2 -2
- package/src/queue/README.md +142 -1
- package/src/queue/RunnableJob.ts +272 -0
- package/src/queue/console.ts +54 -21
- package/src/queue/contracts/QueueDriver.ts +9 -0
- package/src/queue/contracts/QueueJobRunner.ts +4 -0
- package/src/queue/contracts/QueuePayload.ts +1 -1
- package/src/queue/contracts/QueueSelection.ts +11 -0
- package/src/queue/contracts/QueueService.ts +19 -4
- package/src/queue/contracts/QueueWorkerLifecycle.ts +2 -2
- package/src/queue/contracts/QueueWorkerSnapshot.ts +15 -0
- package/src/queue/contracts/QueueableJob.ts +7 -3
- package/src/queue/contracts/QueueableJobContract.ts +13 -0
- package/src/queue/contracts/index.ts +6 -0
- package/src/queue/drivers/DatabaseQueueDriver.ts +25 -3
- package/src/queue/drivers/RedisQueueDriver.ts +36 -0
- package/src/queue/examples/runQueueSelection.ts +34 -0
- package/src/queue/index.ts +7 -0
- package/src/queue/jobEnvelopeHasPrefix.ts +6 -0
- package/src/scheduler/README.md +50 -9
- package/src/scheduler/ScheduledEvent.ts +20 -5
- package/src/scheduler/ScheduledJob.ts +1 -1
- package/src/scheduler/Scheduler.ts +73 -27
- package/src/scheduler/SchedulerCheckpointRecord.ts +51 -0
- package/src/scheduler/SchedulerWorker.ts +54 -19
- package/src/scheduler/console.ts +3 -1
- package/src/scheduler/contracts/Schedule.ts +22 -0
- package/src/scheduler/contracts/Scheduler.ts +21 -6
- package/src/scheduler/contracts/SchedulerCheckpoint.ts +23 -0
- package/src/scheduler/contracts/SchedulerConsole.ts +5 -0
- package/src/scheduler/contracts/SchedulerWorker.ts +6 -1
- package/src/scheduler/contracts/index.ts +2 -0
- package/src/scheduler/examples/runSchedulerWindow.ts +54 -0
- package/src/scheduler/index.ts +1 -0
- package/src/server/App.ts +73 -10
- package/src/server/README.md +95 -2
- package/src/server/contracts/HttpErrorHandlerOptions.ts +29 -0
- package/src/server/contracts/index.ts +1 -0
- package/src/server/examples/createFirstServer.ts +5 -3
- package/src/server/index.ts +4 -0
- package/src/server/publicServerErrorMessage.ts +17 -0
- package/src/server/registerBrowserJsonFormatting.ts +94 -0
- package/src/server/registerHttpErrorHandler.ts +96 -0
- package/src/ssr/README.md +6 -0
- package/src/ssr/vite/contracts/ViteSsrOptions.ts +2 -0
- package/src/ssr/vite/fastifyViteSsr.ts +4 -3
- package/src/storage/drivers/FlystorageDisk.ts +2 -2
- package/src/storage/index.ts +2 -2
- package/src/websocket/README.md +458 -0
- package/src/websocket/WebSocketChannels.ts +99 -0
- package/src/websocket/WebSocketConnection.ts +162 -0
- package/src/websocket/WebSockets.ts +157 -0
- package/src/websocket/client/WebSocketClient.ts +188 -0
- package/src/websocket/client/index.ts +3 -0
- package/src/websocket/contracts/WebSocketChannel.ts +23 -0
- package/src/websocket/contracts/WebSocketClient.ts +48 -0
- package/src/websocket/contracts/WebSocketEndpoint.ts +46 -0
- package/src/websocket/contracts/WebSocketOptions.ts +50 -0
- package/src/websocket/contracts/WebSocketPresence.ts +6 -0
- package/src/websocket/contracts/WebSocketServiceOptions.ts +12 -0
- package/src/websocket/contracts/index.ts +6 -0
- package/src/websocket/defineChannel.ts +14 -0
- package/src/websocket/defineWebSocket.ts +15 -0
- package/src/websocket/examples/board/LiveBoard.ts +97 -0
- package/src/websocket/examples/board/SummarizeBoardJob.ts +52 -0
- package/src/websocket/examples/board/boardSnapshot.ts +16 -0
- package/src/websocket/examples/board/createBoardClient.ts +43 -0
- package/src/websocket/examples/board/createBoardStore.ts +58 -0
- package/src/websocket/examples/board/registerBoardRoutes.ts +31 -0
- package/src/websocket/examples/createChannelSync.ts +66 -0
- package/src/websocket/examples/deploymentEndpoint.ts +13 -0
- package/src/websocket/examples/deploymentSnapshot.ts +36 -0
- package/src/websocket/examples/resourceChannels.ts +18 -0
- package/src/websocket/examples/userEndpoint.ts +27 -0
- package/src/websocket/fastify.ts +39 -0
- package/src/websocket/index.ts +5 -0
- package/templates/AGENTS.md +33 -2
- /package/dist/flows/{FlowValueValidator.d.ts → flowValueValidator.d.ts} +0 -0
- /package/dist/flows/{FlowValueValidator.js → flowValueValidator.js} +0 -0
- /package/dist/storage/{mime.d.ts → mimeTypeFromPath.d.ts} +0 -0
- /package/dist/storage/{mime.js → mimeTypeFromPath.js} +0 -0
- /package/dist/storage/{contents.d.ts → storageContentsToBuffer.d.ts} +0 -0
- /package/dist/storage/{contents.js → storageContentsToBuffer.js} +0 -0
- /package/src/flows/{FlowValueValidator.ts → flowValueValidator.ts} +0 -0
- /package/src/storage/{mime.ts → mimeTypeFromPath.ts} +0 -0
- /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,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
|
|
35
|
-
|
|
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
|
-
|
|
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
|
|
42
|
+
* Verifies credentials under the identity's durable attempt lock.
|
|
53
43
|
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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.
|
package/src/cli/commands/repl.ts
CHANGED
|
@@ -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',
|
|
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
|
+
}
|
package/src/cli/index.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
*
|
|
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
|
|
17
|
-
* @returns
|
|
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
|
-
|
|
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
|
*/
|
package/src/db/ActiveRecord.ts
CHANGED
|
@@ -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
|
-
|
|
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.
|
package/src/db/connection.ts
CHANGED
|
@@ -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)
|
|
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
|
},
|