@businessdash/sdk 0.9.60 → 0.9.70

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 (233) hide show
  1. package/README.md +1012 -186
  2. package/assets/biab-sw.js +92 -0
  3. package/dist/account-core/index.d.ts +126 -0
  4. package/dist/account-core/index.d.ts.map +1 -0
  5. package/dist/account-core/index.js +191 -0
  6. package/dist/account-core/index.js.map +1 -0
  7. package/dist/account.d.ts +11 -0
  8. package/dist/account.d.ts.map +1 -0
  9. package/dist/account.js +11 -0
  10. package/dist/account.js.map +1 -0
  11. package/dist/auth-handler.d.ts +1 -1
  12. package/dist/auth-links.d.ts +33 -0
  13. package/dist/auth-links.d.ts.map +1 -0
  14. package/dist/auth-links.js +64 -0
  15. package/dist/auth-links.js.map +1 -0
  16. package/dist/blog-core/index.d.ts +112 -0
  17. package/dist/blog-core/index.d.ts.map +1 -0
  18. package/dist/blog-core/index.js +330 -0
  19. package/dist/blog-core/index.js.map +1 -0
  20. package/dist/blog.d.ts +11 -0
  21. package/dist/blog.d.ts.map +1 -0
  22. package/dist/blog.js +11 -0
  23. package/dist/blog.js.map +1 -0
  24. package/dist/chat-core/index.d.ts +97 -0
  25. package/dist/chat-core/index.d.ts.map +1 -0
  26. package/dist/chat-core/index.js +237 -0
  27. package/dist/chat-core/index.js.map +1 -0
  28. package/dist/chat.d.ts +11 -0
  29. package/dist/chat.d.ts.map +1 -0
  30. package/dist/chat.js +11 -0
  31. package/dist/chat.js.map +1 -0
  32. package/dist/checkout-core/index.d.ts +65 -0
  33. package/dist/checkout-core/index.d.ts.map +1 -0
  34. package/dist/checkout-core/index.js +159 -0
  35. package/dist/checkout-core/index.js.map +1 -0
  36. package/dist/checkout.d.ts +10 -0
  37. package/dist/checkout.d.ts.map +1 -0
  38. package/dist/checkout.js +10 -0
  39. package/dist/checkout.js.map +1 -0
  40. package/dist/cli.js +133 -25
  41. package/dist/cli.js.map +1 -1
  42. package/dist/client.d.ts +567 -1
  43. package/dist/client.d.ts.map +1 -1
  44. package/dist/client.js +807 -1
  45. package/dist/client.js.map +1 -1
  46. package/dist/contracts.d.ts +3309 -761
  47. package/dist/contracts.d.ts.map +1 -1
  48. package/dist/contracts.js +541 -4
  49. package/dist/contracts.js.map +1 -1
  50. package/dist/data-model-schema.d.ts +1 -1
  51. package/dist/data-model-schema.d.ts.map +1 -1
  52. package/dist/element.d.ts +30 -0
  53. package/dist/element.d.ts.map +1 -1
  54. package/dist/element.js +56 -0
  55. package/dist/element.js.map +1 -1
  56. package/dist/embed-protocol.d.ts +86 -0
  57. package/dist/embed-protocol.d.ts.map +1 -0
  58. package/dist/embed-protocol.js +60 -0
  59. package/dist/embed-protocol.js.map +1 -0
  60. package/dist/framework-support.d.ts +122 -0
  61. package/dist/framework-support.d.ts.map +1 -0
  62. package/dist/framework-support.js +323 -0
  63. package/dist/framework-support.js.map +1 -0
  64. package/dist/index.cjs +1244 -5
  65. package/dist/legal-core/index.cjs +115 -0
  66. package/dist/legal-core/index.d.ts +160 -0
  67. package/dist/legal-core/index.d.ts.map +1 -0
  68. package/dist/legal-core/index.js +220 -0
  69. package/dist/legal-core/index.js.map +1 -0
  70. package/dist/marketing-core/index.d.ts +66 -0
  71. package/dist/marketing-core/index.d.ts.map +1 -0
  72. package/dist/marketing-core/index.js +154 -0
  73. package/dist/marketing-core/index.js.map +1 -0
  74. package/dist/marketing.d.ts +7 -0
  75. package/dist/marketing.d.ts.map +1 -0
  76. package/dist/marketing.js +7 -0
  77. package/dist/marketing.js.map +1 -0
  78. package/dist/mcp-adapters/index.d.ts +135 -0
  79. package/dist/mcp-adapters/index.d.ts.map +1 -0
  80. package/dist/mcp-adapters/index.js +187 -0
  81. package/dist/mcp-adapters/index.js.map +1 -0
  82. package/dist/mcp-adapters.d.ts +7 -0
  83. package/dist/mcp-adapters.d.ts.map +1 -0
  84. package/dist/mcp-adapters.js +7 -0
  85. package/dist/mcp-adapters.js.map +1 -0
  86. package/dist/model3d-core/index.cjs +95 -0
  87. package/dist/model3d-core/index.d.ts +167 -0
  88. package/dist/model3d-core/index.d.ts.map +1 -0
  89. package/dist/model3d-core/index.js +158 -0
  90. package/dist/model3d-core/index.js.map +1 -0
  91. package/dist/notifications-core/index.cjs +315 -0
  92. package/dist/notifications-core/index.d.ts +229 -0
  93. package/dist/notifications-core/index.d.ts.map +1 -0
  94. package/dist/notifications-core/index.js +445 -0
  95. package/dist/notifications-core/index.js.map +1 -0
  96. package/dist/portal-core/index.d.ts +451 -0
  97. package/dist/portal-core/index.d.ts.map +1 -0
  98. package/dist/portal-core/index.js +819 -0
  99. package/dist/portal-core/index.js.map +1 -0
  100. package/dist/portal.d.ts +7 -0
  101. package/dist/portal.d.ts.map +1 -0
  102. package/dist/portal.js +7 -0
  103. package/dist/portal.js.map +1 -0
  104. package/dist/push-core/index.cjs +171 -0
  105. package/dist/push-core/index.d.ts +149 -0
  106. package/dist/push-core/index.d.ts.map +1 -0
  107. package/dist/push-core/index.js +231 -0
  108. package/dist/push-core/index.js.map +1 -0
  109. package/dist/react-marketing.d.ts +1 -17
  110. package/dist/react-marketing.d.ts.map +1 -1
  111. package/dist/react-marketing.js +4 -0
  112. package/dist/react-marketing.js.map +1 -1
  113. package/dist/react.d.ts +294 -101
  114. package/dist/react.d.ts.map +1 -1
  115. package/dist/react.js +302 -246
  116. package/dist/react.js.map +1 -1
  117. package/dist/seed-core/index.d.ts +354 -0
  118. package/dist/seed-core/index.d.ts.map +1 -0
  119. package/dist/seed-core/index.js +420 -0
  120. package/dist/seed-core/index.js.map +1 -0
  121. package/dist/seed.d.ts +7 -0
  122. package/dist/seed.d.ts.map +1 -0
  123. package/dist/seed.js +7 -0
  124. package/dist/seed.js.map +1 -0
  125. package/dist/seo-core/blog.d.ts +94 -0
  126. package/dist/seo-core/blog.d.ts.map +1 -0
  127. package/dist/seo-core/blog.js +149 -0
  128. package/dist/seo-core/blog.js.map +1 -0
  129. package/dist/seo-core/commerce.d.ts +64 -0
  130. package/dist/seo-core/commerce.d.ts.map +1 -0
  131. package/dist/seo-core/commerce.js +87 -0
  132. package/dist/seo-core/commerce.js.map +1 -0
  133. package/dist/seo-core/index.cjs +751 -0
  134. package/dist/seo-core/index.d.ts +226 -0
  135. package/dist/seo-core/index.d.ts.map +1 -0
  136. package/dist/seo-core/index.js +455 -0
  137. package/dist/seo-core/index.js.map +1 -0
  138. package/dist/seo-core/local.d.ts +181 -0
  139. package/dist/seo-core/local.d.ts.map +1 -0
  140. package/dist/seo-core/local.js +193 -0
  141. package/dist/seo-core/local.js.map +1 -0
  142. package/dist/sitemap-core/index.cjs +167 -0
  143. package/dist/sitemap-core/index.d.ts +155 -0
  144. package/dist/sitemap-core/index.d.ts.map +1 -0
  145. package/dist/sitemap-core/index.js +195 -0
  146. package/dist/sitemap-core/index.js.map +1 -0
  147. package/dist/social-core/index.d.ts +74 -0
  148. package/dist/social-core/index.d.ts.map +1 -0
  149. package/dist/social-core/index.js +72 -0
  150. package/dist/social-core/index.js.map +1 -0
  151. package/dist/social.d.ts +7 -0
  152. package/dist/social.d.ts.map +1 -0
  153. package/dist/social.js +7 -0
  154. package/dist/social.js.map +1 -0
  155. package/dist/solid-compat.d.ts +204 -0
  156. package/dist/solid-compat.d.ts.map +1 -0
  157. package/dist/solid-compat.js +221 -0
  158. package/dist/solid-compat.js.map +1 -0
  159. package/dist/store-core/index.d.ts +141 -0
  160. package/dist/store-core/index.d.ts.map +1 -0
  161. package/dist/store-core/index.js +277 -0
  162. package/dist/store-core/index.js.map +1 -0
  163. package/dist/store.d.ts +12 -0
  164. package/dist/store.d.ts.map +1 -0
  165. package/dist/store.js +12 -0
  166. package/dist/store.js.map +1 -0
  167. package/dist/vanilla.d.ts +111 -0
  168. package/dist/vanilla.d.ts.map +1 -1
  169. package/dist/vanilla.js +206 -0
  170. package/dist/vanilla.js.map +1 -1
  171. package/dist/verify-core/index.d.ts +98 -0
  172. package/dist/verify-core/index.d.ts.map +1 -0
  173. package/dist/verify-core/index.js +186 -0
  174. package/dist/verify-core/index.js.map +1 -0
  175. package/dist/verify.d.ts +8 -0
  176. package/dist/verify.d.ts.map +1 -0
  177. package/dist/verify.js +8 -0
  178. package/dist/verify.js.map +1 -0
  179. package/openapi.json +4316 -1313
  180. package/package.json +121 -9
  181. package/src/account-core/index.ts +309 -0
  182. package/src/account.ts +11 -0
  183. package/src/angular/headless.ts +519 -0
  184. package/src/angular/index.ts +3 -0
  185. package/src/auth-links.ts +76 -0
  186. package/src/blog-core/index.ts +423 -0
  187. package/src/blog.ts +11 -0
  188. package/src/chat-core/index.ts +335 -0
  189. package/src/chat.ts +11 -0
  190. package/src/checkout-core/index.ts +225 -0
  191. package/src/checkout.ts +10 -0
  192. package/src/cli.ts +184 -28
  193. package/src/client.ts +998 -0
  194. package/src/contracts.ts +730 -11
  195. package/src/data-model-schema.ts +1 -1
  196. package/src/element.ts +102 -0
  197. package/src/embed-protocol.ts +100 -0
  198. package/src/framework-support.ts +440 -0
  199. package/src/legal-core/index.ts +276 -0
  200. package/src/marketing-core/index.ts +209 -0
  201. package/src/marketing.ts +7 -0
  202. package/src/mcp-adapters/index.ts +264 -0
  203. package/src/mcp-adapters.ts +7 -0
  204. package/src/model3d-core/index.ts +248 -0
  205. package/src/notifications-core/index.ts +650 -0
  206. package/src/portal-core/index.ts +1564 -0
  207. package/src/portal.ts +7 -0
  208. package/src/push-core/index.ts +335 -0
  209. package/src/qwik.tsx +602 -2
  210. package/src/react-marketing.tsx +5 -0
  211. package/src/react.tsx +434 -319
  212. package/src/seed-core/index.ts +735 -0
  213. package/src/seed.ts +7 -0
  214. package/src/seo-core/blog.ts +216 -0
  215. package/src/seo-core/commerce.ts +138 -0
  216. package/src/seo-core/index.ts +605 -0
  217. package/src/seo-core/local.ts +333 -0
  218. package/src/sitemap-core/index.ts +296 -0
  219. package/src/social-core/index.ts +103 -0
  220. package/src/social.ts +7 -0
  221. package/src/solid-compat.tsx +347 -0
  222. package/src/solid-surfaces.tsx +1311 -0
  223. package/src/solid.tsx +82 -60
  224. package/src/store-core/index.ts +426 -0
  225. package/src/store.ts +12 -0
  226. package/src/svelte/SocialLinks.svelte +84 -0
  227. package/src/svelte/headless.ts +435 -0
  228. package/src/svelte/index.ts +9 -0
  229. package/src/vanilla.ts +367 -0
  230. package/src/verify-core/index.ts +288 -0
  231. package/src/verify.ts +8 -0
  232. package/src/vue/headless.ts +527 -0
  233. package/src/vue/index.ts +3 -0
