@stacksjs/defaults 0.70.355 → 0.70.357

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 (180) hide show
  1. package/ai/skills/stacks-email/SKILL.md +38 -1
  2. package/ai/skills/stacks-orm/SKILL.md +6 -0
  3. package/app/Actions/ContactAction.ts +2 -2
  4. package/app/Actions/Dashboard/Analytics/EventAnalyticsAction.ts +2 -3
  5. package/app/Actions/Dashboard/Analytics/MarketingAnalyticsAction.ts +2 -3
  6. package/app/Actions/Dashboard/Analytics/SalesAnalyticsAction.ts +2 -3
  7. package/app/Actions/Dashboard/Analytics/WebAnalyticsAction.ts +9 -3
  8. package/app/Actions/Dashboard/Auth/MeAction.ts +4 -7
  9. package/app/Actions/Dashboard/Buddy/BuddyChatAction.ts +2 -3
  10. package/app/Actions/Dashboard/Cloud/CloudIndexAction.ts +8 -1
  11. package/app/Actions/Dashboard/Cloud/ServerlessIndexAction.ts +15 -8
  12. package/app/Actions/Dashboard/Commerce/CommerceCategoriesAction.ts +2 -4
  13. package/app/Actions/Dashboard/Commerce/CommerceCouponsAction.ts +2 -4
  14. package/app/Actions/Dashboard/Commerce/CommerceCustomersAction.ts +2 -4
  15. package/app/Actions/Dashboard/Commerce/CommerceDashboardAction.ts +2 -4
  16. package/app/Actions/Dashboard/Commerce/CommerceDeliveryAction.ts +2 -4
  17. package/app/Actions/Dashboard/Commerce/CommerceGiftCardsAction.ts +2 -4
  18. package/app/Actions/Dashboard/Commerce/CommerceOrdersAction.ts +2 -4
  19. package/app/Actions/Dashboard/Commerce/CommercePaymentsAction.ts +2 -4
  20. package/app/Actions/Dashboard/Commerce/CommercePosAction.ts +2 -4
  21. package/app/Actions/Dashboard/Commerce/CommercePosCheckoutAction.ts +75 -61
  22. package/app/Actions/Dashboard/Commerce/CommercePrintDevicesAction.ts +2 -4
  23. package/app/Actions/Dashboard/Commerce/CommercePrintLogsAction.ts +2 -4
  24. package/app/Actions/Dashboard/Commerce/CommerceProductDetailAction.ts +3 -4
  25. package/app/Actions/Dashboard/Commerce/CommerceProductsAction.ts +2 -4
  26. package/app/Actions/Dashboard/Commerce/CommerceTaxesAction.ts +2 -4
  27. package/app/Actions/Dashboard/Commerce/DeliveryRouteIndexAction.ts +2 -4
  28. package/app/Actions/Dashboard/Commerce/DigitalDeliveryIndexAction.ts +2 -4
  29. package/app/Actions/Dashboard/Commerce/DriverIndexAction.ts +2 -4
  30. package/app/Actions/Dashboard/Commerce/LicenseKeyIndexAction.ts +2 -4
  31. package/app/Actions/Dashboard/Commerce/LicenseKeyOptionsAction.ts +2 -4
  32. package/app/Actions/Dashboard/Commerce/ManufacturerIndexAction.ts +2 -4
  33. package/app/Actions/Dashboard/Commerce/ProductUnitIndexAction.ts +2 -4
  34. package/app/Actions/Dashboard/Commerce/ProductVariantIndexAction.ts +2 -4
  35. package/app/Actions/Dashboard/Commerce/ProductWaitlistIndexAction.ts +2 -4
  36. package/app/Actions/Dashboard/Commerce/RestaurantWaitlistIndexAction.ts +2 -4
  37. package/app/Actions/Dashboard/Commerce/ReviewIndexAction.ts +2 -4
  38. package/app/Actions/Dashboard/Commerce/ShippingMethodIndexAction.ts +2 -4
  39. package/app/Actions/Dashboard/Commerce/ShippingRateIndexAction.ts +2 -4
  40. package/app/Actions/Dashboard/Commerce/ShippingZoneIndexAction.ts +2 -4
  41. package/app/Actions/Dashboard/Content/AuthorDestroyAction.ts +23 -9
  42. package/app/Actions/Dashboard/Content/AuthorIndexAction.ts +32 -26
  43. package/app/Actions/Dashboard/Content/AuthorStoreAction.ts +44 -24
  44. package/app/Actions/Dashboard/Content/AuthorUpdateAction.ts +48 -26
  45. package/app/Actions/Dashboard/Content/CategoryDestroyAction.ts +27 -8
  46. package/app/Actions/Dashboard/Content/CategoryIndexAction.ts +21 -15
  47. package/app/Actions/Dashboard/Content/CategoryStoreAction.ts +47 -29
  48. package/app/Actions/Dashboard/Content/CommentDestroyAction.ts +17 -4
  49. package/app/Actions/Dashboard/Content/CommentIndexAction.ts +23 -17
  50. package/app/Actions/Dashboard/Content/CommentUpdateAction.ts +30 -12
  51. package/app/Actions/Dashboard/Content/ContentDashboardAction.ts +43 -37
  52. package/app/Actions/Dashboard/Content/PageDestroyAction.ts +17 -4
  53. package/app/Actions/Dashboard/Content/PageIndexAction.ts +23 -17
  54. package/app/Actions/Dashboard/Content/PageStoreAction.ts +33 -18
  55. package/app/Actions/Dashboard/Content/PageUpdateAction.ts +40 -22
  56. package/app/Actions/Dashboard/Content/PostDestroyAction.ts +24 -11
  57. package/app/Actions/Dashboard/Content/PostIndexAction.ts +55 -49
  58. package/app/Actions/Dashboard/Content/PostStoreAction.ts +43 -33
  59. package/app/Actions/Dashboard/Content/PostUpdateAction.ts +46 -34
  60. package/app/Actions/Dashboard/Content/TagDestroyAction.ts +27 -8
  61. package/app/Actions/Dashboard/Content/TagIndexAction.ts +31 -25
  62. package/app/Actions/Dashboard/Content/TagStoreAction.ts +44 -23
  63. package/app/Actions/Dashboard/Content/content-input.ts +4 -4
  64. package/app/Actions/Dashboard/Content/post-input.ts +60 -8
  65. package/app/Actions/Dashboard/DashboardActivityAction.ts +2 -5
  66. package/app/Actions/Dashboard/Data/ActivityIndexAction.ts +2 -5
  67. package/app/Actions/Dashboard/Data/SubscriberIndexAction.ts +2 -5
  68. package/app/Actions/Dashboard/Data/TeamIndexAction.ts +2 -5
  69. package/app/Actions/Dashboard/Data/UserIndexAction.ts +2 -5
  70. package/app/Actions/Dashboard/Deployments/CreateDeployment.ts +23 -19
  71. package/app/Actions/Dashboard/Deployments/GetAverageDeploymentTime.ts +8 -2
  72. package/app/Actions/Dashboard/Deployments/GetDeployScript.ts +3 -6
  73. package/app/Actions/Dashboard/Deployments/GetDeployment.ts +4 -5
  74. package/app/Actions/Dashboard/Deployments/GetDeploymentCount.ts +7 -1
  75. package/app/Actions/Dashboard/Deployments/GetDeploymentLiveTerminalOutput.ts +10 -9
  76. package/app/Actions/Dashboard/Deployments/GetDeployments.ts +2 -4
  77. package/app/Actions/Dashboard/Deployments/GetRecentDeployments.ts +8 -2
  78. package/app/Actions/Dashboard/Deployments/UpdateDeployScript.ts +18 -5
  79. package/app/Actions/Dashboard/Email/CapturedMailIndexAction.ts +2 -4
  80. package/app/Actions/Dashboard/Email/CapturedMailShowAction.ts +3 -4
  81. package/app/Actions/Dashboard/Infrastructure/InsightsAction.ts +15 -14
  82. package/app/Actions/Dashboard/Infrastructure/LogIndexAction.ts +2 -8
  83. package/app/Actions/Dashboard/Jobs/JobIndexAction.ts +2 -4
  84. package/app/Actions/Dashboard/Jobs/JobShowAction.ts +13 -8
  85. package/app/Actions/Dashboard/Jobs/JobStatsAction.ts +2 -4
  86. package/app/Actions/Dashboard/Kanban/BoardDestroyAction.ts +2 -3
  87. package/app/Actions/Dashboard/Kanban/BoardShowAction.ts +2 -3
  88. package/app/Actions/Dashboard/Kanban/BoardStoreAction.ts +2 -3
  89. package/app/Actions/Dashboard/Kanban/BoardUpdateAction.ts +2 -3
  90. package/app/Actions/Dashboard/Kanban/BoardsIndexAction.ts +2 -3
  91. package/app/Actions/Dashboard/Kanban/BoardsReorderAction.ts +2 -3
  92. package/app/Actions/Dashboard/Kanban/CardAssigneesSyncAction.ts +2 -3
  93. package/app/Actions/Dashboard/Kanban/CardCommentDestroyAction.ts +2 -3
  94. package/app/Actions/Dashboard/Kanban/CardCommentStoreAction.ts +2 -3
  95. package/app/Actions/Dashboard/Kanban/CardCommentUpdateAction.ts +2 -3
  96. package/app/Actions/Dashboard/Kanban/CardDestroyAction.ts +2 -3
  97. package/app/Actions/Dashboard/Kanban/CardLabelsSyncAction.ts +2 -3
  98. package/app/Actions/Dashboard/Kanban/CardShowAction.ts +2 -3
  99. package/app/Actions/Dashboard/Kanban/CardStoreAction.ts +2 -3
  100. package/app/Actions/Dashboard/Kanban/CardUpdateAction.ts +2 -3
  101. package/app/Actions/Dashboard/Kanban/CardsReorderAction.ts +2 -3
  102. package/app/Actions/Dashboard/Kanban/ColumnDestroyAction.ts +2 -3
  103. package/app/Actions/Dashboard/Kanban/ColumnStoreAction.ts +2 -3
  104. package/app/Actions/Dashboard/Kanban/ColumnUpdateAction.ts +2 -3
  105. package/app/Actions/Dashboard/Kanban/ColumnsReorderAction.ts +2 -3
  106. package/app/Actions/Dashboard/Kanban/LabelDestroyAction.ts +2 -3
  107. package/app/Actions/Dashboard/Kanban/LabelStoreAction.ts +2 -3
  108. package/app/Actions/Dashboard/Kanban/LabelUpdateAction.ts +2 -3
  109. package/app/Actions/Dashboard/Kanban/UsersListAction.ts +2 -3
  110. package/app/Actions/Dashboard/Kanban/kanban-response.ts +5 -0
  111. package/app/Actions/Dashboard/Library/CreateComponent.ts +15 -6
  112. package/app/Actions/Dashboard/Library/CreateFunction.ts +15 -6
  113. package/app/Actions/Dashboard/Library/DependencyIndexAction.ts +88 -77
  114. package/app/Actions/Dashboard/Library/GetAverageReleaseTime.ts +21 -15
  115. package/app/Actions/Dashboard/Library/GetComponents.ts +14 -7
  116. package/app/Actions/Dashboard/Library/GetDownloadCount.ts +14 -5
  117. package/app/Actions/Dashboard/Library/GetFunctions.ts +12 -5
  118. package/app/Actions/Dashboard/Library/GetReleaseCount.ts +14 -5
  119. package/app/Actions/Dashboard/Library/GetReleases.ts +17 -10
  120. package/app/Actions/Dashboard/Library/PackageIndexAction.ts +14 -7
  121. package/app/Actions/Dashboard/Models/ModelDestroyAction.ts +7 -5
  122. package/app/Actions/Dashboard/Models/ModelShowAction.ts +82 -174
  123. package/app/Actions/Dashboard/Models/ModelStoreAction.ts +6 -4
  124. package/app/Actions/Dashboard/Models/ModelUpdateAction.ts +9 -7
  125. package/app/Actions/Dashboard/Models/ModelsIndexAction.ts +47 -17
  126. package/app/Actions/Dashboard/Models/model-write.ts +20 -5
  127. package/app/Actions/Dashboard/Notifications/NotificationDeliveryHistoryAction.ts +46 -36
  128. package/app/Actions/Dashboard/Notifications/NotificationDeliveryIndexAction.ts +14 -6
  129. package/app/Actions/Dashboard/Notifications/NotificationDeliveryOverviewAction.ts +96 -84
  130. package/app/Actions/Dashboard/Notifications/NotificationDeliveryRetryAction.ts +34 -21
  131. package/app/Actions/Dashboard/Queries/QueryDashboardAction.ts +2 -4
  132. package/app/Actions/Dashboard/Queries/QueryShowAction.ts +2 -3
  133. package/app/Actions/Dashboard/Queue/QueueStatsAction.ts +2 -4
  134. package/app/Actions/Dashboard/Queue/QueueWorkersAction.ts +2 -4
  135. package/app/Actions/Dashboard/Rbac/PermissionDestroyAction.ts +2 -2
  136. package/app/Actions/Dashboard/Rbac/PermissionStoreAction.ts +2 -2
  137. package/app/Actions/Dashboard/Rbac/PermissionsIndexAction.ts +2 -3
  138. package/app/Actions/Dashboard/Rbac/RoleDestroyAction.ts +2 -2
  139. package/app/Actions/Dashboard/Rbac/RolePermissionsShowAction.ts +2 -2
  140. package/app/Actions/Dashboard/Rbac/RolePermissionsSyncAction.ts +2 -6
  141. package/app/Actions/Dashboard/Rbac/RoleStoreAction.ts +2 -2
  142. package/app/Actions/Dashboard/Rbac/RolesIndexAction.ts +4 -6
  143. package/app/Actions/Dashboard/Rbac/UserRolesShowAction.ts +2 -2
  144. package/app/Actions/Dashboard/Rbac/UserRolesSyncAction.ts +2 -9
  145. package/app/Actions/Dashboard/Rbac/UsersListAction.ts +2 -3
  146. package/app/Actions/Dashboard/Rbac/rbac-response.ts +10 -0
  147. package/app/Actions/Dashboard/Realtime/RealtimeStatsAction.ts +2 -4
  148. package/app/Actions/Dashboard/Search/GlobalSearchAction.ts +68 -55
  149. package/app/Actions/Dashboard/Teams/TeamInvitationDestroyAction.ts +34 -16
  150. package/app/Actions/Dashboard/Teams/TeamInvitationResendAction.ts +52 -29
  151. package/app/Actions/Dashboard/Teams/TeamInviteAction.ts +102 -71
  152. package/app/Actions/Dashboard/Teams/TeamMemberDestroyAction.ts +36 -17
  153. package/app/Actions/Dashboard/Teams/TeamMemberUpdateAction.ts +39 -26
  154. package/app/Actions/Dashboard/Teams/TeamPeopleIndexAction.ts +71 -58
  155. package/app/Actions/Dashboard/Teams/team-invitation-delivery.ts +29 -7
  156. package/app/Actions/Dashboard/Teams/team-member-count.ts +4 -2
  157. package/app/Actions/Dashboard/Teams/team-records.test.ts +2 -0
  158. package/app/Actions/Dashboard/Teams/team-records.ts +2 -0
  159. package/app/Actions/Dashboard/dashboard-response.ts +16 -0
  160. package/app/Actions/SendWelcomeEmail.ts +1 -1
  161. package/app/Actions/Teams/AcceptInvitationAction.ts +74 -51
  162. package/app/Actions/Teams/ShowInvitationAction.ts +21 -14
  163. package/app/Actions/Teams/team-response.ts +23 -0
  164. package/app/Mail/OrderConfirmation.ts +1 -1
  165. package/app/Mail/SubscriptionConfirmation.ts +1 -1
  166. package/app/Mail/TeamInvitation.ts +1 -1
  167. package/app/Models/EmailIdempotency.ts +64 -0
  168. package/app/Models/EmailSuppression.ts +52 -0
  169. package/app/Models/EmailWebhookEvent.ts +51 -0
  170. package/app/Models/TeamInvitation.ts +13 -0
  171. package/functions/data.ts +0 -1
  172. package/functions/jobs.ts +0 -1
  173. package/ide/vscode/package.json +1 -1
  174. package/package.json +1 -1
  175. package/resources/components/Dashboard/Data/DataRecords.stx +0 -4
  176. package/resources/components/Dashboard/Jobs/JobDetail.stx +3 -2
  177. package/resources/components/Dashboard/Models/ModelRecordsDashboard.stx +7 -13
  178. package/resources/components/Dashboard/Models/ModelsOverview.stx +1 -1
  179. package/views/dashboard/.discovered-models.json +30 -0
  180. package/views/dashboard/stores/auth.ts +15 -14
