@stacksjs/defaults 0.75.318 → 0.75.320

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 (251) hide show
  1. package/ai/skills/stacks-browse/SKILL.md +8 -0
  2. package/ai/skills/stacks-browse/scripts/browse.ts +16 -6
  3. package/ai/skills/stacks-browse/scripts/chrome.ts +4 -0
  4. package/ai/skills/stacks-database/SKILL.md +1 -1
  5. package/ai/skills/stacks-migrations/SKILL.md +2 -2
  6. package/ai/skills/stacks-native/CATALOG.md +6 -6
  7. package/app/Actions/Cms/AuthorUpdateAction.ts +4 -4
  8. package/app/Actions/Cms/CategorizableUpdateAction.ts +4 -4
  9. package/app/Actions/Cms/PostUpdateAction.ts +4 -5
  10. package/app/Actions/Cms/TaggableUpdateAction.ts +4 -2
  11. package/app/Actions/Commerce/CustomerStoreAction.ts +4 -6
  12. package/app/Actions/Commerce/CustomerUpdateAction.ts +4 -6
  13. package/app/Actions/Commerce/PaymentStoreAction.ts +5 -8
  14. package/app/Actions/Commerce/PaymentUpdateAction.ts +20 -17
  15. package/app/Actions/Dashboard/Commerce/commerce-product-records.test.ts +7 -0
  16. package/app/Actions/Dashboard/Commerce/commerce-product-records.ts +14 -3
  17. package/app/Actions/Dashboard/Commerce/review-records.test.ts +12 -0
  18. package/app/Actions/Dashboard/Commerce/review-records.ts +10 -5
  19. package/app/Actions/Dashboard/Content/AuthorStoreAction.ts +2 -0
  20. package/app/Actions/Dashboard/Content/CategoryStoreAction.ts +4 -1
  21. package/app/Actions/Dashboard/Content/PageStoreAction.ts +2 -0
  22. package/app/Actions/Dashboard/Content/PostStoreAction.ts +2 -0
  23. package/app/Actions/Dashboard/Content/TagStoreAction.ts +2 -0
  24. package/app/Actions/Dashboard/Content/content-input.ts +9 -4
  25. package/app/Actions/Dashboard/Content/content-record-input.test.ts +20 -1
  26. package/app/Actions/Dashboard/Marketing/CampaignIndexAction.ts +1 -1
  27. package/app/Actions/Dashboard/Marketing/ListIndexAction.ts +1 -1
  28. package/app/Actions/Dashboard/Models/ModelStoreAction.ts +5 -1
  29. package/app/Actions/Dashboard/Models/ModelUpdateAction.ts +5 -1
  30. package/app/Actions/Dashboard/Notifications/NotificationDeliveryOverviewAction.ts +1 -1
  31. package/app/Actions/Storefront/AddToCartAction.ts +5 -2
  32. package/app/Actions/Storefront/PlaceOrderAction.ts +6 -11
  33. package/app/Actions/Storefront/SubmitReviewAction.ts +6 -5
  34. package/app/Models/Activity.ts +1 -0
  35. package/app/Models/AnalyticsEvent.ts +1 -0
  36. package/app/Models/AutomationRun.ts +1 -1
  37. package/app/Models/BoardColumn.ts +0 -1
  38. package/app/Models/Campaign.ts +1 -1
  39. package/app/Models/CampaignSend.ts +3 -3
  40. package/app/Models/Card.ts +0 -1
  41. package/app/Models/CardComment.ts +0 -2
  42. package/app/Models/Content/Author.ts +2 -0
  43. package/app/Models/Content/Categorizable.ts +1 -0
  44. package/app/Models/Content/Menu.ts +2 -0
  45. package/app/Models/Content/MenuItem.ts +2 -0
  46. package/app/Models/Content/PageRevision.ts +2 -0
  47. package/app/Models/Content/Post.ts +2 -0
  48. package/app/Models/Content/Redirect.ts +2 -0
  49. package/app/Models/EmailListSubscriber.ts +2 -2
  50. package/app/Models/Error.ts +5 -0
  51. package/app/Models/Forms/Form.ts +2 -0
  52. package/app/Models/Forms/FormField.ts +2 -0
  53. package/app/Models/Forms/FormSubmission.ts +2 -0
  54. package/app/Models/Label.ts +0 -1
  55. package/app/Models/Log.ts +3 -1
  56. package/app/Models/MagicLinkToken.ts +0 -7
  57. package/app/Models/PaymentProduct.ts +1 -0
  58. package/app/Models/PaymentTransaction.ts +1 -0
  59. package/app/Models/Release.ts +3 -0
  60. package/app/Models/Request.ts +1 -0
  61. package/app/Models/SenderDomain.ts +2 -0
  62. package/app/Models/SmsOptOut.ts +0 -7
  63. package/app/Models/Subscription.ts +2 -0
  64. package/app/Models/commerce/Auction.ts +1 -0
  65. package/app/Models/commerce/AuctionItem.ts +1 -0
  66. package/app/Models/commerce/Cart.ts +83 -2
  67. package/app/Models/commerce/Category.ts +5 -1
  68. package/app/Models/commerce/Coupon.ts +4 -1
  69. package/app/Models/commerce/DigitalDelivery.ts +1 -0
  70. package/app/Models/commerce/GiftCard.ts +1 -0
  71. package/app/Models/commerce/LoyaltyPoint.ts +1 -0
  72. package/app/Models/commerce/LoyaltyReward.ts +3 -1
  73. package/app/Models/commerce/Order.ts +4 -1
  74. package/app/Models/commerce/OrderItem.ts +1 -0
  75. package/app/Models/commerce/Payment.ts +1 -0
  76. package/app/Models/commerce/Product.ts +15 -0
  77. package/app/Models/commerce/ProductVariant.ts +2 -0
  78. package/app/Models/commerce/Receipt.ts +1 -0
  79. package/app/Models/commerce/Review.ts +3 -1
  80. package/app/Models/commerce/Transaction.ts +1 -0
  81. package/app/Models/realtime/Websocket.ts +4 -1
  82. package/functions/dashboard-form.test.ts +10 -1
  83. package/functions/dashboard-form.ts +17 -0
  84. package/ide/vscode/package.json +1 -1
  85. package/package.json +4 -4
  86. package/resources/components/Dashboard/Analytics/AnalyticsPageHeader.stx +1 -1
  87. package/resources/components/Dashboard/App/RequestDetailsDialog.stx +1 -1
  88. package/resources/components/Dashboard/Auth/AccessTokenCreateDialog.stx +1 -1
  89. package/resources/components/Dashboard/Auth/ForgotPassword.stx +1 -1
  90. package/resources/components/Dashboard/Auth/Login.stx +1 -1
  91. package/resources/components/Dashboard/Auth/Register.stx +1 -1
  92. package/resources/components/Dashboard/Auth/ResetPassword.stx +1 -1
  93. package/resources/components/Dashboard/Auth/TwoFactorLogin.stx +1 -1
  94. package/resources/components/Dashboard/CodeEditor.stx +1 -1
  95. package/resources/components/Dashboard/Commerce/CommerceCategoriesDashboard.stx +1 -1
  96. package/resources/components/Dashboard/Commerce/CommerceCategoriesTable.stx +1 -1
  97. package/resources/components/Dashboard/Commerce/CommerceCategoryDeleteDialog.stx +1 -1
  98. package/resources/components/Dashboard/Commerce/CommerceCategoryDetailsDialog.stx +1 -1
  99. package/resources/components/Dashboard/Commerce/CommerceCategoryDialog.stx +4 -13
  100. package/resources/components/Dashboard/Commerce/CommerceCouponDeleteDialog.stx +1 -1
  101. package/resources/components/Dashboard/Commerce/CommerceCouponDetailsDialog.stx +1 -1
  102. package/resources/components/Dashboard/Commerce/CommerceCouponDialog.stx +1 -1
  103. package/resources/components/Dashboard/Commerce/CommerceCouponsTable.stx +1 -1
  104. package/resources/components/Dashboard/Commerce/CommerceCustomerDeleteDialog.stx +1 -1
  105. package/resources/components/Dashboard/Commerce/CommerceCustomerDetailsDialog.stx +1 -1
  106. package/resources/components/Dashboard/Commerce/CommerceCustomerDialog.stx +1 -1
  107. package/resources/components/Dashboard/Commerce/CommerceCustomersTable.stx +1 -1
  108. package/resources/components/Dashboard/Commerce/CommerceGiftCardDeleteDialog.stx +1 -1
  109. package/resources/components/Dashboard/Commerce/CommerceGiftCardDetailsDialog.stx +1 -1
  110. package/resources/components/Dashboard/Commerce/CommerceGiftCardDialog.stx +1 -1
  111. package/resources/components/Dashboard/Commerce/CommerceGiftCardsTable.stx +1 -1
  112. package/resources/components/Dashboard/Commerce/CommerceManufacturerDeleteDialog.stx +1 -1
  113. package/resources/components/Dashboard/Commerce/CommerceManufacturerDialog.stx +1 -1
  114. package/resources/components/Dashboard/Commerce/CommerceManufacturersTable.stx +1 -1
  115. package/resources/components/Dashboard/Commerce/CommerceOrderDeleteDialog.stx +1 -1
  116. package/resources/components/Dashboard/Commerce/CommerceOrderDetailsDialog.stx +1 -1
  117. package/resources/components/Dashboard/Commerce/CommerceOrderDialog.stx +1 -1
  118. package/resources/components/Dashboard/Commerce/CommerceOrdersTable.stx +1 -1
  119. package/resources/components/Dashboard/Commerce/CommercePaymentsTable.stx +1 -1
  120. package/resources/components/Dashboard/Commerce/CommercePosCart.stx +1 -1
  121. package/resources/components/Dashboard/Commerce/CommercePosCatalog.stx +1 -1
  122. package/resources/components/Dashboard/Commerce/CommercePosCheckoutDialog.stx +1 -1
  123. package/resources/components/Dashboard/Commerce/CommercePosReceiptDialog.stx +1 -1
  124. package/resources/components/Dashboard/Commerce/CommerceProductDeleteDialog.stx +1 -1
  125. package/resources/components/Dashboard/Commerce/CommerceProductDialog.stx +27 -2
  126. package/resources/components/Dashboard/Commerce/CommerceProductsTable.stx +1 -1
  127. package/resources/components/Dashboard/Commerce/CommerceReviewDeleteDialog.stx +1 -1
  128. package/resources/components/Dashboard/Commerce/CommerceReviewEditDialog.stx +7 -5
  129. package/resources/components/Dashboard/Commerce/CommerceReviewsDashboard.stx +12 -4
  130. package/resources/components/Dashboard/Commerce/CommerceReviewsTable.stx +3 -3
  131. package/resources/components/Dashboard/Commerce/CommerceTaxDeleteDialog.stx +1 -1
  132. package/resources/components/Dashboard/Commerce/CommerceTaxDialog.stx +1 -1
  133. package/resources/components/Dashboard/Commerce/CommerceTaxesTable.stx +1 -1
  134. package/resources/components/Dashboard/Commerce/CommerceUnitDeleteDialog.stx +1 -1
  135. package/resources/components/Dashboard/Commerce/CommerceUnitDialog.stx +1 -1
  136. package/resources/components/Dashboard/Commerce/CommerceUnitsTable.stx +1 -1
  137. package/resources/components/Dashboard/Commerce/CommerceVariantDeleteDialog.stx +1 -1
  138. package/resources/components/Dashboard/Commerce/CommerceVariantDialog.stx +1 -1
  139. package/resources/components/Dashboard/Commerce/CommerceVariantsTable.stx +1 -1
  140. package/resources/components/Dashboard/Commerce/Delivery/CourierDeleteDialog.stx +1 -1
  141. package/resources/components/Dashboard/Commerce/Delivery/CourierDialog.stx +1 -1
  142. package/resources/components/Dashboard/Commerce/Delivery/CouriersTable.stx +1 -1
  143. package/resources/components/Dashboard/Commerce/Delivery/DeliveryPagination.stx +1 -1
  144. package/resources/components/Dashboard/Commerce/Delivery/DeliveryRouteDeleteDialog.stx +1 -1
  145. package/resources/components/Dashboard/Commerce/Delivery/DeliveryRouteDialog.stx +1 -1
  146. package/resources/components/Dashboard/Commerce/Delivery/DeliveryRoutesTable.stx +1 -1
  147. package/resources/components/Dashboard/Commerce/Delivery/DigitalDeliveryDeleteDialog.stx +1 -1
  148. package/resources/components/Dashboard/Commerce/Delivery/DigitalDeliveryDialog.stx +1 -1
  149. package/resources/components/Dashboard/Commerce/Delivery/DigitalDeliveryTable.stx +1 -1
  150. package/resources/components/Dashboard/Commerce/Delivery/LicenseKeyDeleteDialog.stx +1 -1
  151. package/resources/components/Dashboard/Commerce/Delivery/LicenseKeyDialog.stx +1 -1
  152. package/resources/components/Dashboard/Commerce/Delivery/LicenseKeysTable.stx +1 -1
  153. package/resources/components/Dashboard/Commerce/Delivery/ShippingMethodDeleteDialog.stx +1 -1
  154. package/resources/components/Dashboard/Commerce/Delivery/ShippingMethodDialog.stx +1 -1
  155. package/resources/components/Dashboard/Commerce/Delivery/ShippingMethodsTable.stx +1 -1
  156. package/resources/components/Dashboard/Commerce/Delivery/ShippingRateDeleteDialog.stx +1 -1
  157. package/resources/components/Dashboard/Commerce/Delivery/ShippingRateDialog.stx +1 -1
  158. package/resources/components/Dashboard/Commerce/Delivery/ShippingRatesTable.stx +1 -1
  159. package/resources/components/Dashboard/Commerce/Delivery/ShippingZoneDeleteDialog.stx +1 -1
  160. package/resources/components/Dashboard/Commerce/Delivery/ShippingZoneDialog.stx +1 -1
  161. package/resources/components/Dashboard/Commerce/Delivery/ShippingZonesTable.stx +1 -1
  162. package/resources/components/Dashboard/Commerce/PaymentDetailsDialog.stx +1 -1
  163. package/resources/components/Dashboard/Commerce/PaymentRefundDialog.stx +1 -1
  164. package/resources/components/Dashboard/Commerce/PrintDeviceDeleteDialog.stx +1 -1
  165. package/resources/components/Dashboard/Commerce/PrintDeviceDetailsDialog.stx +1 -1
  166. package/resources/components/Dashboard/Commerce/PrintDeviceDialog.stx +1 -1
  167. package/resources/components/Dashboard/Commerce/PrintDevicesTable.stx +1 -1
  168. package/resources/components/Dashboard/Commerce/PrintLogDeleteDialog.stx +1 -1
  169. package/resources/components/Dashboard/Commerce/PrintLogDetailsDialog.stx +1 -1
  170. package/resources/components/Dashboard/Commerce/PrintLogsTable.stx +1 -1
  171. package/resources/components/Dashboard/Commerce/ProductWaitlistDeleteDialog.stx +1 -1
  172. package/resources/components/Dashboard/Commerce/ProductWaitlistEntryDialog.stx +1 -1
  173. package/resources/components/Dashboard/Commerce/ProductWaitlistTable.stx +1 -1
  174. package/resources/components/Dashboard/Commerce/RestaurantWaitlistDeleteDialog.stx +1 -1
  175. package/resources/components/Dashboard/Commerce/RestaurantWaitlistEntryDialog.stx +1 -1
  176. package/resources/components/Dashboard/Commerce/RestaurantWaitlistTable.stx +1 -1
  177. package/resources/components/Dashboard/Content/AuthorEditorDialog.stx +1 -1
  178. package/resources/components/Dashboard/Content/BlogPostEditorDialog.stx +1 -1
  179. package/resources/components/Dashboard/Content/CreateFolderDialog.stx +1 -1
  180. package/resources/components/Dashboard/Content/FileDetailsDrawer.stx +1 -1
  181. package/resources/components/Dashboard/Content/FileUploadDialog.stx +1 -1
  182. package/resources/components/Dashboard/Content/PageEditorDialog.stx +1 -1
  183. package/resources/components/Dashboard/Content/PostEditorDialog.stx +1 -1
  184. package/resources/components/Dashboard/Content/SeoEditorDialog.stx +1 -1
  185. package/resources/components/Dashboard/Content/TaxonomyEditorDialog.stx +1 -1
  186. package/resources/components/Dashboard/CreateRecordModal.stx +1 -1
  187. package/resources/components/Dashboard/Deployments/DeploymentPreviewDialog.stx +1 -1
  188. package/resources/components/Dashboard/Elements/DropdownNavigation.stx +1 -1
  189. package/resources/components/Dashboard/Email/CapturedMailList.stx +1 -1
  190. package/resources/components/Dashboard/Email/EmailSettingsList.stx +1 -1
  191. package/resources/components/Dashboard/Email/EmailSettingsNavigation.stx +1 -1
  192. package/resources/components/Dashboard/Email/EmailSidebar.stx +1 -1
  193. package/resources/components/Dashboard/Email/InboxComposer.stx +1 -1
  194. package/resources/components/Dashboard/Email/InboxMessageDetail.stx +1 -1
  195. package/resources/components/Dashboard/Email/InboxMessageList.stx +1 -1
  196. package/resources/components/Dashboard/Infrastructure/CloudDetailsDialog.stx +1 -1
  197. package/resources/components/Dashboard/Infrastructure/CloudResourceButton.stx +1 -1
  198. package/resources/components/Dashboard/Infrastructure/DnsRecordDialog.stx +1 -1
  199. package/resources/components/Dashboard/Infrastructure/LogDetailsDialog.stx +1 -1
  200. package/resources/components/Dashboard/Infrastructure/MailboxDetailsDialog.stx +1 -1
  201. package/resources/components/Dashboard/Infrastructure/ServerInspectorDialog.stx +1 -1
  202. package/resources/components/Dashboard/Jobs/JobTable.stx +1 -1
  203. package/resources/components/Dashboard/Library/SourceScaffoldDialog.stx +1 -1
  204. package/resources/components/Dashboard/Management/AssignmentDrawer.stx +1 -1
  205. package/resources/components/Dashboard/Marketing/CampaignDeleteDialog.stx +1 -1
  206. package/resources/components/Dashboard/Marketing/CampaignDeliveryDialog.stx +1 -1
  207. package/resources/components/Dashboard/Marketing/CampaignDialog.stx +1 -1
  208. package/resources/components/Dashboard/Marketing/CampaignsTable.stx +1 -1
  209. package/resources/components/Dashboard/Marketing/MarketingListDeleteDialog.stx +1 -1
  210. package/resources/components/Dashboard/Marketing/MarketingListDialog.stx +1 -1
  211. package/resources/components/Dashboard/Marketing/MarketingListsTable.stx +1 -1
  212. package/resources/components/Dashboard/Marketing/RecoveryCampaignDialog.stx +1 -1
  213. package/resources/components/Dashboard/Marketing/SocialPostDeleteDialog.stx +1 -1
  214. package/resources/components/Dashboard/Marketing/SocialPostDialog.stx +1 -1
  215. package/resources/components/Dashboard/Marketing/SocialPostsTable.stx +1 -1
  216. package/resources/components/Dashboard/MobileSidebar.stx +1 -1
  217. package/resources/components/Dashboard/Modals/BaseModal.stx +1 -1
  218. package/resources/components/Dashboard/Modals/Popups/Alert.stx +1 -1
  219. package/resources/components/Dashboard/Modals/Popups/Toast.stx +1 -1
  220. package/resources/components/Dashboard/Modals/ToastWrapper.stx +1 -1
  221. package/resources/components/Dashboard/Models/EditRecordModal.stx +1 -1
  222. package/resources/components/Dashboard/Models/ModelRecordActions.stx +1 -1
  223. package/resources/components/Dashboard/Monitoring/ErrorTable.stx +1 -1
  224. package/resources/components/Dashboard/Notifications/NotificationDeliveryDialog.stx +1 -1
  225. package/resources/components/Dashboard/Notifications/NotificationDeliveryTable.stx +1 -1
  226. package/resources/components/Dashboard/Operations/RecoveryDestinationDialog.stx +1 -1
  227. package/resources/components/Dashboard/Operations/RecoveryPointTable.stx +1 -1
  228. package/resources/components/Dashboard/Operations/RecoveryPolicyDialog.stx +1 -1
  229. package/resources/components/Dashboard/Operations/RecoveryRestoreDialog.stx +1 -1
  230. package/resources/components/Dashboard/Operations/SchedulerTaskTable.stx +1 -1
  231. package/resources/components/Dashboard/UI/Checkbox.stx +1 -1
  232. package/resources/components/Dashboard/UI/ConfirmDialog.stx +1 -1
  233. package/resources/components/Dashboard/UI/Drawer.stx +1 -1
  234. package/resources/components/Dashboard/UI/Dropdown.stx +1 -1
  235. package/resources/components/Dashboard/UI/Input.stx +1 -1
  236. package/resources/components/Dashboard/UI/Modal.stx +1 -1
  237. package/resources/components/Dashboard/UI/Pagination.stx +1 -1
  238. package/resources/components/Dashboard/UI/SearchFilter.stx +1 -1
  239. package/resources/components/Dashboard/UI/Select.stx +1 -1
  240. package/resources/components/Dashboard/UI/Table.stx +1 -1
  241. package/resources/components/Dashboard/UI/Tabs.stx +1 -1
  242. package/resources/components/Dashboard/UI/Textarea.stx +1 -1
  243. package/resources/components/Dashboard/UI/Toggle.stx +1 -1
  244. package/resources/components/Dashboard/UI/WindowControls.stx +1 -1
  245. package/resources/components/NativeActionSheet.stx +1 -1
  246. package/resources/components/NativeContextMenu.stx +1 -1
  247. package/resources/components/NativeHealthButton.stx +1 -1
  248. package/resources/components/NativePermissionButton.stx +1 -1
  249. package/resources/components/NativePullToRefresh.stx +1 -1
  250. package/resources/components/NativeSegmentedControl.stx +1 -1
  251. package/resources/components/NativeSheet.stx +1 -1