package/src/contracts.ts CHANGED
@@ -541,21 +541,44 @@ export const chatbotFormFieldSchema = z
541
541
  .passthrough();
542
542
  export type ChatbotFormField = z.infer<typeof chatbotFormFieldSchema>;
543
543
 
544
- export const chatbotFormDefinitionSchema = z.object({
545
- id: z.string(),
546
- slug: z.string(),
547
- name: z.string(),
548
- description: z.string(),
549
- fields: z.array(chatbotFormFieldSchema),
550
- addsInquiry: z.boolean(),
551
- addsLead: z.boolean(),
552
- });
544
+ export const chatbotFormDefinitionSchema = z
545
+ .object({
546
+ id: z.string(),
547
+ slug: z.string(),
548
+ name: z.string(),
549
+ description: z.string(),
550
+ fields: z.array(chatbotFormFieldSchema),
551
+ addsInquiry: z.boolean(),
552
+ addsLead: z.boolean(),
553
+ /**
554
+ * The RENDER contract — animation, multi-step + progress style, exit
555
+ * confirmation, URL safety. The server has always sent this; the schema
556
+ * dropped it, so anyone building a form UI on the typed client got a
557
+ * bare field list and none of the behaviour the dashboard configured.
558
+ */
559
+ settings: z.record(z.unknown()).nullish(),
560
+ /** Org avatar for the form header, when the form opts in. */
561
+ orgIcon: z.string().nullish(),
562
+ /** Public action previews ("submitting adds you to the list"). */
563
+ actions: z.array(z.record(z.unknown())).nullish(),
564
+ /** Non-sensitive business functions so `calculation` fields compute. */
565
+ businessFunctions: z.array(z.record(z.unknown())).nullish(),
566
+ })
567
+ // Additive server fields reach consumers instead of being silently dropped
568
+ // — the failure this schema just demonstrated.
569
+ .passthrough();
553
570
  export type ChatbotFormDefinition = z.infer<typeof chatbotFormDefinitionSchema>;