@@ -14,6 +14,7 @@ Multi-driver email system with template rendering, S3-based inbox management, an
14
14
  - Core package: `storage/framework/core/email/src/`
15
15
  - Configuration: `config/email.ts`
16
16
  - Application mail: `app/Mail/`
17
+ - Persistence models: `storage/framework/defaults/app/Models/EmailSuppression.ts`, `EmailIdempotency.ts`, `EmailWebhookEvent.ts`
17
18
  - Email layouts: `storage/framework/defaults/resources/emails/layouts/`
18
19
  - Email resources: `storage/framework/defaults/resources/emails/`
19
20
 
@@ -54,11 +55,30 @@ await mail.send({
54
55
  text: 'Hello!'
55
56
  })
56
57
 
58
+ // Use this when later state depends on successful provider delivery.
59
+ await mail.sendOrFail({
60
+ to: 'user@example.com',
61
+ subject: 'Your invitation',
62
+ text: 'Open the invitation link.'
63
+ })
64
+
57
65
  // Switch driver
58
66
  const sendgridMail = mail.use('sendgrid')
59
67
  await sendgridMail.send(message)
60
68
  ```
61
69
 
70
+ `mail.send()` always returns an `EmailResult`. Provider rejection is represented
71
+ as `{ success: false, message, provider }`, which is useful for campaign jobs
72
+ that aggregate individual outcomes. It may still throw for invalid framework
73
+ configuration. `mail.sendOrFail()` returns the same successful result and throws
74
+ `EmailDeliveryError` for a structured provider failure. Use `sendOrFail()` when
75
+ an action reports that mail was sent, when a caller relies on `.catch()`, or when
76
+ delivery status is persisted after the call.
77
+
78
+ Keep template rendering fallback separate from provider delivery. Resolve HTML
79
+ and text first, fall back to plain text only when rendering fails, then call
80
+ `sendOrFail()` once outside the template `try` block.
81
+
62
82
  ## Email Class
63
83
 
64
84
  ```typescript