@@ -53,6 +53,8 @@ export default new Action({
53
53
  created_at: now,
54
54
  updated_at: now,
55
55
  } as any)
56
+ // PostgreSQL reports no insert id without RETURNING (#3004).
57
+ .returningAll()
56
58
  .executeTakeFirst()
57
59
 
58
60
  const id = insertedId(result)
@@ -20,11 +20,16 @@ export function timestamp(): string {
20
20
  return new Date().toISOString().slice(0, 19).replace('T', ' ')
21
21
  }
22
22
 
23
- /** The new row's id, across the driver shapes we may get back from an insert. */
23
+ /**
24
+ * The new row's id, across the driver shapes an insert answers with: the row
25
+ * itself from `returningAll()` (PostgreSQL, which reports no id at all without
26
+ * RETURNING, and gives a BIGSERIAL id back as a string), `lastInsertRowid`
27
+ * (SQLite) or `insertId` (MySQL). 0 when none of them names a row.
28
+ */
24
29
  export function insertedId(result: unknown): number {
25
- const row = result as { lastInsertRowid?: number | bigint, insertId?: number | bigint } | undefined
26
-
27
- return Number(row?.lastInsertRowid ?? row?.insertId ?? 0)
30
+ const answer = (Array.isArray(result) ? result[0] : result) as { id?: unknown, lastInsertRowid?: unknown, insertId?: unknown } | undefined
31
+ const id = Number(answer?.id ?? answer?.lastInsertRowid ?? answer?.insertId ?? 0)
32
+ return Number.isSafeInteger(id) && id > 0 ? id : 0
28
33
  }
