@stacksjs/defaults 0.75.484 → 0.75.487

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/ai/skills/stacks-actions/SKILL.md +1 -1
  2. package/ai/skills/stacks-auto-imports/SKILL.md +1 -1
  3. package/ai/skills/stacks-dashboard/SKILL.md +33 -2
  4. package/ai/skills/stacks-database/SKILL.md +1 -1
  5. package/ai/skills/stacks-error-handling/SKILL.md +8 -0
  6. package/ai/skills/stacks-migrations/SKILL.md +2 -2
  7. package/ai/skills/stacks-models/SKILL.md +2 -2
  8. package/ai/skills/stacks-native/CATALOG.md +3 -3
  9. package/ai/skills/stacks-orm/SKILL.md +2 -2
  10. package/app/Actions/Dashboard/Content/CategoryDestroyAction.ts +2 -2
  11. package/app/Actions/Dashboard/Content/CategoryIndexAction.ts +2 -13
  12. package/app/Actions/Dashboard/Content/CategoryStoreAction.ts +5 -11
  13. package/app/Actions/Dashboard/Content/ContentDashboardAction.ts +1 -1
  14. package/app/Actions/Dashboard/Content/PostIndexAction.ts +1 -1
  15. package/app/Actions/Dashboard/Content/post-input.ts +1 -1
  16. package/app/Actions/Dashboard/Jobs/JobDefinitionsAction.ts +26 -0
  17. package/app/Actions/Dashboard/Jobs/JobStatsAction.ts +2 -2
  18. package/app/Actions/Dashboard/Models/ModelShowAction.ts +1 -11
  19. package/app/Actions/Dashboard/Models/ModelsIndexAction.ts +9 -41
  20. package/app/Actions/Dashboard/Queue/QueueStatsAction.ts +5 -7
  21. package/app/Actions/Dashboard/Queue/QueueWorkersAction.ts +6 -6
  22. package/app/Actions/Dashboard/Queue/queue-stats.ts +5 -1
  23. package/app/Actions/Dashboard/Search/GlobalSearchAction.ts +47 -89
  24. package/app/Actions/Dashboard/Search/SearchEngineIndexAction.ts +59 -0
  25. package/app/Models/QueueWorker.ts +17 -0
  26. package/functions/jobs.ts +14 -0
  27. package/functions/search-engine.ts +13 -0
  28. package/ide/vscode/package.json +1 -1
  29. package/package.json +2 -2
  30. package/resources/components/Dashboard/Jobs/JobDashboard.stx +24 -5
  31. package/resources/components/Dashboard/Queue/QueueDashboard.stx +2 -3
  32. package/resources/components/Dashboard/Search/SearchEngineDashboard.stx +58 -0
  33. package/resources/functions/dashboard/model-columns.ts +12 -0
  34. package/resources/functions/dashboard/sidebar.ts +4 -2
  35. package/routes/dashboard-api.ts +178 -166
  36. package/views/dashboard/search-engine/index.stx +8 -0
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: stacks-actions
3
- description: Use when working with Stacks server actions - creating actions in app/Actions/, auto-generated API actions from the useApi model trait, the 683 default framework actions (auth, dashboard, commerce, content, deployment, jobs), action request/response handling, or action registration. Covers @stacksjs/actions and storage/framework/defaults/app/Actions/.
3
+ description: Use when working with Stacks server actions - creating actions in app/Actions/, auto-generated API actions from the useApi model trait, the 685 default framework actions (auth, dashboard, commerce, content, deployment, jobs), action request/response handling, or action registration. Covers @stacksjs/actions and storage/framework/defaults/app/Actions/.
4
4
  license: MIT
5
5
  compatibility: Bun >= 1.3.0, TypeScript
6
6
  allowed-tools: Read Edit Write Bash Grep Glob
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: stacks-auto-imports
3
- description: Use before adding imports in Stacks app code, checking available runtime functions, browser/server delivery, model globals, or registry generation. Covers Stacks auto-imports, the generated runtime inventory, stx client delivery and server boot (107 models).
3
+ description: Use before adding imports in Stacks app code, checking available runtime functions, browser/server delivery, model globals, or registry generation. Covers Stacks auto-imports, the generated runtime inventory, stx client delivery and server boot (108 models).
4
4
  license: MIT
5
5
  compatibility: Bun >= 1.3.0, TypeScript
6
6
  allowed-tools: Read Edit Write Bash Grep Glob
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: stacks-dashboard
3
- description: Use when building or customizing the Stacks admin dashboard, including dashboard pages, model management views, analytics widgets, commerce dashboards, content management, settings panels, deployment monitoring, job/queue management, or the 414 built-in dashboard components. Covers the dashboard system at storage/framework/defaults/.
3
+ description: Use when building or customizing the Stacks admin dashboard, including dashboard pages, model management views, analytics widgets, commerce dashboards, content management, settings panels, deployment monitoring, job/queue management, or the 415 built-in dashboard components. Covers the dashboard system at storage/framework/defaults/.
4
4
  license: MIT
5
5
  compatibility: Bun >= 1.3.0, TypeScript
6
6
  allowed-tools: Read Edit Write Bash Grep Glob
@@ -8,7 +8,7 @@ allowed-tools: Read Edit Write Bash Grep Glob
8
8
 
9
9
  # Stacks Dashboard
10
10
 
11
- The Stacks admin dashboard provides a full-featured admin panel with 100+ route views, 414 components, and a multi-section layout.
11
+ The Stacks admin dashboard provides a full-featured admin panel with 100+ route views, 415 components, and a multi-section layout.
12
12
 
13
13
  ## Key Paths
14
14
  - Dashboard components: `storage/framework/defaults/resources/components/Dashboard/`
@@ -684,3 +684,34 @@ or separate routes generated by `useApi`. Configure `security.api.models` or
684
684
  `STACKS_MODEL_APIS`, middleware, ownership and policies for those APIs. The