554
571
 
555
572
  export const chatbotFormSubmitInputSchema = z.object({
556
573
  data: z.record(z.unknown()),
557
574
  submitterEmail: z.string().email().optional(),
558
575
  submitterName: z.string().optional(),
576
+ /** Validate and run the full pipeline WITHOUT persisting. */
577
+ dryRun: z.boolean().optional(),
578
+ /** Provenance the route accepts and this schema previously omitted. */
579
+ source: z.string().max(200).optional(),
580
+ referrer: z.string().max(500).optional(),
581
+ metadata: z.record(z.unknown()).optional(),
559
582
  priorChat: z
560
583
  .array(
561
584
  z.object({
@@ -573,8 +596,14 @@ export type ChatbotFormSubmitInput = z.infer<
573
596
 
574
597
  export const chatbotFormSubmitResponseSchema = z.object({
575
598
  success: z.literal(true),
576
- submissionId: z.string(),
599
+ /**
600
+ * NULL on a dry run — the submission validated but was deliberately not
601
+ * persisted. Requiring a string here made every test submit throw.
602
+ */
603
+ submissionId: z.string().nullable(),
577
604
  slug: z.string(),
605
+ /** True when nothing was written. Pairs with a null `submissionId`. */
606
+ dryRun: z.boolean().optional(),
578
607
  });
579
608
  export type ChatbotFormSubmitResponse = z.infer<
580
609
  typeof chatbotFormSubmitResponseSchema
@@ -1496,6 +1525,16 @@ export const customerReturnRequestSchema = z.object({
1496
1525
  * are separate enums and each is spelled as the database spells it.
1497
1526
  */
1498
1527
  status: openEnum("requested", "approved", "denied", "refunded", "cancelled"),
1528
+ /**
1529
+ * What was asked for: money back, or a replacement. Mirrors
1530
+ * `order_return_request_kind`. Rows created before this existed read
1531
+ * `refund`, which is what they were.
1532
+ */
1533
+ kind: openEnum("refund", "exchange"),
1534
+ /** On an exchange: the variant wanted instead, when one was chosen. */
1535
+ exchangeForVariantId: z.string().nullable(),
1536
+ /** Set once a replacement ships — track it like any other shipment. */
1537
+ exchangeShipmentId: z.string().nullable(),
1499
1538
  reason: z.string().nullable(),
1500
1539
  items: z
1501
1540
  .array(
@@ -1542,6 +1581,16 @@ export const customerOrderDetailSchema = z.object({
1542
1581
 
1543
1582
  /** Input for `submitReturnRequest`. Omit `items` for a whole-order request. */
1544
1583
  export const customerReturnRequestInputSchema = z.object({
1584
+ /**
1585
+ * `refund` returns the money; `exchange` asks for a replacement. Defaults
1586
+ * to `refund`, so an omitted field means what it always meant.
1587
+ */
1588
+ kind: z.enum(["refund", "exchange"]).optional(),
1589
+ /**
1590
+ * On an exchange, the variant wanted instead. Optional even then — "a
1591
+ * different size" is a real request that needs a conversation first.
1592
+ */
1593
+ exchangeForVariantId: z.string().uuid().optional(),
1545
1594
  reason: z.string().max(120).optional(),
1546
1595
  customerNote: z.string().max(2000).optional(),
1547
1596
  items: z
@@ -1559,6 +1608,372 @@ export const customerReturnRequestCreatedSchema = z.object({
1559
1608
  id: z.string().nullable(),
1560
1609
  /** Mirrors the `order_return_request_status` pg enum. */
1561
1610
  status: openEnum("requested", "approved", "denied", "refunded", "cancelled"),
1611
+ /** Echoes back what was opened, so a UI need not assume. */
1612
+ kind: openEnum("refund", "exchange"),
1613
+ createdAt: z.string().nullable(),
1614
+ });
1615
+
1616
+ /**
1617
+ * Result of reporting that a parcel never arrived.
1618
+ *
1619
+ * `carrierStatus` is the carrier's own verdict, UNTOUCHED by the report — the
1620
+ * two facts travel together on purpose. When `contested` is true the carrier
1621
+ * says delivered and the customer says otherwise, which is the case an org
1622
+ * needs to see first.
1623
+ */
1624
+ export const customerNotReceivedResultSchema = z.object({
1625
+ id: z.string(),
1626
+ /** True when a claim was already open; the original report is unchanged. */
1627
+ alreadyOpen: z.boolean(),
1628
+ reportedAt: z.string(),
1629
+ carrierStatus: openEnum(
1630
+ "pending",
1631
+ "label_created",
1632
+ "in_transit",
1633
+ "delivered",
1634
+ "returned",
1635
+ ),
1636
+ carrierDeliveredAt: z.string().nullable(),
1637
+ contested: z.boolean().optional(),
1638
+ });
1639
+
1640
+ /** How an org's legal pages are dressed. Shared by all of them. */
1641
+ export const legalPresentationSchema = z.object({
1642
+ /** Already resolved against the company logo — render as given. */
1643
+ logoUrl: z.string().nullable(),
1644
+ showLogoInHeader: z.boolean(),
1645
+ showLogoInFooter: z.boolean(),
1646
+ headerHtml: z.string().nullable(),
1647
+ footerHtml: z.string().nullable(),
1648
+ });
1649
+
1650
+ /** A legal page in a list — enough for a footer, without the body. */
1651
+ export const legalDocumentSummarySchema = z.object({
1652
+ /** The URL path, without a leading slash: `privacy`, `terms`, … */
1653
+ slug: z.string(),
1654
+ title: z.string(),
1655
+ version: z.number(),
1656
+ /** ISO 8601. Render as "Last updated". */
1657
+ effectiveDate: z.string().nullable(),
1658
+ showInFooter: z.boolean(),
1659
+ updatedAt: z.string(),
1660
+ });
1661
+
1662
+ export const legalListResponseSchema = z.object({
1663
+ items: z.array(legalDocumentSummarySchema),
1664
+ presentation: legalPresentationSchema,
1665
+ });
1666
+
1667
+ /** One legal page, body included. */
1668
+ export const legalDocumentSchema = legalDocumentSummarySchema.extend({
1669
+ /** Sanitised server-side on write, so this is safe to render as HTML. */
1670
+ contentHtml: z.string(),
1671
+ presentation: legalPresentationSchema,
1672
+ });
1673
+
1674
+ export type LegalPresentation = z.infer<typeof legalPresentationSchema>;
1675
+ export type LegalDocumentSummary = z.infer<typeof legalDocumentSummarySchema>;
1676
+ export type LegalListResponse = z.infer<typeof legalListResponseSchema>;
1677
+ export type LegalDocument = z.infer<typeof legalDocumentSchema>;
1678
+
1679
+ /** One browser a customer has registered for push. */
1680
+ export const customerPushDeviceSchema = z.object({
1681
+ id: z.string(),
1682
+ endpoint: z.string(),
1683
+ userAgent: z.string().nullable(),
1684
+ label: z.string().nullable(),
1685
+ /** Null = every category. `[]` = none, but the device stays registered. */
1686
+ categories: z.array(z.string()).nullable(),
1687
+ lastSeenAt: z.string().nullable(),
1688
+ createdAt: z.string(),
1689
+ });
1690
+
1691
+ /**
1692
+ * Push config plus this customer's devices.
1693
+ *
1694
+ * `vapidPublicKey` rides along because a page cannot call `subscribe()` without
1695
+ * it, and a second round trip for one string is one nobody remembers to make.
1696
+ * `enabled: false` means the platform has no key configured — render nothing
1697
+ * rather than a button that cannot work.
1698
+ */
1699
+ export const customerPushConfigSchema = z.object({
1700
+ vapidPublicKey: z.string().nullable(),
1701
+ enabled: z.boolean(),
1702
+ devices: z.array(customerPushDeviceSchema),
1703
+ });
1704
+
1705
+ export type CustomerPushDevice = z.infer<typeof customerPushDeviceSchema>;
1706
+ export type CustomerPushConfig = z.infer<typeof customerPushConfigSchema>;
1707
+
1708
+ /** Result of handing a customer-portal invite link out again. */
1709
+ export const customerInviteResendResponseSchema = z.object({
1710
+ id: z.string(),
1711
+ /** The NEW link. The previous one no longer works. */
1712
+ url: z.string(),
1713
+ expiresAt: z.string().nullable(),
1714
+ /** Includes this send, so a list can show "sent 3 times". */
1715
+ sendCount: z.number(),
1716
+ });
1717
+
1718
+ export type CustomerInviteResendResponse = z.infer<
1719
+ typeof customerInviteResendResponseSchema
1720
+ >;
1721
+
1722
+ // ── Scheduling / conference calls ─────────────────────────────────────
1723
+ //
1724
+ // The booking surface: what an org offers, when it is free, and the three
1725
+ // token-gated things an invitee can do afterwards.
1726
+ //
1727
+ // `conferenceLink` is the conference-call half — a Meet/Zoom/Teams URL minted
1728
+ // when the event type has a provider. It is null until the booking is
1729
+ // confirmed, and re-minted on reschedule, so a UI must read it back after
1730
+ // either rather than caching the one it first saw.
1731
+
1732
+ export const schedulingEventTypeSchema = z.object({
1733
+ id: z.string(),
1734
+ name: z.string(),
1735
+ slug: z.string(),
1736
+ description: z.string().nullable(),
1737
+ durationMinutes: z.number(),
1738
+ locationType: z.string(),
1739
+ /** google_meet | zoom | teams | null when the meeting is in person. */
1740
+ meetingProvider: z.string().nullable(),
1741
+ color: z.string().nullable(),
1742
+ /** When true a booking lands `pending`, not `confirmed`. */
1743
+ requiresApproval: z.boolean(),
1744
+ });
1745
+
1746
+ export const schedulingQuestionSchema = z
1747
+ .object({
1748
+ id: z.string(),
1749
+ label: z.string(),
1750
+ })
1751
+ .passthrough();
1752
+
1753
+ export const schedulingEventTypeListSchema = z.object({
1754
+ eventTypes: z.array(schedulingEventTypeSchema),
1755
+ });
1756
+
1757
+ export const schedulingEventTypeDetailSchema = z.object({
1758
+ eventType: schedulingEventTypeSchema,
1759
+ questions: z.array(schedulingQuestionSchema),
1760
+ });
1761
+
1762
+ export const schedulingSlotSchema = z.object({
1763
+ startAt: z.string(),
1764
+ endAt: z.string(),
1765
+ });
1766
+
1767
+ export const schedulingSlotsResponseSchema = z.object({
1768
+ slots: z.array(schedulingSlotSchema),
1769
+ });
1770
+
1771
+ export const schedulingBookingStatusSchema = openEnum(
1772
+ "pending",
1773
+ "confirmed",
1774
+ "canceled",
1775
+ "rescheduled",
1776
+ );
1777
+
1778
+ export const schedulingBookingViewSchema = z.object({
1779
+ booking: z.object({
1780
+ id: z.string(),
1781
+ startAt: z.string(),
1782
+ endAt: z.string(),
1783
+ status: schedulingBookingStatusSchema,
1784
+ title: z.string(),
1785
+ notes: z.string().nullable(),
1786
+ /** The conference URL, once there is one. */
1787
+ conferenceLink: z.string().nullable(),
1788
+ }),
1789
+ invitee: z
1790
+ .object({ name: z.string(), email: z.string(), timezone: z.string() })
1791
+ .nullable(),
1792
+ });
1793
+
1794
+ export const schedulingBookingResultSchema = z
1795
+ .object({
1796
+ bookingId: z.string(),
1797
+ status: schedulingBookingStatusSchema,
1798
+ /**
1799
+ * Three separate signed tokens, deliberately.
1800
+ *
1801
+ * A cancel link in an email must not also be able to reschedule, and
1802
+ * neither should read the whole booking. Handing out one token for all
1803
+ * three would make every emailed link as powerful as the strongest
1804
+ * action it enables.
1805
+ */
1806
+ manageToken: z.string(),
1807
+ rescheduleToken: z.string(),
1808
+ cancelToken: z.string(),
1809
+ })
1810
+ .passthrough();
1811
+
1812
+ export const schedulingRescheduleResultSchema = z
1813
+ .object({
1814
+ bookingId: z.string(),
1815
+ startAt: z.string(),
1816
+ endAt: z.string(),
1817
+ /** Re-minted on reschedule — read it back rather than reusing the old one. */
1818
+ conferenceLink: z.string().nullable().optional(),
1819
+ })
1820
+ .passthrough();
1821
+
1822
+ export const schedulingCancelResultSchema = z
1823
+ .object({ ok: z.boolean() })
1824
+ .passthrough();
1825
+
1826
+ export type SchedulingEventTypeContract = z.infer<
1827
+ typeof schedulingEventTypeSchema
1828
+ >;
1829
+ export type SchedulingEventTypeList = z.infer<
1830
+ typeof schedulingEventTypeListSchema
1831
+ >;
1832
+ export type SchedulingEventTypeDetail = z.infer<
1833
+ typeof schedulingEventTypeDetailSchema
1834
+ >;
1835
+ export type SchedulingSlotsResponse = z.infer<
1836
+ typeof schedulingSlotsResponseSchema
1837
+ >;
1838
+ export type SchedulingBookingView = z.infer<typeof schedulingBookingViewSchema>;
1839
+ export type SchedulingBookingResult = z.infer<
1840
+ typeof schedulingBookingResultSchema
1841
+ >;
1842
+ export type SchedulingRescheduleResult = z.infer<
1843
+ typeof schedulingRescheduleResultSchema
1844
+ >;
1845
+ export type SchedulingCancelResult = z.infer<
1846
+ typeof schedulingCancelResultSchema
1847
+ >;
1848
+
1849
+ /** One of the org's live subscription offerings. */
1850
+ export const customerSubscriptionOfferingSchema = z.object({
1851
+ id: z.string(),
1852
+ name: z.string(),
1853
+ description: z.string().nullable(),
1854
+ imageUrl: z.string().nullable(),
1855
+ amountCents: z.number(),
1856
+ interval: z.string(),
1857
+ });
1858
+
1859
+ /** The customer's subscription state with one org. */
1860
+ export const customerSubscriptionSchema = z.object({
1861
+ /** active | expired | cancelled | pending | failed */
1862
+ status: openEnum("active", "expired", "cancelled", "pending", "failed"),
1863
+ /** trial | monthly_subscribed | annual_subscribed | lifetime */
1864
+ type: openEnum(
1865
+ "trial",
1866
+ "monthly_subscribed",
1867
+ "annual_subscribed",
1868
+ "lifetime",
1869
+ ).nullable(),
1870
+ /**
1871
+ * The entitlement decision, computed server-side by the same function the
1872
+ * content gates use.
1873
+ *
1874
+ * **Render from this, not from `status`.** Two cases break a naive
1875
+ * `status === "active"` check: a lifetime purchase has no period to expire,
1876
+ * and a cancelled subscription keeps access until the period already paid
1877
+ * for runs out.
1878
+ */
1879
+ hasAccess: z.boolean(),
1880
+ /**
1881
+ * Cancelled, but the paid period has not ended. The honest label is
1882
+ * "active until {expiresAt}" — not "cancelled".
1883
+ */
1884
+ cancelledButActive: z.boolean(),
1885
+ startedAt: z.string().nullable(),
1886
+ /** End of the paid period. Null for lifetime. */
1887
+ expiresAt: z.string().nullable(),
1888
+ cancelledAt: z.string().nullable(),
1889
+ renewedAt: z.string().nullable(),
1890
+ trialEndsAt: z.string().nullable(),
1891
+ trialUsed: z.boolean(),
1892
+ provider: z.string().nullable(),
1893
+ /** The offering they are on, when it still exists. */
1894
+ plan: customerSubscriptionOfferingSchema.nullable(),
1895
+ /** Null means the plan includes no service calls at all. */
1896
+ includedServiceCallsPerPeriod: z.number().nullable(),
1897
+ serviceCallsUsedInPeriod: z.number(),
1898
+ periodStartedAt: z.string().nullable(),
1899
+ });
1900
+
1901
+ export const customerSubscriptionResponseSchema = z.object({
1902
+ /** Null when this customer has never subscribed to this org. */
1903
+ subscription: customerSubscriptionSchema.nullable(),
1904
+ /** The org's live offerings, cheapest first. */
1905
+ offerings: z.array(customerSubscriptionOfferingSchema),
1906
+ });
1907
+
1908
+ /** Result of cancelling — or un-cancelling — a subscription. */
1909
+ export const customerSubscriptionCancelResponseSchema = z.object({
1910
+ /** True when a cancellation is now pending at the end of the period. */
1911
+ cancelAtPeriodEnd: z.boolean(),
1912
+ resumed: z.boolean(),
1913
+ /**
1914
+ * Access continues until this moment. Say "active until {this}", because
1915
+ * that is what is true — cancelling ends the renewal, not the access.
1916
+ */
1917
+ accessUntil: z.string().nullable(),
1918
+ /**
1919
+ * The stored status still reads its old value until the provider's webhook
1920
+ * lands. Render from `cancelAtPeriodEnd`, not from a re-read.
1921
+ */
1922
+ statusPending: z.boolean(),
1923
+ });
1924
+
1925
+ /** One piece of subscriber-gated content. */
1926
+ export const customerSubscriberContentItemSchema = z.object({
1927
+ id: z.string(),
1928
+ slug: z.string(),
1929
+ title: z.string(),
1930
+ /** The pitch. Travels even when locked — the BODY never does. */
1931
+ excerpt: z.string().nullable(),
1932
+ featuredImageUrl: z.string().nullable(),
1933
+ publishedAt: z.string().nullable(),
1934
+ tags: z.array(z.string()),
1935
+ accessLevel: z.string(),
1936
+ downloadAccessLevel: z.string(),
1937
+ /** False when the post is readable but its downloads are not. */
1938
+ downloadsAvailable: z.boolean(),
1939
+ });
1940
+
1941
+ export const customerSubscriberContentResponseSchema = z.object({
1942
+ items: z.array(customerSubscriberContentItemSchema),
1943
+ /**
1944
+ * False means these are LOCKED previews, not an empty entitlement. Show
1945
+ * them with the offer — an empty list would hide the pitch at exactly the
1946
+ * moment it is most relevant.
1947
+ */
1948
+ entitled: z.boolean(),
1949
+ });
1950
+
1951
+ /** One product review the customer has written. */
1952
+ export const customerProductReviewSchema = z.object({
1953
+ id: z.string(),
1954
+ productId: z.string(),
1955
+ productName: z.string(),
1956
+ rating: z.number(),
1957
+ content: z.string(),
1958
+ /**
1959
+ * False means QUEUED for the org to approve, not rejected. Surface it as
1960
+ * "awaiting review" — a customer who thinks their review vanished writes it
1961
+ * again, or writes to support.
1962
+ */
1963
+ isApproved: z.boolean(),
1964
+ createdAt: z.string(),
1965
+ });
1966
+
1967
+ export const customerProductReviewListSchema = z.object({
1968
+ items: z.array(customerProductReviewSchema),
1969
+ });
1970
+
1971
+ /** Result of submitting a product review from the portal. */
1972
+ export const customerProductReviewCreatedSchema = z.object({
1973
+ id: z.string().nullable(),
1974
+ isApproved: z.boolean(),
1975
+ /** The inverse of `isApproved`, named for what the UI should say. */
1976
+ awaitingModeration: z.boolean(),
1562
1977
  createdAt: z.string().nullable(),
1563
1978
  });
1564
1979
 
@@ -1615,6 +2030,32 @@ export type CustomerReturnRequestInput = z.infer<
1615
2030
  export type CustomerReturnRequestCreated = z.infer<
1616
2031
  typeof customerReturnRequestCreatedSchema
1617
2032
  >;
2033
+ export type CustomerNotReceivedResult = z.infer<
2034
+ typeof customerNotReceivedResultSchema
2035
+ >;
2036
+ export type CustomerProductReview = z.infer<typeof customerProductReviewSchema>;
2037
+ export type CustomerProductReviewList = z.infer<
2038
+ typeof customerProductReviewListSchema
2039
+ >;
2040
+ export type CustomerProductReviewCreated = z.infer<
2041
+ typeof customerProductReviewCreatedSchema
2042
+ >;
2043
+ export type CustomerSubscriptionOffering = z.infer<
2044
+ typeof customerSubscriptionOfferingSchema
2045
+ >;
2046
+ export type CustomerSubscription = z.infer<typeof customerSubscriptionSchema>;
2047
+ export type CustomerSubscriptionResponse = z.infer<
2048
+ typeof customerSubscriptionResponseSchema
2049
+ >;
2050
+ export type CustomerSubscriptionCancelResponse = z.infer<
2051
+ typeof customerSubscriptionCancelResponseSchema
2052
+ >;
2053
+ export type CustomerSubscriberContentItem = z.infer<
2054
+ typeof customerSubscriberContentItemSchema
2055
+ >;
2056
+ export type CustomerSubscriberContentResponse = z.infer<
2057
+ typeof customerSubscriberContentResponseSchema
2058
+ >;
1618
2059
  export type CustomerDownload = z.infer<typeof customerDownloadSchema>;
1619
2060
  export type CustomerDownloadsResponse = z.infer<
1620
2061
  typeof customerDownloadsResponseSchema
@@ -1646,6 +2087,148 @@ export type CustomerInvoicePayment = z.infer<
1646
2087
  export type CustomerPaymentHistoryEntry = z.infer<
1647
2088
  typeof customerPaymentHistoryEntrySchema
1648
2089
  >;
2090
+ // ── Customer portal: list endpoints (0.9.61) ──────────────────────────
2091
+ //
2092
+ // The portal had detail-by-id routes for invoices, quotes, contracts and
2093
+ // shipments and no way to LIST any of them, so "show me everything I owe" had
2094
+ // no call behind it.
2095
+ //
2096
+ // The rows reuse the existing `customer*SummarySchema` shapes rather than
2097
+ // defining parallel ones — those already carry the right open enums, and two
2098
+ // definitions of "an invoice summary" would drift.
2099
+
2100
+ export const customerInvoiceListResponseSchema = z.object({
2101
+ items: z.array(customerInvoiceSummarySchema),
2102
+ });
2103
+
2104
+ export const customerQuoteListResponseSchema = z.object({
2105
+ items: z.array(customerQuoteSummarySchema),
2106
+ });
2107
+
2108
+ export const customerContractListResponseSchema = z.object({
2109
+ items: z.array(customerContractSummarySchema),
2110
+ });
2111
+
2112
+ /** No summary shape existed for shipments, so this is the first one. */
2113
+ export const customerShipmentSummarySchema = z.object({
2114
+ id: z.string(),
2115
+ orderId: z.string(),
2116
+ orderNumber: z.string().nullable(),
2117
+ carrier: z.string().nullable(),
2118
+ trackingNumber: z.string().nullable(),
2119
+ trackingUrl: z.string().nullable(),
2120
+ serviceName: z.string().nullable(),
2121
+ status: z.string(),
2122
+ /** A date STRING, not a timestamp — the column is text. */
2123
+ estimatedDelivery: z.string().nullable(),
2124
+ shippedAt: z.string().nullable(),
2125
+ deliveredAt: z.string().nullable(),
2126
+ });
2127
+
2128
+ export const customerShipmentListResponseSchema = z.object({
2129
+ items: z.array(customerShipmentSummarySchema),
2130
+ });
2131
+
2132
+ export type CustomerInvoiceListResponse = z.infer<
2133
+ typeof customerInvoiceListResponseSchema
2134
+ >;
2135
+ export type CustomerQuoteListResponse = z.infer<
2136
+ typeof customerQuoteListResponseSchema
2137
+ >;
2138
+ export type CustomerContractListResponse = z.infer<
2139
+ typeof customerContractListResponseSchema
2140
+ >;
2141
+ export type CustomerShipmentSummary = z.infer<
2142
+ typeof customerShipmentSummarySchema
2143
+ >;
2144
+ export type CustomerShipmentListResponse = z.infer<
2145
+ typeof customerShipmentListResponseSchema
2146
+ >;
2147
+
2148
+ // ── Customer portal: staff chat (0.9.61) ──────────────────────────────
2149
+ //
2150
+ // `org_customer_messages` has always carried a `portal` channel and staff have
2151
+ // always read and written it from the CRM timeline. What was missing was any
2152
+ // package-api route, so a customer could be messaged and had no way to read it
2153
+ // back or reply. Both directions land in the SAME table the staff timeline
2154
+ // reads — a reply appears in the CRM beside everything else about that
2155
+ // customer, not in a parallel inbox nobody watches.
2156
+
2157
+ export const customerPortalMessageSchema = z.object({
2158
+ id: z.string(),
2159
+ /** `inbound` = the customer wrote it; `outbound` = staff did. */
2160
+ direction: openEnum("inbound", "outbound"),
2161
+ channel: openEnum("portal", "email", "sms"),
2162
+ authorName: z.string().nullable(),
2163
+ body: z.string(),
2164
+ attachment: z
2165
+ .object({
2166
+ url: z.string(),
2167
+ name: z.string(),
2168
+ mimeType: z.string(),
2169
+ sizeBytes: z.number(),
2170
+ })
2171
+ .nullable(),
2172
+ jobId: z.string().nullable(),
2173
+ readByCustomerAt: z.string().nullable(),
2174
+ createdAt: z.string(),
2175
+ });
2176
+
2177
+ export const customerPortalMessageListResponseSchema = z.object({
2178
+ items: z.array(customerPortalMessageSchema),
2179
+ });
2180
+
2181
+ export const customerPortalMessagePostInputSchema = z.object({
2182
+ body: z.string().min(1).max(4000),
2183
+ /** Thread the message under a specific job. */
2184
+ jobId: z.string().uuid().optional(),
2185
+ });
2186
+
2187
+ export const customerPortalMessagesMarkReadResponseSchema = z.object({
2188
+ marked: z.number(),
2189
+ });
2190
+
2191
+ export type CustomerPortalMessage = z.infer<typeof customerPortalMessageSchema>;
2192
+ export type CustomerPortalMessageListResponse = z.infer<
2193
+ typeof customerPortalMessageListResponseSchema
2194
+ >;
2195
+ export type CustomerPortalMessagePostInput = z.infer<
2196
+ typeof customerPortalMessagePostInputSchema
2197
+ >;
2198
+ export type CustomerPortalMessagesMarkReadResponse = z.infer<
2199
+ typeof customerPortalMessagesMarkReadResponseSchema
2200
+ >;
2201
+
2202
+ // ── Customer portal: return status readback (0.9.61) ──────────────────
2203
+ //
2204
+ // Closes the audit's gap #5. The submit route existed and nothing read the
2205
+ // status back, so a customer could start a return and then had no way to see
2206
+ // what happened to it — which is the exact moment they pick up the phone.
2207
+
2208
+ export const customerReturnRequestStatusSchema = z.object({
2209
+ id: z.string(),
2210
+ /** Mirrors the `order_return_request_status` pg enum. */
2211
+ status: openEnum("requested", "approved", "denied", "refunded", "cancelled"),
2212
+ reason: z.string().nullable(),
2213
+ customerNote: z.string().nullable(),
2214
+ /** Staff's answer. The whole point when a return is denied. */
2215
+ resolutionNote: z.string().nullable(),
2216
+ items: z.array(z.record(z.string(), z.unknown())).nullable(),
2217
+ resolvedAt: z.string().nullable(),
2218
+ createdAt: z.string(),
2219
+ });
2220
+
2221
+ export const customerReturnRequestListResponseSchema = z.object({
2222
+ items: z.array(customerReturnRequestStatusSchema),
2223
+ });
2224
+
2225
+ export type CustomerReturnRequestStatus = z.infer<
2226
+ typeof customerReturnRequestStatusSchema
2227
+ >;
2228
+ export type CustomerReturnRequestListResponse = z.infer<
2229
+ typeof customerReturnRequestListResponseSchema
2230
+ >;
2231
+
1649
2232
  export type CustomerPaymentHistoryResponse = z.infer<
1650
2233
  typeof customerPaymentHistoryResponseSchema
1651
2234
  >;
@@ -1752,7 +2335,19 @@ export type CustomerContactCompanyResponse = z.infer<
1752
2335
  export const customerJobEtaAssignmentSchema = z.object({
1753
2336
  displayName: z.string(),
1754
2337
  role: z.string().nullable(),
1755
- dispatchStatus: z.string(),
2338
+ /**
2339
+ * This technician's own stage. `openEnum` because the column is TEXT and
2340
+ * an org's board can grow a value before the SDK knows about it — an
2341
+ * unknown status should render as itself, not break the response.
2342
+ */
2343
+ dispatchStatus: openEnum(
2344
+ "pending",
2345
+ "en_route",
2346
+ "on_site",
2347
+ "in_progress",
2348
+ "completed",
2349
+ "declined",
2350
+ ),
1756
2351
  lastKnownLocation: z
1757
2352
  .object({
1758
2353
  lat: z.number(),
@@ -1763,6 +2358,35 @@ export const customerJobEtaAssignmentSchema = z.object({
1763
2358
  });
1764
2359
 
1765
2360
  export const customerJobEtaResponseSchema = z.object({
2361
+ /**
2362
+ * Where the JOB is — promoted from the assignees by the dispatch board,
2363
+ * NOT the same as any one technician's status.
2364
+ *
2365
+ * The promotion rules are why this is returned rather than left to the
2366
+ * consumer: `en_route`/`arrived`/`started` land on the FIRST assignee to
2367
+ * reach them, while `completed` waits for the LAST. Aggregating that
2368
+ * client-side gets it wrong in the direction that tells a customer the work
2369
+ * is finished when one of three techs has packed up.
2370
+ */
2371
+ dispatchStatus: openEnum(
2372
+ "unscheduled",
2373
+ "scheduled",
2374
+ "en_route",
2375
+ "arrived",
2376
+ "started",
2377
+ "completed",
2378
+ ),
2379
+ /**
2380
+ * When each stage happened. A customer reading "en route" wants to know
2381
+ * since when — a status with no timestamp is why people phone to ask.
2382
+ */
2383
+ timeline: z.object({
2384
+ scheduledStartAt: z.string().nullable(),
2385
+ enRouteAt: z.string().nullable(),
2386
+ arrivedAt: z.string().nullable(),
2387
+ startedAt: z.string().nullable(),
2388
+ completedAt: z.string().nullable(),
2389
+ }),
1766
2390
  assignments: z.array(customerJobEtaAssignmentSchema),
1767
2391
  eta: z
1768
2392
  .object({
@@ -1973,6 +2597,30 @@ export const storefrontProductRowSchema = z
1973
2597
  /** Resolved category (name/description), when the product is categorized. */
1974
2598
  category: storefrontProductCategorySchema.nullable().optional(),
1975
2599
  images: z.array(z.string()).nullable().optional(),
2600
+ /**
2601
+ * Web-renderable 3D model — `.glb`, `.gltf`, `.obj` or `.stl`.
2602
+ *
2603
+ * All four fields below are ABSENT (not null) unless the org has the
2604
+ * `ecommerce.product_3d` add-on. Absence is the normal case: most
2605
+ * businesses do not sell anything with a model. Use
2606
+ * `@businessdash/sdk/model3d` to turn these into something renderable.
2607
+ */
2608
+ model3dUrl: z.string().nullable().optional(),
2609
+ /** Apple AR Quick Look companion, `.usdz`. iOS/iPadOS/visionOS Safari. */
2610
+ model3dUsdzUrl: z.string().nullable().optional(),
2611
+ /**
2612
+ * True when the org attached a CAD file (`.step`/`.iges`/native).
2613
+ *
2614
+ * The URL itself is deliberately NOT here. CAD files are the largest
2615
+ * thing the platform serves and are the org's intellectual property, so
2616
+ * they are issued by `GET storefront/products/{id}/cad`, which requires
2617
+ * cookies, rate-limits the caller and re-checks the entitlement.
2618
+ * Publishing the raw URL in a payload any visitor can fetch would route
2619
+ * around all three.
2620
+ */
2621
+ model3dCadAvailable: z.boolean().optional(),
2622
+ /** Filename for the download link — "bracket-v3.step". */
2623
+ model3dCadName: z.string().nullable().optional(),
1976
2624
  createdAt: z.string(),
1977
2625
  })
1978
2626
  .passthrough();
@@ -1982,6 +2630,75 @@ export const storefrontListProductsResponseSchema = z.object({
1982
2630
  nextCursor: z.number().nullable(),
1983
2631
  });
1984
2632
 
2633
+ /**
2634
+ * Result of pushing the catalog to the org's own Stripe account.
2635
+ * Per-product outcomes rather than one all-or-nothing verdict: a single bad
2636
+ * product shouldn't abandon a batch, and the caller needs to know which.
2637
+ */
2638
+ export const couponSchema = z.object({
2639
+ id: z.string(),
2640
+ code: z.string(),
2641
+ discountType: z.string(),
2642
+ value: z.number(),
2643
+ isActive: z.boolean(),
2644
+ maxUses: z.number().nullable(),
2645
+ usedCount: z.number(),
2646
+ validUntil: z.string().nullable(),
2647
+ stripeCouponId: z.string().nullable(),
2648
+ stripePromotionCodeId: z.string().nullable(),
2649
+ });
2650
+ export type Coupon = z.infer<typeof couponSchema>;
2651
+
2652
+ export const couponCreateInputSchema = z.object({
2653
+ code: z.string().min(3).max(64),
2654
+ discountType: z.enum(["percent", "amount"]),
2655
+ value: z.number().positive(),
2656
+ validFrom: z.coerce.date().optional(),
2657
+ validUntil: z.coerce.date().nullish(),
2658
+ maxUses: z.number().int().positive().nullish(),
2659
+ isActive: z.boolean().optional(),
2660
+ duration: z.enum(["once", "forever", "repeating"]).optional(),
2661
+ durationInMonths: z.number().int().positive().nullish(),
2662
+ appliesTo: z.enum(["order", "shipping", "both"]).optional(),
2663
+ });
2664
+ export type CouponCreateInput = z.input<typeof couponCreateInputSchema>;
2665
+
2666
+ /**
2667
+ * `stripeSynced: false` means the coupon EXISTS locally but is not yet live in
2668
+ * Stripe — reported rather than thrown, so the caller can retry the sync
2669
+ * without re-creating the row.
2670
+ */
2671
+ export const couponCreateResponseSchema = z.object({
2672
+ coupon: couponSchema,
2673
+ stripeSynced: z.boolean(),
2674
+ stripeError: z.string().nullable(),
2675
+ });
2676
+ export type CouponCreateResponse = z.infer<typeof couponCreateResponseSchema>;
2677
+
2678
+ export const couponListResponseSchema = z.object({
2679
+ coupons: z.array(couponSchema),
2680
+ });
2681
+ export type CouponListResponse = z.infer<typeof couponListResponseSchema>;
2682
+
2683
+ export const storefrontSyncToStripeResponseSchema = z.object({
2684
+ synced: z.array(
2685
+ z.object({
2686
+ productId: z.string(),
2687
+ stripeProductId: z.string(),
2688
+ /** Variant combinations that got a Stripe Price. */
2689
+ combinationsSynced: z.number(),
2690
+ }),
2691
+ ),
2692
+ failed: z.array(z.object({ productId: z.string(), error: z.string() })),
2693
+ /** Products still unsynced after this call — keep calling until 0. */
2694
+ remaining: z.number(),
2695
+ /** Connected account written to; null means the platform account. */
2696
+ connectedAccountId: z.string().nullable(),
2697
+ });
2698
+ export type StorefrontSyncToStripeResponse = z.infer<
2699
+ typeof storefrontSyncToStripeResponseSchema
2700
+ >;
2701
+
1985
2702
  export const storefrontProductDetailSchema = storefrontProductRowSchema.and(
1986
2703
  z.object({
1987
2704
  variants: z.array(z.record(z.string(), z.unknown())),
@@ -2799,6 +3516,8 @@ const bundleCompanySchema = z.object({
2799
3516
 
2800
3517
  const bundleGalleryItemSchema = z.object({
2801
3518
  id: z.string(),
3519
+ /** Sent by the server on every item and dropped here until now. */
3520
+ description: z.string().nullish(),
2802
3521
  type: z.enum(["image", "video"]),
2803
3522
  src: z.string(),
2804
3523
  width: z.number().nullable(),