29
34
 
30
35
  /** The `{id}` route param, or 0 when it is not a usable row id. */
@@ -1,6 +1,6 @@
1
1
  import type { RequestInstance } from '@stacksjs/types'
2
2
  import { describe, expect, test } from 'bun:test'
3
- import { rowId } from './content-input'
3
+ import { insertedId, rowId } from './content-input'
4
4
  import { parseAuthorInput } from './author-input'
5
5
  import { parsePageInput, parsePublished } from './page-input'
6
6
 
@@ -50,3 +50,22 @@ test('content identifiers use the shared safe-integer contract', () => {
50
50
  for (const value of [true, {}, [], Number.MAX_SAFE_INTEGER + 1]) expect(rowId({ getParam: () => value } as unknown as RequestInstance)).toBe(0)
51
51
  expect(rowId({ getParam: () => '42' } as unknown as RequestInstance)).toBe(42)
52
52
  })
53
+
54
+ // What an insert answers with on each driver (stacksjs/stacks#3004).
55
+ describe('the id of an inserted content row', () => {
56
+ test('reads the row returningAll() gives back, with PostgreSQL\'s string id', () => {
57
+ expect(insertedId({ id: '64', name: 'Example' })).toBe(64)
58
+ expect(insertedId([{ id: 7 }])).toBe(7)
59
+ })
60
+
61
+ test('reads SQLite\'s and MySQL\'s insert reports', () => {
62
+ expect(insertedId({ changes: 1, lastInsertRowid: 12 })).toBe(12)
63
+ expect(insertedId({ insertId: 9n })).toBe(9)
64
+ })
65
+
66
+ test('is 0 when nothing names a row, as PostgreSQL answers without RETURNING', () => {
67
+ expect(insertedId([])).toBe(0)
68
+ expect(insertedId(undefined)).toBe(0)
69
+ expect(insertedId({ id: 'not-an-id' })).toBe(0)
70
+ })
71
+ })
@@ -32,7 +32,7 @@ export default new Action({
32
32
  db
33
33
  .selectFrom('campaign_sends')
34
34
  .select(['campaign_id', 'status', db.fn.count('id').as('count')])
35
- .groupBy(['campaign_id', 'status'])
35
+ .groupBy('campaign_id', 'status')
36
36
  .execute(),
37
37
  db
38
38
  .selectFrom('campaign_sends')
@@ -22,7 +22,7 @@ export default new Action({
22
22
  db
23
23
  .selectFrom('email_list_subscribers')
24
24
  .select(['email_list_id', 'status', db.fn.count('id').as('count')])
25
- .groupBy(['email_list_id', 'status'])
25
+ .groupBy('email_list_id', 'status')
26
26
  .execute(),
27
27
  db
28
28
  .selectFrom('email_list_subscribers')
@@ -1,6 +1,6 @@
1
1
  import type { RequestInstance } from '@stacksjs/types'
2
2
  import { Action } from '@stacksjs/actions/runtime'
3
- import { toSnakeCaseKeys } from '@stacksjs/orm'
3
+ import { ModelValidationError, toSnakeCaseKeys } from '@stacksjs/orm'
4
4
  import { response } from '@stacksjs/router'
5
5
  import { dashboardOperationalError } from '../dashboard-response'
6
6
  import { prepareModelFields, resolveWritableModel } from './model-write'
@@ -35,6 +35,10 @@ export default new Action({
35
35
  return { ok: true, id }
36
36
  }
37
37
  catch (error) {
38
+ // The model's own rules rejected the row: the caller's mistake, named
39
+ // field by field, not a server failure (stacksjs/stacks#3007).
40
+ if (error instanceof ModelValidationError)
41
+ return response.json({ message: 'Validation failed.', errors: error.errors }, 422)
38
42
  return dashboardOperationalError(error, 'The model record could not be created.', 'ModelStoreAction', 500)
39
43
  }
40
44
  },
@@ -1,6 +1,6 @@
1
1
  import type { RequestInstance } from '@stacksjs/types'
2
2
  import { Action } from '@stacksjs/actions/runtime'
3
- import { toSnakeCaseKeys } from '@stacksjs/orm'
3
+ import { ModelValidationError, toSnakeCaseKeys } from '@stacksjs/orm'
4
4
  import { response } from '@stacksjs/router'
5
5
  import { dashboardOperationalError } from '../dashboard-response'
6
6
  import { parseRowId, prepareModelFields, resolveWritableModel } from './model-write'
@@ -55,6 +55,10 @@ export default new Action({
55
55
  return { ok: true, id, changed: Object.keys(changes) }
56
56
  }
57
57
  catch (error) {
58
+ // The model's own rules rejected the row: the caller's mistake, named
59
+ // field by field, not a server failure (stacksjs/stacks#3007).
60
+ if (error instanceof ModelValidationError)
61
+ return response.json({ message: 'Validation failed.', errors: error.errors }, 422)
58
62
  return dashboardOperationalError(error, 'The model record could not be updated.', 'ModelUpdateAction', 500)
59
63
  }
60
64
  },
@@ -42,7 +42,7 @@ async function notificationDeliveryOverview() {
42
42
  db
43
43
  .selectFrom('notification_deliveries')
44
44
  .select(['channel', 'status', db.fn.count('id').as('count')])
45
- .groupBy(['channel', 'status'])
45
+ .groupBy('channel', 'status')
46
46
  .execute() as unknown as Promise<ChannelStatusCountRow[]>,
47
47
  db
48
48
  .selectFrom('notification_deliveries')
@@ -1,6 +1,6 @@
1
1
  import { Action } from '@stacksjs/actions/runtime'
2
2
  import { config } from '@stacksjs/config'
3
- import { db } from '@stacksjs/database/runtime'
3
+ import { db, sqlDateTime } from '@stacksjs/database/runtime'
4
4
  import { response } from '@stacksjs/router'
5
5
  import { randomUUIDv7 } from 'bun'
6
6
  import { readCartCookie, writeCartCookie } from '../../Storefront/CartCookie'
@@ -41,7 +41,8 @@ export default new Action({
41
41
  const product = await (db as any)
42
42
  .selectFrom('products')
43
43
  .where('slug', '=', slug)
44
- .where('is_available', '=', 1)
44
+ // A boolean, not 1: PostgreSQL has no boolean = integer (#3005).
45
+ .where('is_available', '=', true)
45
46
  .selectAll()
46
47
  .executeTakeFirst()
47
48
 
@@ -70,6 +71,8 @@ export default new Action({
70
71
  currency: storefrontCurrency(config.commerce?.currency),
71
72
  session_token: token,
72
73
  checkout_step: 'cart',
74
+ // The cart lasts as long as the cookie that finds it (#3017).
75
+ expires_at: sqlDateTime(new Date(Date.now() + COOKIE_OPTS.maxAge * 1000)),
73
76
  })
74
77
  .execute()
75
78
  // Re-read by session_token: bun-query-builder's `returningAll()`
@@ -1,4 +1,6 @@
1
+ import { randomUUIDv7 } from 'bun'
1
2
  import { Action } from '@stacksjs/actions/runtime'
3
+ import { customers } from '@stacksjs/commerce'
2
4
  import { config } from '@stacksjs/config'
3
5
  import { db } from '@stacksjs/database/runtime'
4
6
  import { response } from '@stacksjs/router'
@@ -62,17 +64,10 @@ export default new Action({
62
64
  .selectAll()
63
65
  .executeTakeFirst()
64
66
 
65
- if (!customer) {
66
- await (db as any)
67
- .insertInto('customers')
68
- .values({ name: cart.shipping_name, email: cart.email })
69
- .execute()
70
- customer = await (db as any)
71
- .selectFrom('customers')
72
- .where('email', '=', cart.email)
73
- .selectAll()
74
- .executeTakeFirst()
75
- }
67
+ // Through commerce's store, which fills what the table requires and the
68
+ // checkout does not ask for, such as the avatar (#3017).
69
+ if (!customer)
70
+ customer = await customers.store({ name: cart.shipping_name, email: cart.email, uuid: randomUUIDv7() } as never)
76
71
 
77
72
  // Integer minor units of the cart's currency, like every amount here.
78
73
  const currency = storefrontCurrency(cart.currency)
@@ -14,9 +14,9 @@ const CART_COOKIE = 'stacks_cart'
14
14
  * `email` on the order against the `email` they're posting with,
15
15
  * and additionally accepts a still-valid cart cookie path for
16
16
  * guest shoppers who haven't enrolled in account auth.
17
- * - Marks `is_verified_purchase=1` only when that email-on-order
17
+ * - Marks `is_verified_purchase` true only when that email-on-order
18
18
  * check passes.
19
- * - Always inserts with `is_approved=0`. The dashboard's reviews
19
+ * - Always inserts with `is_approved` false. The dashboard's reviews
20
20
  * index shows pending entries to admins for moderation.
21
21
  *
22
22
  * Spam handling: rate-limit per IP (5/min) so a hostile actor can't
@@ -123,9 +123,10 @@ export default new Action({
123
123
  rating,
124
124
  title,
125
125
  content,
126
- is_verified_purchase: 1,
127
- is_approved: 0,
128
- is_featured: 0,
126
+ // Booleans, which PostgreSQL requires of these columns (#3005).
127
+ is_verified_purchase: true,
128
+ is_approved: false,
129
+ is_featured: false,
129
130
  helpful_votes: 0,
130
131
  unhelpful_votes: 0,
131
132
  purchase_date: order.created_at || new Date().toISOString(),
@@ -78,6 +78,7 @@ export default defineModel({
78
78
  properties: {
79
79
  required: false,
80
80
  fillable: true,
81
+ type: 'text',
81
82
  validation: {
82
83
  rule: schema.string(),
83
84
  },
@@ -67,6 +67,7 @@ export default defineModel({
67
67
 
68
68
  properties: {
69
69
  fillable: true,
70
+ type: 'text',
70
71
  validation: {
71
72
  rule: schema.string(),
72
73
  },
@@ -21,6 +21,6 @@ export default defineModel({
21
21
  idempotencyKey: { required: true, fillable: true, validation: { rule: schema.string().max(255) }, factory: faker => faker.string.uuid() },
22
22
  startedAt: { required: false, fillable: true, validation: { rule: schema.timestamp() }, factory: () => null },
23
23
  finishedAt: { required: false, fillable: true, validation: { rule: schema.timestamp() }, factory: () => null },
24
- error: { required: false, fillable: true, validation: { rule: schema.string() }, factory: () => null },
24
+ error: { type: 'text', required: false, fillable: true, validation: { rule: schema.string() }, factory: () => null },
25
25
  },
26
26
  } as const)
@@ -50,7 +50,6 @@ export default defineModel({
50
50
  order: 1,
51
51
  fillable: true,
52
52
  validation: { rule: schema.number().required() },
53
- factory: () => 1,
54
53
  },
55
54
 
56
55
  name: {
@@ -103,6 +103,7 @@ export default defineModel({
103
103
  text: {
104
104
  required: false,
105
105
  fillable: true,
106
+ type: 'text',
106
107
  validation: {
107
108
  rule: schema.string(),
108
109
  },
@@ -197,7 +198,6 @@ export default defineModel({
197
198
  validation: {
198
199
  rule: schema.number(),
199
200
  },
200
- factory: faker => faker.number.int({ min: 1, max: 8 }),
201
201
  },
202
202
 
203
203
  scheduledAt: {
@@ -17,6 +17,8 @@ export default defineModel({
17
17
  ],
18
18
 
19
19
  traits: {
20
+ // So a fresh seed fills its dashboard page (stacksjs/stacks#3000).
21
+ useSeeder: { count: 20 },
20
22
  gdpr: { subject: { via: 'Subscriber' }, erasure: 'anonymize', basis: 'legitimate_interests', purpose: 'Campaign delivery statistics' },
21
23
  useUuid: true,
22
24
  useTimestamps: true,
@@ -37,7 +39,6 @@ export default defineModel({
37
39
  validation: {
38
40
  rule: schema.number(),
39
41
  },
40
- factory: faker => faker.number.int({ min: 1, max: 10 }),
41
42
  },
42
43
 
43
44
  subscriberId: {
@@ -46,7 +47,6 @@ export default defineModel({
46
47
  validation: {
47
48
  rule: schema.number(),
48
49
  },
49
- factory: () => null,
50
50
  },
51
51
 
52
52
  emailListId: {
@@ -55,7 +55,6 @@ export default defineModel({
55
55
  validation: {
56
56
  rule: schema.number(),
57
57
  },
58
- factory: () => null,
59
58
  },
60
59
 
61
60
  status: {
@@ -112,6 +111,7 @@ export default defineModel({
112
111
  error: {
113
112
  required: false,
114
113
  fillable: true,
114
+ type: 'text',
115
115
  validation: {
116
116
  rule: schema.string(),
117
117
  },
@@ -62,7 +62,6 @@ export default defineModel({
62
62
  // query stays single-table. The kanban API keeps it in sync on
63
63
  // card create/move (Phase 2).
64
64
  validation: { rule: schema.number().required() },
65
- factory: () => 1,
66
65
  },
67
66
 
68
67
  title: {
@@ -47,14 +47,12 @@ export default defineModel({
47
47
  order: 1,
48
48
  fillable: true,
49
49
  validation: { rule: schema.number().required() },
50
- factory: () => 1,
51
50
  },
52
51
 
53
52
  userId: {
54
53
  order: 2,
55
54
  fillable: true,
56
55
  validation: { rule: schema.number() },
57
- factory: () => null,
58
56
  },
59
57
 
60
58
  body: {
@@ -16,6 +16,8 @@ export default defineModel({
16
16
  ],
17
17
 
18
18
  traits: {
19
+ // So a fresh seed fills its dashboard page (stacksjs/stacks#3000).
20
+ useSeeder: { count: 5 },
19
21
  gdpr: { erasure: 'anonymize', basis: 'legitimate_interests', purpose: 'Byline on published content' },
20
22
  useAuth: {
21
23
  usePasskey: true,
@@ -47,6 +47,7 @@ export default defineModel({
47
47
  required: false,
48
48
  nullable: true,
49
49
  fillable: true,
50
+ type: 'text',
50
51
  validation: { rule: schema.string() },
51
52
  },
52
53
  isActive: {
@@ -25,6 +25,8 @@ export default defineModel({
25
25
  ownership: siteOwnership(),
26
26
 
27
27
  traits: {
28
+ // So a fresh seed fills its dashboard page (stacksjs/stacks#3000).
29
+ useSeeder: { count: 3 },
28
30
  useTimestamps: true,
29
31
  useApi: {
30
32
  middleware: ['auth'],
@@ -17,6 +17,8 @@ export default defineModel({
17
17
  ownership: parentOwnership('Menu', 'menu_id'),
18
18
 
19
19
  traits: {
20
+ // So a fresh seed fills its dashboard page (stacksjs/stacks#3000).
21
+ useSeeder: { count: 10 },
20
22
  useTimestamps: true,
21
23
  useApi: {
22
24
  middleware: ['auth'],
@@ -25,6 +25,8 @@ export default defineModel({
25
25
  ],
26
26
 
27
27
  traits: {
28
+ // So a fresh seed fills its dashboard page (stacksjs/stacks#3000).
29
+ useSeeder: { count: 10 },
28
30
  useTimestamps: true,
29
31
  },
30
32
 
@@ -12,6 +12,8 @@ export default defineModel({
12
12
  ownership: siteOwnership(),
13
13
 
14
14
  traits: {
15
+ // So a fresh seed fills its dashboard page (stacksjs/stacks#3000).
16
+ useSeeder: { count: 10 },
15
17
  useUuid: true,
16
18
  useTimestamps: true,
17
19
  useSearch: {
@@ -26,6 +26,8 @@ export default defineModel({
26
26
  ownership: siteOwnership(),
27
27
 
28
28
  traits: {
29
+ // So a fresh seed fills its dashboard page (stacksjs/stacks#3000).
30
+ useSeeder: { count: 5 },
29
31
  useTimestamps: true,
30
32
  useApi: {
31
33
  middleware: ['auth'],
@@ -18,6 +18,8 @@ export default defineModel({
18
18
  belongsTo: ['EmailList', 'Subscriber'],
19
19
 
20
20
  traits: {
21
+ // So a fresh seed fills its dashboard page (stacksjs/stacks#3000).
22
+ useSeeder: { count: 20 },
21
23
  gdpr: { subject: { via: 'Subscriber' }, erasure: 'delete', basis: 'consent', purpose: 'Mailing list membership' },
22
24
  useUuid: true,
23
25
  useTimestamps: true,
@@ -39,7 +41,6 @@ export default defineModel({
39
41
  validation: {
40
42
  rule: schema.number(),
41
43
  },
42
- factory: faker => faker.number.int({ min: 1, max: 8 }),
43
44
  },
44
45
 
45
46
  subscriberId: {
@@ -48,7 +49,6 @@ export default defineModel({
48
49
  validation: {
49
50
  rule: schema.number(),
50
51
  },
51
- factory: faker => faker.number.int({ min: 1, max: 100 }),
52
52
  },
53
53
 
54
54
  status: {
@@ -15,6 +15,8 @@ export default defineModel({
15
15
  ],
16
16
 
17
17
  traits: {
18
+ // So a fresh seed fills its dashboard page (stacksjs/stacks#3000).
19
+ useSeeder: { count: 10 },
18
20
  useTimestamps: true,
19
21
  },
20
22
 
@@ -32,6 +34,7 @@ export default defineModel({
32
34
 
33
35
  message: {
34
36
  fillable: true,
37
+ type: 'text',
35
38
  validation: {
36
39
  rule: schema.string().required(),
37
40
  message: {
@@ -43,6 +46,7 @@ export default defineModel({
43
46
 
44
47
  stack: {
45
48
  fillable: true,
49
+ type: 'text',
46
50
  validation: {
47
51
  rule: schema.string(),
48
52
  message: {
@@ -86,6 +90,7 @@ export default defineModel({
86
90
 
87
91
  additional_info: {
88
92
  fillable: true,
93
+ type: 'text',
89
94
  validation: {
90
95
  rule: schema.string(),
91
96
  message: {
@@ -28,6 +28,8 @@ export default defineModel({
28
28
  ownership: siteOwnership(),
29
29
 
30
30
  traits: {
31
+ // So a fresh seed fills its dashboard page (stacksjs/stacks#3000).
32
+ useSeeder: { count: 3 },
31
33
  useUuid: true,
32
34
  useTimestamps: true,
33
35
  useApi: {
@@ -27,6 +27,8 @@ export default defineModel({
27
27
  ownership: parentOwnership('Form', 'form_id'),
28
28
 
29
29
  traits: {
30
+ // So a fresh seed fills its dashboard page (stacksjs/stacks#3000).
31
+ useSeeder: { count: 10 },
30
32
  useTimestamps: true,
31
33
  useApi: {
32
34
  middleware: ['auth'],
@@ -18,6 +18,8 @@ export default defineModel({
18
18
  autoIncrement: true,
19
19
 
20
20
  traits: {
21
+ // So a fresh seed fills its dashboard page (stacksjs/stacks#3000).
22
+ useSeeder: { count: 10 },
21
23
  gdpr: { subject: { email: 'email' }, erasure: 'anonymize', basis: 'consent', purpose: 'Form submissions' },
22
24
  useUuid: true,
23
25
  useTimestamps: true,
@@ -47,7 +47,6 @@ export default defineModel({
47
47
  order: 1,
48
48
  fillable: true,
49
49
  validation: { rule: schema.number().required() },
50
- factory: () => 1,
51
50
  },
52
51
 
53
52
  name: {
package/app/Models/Log.ts CHANGED
@@ -49,7 +49,9 @@ export default defineModel({
49
49
  order: 1,
50
50
  fillable: true,
51
51
  validation: {
52
- rule: schema.number().required(),
52
+ // Milliseconds since the epoch, so a bigint column, as on Websocket.time
53
+ // (stacksjs/stacks#2996).
54
+ rule: schema.unix().required(),
53
55
  message: {
54
56
  date: 'Timestamp must be a valid date',
55
57
  },
@@ -15,13 +15,6 @@ export default defineModel({
15
15
  primaryKey: 'id',
16
16
  autoIncrement: true,
17
17
 
18
- indexes: [
19
- {
20
- name: 'magic_link_tokens_token_unique',
21
- columns: ['token'],
22
- unique: true,
23
- },
24
- ],
25
18
 
26
19
  traits: {
27
20
  gdpr: { erasure: 'delete', basis: 'contract', purpose: 'Passwordless sign-in links' },
@@ -28,6 +28,7 @@ export default defineModel({
28
28
 
29
29
  description: {
30
30
  fillable: true,
31
+ type: 'text',
31
32
  validation: {
32
33
  rule: schema.string(),
33
34
  message: {
@@ -32,6 +32,7 @@ export default defineModel({
32
32
 
33
33
  description: {
34
34
  fillable: true,
35
+ type: 'text',
35
36
  validation: {
36
37
  rule: schema.string(),
37
38
  message: {
@@ -8,6 +8,8 @@ export default defineModel({
8
8
  autoIncrement: true,
9
9
 
10
10
  traits: {
11
+ // So a fresh seed fills its dashboard page (stacksjs/stacks#3000).
12
+ useSeeder: { count: 5 },
11
13
  useUuid: true,
12
14
  useTimestamps: true,
13
15
  useSearch: {
@@ -59,6 +61,7 @@ export default defineModel({
59
61
  notes: {
60
62
  order: 4,
61
63
  fillable: true,
64
+ type: 'text',
62
65
  validation: {
63
66
  rule: schema.string(),
64
67
  },
@@ -132,6 +132,7 @@ export default defineModel({
132
132
 
133
133
  error_message: {
134
134
  fillable: true,
135
+ type: 'text',
135
136
  validation: {
136
137
  rule: schema.string(),
137
138
  message: {
@@ -6,6 +6,8 @@ export default defineModel({
6
6
  table: 'sender_domains',
7
7
  belongsTo: ['Team'],
8
8
  traits: {
9
+ // So a fresh seed fills its dashboard page (stacksjs/stacks#3000).
10
+ useSeeder: { count: 3 },
9
11
  useUuid: true,
10
12
  useTimestamps: true,
11
13
  useApi: { uri: 'sender-domains', routes: ['index', 'store', 'show', 'update', 'destroy'], middleware: ['auth', 'team'] },
@@ -13,13 +13,6 @@ export default defineModel({
13
13
  primaryKey: 'id',
14
14
  autoIncrement: true,
15
15
 
16
- indexes: [
17
- {
18
- name: 'sms_opt_outs_phone_unique',
19
- columns: ['phone'],
20
- unique: true,
21
- },
22
- ],
23
16
 
24
17
  traits: {
25
18
  gdpr: { erasure: 'keep', basis: 'legal_obligation', purpose: 'Honouring SMS STOP replies, keyed by phone number' },
@@ -8,6 +8,8 @@ export default defineModel({
8
8
  autoIncrement: true, // defaults to true
9
9
  belongsTo: ['User'],
10
10
  traits: {
11
+ // So a fresh seed fills its dashboard page (stacksjs/stacks#3000).
12
+ useSeeder: { count: 10 },
11
13
  gdpr: { erasure: 'keep', basis: 'legal_obligation', purpose: 'Billing records' },
12
14
  useUuid: true,
13
15
  },
@@ -75,6 +75,7 @@ export default defineModel({
75
75
  description: {
76
76
  order: 3,
77
77
  fillable: true,
78
+ type: 'text',
78
79
  validation: {
79
80
  rule: schema.string(),
80
81
  },
@@ -74,6 +74,7 @@ export default defineModel({
74
74
  description: {
75
75
  order: 3,
76
76
  fillable: true,
77
+ type: 'text',
77
78
  validation: {
78
79
  rule: schema.string(),
79
80
  },