685
685
  new boundary does not provide row ownership or permission checks for models
686
686
  that remain exposed.
687
+
688
+
689
+ ## Application observability
690
+
691
+ The sidebar, model catalog and global search use the database's native model
692
+ scope: application overrides and discovered package models, with framework
693
+ models as a fallback. Global search uses the configured ORM connection on
694
+ PostgreSQL, SQLite and MySQL. Only stored, visible fields from `useSearch`
695
+ are queried; projected search-document fields are not assumed to be SQL columns.
696
+
697
+ `/search-engine` inspects the configured driver, provider indexes and model
698
+ `useSearch` settings, including custom index names. Provider failure leaves the
699
+ model inventory visible with an unavailable state. It never reindexes on a read.
700
+ `/jobs` discovers application definitions even before they have queued rows.
701
+ Definition import failures are operational failures, not a successful empty list.
702
+
703
+ Queue workers publish snapshots to the model-backed `queue_workers` table every
704
+ 10 seconds and on graceful exit. Publish `QueueWorker` into an application's
705
+ model scope and generate/migrate its schema before using worker metrics. The
706
+ dashboard reads snapshots across processes. A heartbeat older than 60 seconds
707
+ marks the worker stopped and clears live throughput; sessions are retained for
708
+ seven days. Completion/failure totals describe those retained worker sessions,
709
+ not a durable per-job completion history. Queues containing only failed jobs
710
+ stay visible. Database queue rows are authoritative for pending/reserved work.
711
+
712
+ The local error page reads `errors`. An Error model does not capture exceptions
713
+ by itself: configure `createDatabaseErrorReporter()` from
714
+ `@stacksjs/database/error-reporter` in `monitoring.reporters`, publish
715
+ `Error`, and migrate it. Reporting is bounded, buffered, identity-deduplicated
716
+ by `captureError`, and flushed by `flushErrorReporters`/the logger. The database
717
+ reporter stores allowlisted operational context and excludes bodies and headers.
@@ -12,7 +12,7 @@ allowed-tools: Read Edit Write Bash Grep Glob
12
12
  - Database package: `storage/framework/core/database/src/`
13
13
  - Configuration: `config/database.ts`
14
14
  - QB config: `config/query-builder.ts`
15
- - Migrations: `database/migrations/` (247 migration files, `.sql` format)
15
+ - Migrations: `database/migrations/` (248 migration files, `.sql` format)
16
16
  - QB state: `.qb/`
17
17
  - ORM: `storage/framework/orm/`
18
18
 
@@ -82,3 +82,11 @@ http.ts, error-page.ts, reporters.ts, bughq.ts and unique-violation.ts.
82
82
  Tests: result-type.test.ts, http-errors.test.ts, error-page-render.test.ts,
83
83
  custom-error-pages.test.ts and reporters.test.ts under core/error-handling/tests.
84
84
  Keep error payloads and credentials out of public responses and diagnostics.
85
+
86
+
87
+ For the local dashboard error store, add `createDatabaseErrorReporter()` from
88
+ `@stacksjs/database/error-reporter` to `monitoring.reporters`. Publish
89
+ and migrate the native Error model in an application with its own schema.
90
+ The bounded reporter buffers at most 100 writes, isolates storage failures,
91
+ stores only allowlisted operational context, and drains through
92
+ `flushErrorReporters()`. Remote reporters may coexist with the local store.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: stacks-migrations
3
- description: Use when working with database migrations in a Stacks application - creating migration files, running migrations, fresh migration (drop + recreate), seeding after migration, migration file naming conventions, or the 247 built-in migration files. For the database API itself (queries, connections, SQL helpers), see stacks-database.
3
+ description: Use when working with database migrations in a Stacks application - creating migration files, running migrations, fresh migration (drop + recreate), seeding after migration, migration file naming conventions, or the 248 built-in migration files. For the database API itself (queries, connections, SQL helpers), see stacks-database.
4
4
  license: MIT
5
5
  compatibility: Bun >= 1.3.0, TypeScript, SQLite >= 3.47.2
6
6
  allowed-tools: Read Edit Write Bash Grep Glob
@@ -236,7 +236,7 @@ lossless widenings; reports everything destructive), then
236
236
  - Do not commit a second snapshot under `.qb/`; that indicates a missing `snapshotDir` configuration
237
237
  - If a generated SQLite migration rebuilds tables, test it against a copy of the current database and run `PRAGMA integrity_check` plus `PRAGMA foreign_key_check`
238
238
  - `--seed` flag after `migrate:fresh` seeds the database with factory data
239
- - 247 migration files exist by default for all framework models
239
+ - 248 migration files exist by default for all framework models
240
240
  - SQLite >= 3.47.2 is required (system requirement)
241
241
  - For the database API (queries, connections), see the `stacks-database` skill
242
242
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: stacks-models
3
- description: Use when defining or extending Stacks models, deriving migrations and CRUD, configuring attributes/relationships/traits/ownership, or preserving model inference. Covers defineModel, extendModel, model types, native data workflows, and the 107 built-in framework models.
3
+ description: Use when defining or extending Stacks models, deriving migrations and CRUD, configuring attributes/relationships/traits/ownership, or preserving model inference. Covers defineModel, extendModel, model types, native data workflows, and the 108 built-in framework models.
4
4
  license: MIT
5
5
  compatibility: Bun >= 1.3.0, TypeScript, SQLite >= 3.47.2
6
6
  allowed-tools: Read Edit Write Bash Grep Glob
@@ -420,7 +420,7 @@ honoured: fewer rows are seeded rather than a duplicate combination.
420
420
  ## Built-in models by category
421
421
 
422
422
  The ones below are worth knowing by name. They are a selection, not the set:
