@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.
- package/README.md +68 -9
- 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
package/src/queue/README.md
CHANGED
|
@@ -134,7 +134,7 @@ For a continuous worker, use a separate terminal:
|
|
|
134
134
|
npx tsx examples/reportConsole.ts queue:work --queue=reports --max-jobs=1
|
|
135
135
|
```
|
|
136
136
|
|
|
137
|
-
Ctrl-C
|
|
137
|
+
Ctrl-C finishes the active job before closing the App. To replay a repaired
|
|
138
138
|
terminal failure, use its failed-record ID, not its original active-job ID:
|
|
139
139
|
|
|
140
140
|
```sh
|
|
@@ -177,8 +177,53 @@ work per tick; it is not concurrency. Use more supervised worker processes for
|
|
|
177
177
|
parallelism. Polling defaults to 1000 ms and valid intervals are clamped to at
|
|
178
178
|
least 100 ms. `queue:work --once` checks once and exits, even if delayed work exists.
|
|
179
179
|
|
|
180
|
+
### Share worker capacity
|
|
181
|
+
|
|
182
|
+
Jobs still target one queue. Workers can consume several exact names or all
|
|
183
|
+
names in their database/Redis namespace:
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
const general = app.queue.startWorker({
|
|
187
|
+
queues: '*',
|
|
188
|
+
excludeQueues: ['articles', 'article-images'],
|
|
189
|
+
});
|
|
190
|
+
const shared = app.queue.startWorker({ queues: '*' });
|
|
191
|
+
// Fixed selection: { queues: ['default', 'articles'] }.
|
|
192
|
+
// At shutdown:
|
|
193
|
+
await Promise.all([general?.stopAndDrain(), shared?.stopAndDrain()]);
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Exclusions win before claiming. Names are exact, without patterns; duplicates
|
|
197
|
+
are removed. Empty names/arrays and fully excluded explicit selections fail.
|
|
198
|
+
Omitting selection retains the configured default. Each worker executes one
|
|
199
|
+
job at a time, rotates after a claim and skips unavailable queues. Round-robin
|
|
200
|
+
shares claim opportunities, not processing time; running jobs are not preempted.
|
|
201
|
+
|
|
202
|
+
Wildcard discovery refreshes before each claim/poll and includes delayed and
|
|
203
|
+
reserved queues. Claims still enforce availability and lease ownership.
|
|
204
|
+
Database and Redis support discovery; custom drivers need `queueNames()`.
|
|
205
|
+
Redis uses SCAN over existing queue keys. Prefer explicit names on large shared
|
|
206
|
+
keyspaces. Newly introduced queues automatically become eligible for `'*'`.
|
|
207
|
+
|
|
208
|
+
```sh
|
|
209
|
+
node --import tsx server/console.ts queue:work --queues=default,articles
|
|
210
|
+
node --import tsx server/console.ts queue:work --queues='*' --exclude-queues=articles,article-images
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Pass selections in `runQueueConsole({ app, workers: { general: selection } })`
|
|
214
|
+
and use `--pool=general`, without queue flags. `--once` applies the same rules
|
|
215
|
+
and closes the app on success/failure. Existing `--queue=reports` still works.
|
|
216
|
+
For timer-free processing, use `new QueueWorker(app.queue, selection).workOnce()`;
|
|
217
|
+
concurrent calls on one worker fail. See the disposable SQL
|
|
218
|
+
[selection lab](examples/runQueueSelection.ts).
|
|
219
|
+
|
|
220
|
+
Routing changes affect new jobs only: retain consumers for old names during
|
|
221
|
+
rollout/rollback. Monitor each workload queue independently of general health.
|
|
222
|
+
|
|
180
223
|
## Retries and repair
|
|
181
224
|
|
|
225
|
+
`QueueTerminalError` stops immediately regardless of remaining tries, retaining the failed job and invoking final-failure hooks. Use it when ordinary retries cannot repair the cause.
|
|
226
|
+
|
|
182
227
|
Ordinary errors consume attempts. Dispatch defaults are three attempts,
|
|
183
228
|
exponential backoff starting at 15 seconds, capped at 3600 seconds, with jitter
|
|
184
229
|
off unless changed in queue options/environment. The supported strategies are
|
|
@@ -366,3 +411,99 @@ handler throws so a generated skeleton cannot silently consume real work.
|
|
|
366
411
|
Editor or Studio integrations can use `jobTemplate(name)` to preview the same
|
|
367
412
|
source and `createJob({ appDirectory, name })` to create the file. These are
|
|
368
413
|
shared framework operations; a Studio screen is not included in this package.
|
|
414
|
+
|
|
415
|
+
## Atomic application dispatch
|
|
416
|
+
|
|
417
|
+
The database driver's `push` participates in an explicit `ActiveRecord.withDb`
|
|
418
|
+
scope, including one established by `app().db.transaction`. Application records
|
|
419
|
+
and queued jobs can therefore commit or roll back together. Outside a scope it
|
|
420
|
+
uses its configured database. Redis dispatch is not transactional with SQL.
|
|
421
|
+
Dispatch lifecycle events may run before the surrounding transaction commits;
|
|
422
|
+
observers must not perform irreversible external work on those events.
|
|
423
|
+
|
|
424
|
+
## Retained runnable records
|
|
425
|
+
|
|
426
|
+
Use `RunnableJob` when an operation needs durable progress and history. It is an
|
|
427
|
+
ActiveRecord implementing the queue contract; concrete subclasses share
|
|
428
|
+
`job_runs`. Register `RunnableJob`, `JobRunAttempt` and `JobRunEvent` with the
|
|
429
|
+
application's migration registry, alongside `QueuedJob` and `FailedJob`.
|
|
430
|
+
|
|
431
|
+
```ts
|
|
432
|
+
import { RunnableJob, type QueueableJobContext } from '@db3.ai/app/queue';
|
|
433
|
+
|
|
434
|
+
/** Retains one organization's progress between delivery attempts. */
|
|
435
|
+
class CountItemsJob extends RunnableJob {
|
|
436
|
+
/** Saves a checkpoint after each bounded unit of work. */
|
|
437
|
+
override async handle(_context: QueueableJobContext): Promise<void> {
|
|
438
|
+
this.total = 10;
|
|
439
|
+
for (let index = this.completed ?? 0; index < 10; index++) {
|
|
440
|
+
await this.assertActive();
|
|
441
|
+
await this.checkpoint(run => {
|
|
442
|
+
run.completed = index + 1;
|
|
443
|
+
run.state = { lastItem: index };
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
await this.log('All items processed');
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const run = CountItemsJob.create({ tenantId: organizationId, input: {} });
|
|
451
|
+
await app.queue.dispatch(run); // persists before execution, then queues its ID
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
`prepare()` can resolve tenant/input scope before initial persistence. `save()`
|
|
455
|
+
during `handle()` saves application state under the current execution fence.
|
|
456
|
+
Use `checkpoint(callback)` when concurrent requests merge counters or JSON.
|
|
457
|
+
The application authorizes launching/reading runs; a tenant ID alone is not an
|
|
458
|
+
access-control policy. `input`, tenant and subject are immutable during execution.
|
|
459
|
+
|
|
460
|
+
The runtime records waiting/running/completed/failed/blocked/cancelled outcomes,
|
|
461
|
+
start/end/heartbeat timestamps, and separate attempts. Queue retries reconstruct
|
|
462
|
+
the same run, retaining its checkpoint. A recovered expired reservation marks
|
|
463
|
+
the preceding attempt interrupted. The queue still delivers at least once:
|
|
464
|
+
external effects must be idempotent and jobs must check ownership before
|
|
465
|
+
publishing them. `assertActive()` also detects an operator-set
|
|
466
|
+
`cancelRequestedAt`; `signal` allows requests to cooperate with cancellation.
|
|
467
|
+
`block(reason)` retains a stopped outcome without retrying a known bad condition.
|
|
468
|
+
|
|
469
|
+
Run-specific concurrency, request budgets, exception thresholds and cost
|
|
470
|
+
accounting belong to the concrete job. Keep JSON checkpoints bounded (1 MB) and
|
|
471
|
+
use `log(message, data, level)` for bounded events (16 KB each). Omit secrets and
|
|
472
|
+
provider bodies. Historical evidence needs an application retention policy.
|
|
473
|
+
|
|
474
|
+
With the database queue, reserving the dispatch generation and inserting its
|
|
475
|
+
reference are atomic. The run is saved first, so a failed enqueue remains
|
|
476
|
+
inspectable. An external broker cannot share that SQL transaction; adopting one
|
|
477
|
+
requires a transactional outbox/reconciler for the publish gap. Tracked terminal
|
|
478
|
+
failures remain in `job_runs` instead of adding failed queue-delivery rows.
|
|
479
|
+
Ordinary `QueueableJob` classes and their existing failure behavior are unchanged.
|
|
480
|
+
|
|
481
|
+
### Active run context
|
|
482
|
+
|
|
483
|
+
`RunnableJob.current()` returns the run executing in the current asynchronous
|
|
484
|
+
context. Dispatching a retained child automatically records its parent ID and
|
|
485
|
+
checks that the parent still owns its execution. Cancellation is checked through
|
|
486
|
+
the parent ancestry before further checkpoints and work. Applications own which
|
|
487
|
+
job types support operator cancellation and any external-effect cleanup.
|
|
488
|
+
|
|
489
|
+
A registered retained implementation also handles named dispatches and ordinary
|
|
490
|
+
queue producers registered under the same job name. Producers must register the
|
|
491
|
+
retained implementation before dispatch. The scheduler accepts either retained
|
|
492
|
+
or lightweight queue contracts; it does not poll retained history.
|
|
493
|
+
|
|
494
|
+
### Live worker presence
|
|
495
|
+
|
|
496
|
+
Set `QueueOptions.onWorkerHeartbeat` to persist or publish `QueueWorkerSnapshot`
|
|
497
|
+
records in the application's own monitoring store. Every worker has a unique ID,
|
|
498
|
+
host/PID, exact queue selection and exclusions, current job identity, start time
|
|
499
|
+
and heartbeat. Polling workers report idle/busy/stopping/stopped transitions and
|
|
500
|
+
a heartbeat every ten seconds, including during long jobs. One-shot workers
|
|
501
|
+
report a final stopped state. Sink failures do not fail jobs; stale heartbeats
|
|
502
|
+
must be treated as unavailable rather than counted as idle capacity.
|
|
503
|
+
The sink is optional, coalesces pending updates, and never receives job payloads.
|
|
504
|
+
Applications own access control, expiry and retention. Counts across overlapping
|
|
505
|
+
queue selections are shared capacity, not additional worker processes.
|
|
506
|
+
|
|
507
|
+
App queues report listener/hook exceptions and job identity through `app.log`;
|
|
508
|
+
`onLifecycleError` overrides this. Standalone queues use stderr. Committed queue
|
|
509
|
+
transitions remain unchanged: essential settlement must be idempotent and recoverable.
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { ActiveRecord } from '../db/ActiveRecord';
|
|
4
|
+
import { JobRunAttempt } from './JobRunAttempt';
|
|
5
|
+
import { JobRunEvent } from './JobRunEvent';
|
|
6
|
+
import { JobRunStoppedError } from './JobRunStoppedError';
|
|
7
|
+
import { QueueTerminalError } from './QueueTerminalError';
|
|
8
|
+
import { isQueueRetryLaterError } from './QueueRetryLaterError';
|
|
9
|
+
import type { Queue } from './Queue';
|
|
10
|
+
import type { DispatchOptions, QueueJobId, QueueableJobContext, QueueableJobContract, QueueableJobFailureContext, QueueableJobRetryContext, SerializedQueueableJob } from './contracts';
|
|
11
|
+
|
|
12
|
+
/** Persisted lifecycle values; waiting includes scheduled retries, blocked requires an explicit new run. */
|
|
13
|
+
export const JOB_RUN_STATUSES = ['waiting', 'running', 'blocked', 'completed', 'failed', 'cancelled'] as const;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A retained run with application-owned input, state and behaviour. Register only
|
|
17
|
+
* this base model in migrations; concrete job classes share its table. Jobs must
|
|
18
|
+
* use checkpoint/save for durable work and make external effects idempotent.
|
|
19
|
+
* Tenant and subject identities are immutable; applications authorize launches
|
|
20
|
+
* and reads. Queue serialization contains only the saved run ID and generation.
|
|
21
|
+
*/
|
|
22
|
+
export class RunnableJob extends ActiveRecord.define({
|
|
23
|
+
table: 'job_runs',
|
|
24
|
+
fields: field => ({
|
|
25
|
+
id: field.ulid(),
|
|
26
|
+
jobType: field.string({ column: 'job_type', required: true, length: 200, index: true }),
|
|
27
|
+
tenantId: field.string({ column: 'tenant_id', required: true, length: 100, indexes: [{ name: 'job_runs_tenant_history', columns: ['tenant_id', 'id'] }] }),
|
|
28
|
+
subjectId: field.string({ column: 'subject_id', length: 100, indexes: [{ name: 'job_runs_subject_history', columns: ['subject_id', 'id'] }] }),
|
|
29
|
+
actorId: field.string({ column: 'actor_id', length: 100 }),
|
|
30
|
+
parentRunId: field.string({ column: 'parent_run_id', length: 26, index: true }),
|
|
31
|
+
trigger: field.string({ required: true, default: 'manual', length: 40 }),
|
|
32
|
+
status: field.choice({ choices: [...JOB_RUN_STATUSES], required: true, default: 'waiting', index: true }),
|
|
33
|
+
input: field.json<Record<string, unknown>>({ required: true, default: {}, comment: 'Immutable application inputs captured when the run is created.' }),
|
|
34
|
+
state: field.json<Record<string, unknown>>({ required: true, default: {}, comment: 'Mutable application checkpoint for progress, diagnosis and explicit recovery; may contain private data.' }),
|
|
35
|
+
result: field.json<Record<string, unknown>>({ comment: 'Application outcome or result summary, separate from mutable progress state.' }),
|
|
36
|
+
error: field.text(),
|
|
37
|
+
stage: field.string({ length: 200, comment: 'Job-owned current phase; lifecycle outcome is stored in status.' }),
|
|
38
|
+
completed: field.integer({ required: true, default: 0, comment: 'Job-owned processed unit count, e.g. pages scanned or 1 for one operation; not a completion flag.' }),
|
|
39
|
+
total: field.integer({ comment: 'Planned units in the same measure as completed; null when unknown.' }),
|
|
40
|
+
failures: field.integer({ required: true, default: 0, comment: 'Job-owned failed unit count; units may also be counted as completed. Job failure is recorded in status.' }),
|
|
41
|
+
attemptCount: field.integer({ column: 'attempt_count', required: true, default: 0 }),
|
|
42
|
+
deliveryKey: field.string({ column: 'delivery_key', length: 36 }),
|
|
43
|
+
leaseToken: field.string({ column: 'lease_token', length: 36 }),
|
|
44
|
+
cancelRequestedAt: field.timestamp({ column: 'cancel_requested_at' }),
|
|
45
|
+
queuedAt: field.timestamp({ column: 'queued_at' }),
|
|
46
|
+
startedAt: field.timestamp({ column: 'started_at' }),
|
|
47
|
+
heartbeatAt: field.timestamp({ column: 'heartbeat_at' }),
|
|
48
|
+
completedAt: field.timestamp({ column: 'completed_at' }),
|
|
49
|
+
createdAt: field.timestamp({ column: 'created_at', auto: 'create' }),
|
|
50
|
+
updatedAt: field.timestamp({ column: 'updated_at', auto: 'update' }),
|
|
51
|
+
}),
|
|
52
|
+
}) implements QueueableJobContract {
|
|
53
|
+
/** Stable name may be overridden to preserve queued identities across class renames. */
|
|
54
|
+
static jobName?: string;
|
|
55
|
+
static #active = new AsyncLocalStorage<RunnableJob>();
|
|
56
|
+
|
|
57
|
+
/** Returns the active run for child dispatch correlation within this asynchronous execution only. */
|
|
58
|
+
static current(): RunnableJob | undefined { return RunnableJob.#active.getStore(); }
|
|
59
|
+
#context: QueueableJobContext | null = null;
|
|
60
|
+
#token: string | null = null;
|
|
61
|
+
#updates: Promise<unknown> = Promise.resolve();
|
|
62
|
+
#stop = new AbortController();
|
|
63
|
+
#publishing = false;
|
|
64
|
+
|
|
65
|
+
/** Internal dispatch guard; a saved delivery cannot be enqueued twice through the public API. */
|
|
66
|
+
get publishingDelivery(): boolean { return this.#publishing; }
|
|
67
|
+
|
|
68
|
+
/** Cooperative abort signal. Application requests should combine it with their own deadline. */
|
|
69
|
+
get signal(): AbortSignal { return this.#stop.signal; }
|
|
70
|
+
|
|
71
|
+
/** Loads the authoritative run through its concrete registered class, rejecting mismatched deliveries. */
|
|
72
|
+
static async fromJSON<T extends typeof RunnableJob>(this: T, data: Record<string, unknown>): Promise<InstanceType<T>> {
|
|
73
|
+
if (typeof data.runId !== 'string' || typeof data.deliveryKey !== 'string') throw new Error('Invalid runnable job reference.');
|
|
74
|
+
const run = await this.findOrFail(data.runId);
|
|
75
|
+
if (run.jobType !== (this.jobName ?? this.name) || run.deliveryKey !== data.deliveryKey) throw new Error('Runnable job identity does not match its delivery.');
|
|
76
|
+
return run as InstanceType<T>;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Saves a new run before dispatch. During execution, saves only application-owned fields under the attempt fence. */
|
|
80
|
+
override async save(): Promise<this> {
|
|
81
|
+
if (!this.isPersisted()) {
|
|
82
|
+
if (this.constructor === RunnableJob) throw new Error('Create a concrete RunnableJob subclass.');
|
|
83
|
+
this.jobType = (this.constructor as typeof RunnableJob).jobName ?? this.constructor.name;
|
|
84
|
+
return super.save();
|
|
85
|
+
}
|
|
86
|
+
if (!this.#token) throw new Error('Existing run records may only be saved by their owning execution.');
|
|
87
|
+
const values = structuredClone({ state: this.state, result: this.result, stage: this.stage, completed: this.completed, total: this.total, failures: this.failures });
|
|
88
|
+
await this.#serializeUpdate(() => this.#write(values));
|
|
89
|
+
return this;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Serializes concurrent progress callbacks so state merges cannot lose another completed item's update. */
|
|
93
|
+
async checkpoint(update: (run: this) => void): Promise<void> {
|
|
94
|
+
await this.#serializeUpdate(async () => {
|
|
95
|
+
await this.assertActive();
|
|
96
|
+
update(this);
|
|
97
|
+
await this.#write({ state: this.state, result: this.result, stage: this.stage, completed: this.completed, total: this.total, failures: this.failures });
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Checks delivery ownership and operator cancellation before launching or publishing another unit of work. */
|
|
102
|
+
async assertActive(): Promise<void> {
|
|
103
|
+
this.signal.throwIfAborted();
|
|
104
|
+
await this.#context?.assertOwnership?.();
|
|
105
|
+
const row = await RunnableJob.where({ id: this.id, leaseToken: this.#token, status: 'running' }).first();
|
|
106
|
+
if (!this.#token || !row) throw new Error('Runnable job execution ownership was lost.');
|
|
107
|
+
let cancellation = Boolean(row.cancelRequestedAt);
|
|
108
|
+
let parentId = row.parentRunId;
|
|
109
|
+
const visited = new Set<string>();
|
|
110
|
+
while (!cancellation && parentId) {
|
|
111
|
+
if (visited.has(parentId) || visited.size >= 32) throw new Error('Invalid run ancestry.');
|
|
112
|
+
visited.add(parentId);
|
|
113
|
+
const parent = await RunnableJob.find(parentId);
|
|
114
|
+
cancellation = Boolean(parent?.cancelRequestedAt);
|
|
115
|
+
parentId = parent?.parentRunId ?? null;
|
|
116
|
+
}
|
|
117
|
+
if (cancellation) {
|
|
118
|
+
const reason = new JobRunStoppedError('cancelled', 'Cancelled by the operator.');
|
|
119
|
+
this.#stop.abort(reason);
|
|
120
|
+
throw reason;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Appends bounded structured diagnostics; callers must omit credentials and private provider payloads. */
|
|
125
|
+
async log(message: string, data: Record<string, unknown> = {}, level: 'info' | 'warning' | 'error' = 'info'): Promise<void> {
|
|
126
|
+
await this.assertActive();
|
|
127
|
+
if (JSON.stringify(data).length > 16000) throw new Error('Job event data exceeds 16,000 characters.');
|
|
128
|
+
await JobRunEvent.create({ runId: this.id, tenantId: this.tenantId, attempt: this.attemptCount, message: message.slice(0, 1024), data, level }).save();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Stops launching further work until an operator creates a resumed run using the saved checkpoint. */
|
|
132
|
+
block(reason: string): never { throw new JobRunStoppedError('blocked', reason); }
|
|
133
|
+
|
|
134
|
+
/** Resolves application-owned input/scope before first persistence. Override in a tenant-aware application base class. */
|
|
135
|
+
async prepare(): Promise<void> {}
|
|
136
|
+
|
|
137
|
+
/** Saves a run and its dispatch generation before delivery. Failed dispatches remain visible in history. */
|
|
138
|
+
async enqueue(queue: Queue, options: DispatchOptions = {}): Promise<QueueJobId> {
|
|
139
|
+
if (this.deliveryKey) throw new Error('This run has already been dispatched.');
|
|
140
|
+
if (!this.isPersisted()) { await this.prepare(); await this.save(); }
|
|
141
|
+
const key = randomUUID();
|
|
142
|
+
let reserved = false;
|
|
143
|
+
/** Reserves the delivery generation and publishes its small queue reference. */
|
|
144
|
+
const publish = async (): Promise<QueueJobId> => {
|
|
145
|
+
const changed = await RunnableJob.where({ id: this.id, status: 'waiting' }).whereNull('deliveryKey').patch({ deliveryKey: key, queuedAt: new Date() });
|
|
146
|
+
if (changed !== 1) throw new Error('This run has already been dispatched.');
|
|
147
|
+
reserved = true;
|
|
148
|
+
this.deliveryKey = key;
|
|
149
|
+
this.#publishing = true;
|
|
150
|
+
try { return await queue.dispatch(this, options); } finally { this.#publishing = false; }
|
|
151
|
+
};
|
|
152
|
+
try {
|
|
153
|
+
return queue.driverName === 'database'
|
|
154
|
+
? await RunnableJob.getDb().transaction(transaction => ActiveRecord.withDb(transaction, publish))
|
|
155
|
+
: await publish();
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
if (!reserved) throw error;
|
|
159
|
+
const failedDispatch = RunnableJob.where({ id: this.id, status: 'waiting' });
|
|
160
|
+
if (queue.driverName === 'database') failedDispatch.whereNull('deliveryKey'); else failedDispatch.where('deliveryKey', key);
|
|
161
|
+
await failedDispatch.patch({ status: 'failed', error: `Dispatch failed: ${error instanceof Error ? error.message : String(error)}`, completedAt: new Date() });
|
|
162
|
+
throw error;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Produces a small immutable queue reference; never copies mutable model JSON into delivery storage. */
|
|
167
|
+
serialize(): SerializedQueueableJob {
|
|
168
|
+
if (!this.isPersisted() || !this.deliveryKey) throw new Error('Call run.enqueue(queue) before queueing a runnable record.');
|
|
169
|
+
return { job: this.jobType!, displayName: this.jobType!, data: { runId: this.id, deliveryKey: this.deliveryKey } };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Concrete application jobs implement their own bounded work, checkpoints and result publication. */
|
|
173
|
+
async handle(_context: QueueableJobContext): Promise<void> { throw new Error('RunnableJob.handle must be implemented.'); }
|
|
174
|
+
|
|
175
|
+
/** Lifecycle is saved before delivery transitions; ordinary queue retry hooks are intentionally empty. */
|
|
176
|
+
async onRetry(_context: QueueableJobRetryContext): Promise<void> {}
|
|
177
|
+
/** Lifecycle is saved before delivery transitions, including terminal failure. */
|
|
178
|
+
async onFinalFailure(context: QueueableJobFailureContext): Promise<void> {
|
|
179
|
+
// A deadline can pass between handler failure and the driver's final disposition.
|
|
180
|
+
const error = context.error instanceof Error ? context.error.message : String(context.error);
|
|
181
|
+
await RunnableJob.getDb().transaction(transaction => ActiveRecord.withDb(transaction, async () => {
|
|
182
|
+
const changed = await RunnableJob.where({ id: this.id, status: 'waiting', attemptCount: this.attemptCount }).patch({ status: 'failed', error, completedAt: new Date() });
|
|
183
|
+
if (changed) {
|
|
184
|
+
await JobRunAttempt.where({ runId: this.id, attempt: this.attemptCount, status: 'retrying' }).patch({ status: 'failed', error });
|
|
185
|
+
await JobRunEvent.create({ runId: this.id, tenantId: this.tenantId, attempt: this.attemptCount, level: 'error', message: error.slice(0, 1024), data: { status: 'failed', queue: context.job.queue, deliveryId: context.job.id, jobUuid: context.job.payload.uuid, maxTries: context.job.payload.maxTries } }).save();
|
|
186
|
+
this.status = 'failed';
|
|
187
|
+
}
|
|
188
|
+
}));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Runs one fenced attempt. Expired delivery recovery preserves checkpoints and records the interrupted attempt. */
|
|
192
|
+
async execute(context: QueueableJobContext): Promise<void> {
|
|
193
|
+
await context.assertOwnership?.();
|
|
194
|
+
this.#context = context;
|
|
195
|
+
this.#updates = Promise.resolve();
|
|
196
|
+
const token = randomUUID();
|
|
197
|
+
const claimed = await RunnableJob.getDb().transaction(transaction => ActiveRecord.withDb(transaction, async () => {
|
|
198
|
+
const raw = await RunnableJob.where('id', this.id).toKnex().forUpdate().first();
|
|
199
|
+
if (!raw) throw new Error('Run no longer exists.');
|
|
200
|
+
const current = RunnableJob.fromDb(raw);
|
|
201
|
+
if (current.deliveryKey !== this.deliveryKey) throw new Error('Run delivery was replaced.');
|
|
202
|
+
if (!['waiting', 'running'].includes(current.status ?? '')) return false;
|
|
203
|
+
if (current.status === 'running') await JobRunAttempt.where({ runId: this.id, status: 'running' }).patch({ status: 'interrupted', completedAt: new Date(), error: 'Worker reservation expired before completion.' });
|
|
204
|
+
const now = new Date();
|
|
205
|
+
const values = { status: 'running' as const, leaseToken: token, attemptCount: (current.attemptCount ?? 0) + 1, startedAt: current.startedAt ?? now, heartbeatAt: now, completedAt: null, error: null };
|
|
206
|
+
await RunnableJob.where('id', this.id).patch(values);
|
|
207
|
+
this.assign({ ...current.toJSON(), ...values });
|
|
208
|
+
await JobRunAttempt.create({ runId: this.id, tenantId: this.tenantId, attempt: this.attemptCount, deliveryId: String(context.job.id), status: 'running', startedAt: now }).save();
|
|
209
|
+
return true;
|
|
210
|
+
}));
|
|
211
|
+
if (!claimed) return;
|
|
212
|
+
this.#token = token;
|
|
213
|
+
this.#stop = new AbortController();
|
|
214
|
+
let heartbeat: Promise<void> = Promise.resolve();
|
|
215
|
+
const timer = setInterval(() => {
|
|
216
|
+
heartbeat = heartbeat.then(async () => {
|
|
217
|
+
await this.assertActive();
|
|
218
|
+
await RunnableJob.where({ id: this.id, leaseToken: token }).patch({ heartbeatAt: new Date() });
|
|
219
|
+
}).catch(error => { this.#stop.abort(error); });
|
|
220
|
+
}, 10000);
|
|
221
|
+
try {
|
|
222
|
+
await this.log('Attempt started');
|
|
223
|
+
await RunnableJob.#active.run(this, () => this.handle(context));
|
|
224
|
+
await this.#updates;
|
|
225
|
+
await this.assertActive();
|
|
226
|
+
await this.#finish('completed', null);
|
|
227
|
+
} catch (error) {
|
|
228
|
+
await this.#updates.catch(() => undefined);
|
|
229
|
+
await context.assertOwnership?.();
|
|
230
|
+
const stopped = error instanceof JobRunStoppedError;
|
|
231
|
+
const retry = context.willRetry ? context.willRetry(error) : isQueueRetryLaterError(error) || !(error instanceof QueueTerminalError) && context.job.attempts < context.job.payload.maxTries;
|
|
232
|
+
const status = stopped ? error.status : retry ? 'waiting' : 'failed';
|
|
233
|
+
await this.#finish(status, error instanceof Error ? error.message : String(error));
|
|
234
|
+
if (!stopped) throw error;
|
|
235
|
+
} finally {
|
|
236
|
+
clearInterval(timer);
|
|
237
|
+
await heartbeat;
|
|
238
|
+
this.#context = null;
|
|
239
|
+
this.#token = null;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** Writes application-owned state with a database fence; unrelated runtime fields cannot be overwritten. */
|
|
244
|
+
async #write(values: Record<string, unknown>): Promise<void> {
|
|
245
|
+
await this.assertActive();
|
|
246
|
+
if (JSON.stringify(values).length > 1000000) throw new Error('Run checkpoint exceeds one megabyte. Store large evidence separately.');
|
|
247
|
+
const count = await RunnableJob.where({ id: this.id, leaseToken: this.#token, status: 'running' }).whereNull('cancelRequestedAt').patch({ ...values, heartbeatAt: new Date(), updatedAt: new Date() });
|
|
248
|
+
if (count !== 1) {
|
|
249
|
+
await this.assertActive();
|
|
250
|
+
throw new Error('Runnable job checkpoint ownership was lost.');
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** Records terminal/retry state, attempt outcome and timeline in the same transaction. */
|
|
255
|
+
async #finish(status: typeof JOB_RUN_STATUSES[number], error: string | null): Promise<void> {
|
|
256
|
+
await RunnableJob.getDb().transaction(transaction => ActiveRecord.withDb(transaction, async () => {
|
|
257
|
+
const now = new Date();
|
|
258
|
+
const changed = await RunnableJob.where({ id: this.id, leaseToken: this.#token, status: 'running' }).patch({ status, error, completedAt: status === 'waiting' ? null : now, heartbeatAt: now, leaseToken: null, updatedAt: now });
|
|
259
|
+
if (changed !== 1) throw new Error('Runnable job completion ownership was lost.');
|
|
260
|
+
await JobRunAttempt.where({ runId: this.id, attempt: this.attemptCount, status: 'running' }).patch({ status: status === 'waiting' ? 'retrying' : status, error, completedAt: now });
|
|
261
|
+
await JobRunEvent.create({ runId: this.id, tenantId: this.tenantId, attempt: this.attemptCount, level: status === 'failed' ? 'error' : error ? 'warning' : 'info', message: error?.slice(0, 1024) ?? 'Run completed', data: { status, queue: this.#context?.job.queue, deliveryId: this.#context?.job.id, jobUuid: this.#context?.job.payload.uuid, maxTries: this.#context?.job.payload.maxTries } }).save();
|
|
262
|
+
this.status = status;
|
|
263
|
+
}));
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** Queues state writes on this instance while allowing callers to observe their own failures. */
|
|
267
|
+
#serializeUpdate(action: () => Promise<void>): Promise<void> {
|
|
268
|
+
const next = this.#updates.then(action);
|
|
269
|
+
this.#updates = next;
|
|
270
|
+
return next;
|
|
271
|
+
}
|
|
272
|
+
}
|
package/src/queue/console.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { parseJsonRecord, unknownErrorMessage } from '@db3.ai/pure';
|
|
|
3
3
|
import type { Logger } from '../logging';
|
|
4
4
|
import type * as queue from './contracts';
|
|
5
5
|
import type { Queue } from './Queue';
|
|
6
|
+
import { QueueWorker } from './QueueWorker';
|
|
6
7
|
|
|
7
8
|
interface QueueConsoleLogOptions {
|
|
8
9
|
verbose?: boolean;
|
|
@@ -37,8 +38,11 @@ export interface QueueConsoleCommand {
|
|
|
37
38
|
run(context: QueueConsoleCommandContext): void | Promise<void>;
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
/** Application bootstrap, named worker selections and extra commands shared by queue CLI invocations. */
|
|
40
42
|
export interface QueueConsoleOptions {
|
|
41
43
|
app: () => QueueConsoleApp;
|
|
44
|
+
/** Checked-in worker selections selected with --pool=<name>; credentials remain in the application environment. */
|
|
45
|
+
workers?: Readonly<Record<string, queue.QueueSelection>>;
|
|
42
46
|
bootstrap?: () => void | Promise<void>;
|
|
43
47
|
commands?: QueueConsoleCommand[];
|
|
44
48
|
helpText?: string;
|
|
@@ -283,14 +287,13 @@ async function bootstrap(options: QueueConsoleOptions): Promise<void> {
|
|
|
283
287
|
await options.bootstrap?.();
|
|
284
288
|
}
|
|
285
289
|
|
|
290
|
+
/** Runs a configured worker selection, with identical admission for one-shot and polling execution. */
|
|
286
291
|
async function workQueue(
|
|
287
292
|
options: QueueConsoleOptions,
|
|
288
293
|
parsed: ParsedQueueConsoleArgs,
|
|
289
294
|
): Promise<void> {
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
|| process.env.QUEUE_NAME
|
|
293
|
-
|| 'default';
|
|
295
|
+
const selection = queueConsoleSelection(parsed, options.workers);
|
|
296
|
+
const queue = selection.queues === '*' ? '*' : selection.queues?.join(', ') ?? 'default';
|
|
294
297
|
const once = queueConsoleOptionBoolean(parsed, 'once');
|
|
295
298
|
const verbose = queueConsoleVerbose(parsed);
|
|
296
299
|
|
|
@@ -301,30 +304,32 @@ async function workQueue(
|
|
|
301
304
|
const app = options.app();
|
|
302
305
|
|
|
303
306
|
if (once) {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
+
try {
|
|
308
|
+
if (verbose) {
|
|
309
|
+
console.log(`[queue] Checking "${queue}" once.`);
|
|
310
|
+
}
|
|
307
311
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
312
|
+
const result = await new QueueWorker(app.queue, selection).workOnce({
|
|
313
|
+
onClaimed: job => logQueueClaimed(job, {
|
|
314
|
+
verbose,
|
|
315
|
+
}),
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
if (!result) {
|
|
319
|
+
console.log(`[queue] No jobs available on "${queue}".`);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
313
322
|
|
|
314
|
-
|
|
315
|
-
|
|
323
|
+
logQueueProcessResult(result, {
|
|
324
|
+
verbose,
|
|
325
|
+
});
|
|
326
|
+
} finally {
|
|
316
327
|
await app.close();
|
|
317
|
-
return;
|
|
318
328
|
}
|
|
319
|
-
|
|
320
|
-
logQueueProcessResult(result, {
|
|
321
|
-
verbose,
|
|
322
|
-
});
|
|
323
|
-
await app.close();
|
|
324
329
|
return;
|
|
325
330
|
}
|
|
326
331
|
|
|
327
|
-
const worker = app.queue.startWorker(
|
|
332
|
+
const worker = app.queue.startWorker(selection, {
|
|
328
333
|
force: true,
|
|
329
334
|
logger: app.log?.child({
|
|
330
335
|
component: 'queue-worker',
|
|
@@ -357,6 +362,31 @@ async function workQueue(
|
|
|
357
362
|
});
|
|
358
363
|
}
|
|
359
364
|
|
|
365
|
+
/**
|
|
366
|
+
* Resolves one worker selection from explicit CLI flags or a checked-in named pool.
|
|
367
|
+
* Pool admission cannot be silently broadened by combining it with queue flags.
|
|
368
|
+
*/
|
|
369
|
+
export function queueConsoleSelection(parsed: ParsedQueueConsoleArgs, workers: QueueConsoleOptions['workers'] = {}): queue.QueueSelection {
|
|
370
|
+
for (const flag of ['pool', 'queues', 'queue', 'exclude-queues']) {
|
|
371
|
+
if (Object.hasOwn(parsed.options, flag) && queueConsoleOptionString(parsed, flag) === undefined) throw new Error(`--${flag} requires a non-empty value.`);
|
|
372
|
+
}
|
|
373
|
+
const pool = queueConsoleOptionString(parsed, 'pool');
|
|
374
|
+
const queues = queueConsoleOptionString(parsed, 'queues');
|
|
375
|
+
const single = queueConsoleOptionString(parsed, 'queue') || parsed.args[0];
|
|
376
|
+
const excluded = queueConsoleOptionString(parsed, 'exclude-queues');
|
|
377
|
+
if (pool) {
|
|
378
|
+
if (queues !== undefined || single !== undefined || excluded !== undefined) throw new Error('--pool cannot be combined with queue selection flags.');
|
|
379
|
+
if (!Object.hasOwn(workers, pool)) throw new Error(`Unknown worker pool "${pool}".`);
|
|
380
|
+
return workers[pool]!;
|
|
381
|
+
}
|
|
382
|
+
if (queues !== undefined && single !== undefined) throw new Error('Use --queues or --queue, not both.');
|
|
383
|
+
const names = queues ?? single ?? process.env.QUEUE_NAME ?? 'default';
|
|
384
|
+
return {
|
|
385
|
+
queues: names === '*' ? '*' : names.split(',').map(name => name.trim()),
|
|
386
|
+
excludeQueues: excluded === undefined ? [] : excluded.split(',').map(name => name.trim()),
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
|
|
360
390
|
async function dispatchQueueJob(
|
|
361
391
|
app: QueueConsoleApp,
|
|
362
392
|
parsed: ParsedQueueConsoleArgs,
|
|
@@ -429,6 +459,9 @@ function defaultHelpText(): string {
|
|
|
429
459
|
|
|
430
460
|
Options:
|
|
431
461
|
--queue=<name> Queue name, defaults to "default".
|
|
462
|
+
--queues=<names|*> Worker queue names, comma-separated; quote '*'.
|
|
463
|
+
--exclude-queues=<names> Exact queue names this worker cannot claim.
|
|
464
|
+
--pool=<name> Checked-in worker selection; cannot combine with queue flags.
|
|
432
465
|
--delay=<seconds> Delay before the job is available.
|
|
433
466
|
--tries=<count> Maximum attempts before jobs_failed.
|
|
434
467
|
--once Process one available job, then exit.
|
|
@@ -50,9 +50,18 @@ export interface QueueFailedJob {
|
|
|
50
50
|
* recording. Queue owns handler execution and retry decisions.
|
|
51
51
|
*/
|
|
52
52
|
export interface QueueDriver {
|
|
53
|
+
/** Inspects pending, delayed, reserved and chained work for maintenance; callers must quiesce producers first. */
|
|
54
|
+
hasPendingJobs?(jobPrefix: string): Promise<boolean>;
|
|
53
55
|
/** Driver name stored on failed job records and shown in diagnostics. */
|
|
54
56
|
readonly name: QueueDriverName | string;
|
|
55
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Discovers names with pending, delayed or reserved work in this driver's namespace.
|
|
60
|
+
* Required for wildcard workers; explicit selections do not need discovery.
|
|
61
|
+
* Returned names may disappear before a claim, which must still be atomic.
|
|
62
|
+
*/
|
|
63
|
+
queueNames?(): Promise<string[]>;
|
|
64
|
+
|
|
56
65
|
/**
|
|
57
66
|
* Persists a job so a worker can claim it later.
|
|
58
67
|
*
|
|
@@ -89,7 +89,7 @@ export interface DispatchOptions {
|
|
|
89
89
|
/**
|
|
90
90
|
* Named queue/channel the job should be pushed onto.
|
|
91
91
|
*
|
|
92
|
-
* Workers
|
|
92
|
+
* Workers select eligible named queues, so this lets callers route
|
|
93
93
|
* different classes of work to different worker pools. When omitted, the
|
|
94
94
|
* queue service uses its configured default queue name.
|
|
95
95
|
*/
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Named queues a worker may consume. Selection never changes a job's queue.
|
|
3
|
+
* Wildcards discover queues within the configured driver connection/namespace;
|
|
4
|
+
* exclusions always win. Names are exact, with no glob or job-class matching.
|
|
5
|
+
*/
|
|
6
|
+
export interface QueueSelection {
|
|
7
|
+
/** Exact queue names, or all currently discoverable names. Defaults to the configured default queue. */
|
|
8
|
+
queues?: readonly string[] | '*';
|
|
9
|
+
/** Exact names that this worker must never claim, including when using '*'. */
|
|
10
|
+
excludeQueues?: readonly string[];
|
|
11
|
+
}
|