@@ -230,7 +250,7 @@ export async function sendSubscriptionConfirmation({ to, subscriberUuid }: Optio
230
250
  }
231
251
  })
232
252
 
233
- await mail.send({
253
+ await mail.sendOrFail({
234
254
  from: { name: config.app.name, address: config.email.from.address },
235
255
  to,
236
256
  subject: 'Confirm your subscription',
@@ -240,6 +260,21 @@ export async function sendSubscriptionConfirmation({ to, subscriberUuid }: Optio
240
260
  }
241
261
  ```
242
262
 
263
+ ## Delivery persistence models
264
+
265
+ Stacks ships three internal models and their generated migrations:
266
+
267
+ - `EmailSuppression` uses `email_suppressions` and uniquely keys `email + type`.
268
+ - `EmailIdempotency` uses `email_idempotency` and uniquely keys `idempotency_key`.
269
+ - `EmailWebhookEvent` uses `email_webhook_events` and uniquely keys `provider + event_id`.
270
+
271
+ Each model declares authenticated `useApi` index, show, and destroy routes, but
272
+ sets `dashboard.enabled` to false so operational records do not clutter the
273
+ generic model catalog. Sensitive idempotency keys, recipients, subjects, and
274
+ provider event IDs are hidden from generated responses. Run `buddy migrate`
275
+ after upgrading so suppression, send deduplication, and webhook deduplication
276
+ are enforced rather than using their legacy warn-once compatibility path.
277
+
243
278
  ## Inbound MIME and attachment storage
244
279
 
245
280
  `parseInboundEmail()` parses RFC messages with bounded header, nesting, total-size, and attachment-count limits. It returns normalized sender and recipient data, text and HTML bodies, and binary-safe attachments. Attachment filenames are sanitized before they become S3 keys.
@@ -274,6 +309,8 @@ Reprocessing refreshes existing messages instead of skipping them, preserves the
274
309
  - The `mail` singleton auto-registers all 5 drivers on initialization
275
310
  - SMTP driver handles TLS handshake manually (not via node:tls)
276
311
  - SendGrid/Mailgun retry with exponential backoff on failure
312
+ - `mail.send()` returns structured failures; use `mail.sendOrFail()` when success is required
313
+ - Suppression, send idempotency, and webhook dedup are backed by built-in `useApi` models
277
314
  - Mailtrap requires `inboxId` for sandbox mode
278
315
  - EmailSDK reads inbox from S3 (bucket configured via env)
279
316
  - EmailSDK attachment downloads use binary-safe S3 reads and opaque IDs
@@ -196,6 +196,12 @@ await createUser('Alice', 'alice@example.com') // auto-wrapped
196
196
 
197
197
  Both `transaction()` and `savepoint()` delegate to `db.transaction()` and `db.savepoint()` from `@stacksjs/database`.
198
198
 
199
+ ### Transaction executor boundary
200
+
201
+ Every query that must commit or roll back together must use the callback handle (`tx` or `sp`), including validation reads, pivot writes, and the final readback. Do not mix `Model.find()`, `Model.create()`, instance `update()` / `delete()`, or instance relation calls into a raw query-builder transaction. The model executor is a separate execution surface and is not rebound to the callback handle. On SQLite it may use a separate connection, so it cannot observe an uncommitted row written through `tx`.
202
+
203
+ `runInTransactionScope()` buffers supported side effects until commit, but it does not rebind model queries. For a transaction-backed custom action, use the model definition as the schema and relationship source of truth, then execute the complete persistence workflow through `tx`. Read the created or updated row through `tx` before returning so a readback failure also rolls back the mutation.
204
+
199
205
  ## Trait Methods (traits/)
200
206
 
201
207
  ### Taggable (when `traits.taggable: true`)
@@ -53,7 +53,7 @@ export default new Action({
53
53
  ].join('\n')
54
54
 
55
55
  try {
56
- await mail.send({
56
+ await mail.sendOrFail({
57
57
  to: [toAddress],
58
58
  from: { name: appName, address: fromAddress },
59
59
  replyTo: email,
@@ -63,7 +63,7 @@ export default new Action({
63
63
  }
64
64
  catch (err) {
65
65
  const errMessage = err instanceof Error ? err.message : String(err)
66
- console.error('[ContactAction] mail.send failed:', errMessage)
66
+ console.error('[ContactAction] mail.sendOrFail failed:', errMessage)
67
67
  return { success: false, message: 'We could not send your message. Try again in a moment.' }
68
68
  }
69
69
 
@@ -2,6 +2,7 @@ import type { RequestInstance } from '@stacksjs/types'
2
2
  import { Action } from '@stacksjs/actions'
3
3
  import { AnalyticsEvent } from '@stacksjs/orm'
4
4
  import { response } from '@stacksjs/router'
5
+ import { dashboardOperationalError } from '../dashboard-response'
5
6
  import {
6
7
  analyticsCurrency,
7
8
  analyticsIdentifier,
@@ -50,9 +51,7 @@ export default new Action({
50
51
  )
51
52
  }
52
53
  catch (error) {
53
- return response.json({
54
- message: error instanceof Error ? error.message : 'Event analytics records could not be read.',
55
- }, 503)
54
+ return dashboardOperationalError(error, 'Event analytics records could not be read.', 'EventAnalyticsAction')
56
55
  }
57
56
  },
58
57
  })
@@ -2,6 +2,7 @@ import type { RequestInstance } from '@stacksjs/types'
2
2
  import { Action } from '@stacksjs/actions'
3
3
  import { Campaign } from '@stacksjs/orm'
4
4
  import { response } from '@stacksjs/router'
5
+ import { dashboardOperationalError } from '../dashboard-response'
5
6
  import {
6
7
  analyticsCurrency,
7
8
  analyticsIdentifier,
@@ -55,9 +56,7 @@ export default new Action({
55
56
  )
56
57
  }
57
58
  catch (error) {
58
- return response.json({
59
- message: error instanceof Error ? error.message : 'Campaign analytics records could not be read.',
60
- }, 503)
59
+ return dashboardOperationalError(error, 'Campaign analytics records could not be read.', 'MarketingAnalyticsAction')
61
60
  }
62
61
  },
63
62
  })
@@ -2,6 +2,7 @@ import type { RequestInstance } from '@stacksjs/types'
2
2
  import { Action } from '@stacksjs/actions'
3
3
  import { Category, Order, OrderItem, Payment, Product } from '@stacksjs/orm'
4
4
  import { response } from '@stacksjs/router'
5
+ import { dashboardOperationalError } from '../dashboard-response'
5
6
  import {
6
7
  analyticsCurrency,
7
8
  analyticsIdentifier,
@@ -93,9 +94,7 @@ export default new Action({
93
94
  )
94
95
  }
95
96
  catch (error) {
96
- return response.json({
97
- message: error instanceof Error ? error.message : 'Sales analytics records could not be read.',
98
- }, 503)
97
+ return dashboardOperationalError(error, 'Sales analytics records could not be read.', 'SalesAnalyticsAction')
99
98
  }
100
99
  },
101
100
  })
@@ -2,6 +2,7 @@ import type { RequestInstance } from '@stacksjs/types'
2
2
  import { Action } from '@stacksjs/actions'
3
3
  import { Request } from '@stacksjs/orm'
4
4
  import { response } from '@stacksjs/router'
5
+ import { dashboardOperationalError } from '../dashboard-response'
5
6
  import {
6
7
  buildWebAnalytics,
7
8
  normalizeAnalyticsRange,
@@ -27,9 +28,14 @@ export default new Action({
27
28
  message: error instanceof Error ? error.message : 'The analytics query is invalid.',
28
29
  }, 422)
29
30
  }
30
- const records = await Request.orderByDesc('id').limit(10_000).get()
31
- const rows = records.map(requestAnalyticsRow)
31
+ try {
32
+ const records = await Request.orderByDesc('id').limit(10_000).get()
33
+ const rows = records.map(requestAnalyticsRow)
32
34
 
33
- return buildWebAnalytics(rows, range, new Date(), scope)
35
+ return buildWebAnalytics(rows, range, new Date(), scope)
36
+ }
37
+ catch (error) {
38
+ return dashboardOperationalError(error, 'Web analytics records could not be read.', 'WebAnalyticsAction')
39
+ }
34
40
  },
35
41
  })
@@ -1,6 +1,7 @@
1
1
  import type { RequestInstance } from '@stacksjs/types'
2
2
  import { Action } from '@stacksjs/actions'
3
3
  import { getUserRoles } from '@stacksjs/auth'
4
+ import { dashboardOperationalError } from '../dashboard-response'
4
5
 
5
6
  interface MeResponse {
6
7
  user: {
@@ -59,17 +60,13 @@ export default new Action({
59
60
 
60
61
  const userId = Number(user.id)
61
62
 
62
- let roles: string[] = []
63
+ let roles: string[]
63
64
  try {
64
65
  const records = await getUserRoles(userId)
65
66
  roles = records.map(r => r.name)
66
67
  }
67
- catch (err) {
68
- // RBAC store not configured, migrations not run, or DB unreachable.
69
- // Empty roles → useRole will fall back to its dev default. We don't
70
- // want to 500 the dashboard just because the role tables haven't
71
- // been migrated yet on a fresh project.
72
- console.warn('[dashboard/auth/me] getUserRoles failed:', err instanceof Error ? err.message : err)
68
+ catch (error) {
69
+ return dashboardOperationalError(error, 'Dashboard identity could not be loaded.', 'MeAction')
73
70
  }
74
71
 
75
72
  const res: MeResponse = {
@@ -4,6 +4,7 @@ import { Action } from '@stacksjs/actions'
4
4
  import { buddyState, createAIClient } from '@stacksjs/ai'
5
5
  import { config } from '@stacksjs/config'
6
6
  import { response } from '@stacksjs/router'
7
+ import { dashboardOperationalError } from '../dashboard-response'
7
8
  import { buddySystemPrompt, isBuddyProviderConfigured, publicBuddyHistory, resolveBuddyProvider } from './buddy-chat'
8
9
 
9
10
  export default new Action({
@@ -49,9 +50,7 @@ export default new Action({
49
50
  }
50
51
  }
51
52
  catch (error) {
52
- return response.json({
53
- message: error instanceof Error ? error.message : 'Buddy could not answer the question.',
54
- }, 502)
53
+ return dashboardOperationalError(error, 'Buddy could not answer the question.', 'BuddyChatAction', 502)
55
54
  }
56
55
  },
57
56
  })
@@ -1,12 +1,19 @@
1
1
  import { Action } from '@stacksjs/actions'
2
2
  import { tsCloud } from '~/config/cloud'
3
+ import { dashboardOperationalError } from '../dashboard-response'
3
4
  import { getDashboardCloudSnapshot } from './cloud-overview'
4
5
 
5
6
  export default new Action({
6
7
  name: 'CloudIndexAction',
7
8
  description: 'Returns configured cloud infrastructure and persisted deployment state.',
8
9
  method: 'GET',
10
+ apiResponse: true,
9
11
  async handle() {
10
- return getDashboardCloudSnapshot(tsCloud)
12
+ try {
13
+ return await getDashboardCloudSnapshot(tsCloud)
14
+ }
15
+ catch (error) {
16
+ return dashboardOperationalError(error, 'Cloud infrastructure could not be loaded.', 'CloudIndexAction')
17
+ }
11
18
  },
12
19
  })
@@ -1,20 +1,27 @@
1
1
  import { Action } from '@stacksjs/actions'
2
2
  import { tsCloud } from '~/config/cloud'
3
+ import { dashboardOperationalError } from '../dashboard-response'
3
4
  import { getDashboardCloudSnapshot } from './cloud-overview'
4
5
 
5
6
  export default new Action({
6
7
  name: 'ServerlessIndexAction',
7
8
  description: 'Returns serverless services derived from environment application manifests.',
8
9
  method: 'GET',
10
+ apiResponse: true,
9
11
  async handle() {
10
- const snapshot = await getDashboardCloudSnapshot(tsCloud)
11
- return {
12
- project: snapshot.project,
13
- environments: snapshot.environments.filter(environment => environment.serverless),
14
- services: snapshot.serverlessServices,
15
- links: snapshot.serverlessLinks,
16
- events: snapshot.events.filter(event => event.service.toLowerCase().includes('serverless')),
17
- generatedAt: snapshot.generatedAt,
12
+ try {
13
+ const snapshot = await getDashboardCloudSnapshot(tsCloud)
14
+ return {
15
+ project: snapshot.project,
16
+ environments: snapshot.environments.filter(environment => environment.serverless),
17
+ services: snapshot.serverlessServices,
18
+ links: snapshot.serverlessLinks,
19
+ events: snapshot.events.filter(event => event.service.toLowerCase().includes('serverless')),
20
+ generatedAt: snapshot.generatedAt,
21
+ }
22
+ }
23
+ catch (error) {
24
+ return dashboardOperationalError(error, 'Serverless infrastructure could not be loaded.', 'ServerlessIndexAction')
18
25
  }
19
26
  },
20
27
  })
@@ -1,6 +1,6 @@
1
1
  import { Action } from '@stacksjs/actions'
2
2
  import { Category } from '@stacksjs/orm'
3
- import { response } from '@stacksjs/router'
3
+ import { dashboardOperationalError } from '../dashboard-response'
4
4
  import { normalizeCommerceCategoryRecord, summarizeCommerceCategories } from './commerce-category-records'
5
5
 
6
6
  export default new Action({
@@ -24,9 +24,7 @@ export default new Action({
24
24
  }
25
25
  }
26
26
  catch (error) {
27
- return response.json({
28
- message: error instanceof Error ? error.message : 'Category records could not be read.',
29
- }, 503)
27
+ return dashboardOperationalError(error, 'Category records could not be read.', 'CommerceCategoriesAction')
30
28
  }
31
29
  },
32
30
  })
@@ -1,6 +1,6 @@
1
1
  import { Action } from '@stacksjs/actions'
2
2
  import { Coupon, Product } from '@stacksjs/orm'
3
- import { response } from '@stacksjs/router'
3
+ import { dashboardOperationalError } from '../dashboard-response'
4
4
  import { normalizeCouponRecord, summarizeCoupons } from './coupon-records'
5
5
  import { commerceIdentifier, commerceValue } from './commerce-record'
6
6
 
@@ -26,9 +26,7 @@ export default new Action({
26
26
  }
27
27
  }
28
28
  catch (error) {
29
- return response.json({
30
- message: error instanceof Error ? error.message : 'Coupon records could not be read.',
31
- }, 503)
29
+ return dashboardOperationalError(error, 'Coupon records could not be read.', 'CommerceCouponsAction')
32
30
  }
33
31
  },
34
32
  })
@@ -1,7 +1,7 @@
1
1
  import { Action } from '@stacksjs/actions'
2
2
  import { config } from '@stacksjs/config'
3
3
  import { Customer } from '@stacksjs/orm'
4
- import { response } from '@stacksjs/router'
4
+ import { dashboardOperationalError } from '../dashboard-response'
5
5
  import {
6
6
  normalizeCommerceCustomerCurrency,
7
7
  normalizeCommerceCustomerRecord,
@@ -25,9 +25,7 @@ export default new Action({
25
25
  }
26
26
  }
27
27
  catch (error) {
28
- return response.json({
29
- message: error instanceof Error ? error.message : 'Customer records could not be read.',
30
- }, 503)
28
+ return dashboardOperationalError(error, 'Customer records could not be read.', 'CommerceCustomersAction')
31
29
  }
32
30
  },
33
31
  })
@@ -1,7 +1,7 @@
1
1
  import type { RequestInstance } from '@stacksjs/types'
2
2
  import { Action } from '@stacksjs/actions'
3
3
  import { Customer, Order, OrderItem, Product } from '@stacksjs/orm'
4
- import { response } from '@stacksjs/router'
4
+ import { dashboardOperationalError } from '../dashboard-response'
5
5
  import {
6
6
  buildCommerceDashboard,
7
7
  commerceDashboardQueryStart,
@@ -56,9 +56,7 @@ export default new Action({
56
56
  )
57
57
  }
58
58
  catch (error) {
59
- return response.json({
60
- message: error instanceof Error ? error.message : 'Commerce overview records could not be read.',
61
- }, 503)
59
+ return dashboardOperationalError(error, 'Commerce overview records could not be read.', 'CommerceDashboardAction')
62
60
  }
63
61
  },
64
62
  })
@@ -1,7 +1,7 @@
1
1
  import { Action } from '@stacksjs/actions'
2
2
  import { config } from '@stacksjs/config'
3
3
  import { DeliveryRoute, Driver, ShippingMethod, ShippingZone } from '@stacksjs/orm'
4
- import { response } from '@stacksjs/router'
4
+ import { dashboardOperationalError } from '../dashboard-response'
5
5
  import { buildDeliveryOverview } from './commerce-delivery'
6
6
 
7
7
  export default new Action({
@@ -28,9 +28,7 @@ export default new Action({
28
28
  )
29
29
  }
30
30
  catch (error) {
31
- return response.json({
32
- message: error instanceof Error ? error.message : 'Delivery records could not be read.',
33
- }, 503)
31
+ return dashboardOperationalError(error, 'Delivery records could not be read.', 'CommerceDeliveryAction')
34
32
  }
35
33
  },
36
34
  })
@@ -1,6 +1,6 @@
1
1
  import { Action } from '@stacksjs/actions'
2
2
  import { Customer, GiftCard } from '@stacksjs/orm'
3
- import { response } from '@stacksjs/router'
3
+ import { dashboardOperationalError } from '../dashboard-response'
4
4
  import { normalizeGiftCardRecord, summarizeGiftCards } from './gift-card-records'
5
5
  import { commerceIdentifier, commerceValue } from './commerce-record'
6
6
 
@@ -26,9 +26,7 @@ export default new Action({
26
26
  }
27
27
  }
28
28
  catch (error) {
29
- return response.json({
30
- message: error instanceof Error ? error.message : 'Gift card records could not be read.',
31
- }, 503)
29
+ return dashboardOperationalError(error, 'Gift card records could not be read.', 'CommerceGiftCardsAction')
32
30
  }
33
31
  },
34
32
  })
@@ -1,7 +1,7 @@
1
1
  import { Action } from '@stacksjs/actions'
2
2
  import { config } from '@stacksjs/config'
3
3
  import { Coupon, Customer, Order, OrderItem } from '@stacksjs/orm'
4
- import { response } from '@stacksjs/router'
4
+ import { dashboardOperationalError } from '../dashboard-response'
5
5
  import {
6
6
  addOrderItemQuantity,
7
7
  normalizeCommerceOrderRecord,
@@ -54,9 +54,7 @@ export default new Action({
54
54
  }
55
55
  }
56
56
  catch (error) {
57
- return response.json({
58
- message: error instanceof Error ? error.message : 'Order records could not be read.',
59
- }, 503)
57
+ return dashboardOperationalError(error, 'Order records could not be read.', 'CommerceOrdersAction')
60
58
  }
61
59
  },
62
60
  })
@@ -1,6 +1,6 @@
1
1
  import { Action } from '@stacksjs/actions'
2
2
  import { Customer, Order, Payment } from '@stacksjs/orm'
3
- import { response } from '@stacksjs/router'
3
+ import { dashboardOperationalError } from '../dashboard-response'
4
4
  import { commerceIdentifier, commerceValue } from './commerce-record'
5
5
  import { normalizePaymentRecord, summarizePayments } from './payment-records'
6
6
 
@@ -32,9 +32,7 @@ export default new Action({
32
32
  }
33
33
  }
34
34
  catch (error) {
35
- return response.json({
36
- message: error instanceof Error ? error.message : 'Payment records could not be read.',
37
- }, 503)
35
+ return dashboardOperationalError(error, 'Payment records could not be read.', 'CommercePaymentsAction')
38
36
  }
39
37
  },
40
38
  })
@@ -1,7 +1,7 @@
1
1
  import { Action } from '@stacksjs/actions'
2
2
  import { config } from '@stacksjs/config'
3
3
  import { Category, Customer, Manufacturer, Product, TaxRate } from '@stacksjs/orm'
4
- import { response } from '@stacksjs/router'
4
+ import { dashboardOperationalError } from '../dashboard-response'
5
5
  import {
6
6
  isCommercePosCustomerActive,
7
7
  normalizeCommercePosCustomer,
@@ -63,9 +63,7 @@ export default new Action({
63
63
  }
64
64
  }
65
65
  catch (error) {
66
- return response.json({
67
- message: error instanceof Error ? error.message : 'Point of sale records could not be read.',
68
- }, 503)
66
+ return dashboardOperationalError(error, 'Point of sale records could not be read.', 'CommercePosAction')
69
67
  }
70
68
  },
71
69
  })