423
- `storage/framework/defaults/app/Models/` holds 107, and that directory is the
423
+ `storage/framework/defaults/app/Models/` holds 108, and that directory is the
424
424
  authority. This section said "All 62 built-in models by category" while
425
425
  listing fewer than that against 102 on disk, so an agent reading to the end
426
426
  had no way to tell it was short.
@@ -637,7 +637,7 @@ Evidence directory: [10 retained tests](https://github.com/stacksjs/stacks/blob/
637
637
 
638
638
  Guidance: [`stacks-orm`](../stacks-orm/SKILL.md). Source: [storage/framework/core/orm/src/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/index.ts).
639
639
 
640
- Declared runtime exports: `Activity`, `AnalyticsEvent`, `Auction`, `AuctionItem`, `Author`, `Automation`, `AutomationRun`, `BILLABLE_INSTANCE_METHODS`, `Bid`, `Board`, `BoardColumn`, `Campaign`, `CampaignSend`, `CampaignVariant`, `Card`, `CardAttachment`, `CardComment`, `Cart`, `CartItem`, `Categorizable`, `Category`, `Comment`, `CommunicationSuppression`, `ConsentEvent`, `Coupon`, `Courier`, `CourierPing`, `Customer`, `DeliveryRoute`, `DeliveryStop`, `Deployment`, `DigitalDelivery`, `EmailIdempotency`, `EmailList`, `EmailListSubscriber`, `EmailSuppression`, `EmailWebhookEvent`, `ErrorModel`, `FailedJob`, `FeedbackToken`, `Form`, `FormField`, `FormSubmission`, `GdprRequest`, `GiftCard`, `Job`, `Label`, `LicenseKey`, `Log`, `LoyaltyPoint`, `LoyaltyReward`, `MagicLinkToken`, `MailPreference`, `Manufacturer`, `Menu`, `MenuItem`, `ModelUnavailableError`, `Notification`, `NotificationDelivery`, `Order`, `OrderIdempotency`, `OrderItem`, `Page`, `PageRevision`, `Payment`, `PaymentMethod`, `PaymentProduct`, `PaymentTransaction`, `PaymentWebhookEvent`, `PersonalAccessToken`, `Pledge`, `Post`, `PrintDevice`, `Product`, `ProductUnit`, `ProductVariant`, `QueryLog`, `Receipt`, `Redirect`, `Referral`, `ReferralCode`, `Release`, `Request`, `Review`, `SenderDomain`, `ShippingMethod`, `ShippingRate`, `ShippingZone`, `Site`, `SiteDomain`, `SmsOptOut`, `SocialAccount`, `SocialPost`, `StorageItem`, `StorageItemTask`, `Subscriber`, `SubscriberEmail`, `Subscription`, `SubscriptionNotOwnedError`, `Tag`, `TaxRate`, `Team`, `TeamInvitation`, `TeamMember`, `Transaction`, `UsageEvent`, `User`, `WaitlistProduct`, `WaitlistRestaurant`, `Websocket`, `buildDatabaseSchema`, `collectEncryptedAttributes`, `createAuditMethods`, `createCategorizableMethods`, `createCommentableMethods`, `createLikeableMethods`, `createTaggableMethods`, `customerOwnership`, `decryptValue`, `encryptValue`, `enrichPaginatorUrls`, `filterFillable`, `frameworkModelFeature`, `getWritableFields`, `isBillable`, `isCursorPaginator`, `isEncrypted`, `isPaginator`, `isSimplePaginator`, `isUniqueViolation`, `mapWriteError`, `modelGlobalNames`, `ormReady`, `parentOwnership`, `parseCursor`, `renderDatabaseTypeFile`, `resolveCursorArgs`, `resolvePageArgs`, `selfOwnership`, `setAuditUser`, `siteOwnership`, `teamMembershipOwnership`, `toCursorPaginator`, `toPaginator`, `toSimplePaginator`, `toSnakeCaseKeys`.
640
+ Declared runtime exports: `Activity`, `AnalyticsEvent`, `Auction`, `AuctionItem`, `Author`, `Automation`, `AutomationRun`, `BILLABLE_INSTANCE_METHODS`, `Bid`, `Board`, `BoardColumn`, `Campaign`, `CampaignSend`, `CampaignVariant`, `Card`, `CardAttachment`, `CardComment`, `Cart`, `CartItem`, `Categorizable`, `Category`, `Comment`, `CommunicationSuppression`, `ConsentEvent`, `Coupon`, `Courier`, `CourierPing`, `Customer`, `DeliveryRoute`, `DeliveryStop`, `Deployment`, `DigitalDelivery`, `EmailIdempotency`, `EmailList`, `EmailListSubscriber`, `EmailSuppression`, `EmailWebhookEvent`, `ErrorModel`, `FailedJob`, `FeedbackToken`, `Form`, `FormField`, `FormSubmission`, `GdprRequest`, `GiftCard`, `Job`, `Label`, `LicenseKey`, `Log`, `LoyaltyPoint`, `LoyaltyReward`, `MagicLinkToken`, `MailPreference`, `Manufacturer`, `Menu`, `MenuItem`, `ModelUnavailableError`, `Notification`, `NotificationDelivery`, `Order`, `OrderIdempotency`, `OrderItem`, `Page`, `PageRevision`, `Payment`, `PaymentMethod`, `PaymentProduct`, `PaymentTransaction`, `PaymentWebhookEvent`, `PersonalAccessToken`, `Pledge`, `Post`, `PrintDevice`, `Product`, `ProductUnit`, `ProductVariant`, `QueryLog`, `QueueWorker`, `Receipt`, `Redirect`, `Referral`, `ReferralCode`, `Release`, `Request`, `Review`, `SenderDomain`, `ShippingMethod`, `ShippingRate`, `ShippingZone`, `Site`, `SiteDomain`, `SmsOptOut`, `SocialAccount`, `SocialPost`, `StorageItem`, `StorageItemTask`, `Subscriber`, `SubscriberEmail`, `Subscription`, `SubscriptionNotOwnedError`, `Tag`, `TaxRate`, `Team`, `TeamInvitation`, `TeamMember`, `Transaction`, `UsageEvent`, `User`, `WaitlistProduct`, `WaitlistRestaurant`, `Websocket`, `buildDatabaseSchema`, `collectEncryptedAttributes`, `createAuditMethods`, `createCategorizableMethods`, `createCommentableMethods`, `createLikeableMethods`, `createTaggableMethods`, `customerOwnership`, `decryptValue`, `encryptValue`, `enrichPaginatorUrls`, `filterFillable`, `frameworkModelFeature`, `getWritableFields`, `isBillable`, `isCursorPaginator`, `isEncrypted`, `isPaginator`, `isSimplePaginator`, `isUniqueViolation`, `mapWriteError`, `modelGlobalNames`, `ormReady`, `parentOwnership`, `parseCursor`, `renderDatabaseTypeFile`, `resolveCursorArgs`, `resolvePageArgs`, `selfOwnership`, `setAuditUser`, `siteOwnership`, `teamMembershipOwnership`, `toCursorPaginator`, `toPaginator`, `toSimplePaginator`, `toSnakeCaseKeys`.
641
641
 
642
642
  Local source modules: [src/auto-crud.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/auto-crud.ts), [src/batch-loader.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/batch-loader.ts), [src/db.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/db.ts), [src/define-model.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/define-model.ts), [src/extend-model.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/extend-model.ts), [src/gdpr/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/gdpr/index.ts), [src/generate-database-schema.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/generate-database-schema.ts), [src/locked-row.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/locked-row.ts), [src/messaging.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/messaging.ts), [src/model-types.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/model-types.ts), [src/ownership.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/ownership.ts), [src/paginator-request.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/paginator-request.ts), [src/row-token.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/row-token.ts), [src/subquery.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/subquery.ts), [src/traits/audit.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/traits/audit.ts), [src/traits/billable.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/traits/billable.ts), [src/traits/categorizable.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/traits/categorizable.ts), [src/traits/commentable.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/traits/commentable.ts), [src/traits/likeable.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/traits/likeable.ts), [src/traits/taggable.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/traits/taggable.ts), [src/transaction.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/transaction.ts), [src/types.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/types.ts), [src/utils/encrypted.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/utils/encrypted.ts), [src/utils/prunable.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/utils/prunable.ts).
643
643
 
@@ -695,9 +695,9 @@ Evidence directory: [7 retained tests](https://github.com/stacksjs/stacks/blob/m
695
695
 
696
696
  Guidance: [`stacks-queue`](../stacks-queue/SKILL.md). Source: [storage/framework/core/queue/src/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/index.ts).
697
697
 
698
- Declared runtime exports: `Batch`, `DispatchedBatch`, `FailedJobNotifier`, `JOB_ENVELOPE_VERSION`, `Job`, `Jobs`, `OnQueueEvent`, `PendingBatch`, `QueueEvents`, `QueueMetrics`, `QueueTester`, `assertEnvelopeSerializable`, `cancelJob`, `checkQueueHealth`, `claimDispatchKey`, `clearEnvelopeWarnings`, `clearJobState`, `configureFailedJobNotifications`, `createEnvelope`, `createHealthCheckHandler`, `createQueueTester`, `discoverJobs`, `emitQueueEvent`, `executeFailedJobs`, `executeJob`, `expectJobToFail`, `fake`, `getActionRunner`, `getActiveJobCount`, `getAllJobs`, `getBatchCallbacks`, `getFailedJobNotifier`, `getFakeQueue`, `getGlobalMetrics`, `getJob`, `getJobProgress`, `getQueueEvents`, `getRedisQueue`, `getRegisteredJobs`, `getScheduledJobs`, `getSchedulerStatus`, `getWorkerTracker`, `hasDispatchedKey`, `hashPayload`, `isBatchCancelled`, `isCircuitOpen`, `isFaked`, `isJobCancelled`, `isQuarantined`, `isQueueHealthy`, `isSchedulerRunning`, `isWorkerRunning`, `job`, `jobBatch`, `jobRegistry`, `listCircuitState`, `listDeadLetterJobs`, `listQuarantined`, `moveToDeadLetter`, `notifyJobFailed`, `onQueueEvent`, `parseEnvelope`, `pauseQueue`, `purgeDeadLetterJobs`, `quarantineJob`, `queuedJobState`, `recordBatchJobCompletion`, `recordBatchJobFailure`, `recordCircuitFailure`, `recordCircuitSuccess`, `recordDispatchedKey`, `recordFailureForPoison`, `releaseDispatchKey`, `resolveQueueDriver`, `restore`, `resumeQueue`, `retryDeadLetterJob`, `retryFailedJob`, `runJob`, `runTestJob`, `serializeEnvelope`, `setActionRunner`, `setJobProgress`, `startProcessor`, `startScheduler`, `stopProcessor`, `stopScheduler`, `toJobOptions`, `triggerJob`, `unquarantineJob`, `withEvents`.
698
+ Declared runtime exports: `Batch`, `DispatchedBatch`, `FailedJobNotifier`, `JOB_ENVELOPE_VERSION`, `Job`, `Jobs`, `OnQueueEvent`, `PendingBatch`, `QueueEvents`, `QueueMetrics`, `QueueTester`, `aggregateWorkerMetrics`, `assertEnvelopeSerializable`, `cancelJob`, `checkQueueHealth`, `claimDispatchKey`, `clearEnvelopeWarnings`, `clearJobState`, `configureFailedJobNotifications`, `createEnvelope`, `createHealthCheckHandler`, `createQueueTester`, `discoverJobs`, `emitQueueEvent`, `executeFailedJobs`, `executeJob`, `expectJobToFail`, `fake`, `getActionRunner`, `getActiveJobCount`, `getAllJobs`, `getBatchCallbacks`, `getFailedJobNotifier`, `getFakeQueue`, `getGlobalMetrics`, `getJob`, `getJobProgress`, `getQueueEvents`, `getRedisQueue`, `getRegisteredJobs`, `getScheduledJobs`, `getSchedulerStatus`, `getWorkerTracker`, `hasDispatchedKey`, `hashPayload`, `isBatchCancelled`, `isCircuitOpen`, `isFaked`, `isJobCancelled`, `isQuarantined`, `isQueueHealthy`, `isSchedulerRunning`, `isWorkerRunning`, `job`, `jobBatch`, `jobRegistry`, `listCircuitState`, `listDeadLetterJobs`, `listQuarantined`, `moveToDeadLetter`, `notifyJobFailed`, `onQueueEvent`, `parseEnvelope`, `pauseQueue`, `purgeDeadLetterJobs`, `quarantineJob`, `queuedJobState`, `readWorkerSnapshots`, `recordBatchJobCompletion`, `recordBatchJobFailure`, `recordCircuitFailure`, `recordCircuitSuccess`, `recordDispatchedKey`, `recordFailureForPoison`, `releaseDispatchKey`, `resolveQueueDriver`, `restore`, `resumeQueue`, `retryDeadLetterJob`, `retryFailedJob`, `runJob`, `runTestJob`, `serializeEnvelope`, `setActionRunner`, `setJobProgress`, `startProcessor`, `startScheduler`, `stopProcessor`, `stopScheduler`, `toJobOptions`, `triggerJob`, `unquarantineJob`, `withEvents`.
699
699
 
700
- Local source modules: [src/action-runner.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/action-runner.ts), [src/action.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/action.ts), [src/batch.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/batch.ts), [src/bun-queue.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/bun-queue.ts), [src/circuit-breaker.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/circuit-breaker.ts), [src/dead-letter.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/dead-letter.ts), [src/discovery.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/discovery.ts), [src/driver.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/driver.ts), [src/drivers/redis.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/drivers/redis.ts), [src/envelope.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/envelope.ts), [src/events.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/events.ts), [src/health.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/health.ts), [src/idempotency.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/idempotency.ts), [src/job-progress.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/job-progress.ts), [src/job.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/job.ts), [src/notifications.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/notifications.ts), [src/poison.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/poison.ts), [src/scheduler.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/scheduler.ts), [src/testing.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/testing.ts), [src/worker.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/worker.ts).
700
+ Local source modules: [src/action-runner.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/action-runner.ts), [src/action.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/action.ts), [src/batch.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/batch.ts), [src/bun-queue.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/bun-queue.ts), [src/circuit-breaker.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/circuit-breaker.ts), [src/dead-letter.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/dead-letter.ts), [src/discovery.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/discovery.ts), [src/driver.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/driver.ts), [src/drivers/redis.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/drivers/redis.ts), [src/envelope.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/envelope.ts), [src/events.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/events.ts), [src/health.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/health.ts), [src/idempotency.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/idempotency.ts), [src/job-progress.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/job-progress.ts), [src/job.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/job.ts), [src/notifications.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/notifications.ts), [src/poison.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/poison.ts), [src/scheduler.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/scheduler.ts), [src/testing.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/testing.ts), [src/worker-monitor.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/worker-monitor.ts), [src/worker.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/src/worker.ts).
701
701
 
702
702
  Evidence directory: [56 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/queue/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
703
703
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: stacks-orm
3
- description: Use when querying or writing Stacks models, using transactions and relationships, preserving inferred model types, or enabling native model traits. Covers @stacksjs/orm, the model runtime, and 107 built-in models.
3
+ description: Use when querying or writing Stacks models, using transactions and relationships, preserving inferred model types, or enabling native model traits. Covers @stacksjs/orm, the model runtime, and 108 built-in models.
4
4
  license: MIT
5
5
  compatibility: Bun >= 1.3.0, TypeScript
6
6
  allowed-tools: Read Edit Write Bash Grep Glob
@@ -10,7 +10,7 @@ allowed-tools: Read Edit Write Bash Grep Glob
10
10
 
11
11
  `defineModel` produces an immediately usable query surface over
12
12
  `bun-query-builder`. Model-driven migrations change the schema; there are no
13
- per-model ORM classes to generate. The framework declares 107 models on disk,
13
+ per-model ORM classes to generate. The framework declares 108 models on disk,
14
14
  but optional feature gates determine which built-ins load in a running app.
15
15
 
16
16
  ## Definitions and typed rows
@@ -25,7 +25,7 @@ export default new Action({
25
25
  try {
26
26
  const deleted = await transaction(async (rawTrx) => {
27
27
  const trx = rawTrx as unknown as typeof db
28
- const category = await trx.selectFrom('categories').select(['id']).where('id', '=', id).executeTakeFirst()
28
+ const category = await trx.selectFrom('categorizables').where('categorizable_type', '=', 'posts').select(['id']).where('id', '=', id).executeTakeFirst()
29
29
  if (!category)
30
30
  return false
31
31
 
@@ -34,7 +34,7 @@ export default new Action({
34
34
  .where('category_id', '=', id)
35
35
  .where('categorizable_type', '=', 'posts')
36
36
  .execute()
37
- await trx.deleteFrom('categories').where('id', '=', id).execute()
37
+ await trx.deleteFrom('categorizables').where('categorizable_type', '=', 'posts').where('id', '=', id).execute()
38
38
  return true
39
39
  })
40
40
 
@@ -12,18 +12,7 @@ interface CategoryRow {
12
12
  updated_at: string | null
13
13
  }
14
14
 
15
- /**
16
- * `GET /api/dashboard/categories` — backs `views/dashboard/content/categories/index.stx`.
17
- *
18
- * Reads the `categories` table via `db`. The previous `Category.all()` call
19
- * threw on every request (the ORM model exposes no query methods) and the catch
20
- * turned that into an empty list, so a broken read was indistinguishable from a
21
- * CMS with no categories. It also mapped `parent_id` and `post_count`, neither
22
- * of which this table has.
23
- *
24
- * Note this is the same `categories` table the commerce `Category` model owns —
25
- * CMS and commerce categories are not separated in the schema today.
26
- */
15
+ /** The CMS catalog owns categorizables; commerce's categories have a separate id space. */
27
16
  export default new Action({
28
17
  name: 'CategoryIndexAction',
29
18
  description: 'Returns CMS categories for the dashboard.',
@@ -32,7 +21,7 @@ export default new Action({
32
21
  async handle() {
33
22
  try {
34
23
  const rows = await db
35
- .selectFrom('categories')
24
+ .selectFrom('categorizables').where('categorizable_type', '=', 'posts')
36
25
  .selectAll()
37
26
  .orderBy('created_at', 'desc')
38
27
  .execute() as unknown as CategoryRow[]
@@ -3,7 +3,6 @@ import { Action } from '@stacksjs/actions/runtime'
3
3
  import { db } from '@stacksjs/database/runtime'
4
4
  import { transaction } from '@stacksjs/orm'
5
5
  import { response } from '@stacksjs/router'
6
- import { randomUUIDv7 } from 'bun'
7
6
  import { dashboardOperationalError } from '../dashboard-response'
8
7
  import { findRow, insertedId, slugify, str, timestamp } from './content-input'
9
8
 
@@ -32,7 +31,7 @@ export default new Action({
32
31
  const category = await transaction(async (rawTrx) => {
33
32
  const trx = rawTrx as unknown as typeof db
34
33
  const duplicate = await trx
35
- .selectFrom('categories')
34
+ .selectFrom('categorizables').where('categorizable_type', '=', 'posts')
36
35
  .select(['id'])
37
36
  .where('slug', '=', slug)
38
37
  .executeTakeFirst()
@@ -41,21 +40,16 @@ export default new Action({
41
40
  return null
42
41
 
43
42
  const now = timestamp()
44
- const displayOrder = await trx
45
- .selectFrom('categories')
46
- .select(db.fn.max('display_order').as('max_display_order'))
47
- .executeTakeFirst()
48
-
49
43
  const result = await trx
50
- .insertInto('categories')
44
+ .insertInto('categorizables')
51
45
  .values({
52
- uuid: randomUUIDv7(),
53
46
  name,
54
47
  slug,
55
48
  description,
56
49
  // A real boolean: on PostgreSQL the column is boolean and refuses 1.
57
50
  is_active: true,
58
- display_order: Number(displayOrder?.max_display_order || 0) + 1,
51
+ categorizable_type: 'posts',
52
+ categorizable_id: 0,
59
53
  created_at: now,
60
54
  updated_at: now,
61
55
  } as any)
@@ -68,7 +62,7 @@ export default new Action({
68
62
  if (!id)
69
63
  throw new Error('Category insert did not return an id.')
70
64
 
71
- const created = await findRow('categories', id, trx)
65
+ const created = await findRow('categorizables', id, trx)
72
66
  if (!created)
73
67
  throw new Error('Created category could not be loaded.')
74
68
  return created
@@ -230,7 +230,7 @@ export default new Action({
230
230
  (since ? postQuery.where('created_at', '>=', since) : postQuery).execute() as unknown as Promise<ContentOverviewPostRow[]>,
231
231
  (since ? pageQuery.where('created_at', '>=', since) : pageQuery).execute() as unknown as Promise<ContentOverviewPageRow[]>,
232
232
  (since ? commentQuery.where('created_at', '>=', since) : commentQuery).execute() as unknown as Promise<ContentOverviewCommentRow[]>,
233
- db.selectFrom('categories').select(['id', 'name']).execute() as unknown as Promise<NamedRow[]>,
233
+ db.selectFrom('categorizables').where('categorizable_type', '=', 'posts').select(['id', 'name']).execute() as unknown as Promise<NamedRow[]>,
234
234
  db.selectFrom('authors').select(['id', 'name']).execute() as unknown as Promise<NamedRow[]>,
235
235
  ])
236
236
 
@@ -76,7 +76,7 @@ export default new Action({
76
76
 
77
77
  const postIds = posts.map(post => post.id)
78
78
  const [categories, tags, authors, categoryRelations, tagRelations] = await Promise.all([
79
- db.selectFrom('categories').select(['id', 'name', 'slug']).orderBy('name').execute(),
79
+ db.selectFrom('categorizables').where('categorizable_type', '=', 'posts').select(['id', 'name', 'slug']).orderBy('name').execute(),
80
80
  db.selectFrom('tags').select(['id', 'name', 'slug']).orderBy('name').execute(),
81
81
  db.selectFrom('authors').select(['id', 'name', 'email']).orderBy('name').execute(),
82
82
  postIds.length
@@ -89,7 +89,7 @@ export async function invalidPostReference(payload: PostPayload, database: typeo
89
89
  ? database.selectFrom('authors').select(['id']).where('id', '=', payload.authorId).executeTakeFirst()
90
90
  : Promise.resolve(undefined),
91
91
  payload.categoryIds.length
92
- ? database.selectFrom('categories').whereIn('id', payload.categoryIds).select(['id']).execute()
92
+ ? database.selectFrom('categorizables').where('categorizable_type', '=', 'posts').whereIn('id', payload.categoryIds).select(['id']).execute()
93
93
  : Promise.resolve([]),
94
94
  payload.tagIds.length
95
95
  ? database.selectFrom('tags').whereIn('id', payload.tagIds).select(['id']).execute()
@@ -0,0 +1,26 @@
1
+ import { Action } from '@stacksjs/actions/runtime'
2
+ import { discoverJobs } from '@stacksjs/queue'
3
+ import { dashboardOperationalError } from '../dashboard-response'
4
+
5
+ export default new Action({
6
+ name: 'Job Definitions',
7
+ description: 'Discover application jobs and their queue, schedule and retry conventions without dispatching them.',
8
+ method: 'GET',
9
+ async handle() {
10
+ try {
11
+ const jobs = await discoverJobs(undefined, { strict: true })
12
+ return { data: jobs.map(job => ({
13
+ name: job.name,
14
+ description: job.config.description ?? '',
15
+ queue: job.config.queue ?? 'default',
16
+ tries: job.config.tries ?? 3,
17
+ timeout: job.config.timeout ?? null,
18
+ schedule: job.config.rate ?? job.config.schedule ?? '',
19
+ enabled: job.config.enabled !== false,
20
+ })).sort((a, b) => a.name.localeCompare(b.name)) }
21
+ }
22
+ catch (error) {
23
+ return dashboardOperationalError(error, 'Job definitions could not be loaded.', 'JobDefinitionsAction')
24
+ }
25
+ },
26
+ })
@@ -1,6 +1,6 @@
1
1
  import { Action } from '@stacksjs/actions/runtime'
2
2
  import { FailedJob, Job } from '@stacksjs/orm'
3
- import { getGlobalMetrics } from '@stacksjs/queue'
3
+ import { aggregateWorkerMetrics, readWorkerSnapshots } from '@stacksjs/queue'
4
4
  import { dashboardOperationalError } from '../dashboard-response'
5
5
 
6
6
  export default new Action({
@@ -14,7 +14,7 @@ export default new Action({
14
14
  FailedJob.count(),
15
15
  ])
16
16
 
17
- const metrics = getGlobalMetrics().getMetrics()
17
+ const metrics = aggregateWorkerMetrics(await readWorkerSnapshots())
18
18
  const completedSeen = metrics.counts.completed
19
19
  const failedSeen = metrics.counts.failed
20
20
  const totalSeen = completedSeen + failedSeen
@@ -1,3 +1,4 @@
1
+ import { hiddenModelColumns } from '../../../../resources/functions/dashboard/model-columns'
1
2
  import type { RequestInstance } from '@stacksjs/types'
2
3
  import { Action } from '@stacksjs/actions/runtime'
3
4
  import { response as routerResponse } from '@stacksjs/router'
@@ -83,17 +84,6 @@ function humanize(column: string): string {
83
84
  // dropped from the response body AND from the sort/filter allowlist: a
84
85
  // column you cannot see is still an enumeration oracle if you can sort or
85
86
  // filter by it.
86
- const HIDDEN_COLUMNS = new Set(['password', 'remember_token', 'api_token', 'access_token', 'refresh_token', 'secret', 'two_factor_secret'])
87
-
88
- function hiddenModelColumns(Model: any): Set<string> {
89
- const hidden = new Set(HIDDEN_COLUMNS)
90
- for (const [name, definition] of Object.entries(Model?.attributes ?? {})) {
91
- if ((definition as { hidden?: boolean })?.hidden)
92
- hidden.add(pascalToSnake(name))
93
- }
94
- return hidden
95
- }
96
-
97
87
  /**
98
88
  * Column identifiers reach the query builder as raw, unquoted SQL, so only
99
89
  * valid identifiers from the table's own column list may ever be used.
@@ -1,8 +1,6 @@
1
1
  import { Action } from '@stacksjs/actions/runtime'
2
- import { existsSync, readdirSync } from 'node:fs'
3
- import { join } from 'node:path'
4
- import process from 'node:process'
5
- import { frameworkDefaultsRoot } from '@stacksjs/path'
2
+ import { overridesReady } from '@stacksjs/config'
3
+ import { resolveModelSources } from '@stacksjs/database/model-sources'
6
4
  import { countRows } from '../../../../resources/functions/dashboard/data'
7
5
  import { dashboardOperationalError, dashboardOperationalIssue } from '../dashboard-response'
8
6
  import { resolveDashboardModel } from '../../../../resources/functions/dashboard/model-access'
@@ -64,25 +62,6 @@ function pluralize(word: string): string {
64
62
  return `${word}s`
65
63
  }
66
64
 
67
- function walkModels(dir: string, recursive: boolean): Array<{ name: string, file: string }> {
68
- const out: Array<{ name: string, file: string }> = []
69
- if (!existsSync(dir))
70
- return out
71
-
72
- const entries = readdirSync(dir, { withFileTypes: true })
73
- for (const ent of entries) {
74
- const full = join(dir, ent.name)
75
- if (ent.isDirectory()) {
76
- if (recursive) out.push(...walkModels(full, true))
77
- continue
78
- }
79
- if (!ent.name.endsWith('.ts')) continue
80
- if (ent.name.startsWith('_') || ent.name === 'index.ts' || ent.name === 'README.ts') continue
81
- out.push({ name: ent.name.replace('.ts', ''), file: full })
82
- }
83
- return out
84
- }
85
-
86
65
  function categorize(model: { source: 'userland' | 'framework', file: string }): string {
87
66
  if (model.source === 'userland') return 'userland'
88
67
  const segs = model.file.split('/Models/')[1]?.split('/') ?? []
@@ -122,30 +101,19 @@ export default new Action({
122
101
  method: 'GET',
123
102
  apiResponse: true,
124
103
  async handle() {
125
- const cwd = process.cwd()
126
- const userlandDir = join(cwd, 'app', 'Models')
127
- const defaultsDir = join(frameworkDefaultsRoot(), 'app/Models')
128
-
129
- let userland: Array<Omit<DiscoveredModel, 'source'>>
130
- let defaults: Array<Omit<DiscoveredModel, 'source'>>
104
+ await overridesReady
105
+ let merged: DiscoveredModel[]
131
106
  try {
132
- userland = walkModels(userlandDir, true)
133
- defaults = walkModels(defaultsDir, true)
107
+ merged = (resolveModelSources()?.models ?? []).map(model => ({
108
+ name: model.name,
109
+ file: model.file,
110
+ source: model.origin === 'framework' ? 'framework' : 'userland',
111
+ }))
134
112
  }
135
113
  catch (error) {
136
114
  return dashboardOperationalError(error, 'Model catalog could not be loaded.', 'ModelsIndexAction.catalog')
137
115
  }
138
116
 
139
- const seen = new Set<string>()
140
- const merged: DiscoveredModel[] = []
141
- for (const list of [userland, defaults]) {
142
- for (const m of list) {
143
- if (seen.has(m.name)) continue
144
- seen.add(m.name)
145
- merged.push({ ...m, source: list === userland ? 'userland' : 'framework' })
146
- }
147
- }
148
-
149
117
  const candidates = await Promise.all(merged.map(async (m): Promise<ModelRow | null> => {
150
118
  let Model: any
151
119
  try {
@@ -1,6 +1,6 @@
1
1
  import { Action } from '@stacksjs/actions/runtime'
2
2
  import { FailedJob, Job } from '@stacksjs/orm'
3
- import { getGlobalMetrics } from '@stacksjs/queue'
3
+ import { aggregateWorkerMetrics, readWorkerSnapshots } from '@stacksjs/queue'
4
4
  import { dashboardOperationalError } from '../dashboard-response'
5
5
  import { readQueueCounts } from './queue-stats'
6
6
 
@@ -32,18 +32,16 @@ export default new Action({
32
32
  total: queue.total,
33
33
  }))
34
34
  const active = counts.reduce((sum, queue) => sum + queue.active, 0)
35
- const completed = 0
36
35
 
37
- // The in-memory metrics tracker only sees jobs processed since the
38
- // current API process started, so it complements (not replaces) the DB
39
- // counts. If the process just booted there's nothing here yet.
40
- const metrics = getGlobalMetrics().getMetrics()
36
+ // Workers publish bounded snapshots through their native QueueWorker model.
37
+ // The dashboard reads those across processes rather than its own empty tracker.
38
+ const metrics = aggregateWorkerMetrics(await readWorkerSnapshots())
41
39
 
42
40
  const stats = {
43
41
  totalQueues: queues.length,
44
42
  totalJobs,
45
43
  activeJobs: active,
46
- completedJobs: completed,
44
+ completedJobs: metrics.counts.completed,
47
45
  failedJobs: failedJobCount,
48
46
  throughputPerMinute: metrics.throughputPerMinute,
49
47
  averageProcessingTimeMs: Math.round(metrics.averageDuration),
@@ -1,5 +1,5 @@
1
1
  import { Action } from '@stacksjs/actions/runtime'
2
- import { getActiveJobCount, getWorkerTracker, isWorkerRunning } from '@stacksjs/queue'
2
+ import { readWorkerSnapshots } from '@stacksjs/queue'
3
3
  import { dashboardOperationalError } from '../dashboard-response'
4
4
 
5
5
  export default new Action({
@@ -8,8 +8,8 @@ export default new Action({
8
8
  method: 'GET',
9
9
  async handle() {
10
10
  try {
11
- const tracker = getWorkerTracker()
12
- const tracked = tracker.getAll()
11
+ const snapshots = await readWorkerSnapshots()
12
+ const tracked = snapshots.map(snapshot => snapshot.worker)
13
13
 
14
14
  // Translate the tracker's internal status into the dashboard's vocabulary
15
15
  // ("running" | "paused" | "stopped") so the existing UI badges keep working.
@@ -29,15 +29,15 @@ export default new Action({
29
29
  jobs_processed: w.processedCount,
30
30
  failed_jobs: w.failedCount,
31
31
  uptime,
32
- last_heartbeat: w.lastActivityAt,
32
+ last_heartbeat: new Date(snapshots.find(snapshot => snapshot.worker.id === w.id)!.heartbeatAt).toISOString(),
33
33
  memory: '-',
34
34
  }
35
35
  })
36
36
 
37
37
  return {
38
38
  data,
39
- worker_running: isWorkerRunning(),
40
- active_jobs: getActiveJobCount(),
39
+ worker_running: tracked.some(worker => worker.status !== 'stopped'),
40
+ active_jobs: snapshots.reduce((sum, snapshot) => sum + snapshot.activeJobs, 0),
41
41
  }
42
42
  }
43
43
  catch (error) {
@@ -22,7 +22,7 @@ const queueName = (value: unknown): string => String(value || 'default')
22
22
  * worker has reserved it, which is `queuedJobState`'s rule in
23
23
  * `@stacksjs/queue`: a `reserved_at` that is set and not 0, so the filter
24
24
  * below is `!= 0`, which NULL never matches. Queues are listed in the order of
25
- * their first job, and a queue with only failures is not listed, as before.
25
+ * their first job, including queues whose only remaining records are failures.
26
26
  */
27
27
  export async function readQueueCounts(database: typeof db = db): Promise<QueueCounts[]> {
28
28
  const [jobRows, failedRows] = await Promise.all([
@@ -57,6 +57,10 @@ export async function readQueueCounts(database: typeof db = db): Promise<QueueCo
57
57
  })
58
58
  }
59
59
 
60
+ for (const name of failed.keys()) {
61
+ if (!queues.has(name)) queues.set(name, { total: 0, active: 0, firstId: Number.POSITIVE_INFINITY })
62
+ }
63
+
60
64
  return [...queues.entries()]
61
65
  .sort(([, a], [, b]) => a.firstId - b.firstId)
62
66
  .map(([name, counts]) => ({