@wix/himalaya-cli 0.1543.0 → 0.1545.0

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 (228) hide show
  1. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/agent/src/index.d.ts +5 -2
  2. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/state/src/index.d.ts +14 -0
  3. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/state/src/read.d.ts +60 -0
  4. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/auth-providers/wix/src/index.d.ts +3 -2
  5. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/activity-counters-v1/src/index.d.ts +19 -0
  6. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/ai-site-chat/src/index.d.ts +19 -0
  7. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/analytics/src/index.d.ts +42 -2
  8. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/async-jobs/src/index.d.ts +19 -0
  9. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/automations-v1/src/index.d.ts +19 -0
  10. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/badges-v2/src/index.d.ts +19 -0
  11. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/billing-tax-v1/src/index.d.ts +19 -0
  12. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/blog-v3/src/extensions.d.ts +5 -4
  13. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/blog-v3/src/index.d.ts +19 -0
  14. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/bookings-v2/src/extensions.d.ts +4 -4
  15. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/bookings-v2/src/index.d.ts +19 -0
  16. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/business-feed/src/index.d.ts +11 -0
  17. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/business-tools-v1/src/index.d.ts +19 -0
  18. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/chat-web/src/index.d.ts +22 -1
  19. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/comments-v1/src/index.d.ts +19 -0
  20. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/contacts/src/index.d.ts +19 -0
  21. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/coupons/src/index.d.ts +24 -2
  22. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/crm-tasks/src/index.d.ts +11 -0
  23. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/dashboard-setup/src/index.d.ts +11 -0
  24. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/data-collections-v1/src/index.d.ts +19 -0
  25. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/donations/src/index.d.ts +19 -0
  26. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/ecom/src/extensions.d.ts +6 -5
  27. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/ecom/src/index.d.ts +19 -0
  28. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/email-marketing-v1/src/index.d.ts +19 -0
  29. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/events/src/index.d.ts +24 -2
  30. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/faq-v2/src/index.d.ts +19 -0
  31. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/forms/src/index.d.ts +19 -0
  32. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/forum/src/index.d.ts +19 -0
  33. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/genie-chat/src/index.d.ts +11 -0
  34. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/gift-cards/src/index.d.ts +19 -0
  35. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/groups/src/index.d.ts +19 -0
  36. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/inbox/src/extensions.d.ts +2 -1
  37. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/inbox/src/index.d.ts +19 -0
  38. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/invoices/src/index.d.ts +24 -2
  39. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/loyalty/src/index.d.ts +28 -4
  40. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/marketing-v1/src/index.d.ts +19 -0
  41. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/members-feed-viewer/src/index.d.ts +23 -1
  42. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/members-v1/src/index.d.ts +25 -3
  43. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/new-releases/src/index.d.ts +23 -1
  44. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/notification-preferences/src/index.d.ts +23 -2
  45. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/notifications-v3/src/index.d.ts +19 -0
  46. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/online-programs/src/index.d.ts +25 -3
  47. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/portfolio/src/index.d.ts +25 -3
  48. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/pricing-plans/src/index.d.ts +28 -6
  49. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/redirects-v1/src/index.d.ts +19 -0
  50. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/restaurants/src/extensions.d.ts +2 -2
  51. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/restaurants/src/index.d.ts +19 -0
  52. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/reviews/src/index.d.ts +24 -2
  53. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/seo-v1/src/index.d.ts +19 -0
  54. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/site-list/src/index.d.ts +19 -0
  55. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/site-media/src/index.d.ts +11 -0
  56. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/social-feed/src/index.d.ts +23 -1
  57. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/stores-collections/src/display.d.ts +3 -2
  58. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/stores-collections/src/index.d.ts +27 -3
  59. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/stores-v1/src/catalog.d.ts +7 -7
  60. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/stores-v1/src/display.d.ts +4 -3
  61. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/stores-v1/src/extensions.d.ts +2 -1
  62. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/stores-v1/src/index.d.ts +19 -0
  63. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/table-reservations/src/index.d.ts +25 -3
  64. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/users/src/index.d.ts +23 -1
  65. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/video/src/index.d.ts +19 -0
  66. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/wix-data/src/index.d.ts +27 -3
  67. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/workflows-v1/src/index.d.ts +19 -0
  68. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk-auto/_shared/runtime.generated.d.ts +8 -0
  69. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/checkout.mjs +12 -4
  70. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/screens.mjs +36 -11
  71. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/state.mjs +206 -12
  72. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-activity-counters.mjs +19 -5
  73. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-ai-site-chat.mjs +19 -5
  74. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-analytics.mjs +21 -7
  75. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-async-jobs.mjs +19 -5
  76. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-automations.mjs +19 -5
  77. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-badges.mjs +19 -5
  78. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-billing-tax.mjs +19 -5
  79. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-blog.mjs +31 -14
  80. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-bookings.mjs +26 -12
  81. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-business-tools.mjs +20 -6
  82. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-chat-web.mjs +29 -7
  83. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-comments.mjs +28 -5
  84. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-contacts.mjs +19 -5
  85. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-coupons.mjs +14 -6
  86. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-data-collections.mjs +19 -5
  87. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-data.mjs +15 -5
  88. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-donations.mjs +19 -5
  89. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-ecom.mjs +33 -16
  90. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-email-marketing.mjs +19 -5
  91. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-events.mjs +14 -6
  92. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-faq.mjs +20 -6
  93. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-forms.mjs +19 -5
  94. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-forum.mjs +19 -5
  95. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-gift-cards.mjs +19 -5
  96. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-groups.mjs +19 -5
  97. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-inbox.mjs +21 -7
  98. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-invoices.mjs +14 -6
  99. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-loyalty.mjs +16 -8
  100. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-marketing.mjs +19 -5
  101. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-members-feed-viewer.mjs +21 -10
  102. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-members.mjs +17 -8
  103. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-new-releases.mjs +17 -5
  104. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-notification-preferences.mjs +20 -6
  105. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-notifications.mjs +19 -5
  106. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-online-programs.mjs +16 -8
  107. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-portfolio.mjs +16 -8
  108. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-pricing-plans.mjs +22 -14
  109. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-redirects.mjs +19 -5
  110. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-restaurants.mjs +22 -7
  111. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-reviews.mjs +14 -6
  112. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-rich-content-editor.mjs +3 -0
  113. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-seo.mjs +19 -5
  114. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-site-list.mjs +19 -5
  115. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-social-feed.mjs +15 -5
  116. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-stores-collections.mjs +20 -10
  117. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-stores.mjs +116 -83
  118. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-table-reservations.mjs +16 -8
  119. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-users.mjs +15 -5
  120. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-video.mjs +9 -1
  121. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-workflows.mjs +19 -5
  122. package/dist/cli.mjs +90 -57
  123. package/dist/preview-assets.generated.bin +0 -0
  124. package/fixtures/wix-canned.json +8 -8
  125. package/guides/dynamic-list.service.ts +38 -29
  126. package/guides/form-submit.service.ts +2 -1
  127. package/guides/index.json +6 -0
  128. package/guides/offline-reading.md +63 -0
  129. package/guides/one-app-to-himi-app.md +5 -3
  130. package/guides/recipe-dynamic-list.md +2 -0
  131. package/guides/recipe-four-state.md +1 -0
  132. package/guides/sdk-reference.json +225 -99
  133. package/guides/testing-himi-apps.md +2 -0
  134. package/guides/tier-5-add-a-screen.md +2 -0
  135. package/guides/wix-api-usage.md +9 -2
  136. package/package.json +2 -2
  137. package/templates/beauty-salon/dev/index.ts +4 -3
  138. package/templates/beauty-salon/tier5-src/_shared/collection-service.ts +6 -9
  139. package/templates/beauty-salon/tier5-src/_shared/iap-presentation.ts +7 -3
  140. package/templates/beauty-salon/tier5-src/himi-branded-lite-home/index.ts +1 -1
  141. package/templates/beauty-salon/tier5-src/himi-branded-lite-orders/index.ts +3 -0
  142. package/templates/beauty-salon-owner/dev/index.ts +19 -19
  143. package/templates/beauty-salon-owner/tier5-src/_shared/collection-service.ts +6 -9
  144. package/templates/beauty-salon-owner/tier5-src/_shared/owner-module-data.ts +1 -1
  145. package/templates/branded-lite/dev/index.ts +38 -20
  146. package/templates/branded-lite/tier5-src/_shared/collection-query-legacy.ts +194 -0
  147. package/templates/branded-lite/tier5-src/_shared/collection-query.ts +158 -99
  148. package/templates/branded-lite/tier5-src/_shared/collection-service.ts +31 -11
  149. package/templates/branded-lite/tier5-src/_shared/iap-presentation.ts +7 -3
  150. package/templates/branded-lite/tier5-src/_shared/read-service.ts +255 -0
  151. package/templates/branded-lite/tier5-src/himi-branded-lite-home/index.ts +1 -1
  152. package/templates/branded-lite/tier5-src/himi-branded-lite-orders/index.ts +3 -0
  153. package/templates/branded-lite/tier5-src/himi-branded-lite-shop/index.ts +202 -159
  154. package/templates/branded-lite/tier5-src/himi-store-product/index.ts +130 -201
  155. package/templates/community-club/dev/index.ts +4 -4
  156. package/templates/community-club/tier5-src/_shared/collection-service.ts +6 -9
  157. package/templates/community-club/tier5-src/_shared/iap-presentation.ts +7 -3
  158. package/templates/community-club/tier5-src/himi-branded-lite-home/index.ts +1 -1
  159. package/templates/community-club-owner/dev/index.ts +19 -19
  160. package/templates/community-club-owner/tier5-src/_shared/collection-service.ts +6 -9
  161. package/templates/community-club-owner/tier5-src/_shared/owner-module-data.ts +1 -1
  162. package/templates/events-meetups/dev/index.ts +3 -3
  163. package/templates/events-meetups/tier5-src/_shared/collection-service.ts +6 -9
  164. package/templates/events-meetups/tier5-src/_shared/iap-presentation.ts +7 -3
  165. package/templates/events-meetups/tier5-src/himi-branded-lite-home/index.ts +1 -1
  166. package/templates/events-meetups-owner/dev/index.ts +17 -17
  167. package/templates/events-meetups-owner/tier5-src/_shared/collection-service.ts +6 -9
  168. package/templates/events-meetups-owner/tier5-src/_shared/owner-module-data.ts +1 -1
  169. package/templates/gym-fitness/dev/index.ts +5 -4
  170. package/templates/gym-fitness/tier5-src/_shared/collection-service.ts +6 -9
  171. package/templates/gym-fitness/tier5-src/_shared/iap-presentation.ts +7 -3
  172. package/templates/gym-fitness/tier5-src/himi-branded-lite-home/index.ts +1 -1
  173. package/templates/gym-fitness/tier5-src/himi-branded-lite-orders/index.ts +3 -0
  174. package/templates/gym-fitness-owner/dev/index.ts +20 -20
  175. package/templates/gym-fitness-owner/tier5-src/_shared/collection-service.ts +6 -9
  176. package/templates/gym-fitness-owner/tier5-src/_shared/owner-module-data.ts +1 -1
  177. package/templates/nonprofit-cause/dev/index.ts +4 -4
  178. package/templates/nonprofit-cause/tier5-src/_shared/collection-service.ts +6 -9
  179. package/templates/nonprofit-cause/tier5-src/_shared/iap-presentation.ts +7 -3
  180. package/templates/nonprofit-cause/tier5-src/himi-branded-lite-home/index.ts +1 -1
  181. package/templates/nonprofit-cause-owner/dev/index.ts +18 -18
  182. package/templates/nonprofit-cause-owner/tier5-src/_shared/collection-service.ts +6 -9
  183. package/templates/nonprofit-cause-owner/tier5-src/_shared/owner-module-data.ts +1 -1
  184. package/templates/owner-lite/dev/index.ts +20 -20
  185. package/templates/owner-lite/tier5-src/_shared/collection-service.ts +6 -9
  186. package/templates/owner-lite/tier5-src/_shared/owner-module-data.ts +1 -1
  187. package/templates/podcast-creator/dev/index.ts +4 -4
  188. package/templates/podcast-creator/tier5-src/_shared/collection-service.ts +6 -9
  189. package/templates/podcast-creator/tier5-src/_shared/iap-presentation.ts +7 -3
  190. package/templates/podcast-creator/tier5-src/himi-branded-lite-home/index.ts +1 -1
  191. package/templates/podcast-creator-owner/dev/index.ts +19 -19
  192. package/templates/podcast-creator-owner/tier5-src/_shared/collection-service.ts +6 -9
  193. package/templates/podcast-creator-owner/tier5-src/_shared/owner-module-data.ts +1 -1
  194. package/templates/portfolio-creative/dev/index.ts +5 -4
  195. package/templates/portfolio-creative/tier5-src/_shared/collection-service.ts +6 -9
  196. package/templates/portfolio-creative/tier5-src/_shared/iap-presentation.ts +7 -3
  197. package/templates/portfolio-creative/tier5-src/himi-branded-lite-home/index.ts +1 -1
  198. package/templates/portfolio-creative/tier5-src/himi-branded-lite-orders/index.ts +3 -0
  199. package/templates/portfolio-creative-owner/dev/index.ts +19 -19
  200. package/templates/portfolio-creative-owner/tier5-src/_shared/collection-service.ts +6 -9
  201. package/templates/portfolio-creative-owner/tier5-src/_shared/owner-module-data.ts +1 -1
  202. package/templates/restaurant/dev/index.ts +3 -3
  203. package/templates/restaurant/tier5-src/_shared/collection-service.ts +6 -9
  204. package/templates/restaurant/tier5-src/_shared/iap-presentation.ts +7 -3
  205. package/templates/restaurant/tier5-src/himi-branded-lite-home/index.ts +1 -1
  206. package/templates/restaurant-owner/dev/index.ts +20 -20
  207. package/templates/restaurant-owner/tier5-src/_shared/collection-service.ts +6 -9
  208. package/templates/restaurant-owner/tier5-src/_shared/owner-module-data.ts +1 -1
  209. package/templates/retail-store/dev/index.ts +19 -1
  210. package/templates/retail-store/tier5-src/_shared/collection-query-legacy.ts +194 -0
  211. package/templates/retail-store/tier5-src/_shared/collection-query.ts +158 -99
  212. package/templates/retail-store/tier5-src/_shared/collection-service.ts +31 -11
  213. package/templates/retail-store/tier5-src/_shared/iap-presentation.ts +7 -3
  214. package/templates/retail-store/tier5-src/_shared/read-service.ts +255 -0
  215. package/templates/retail-store/tier5-src/himi-branded-lite-home/index.ts +1 -1
  216. package/templates/retail-store/tier5-src/himi-branded-lite-orders/index.ts +3 -0
  217. package/templates/retail-store/tier5-src/himi-branded-lite-shop/index.ts +202 -159
  218. package/templates/retail-store/tier5-src/himi-store-product/index.ts +130 -201
  219. package/templates/retail-store-owner/dev/index.ts +18 -18
  220. package/templates/retail-store-owner/tier5-src/_shared/collection-service.ts +6 -9
  221. package/templates/retail-store-owner/tier5-src/_shared/owner-module-data.ts +1 -1
  222. package/templates/yoga-studio/dev/index.ts +4 -4
  223. package/templates/yoga-studio/tier5-src/_shared/collection-service.ts +6 -9
  224. package/templates/yoga-studio/tier5-src/_shared/iap-presentation.ts +7 -3
  225. package/templates/yoga-studio/tier5-src/himi-branded-lite-home/index.ts +1 -1
  226. package/templates/yoga-studio-owner/dev/index.ts +18 -18
  227. package/templates/yoga-studio-owner/tier5-src/_shared/collection-service.ts +6 -9
  228. package/templates/yoga-studio-owner/tier5-src/_shared/owner-module-data.ts +1 -1
@@ -12,6 +12,8 @@ export interface CollectionSaving {
12
12
  type State = Record<string, unknown>;
13
13
  interface BackendCollection {
14
14
  restorePagesOnLoad?: boolean;
15
+ /** Opt in only for templates whose generated screens adopt the shared read status. */
16
+ readLifecycle?: boolean;
15
17
  /** Sees the load action's argument (the screen's onAppear params) BEFORE the query runs. A returned
16
18
  * `{ filter, sort }` resets the criteria for that load (a screen that always opens on one view). */
17
19
  onLoad?(arg: unknown): CollectionCriteriaReset | void;
@@ -83,6 +85,8 @@ export function decorateCollection(name: string, def: { state: State; actions?:
83
85
  })
84
86
  : [];
85
87
  let pendingFilter = '';
88
+ const requestedReadLifecycle = (def as { readLifecycle?: { refreshAction: string; statusPath: string } }).readLifecycle;
89
+ const hasReadLifecycle = !!requestedReadLifecycle || backend?.readLifecycle === true;
86
90
  const applyNavigationCriteria = (arg: unknown, state: State): boolean => {
87
91
  const input = object(arg);
88
92
  let changed = false;
@@ -144,16 +148,18 @@ export function decorateCollection(name: string, def: { state: State; actions?:
144
148
  if (previewId && !rows.some(row => collectionId(row) === previewId)) previewId = '';
145
149
  const filterChoices = [{ id: 'all', label: copyParam('collection.all', 'All') }, ...(config.rowStyle === 'flat' ? [] : [{ id: 'saved', label: copyParam('collection.saved', 'Saved') }]),
146
150
  ...projected.filters.map(label => ({ id: `value:${label}`, label: copyParam(`collection.filter.${label}`, label) }))];
147
- const loading = s.status === 'loading' || s.isLoading === true;
151
+ const readStatus = hasReadLifecycle ? s.readStatus as { hasSnapshot?: boolean; refreshing?: boolean } | undefined : undefined;
152
+ const loading = readStatus ? readStatus.refreshing === true && readStatus.hasSnapshot !== true : s.status === 'loading' || s.isLoading === true;
148
153
  // Older domain reducers expose `errored` rather than `isErrored`; backend
149
154
  // adapters can also intentionally keep their display status "ready" while
150
155
  // the query controller carries a recoverable failure. Treat each shape as
151
156
  // one collection state so the shared UI never renders a blank catalog.
152
157
  const backendState = backend?.controller.getState();
158
+ const read = backend?.readLifecycle ? s.readStatus as { hasSnapshot?: boolean; offline?: boolean; error?: string } | undefined : undefined;
153
159
  const provisional = backendState?.provisional === true;
154
- const errored = s.status === 'error' || s.isErrored === true || s.errored === true || backendState?.status === 'error';
160
+ const errored = !!read?.error || (backend?.readLifecycle === true && !!backendState?.error) || s.status === 'error' || s.isErrored === true || s.errored === true || backendState?.status === 'error';
155
161
  const domainError = typeof s.error === 'string' ? s.error.trim() : '';
156
- const queryError = typeof backendState?.error === 'string' ? backendState.error.trim() : '';
162
+ const queryError = read?.offline && !read.hasSnapshot ? 'This content isn’t available offline. Connect to load it.' : read?.error || (typeof backendState?.error === 'string' ? backendState.error.trim() : '');
157
163
  // Some domains carry their failure sentence in `emptyText` (portfolio, podcasts);
158
164
  // the screen folds that node into this block, so keep the domain's own words.
159
165
  const domainEmptyError = errored && typeof s.emptyText === 'string' ? s.emptyText.trim() : '';
@@ -178,7 +184,7 @@ export function decorateCollection(name: string, def: { state: State; actions?:
178
184
  // "All" and the collection's own default (e.g. Upcoming) are both un-narrowed: Clear
179
185
  // resets to "All", so showing it on either would offer a reset that changes nothing.
180
186
  const narrowed = !!prefs.query.trim() || (prefs.filter !== 'all' && prefs.filter !== initialCollection(config).filter) || domainNarrowed;
181
- const sourceEmpty = !loading && !errored && rows.length === 0 &&
187
+ const sourceEmpty = (backend?.readLifecycle !== true || backendState?.read.hasSnapshot === true) && !loading && !errored && rows.length === 0 &&
182
188
  (!(backend && config.backendQuery) || !narrowed) && !domainNarrowed;
183
189
  const emptySourceText = (typeof s.emptyText === 'string' && s.emptyText.trim()) || copyParam('collection.emptySource', 'Nothing here yet');
184
190
  // Chrome is noise over a small un-narrowed list (fewer than COLLECTION_CHROME_MIN_ITEMS
@@ -196,14 +202,15 @@ export function decorateCollection(name: string, def: { state: State; actions?:
196
202
  noPageError: s.pageError !== true,
197
203
  // Rows read from the device ahead of the live read are shown as they are: "saved offline" is what stays on
198
204
  // screen when the live read failed, not a first visit that is still loading.
199
- noOffline: s.fromCache !== true || provisional,
205
+ noOffline: !(backend?.readLifecycle === true && backendState?.read.stale || s.fromCache === true) || provisional,
200
206
  noError: !errored,
201
207
  noRetry: !canRetry || signInRequired,
202
208
  noSignIn: !signInRequired,
203
209
  errorTitle: signInRequired ? copyParam('collection.signInTitle', 'Sign in to see this') : copyParam('collection.loadErrorTitle', 'This collection could not load'),
204
210
  errorMessage,
205
- // A skeleton only with nothing to show: a search re-running over the rows on screen keeps them.
206
- noLoading: !loading || rows.length > 0,
211
+ isLoading: loading, notLoading: !loading,
212
+ // Legacy searches retain visible rows; opted-in reads also retain valid empty snapshots.
213
+ noLoading: !loading || rows.length > 0 || (backend?.readLifecycle === true && backendState?.read.hasSnapshot === true),
207
214
  // De-dup against a domain that paints its OWN empty block (screen-kit emptyBlock is
208
215
  // gated on the domain's top-level `noEmpty`): only one empty state ever shows.
209
216
  noEmptySource: !sourceEmpty || s.noEmpty === false,
@@ -330,7 +337,11 @@ export function decorateCollection(name: string, def: { state: State; actions?:
330
337
  };
331
338
  actions.refresh = (_s, arg) => {
332
339
  stashCopy(arg); syncSaved();
333
- return backend.controller.query(criteria()).then(() => undefined);
340
+ // Reconnect refreshes the currently visible result. Rebuild every page the
341
+ // user had already opened, using fresh cursors, before replacing the snapshot.
342
+ return (backend.readLifecycle
343
+ ? backend.controller.query(criteria(), true, true)
344
+ : backend.controller.query(criteria())).then(() => undefined);
334
345
  };
335
346
  actions.loadMore = () => backend.controller.loadMore().then(() => undefined);
336
347
  actions.retry = actions.collectionRetry = () => backend.controller.retry().then(() => undefined);
@@ -344,7 +355,10 @@ export function decorateCollection(name: string, def: { state: State; actions?:
344
355
  const retry = actions.refresh ?? actions.load;
345
356
  if (retry) actions.collectionRetry = retry;
346
357
  }
347
- return { state: derive(def.state), actions };
358
+ return { state: derive(def.state), actions,
359
+ ...(requestedReadLifecycle || backend?.readLifecycle ? { readPresentation: derive } : {}),
360
+ ...(requestedReadLifecycle ? { readLifecycle: requestedReadLifecycle }
361
+ : backend?.readLifecycle && runtime?.input?.__himiOwnerPreview !== 'sample' ? { readLifecycle: { refreshAction: 'refresh', statusPath: 'readStatus' } } : {}) };
348
362
  }
349
363
 
350
364
  /** Domain-owned page source with shared query controls. Publishing uses a normal
@@ -357,6 +371,8 @@ export function defineBackendCollection<S extends object, Row extends object>(na
357
371
  onlineWriteMessageFields?: readonly string[];
358
372
  /** Revalidate already opened pages on navigation return; explicit refresh resets. */
359
373
  restorePagesOnLoad?: boolean;
374
+ /** Opt this shared list into the read status and reconnect lifecycle. */
375
+ readLifecycle?: boolean;
360
376
  /** The load action's argument (onAppear params beyond `copy`), before the query runs — for a
361
377
  * domain setting that rides the descriptor, e.g. My Bookings' `weeklyGoal`. */
362
378
  onLoad?(arg: unknown): CollectionCriteriaReset | void;
@@ -367,12 +383,16 @@ export function defineBackendCollection<S extends object, Row extends object>(na
367
383
  actions?: Record<string, (state: S, ...args: unknown[]) => S | undefined | Promise<S | undefined>>;
368
384
  }) {
369
385
  let publish: (() => unknown) | undefined;
370
- const controller = createCollectionQuery(options.source, () => { publish?.(); });
386
+ const controller = createCollectionQuery({ ...options.source,
387
+ // Access-denial clearing belongs to the opt-in read lifecycle. Keep the legacy
388
+ // collection contract unchanged for templates that have not adopted it.
389
+ ...(options.readLifecycle ? { clearOnError: error => /HTTP (401|403)\b|forbidden|principal.changed|identity.changed/i.test(String(error)) || options.source.clearOnError?.(error) === true } : {}) }, () => { publish?.(); });
371
390
  const definition = decorateCollection(name, { state: options.state as State, actions: options.actions as Record<string, Reducer> | undefined }, options.saving, {
372
391
  restorePagesOnLoad: options.restorePagesOnLoad,
392
+ readLifecycle: options.readLifecycle,
373
393
  onLoad: options.onLoad,
374
394
  controller: controller as unknown as BackendCollection['controller'],
375
- apply: (state, page) => options.apply(state as S, page as unknown as CollectionQueryState<Row>) as State,
395
+ apply: (state, page) => ({ ...options.apply(state as S, page as unknown as CollectionQueryState<Row>), ...(options.readLifecycle ? { readStatus: page.read } : {}) }) as State,
376
396
  });
377
397
  const registerService = options.onlineWrites ? withOnlineWrites(options.register ?? register, ['confirmCancel', 'confirmOperation', 'retryOperation', 'commitWrite'], { messageFields: options.onlineWriteMessageFields ?? ['actionMessage'] }) : (options.register ?? register);
378
398
  const handle = registerService(name, definition);
@@ -103,6 +103,8 @@ export function withIapPresentation<S extends object>(options: {
103
103
  // scopeLanded is the completion of the active load, not a stale load result. It
104
104
  // must publish even if another overlapping onAppear load advanced contentVersion.
105
105
  if (!next || (content && action !== "scopeLanded" && version !== contentVersion)) return undefined;
106
+ // A committed read projection supersedes older load results still carrying a skeleton.
107
+ if (action === "readChanged" && definition.readLifecycle) contentVersion++;
106
108
  if (content) {
107
109
  options.onContent?.(next);
108
110
  const nextItems = options.items(next);
@@ -130,9 +132,11 @@ export function withIapPresentation<S extends object>(options: {
130
132
  };
131
133
  try {
132
134
  const next = reduce(state, ...args);
133
- // Keep synchronous sidecars synchronous. Adding an await lets their old
134
- // catalog snapshot commit after a newer asynchronous refresh.
135
- if (content || (next && typeof (next as Promise<S | undefined>).then === "function")) {
135
+ // A cached read projection must commit before another onAppear can consume it.
136
+ // Other content actions retain their scheduling: shop filters and panels can
137
+ // change while a load is pending, and their existing completion order matters.
138
+ const synchronousReadProjection = action === "readChanged" && !!definition.readLifecycle;
139
+ if ((content && !synchronousReadProjection) || (next && typeof (next as Promise<S | undefined>).then === "function")) {
136
140
  return (async () => {
137
141
  try { return presentResult(await next); }
138
142
  finally { finished(); }
@@ -0,0 +1,255 @@
1
+ import { stashCopy } from './himi-copy.js';
2
+ const offlineReadsEnabled = true;
3
+ import { isNotFound } from './owner-load-error.js';
4
+ import { defineService as register, createReadController, initialReadStatus, type ServiceDefinition, type ReadMetadata, type ReadStatus } from '@himalaya/state';
5
+
6
+ export interface ReadOptions {
7
+ cache: 'only-if-cached' | 'reload';
8
+ onCacheResult(metadata: ReadMetadata): void;
9
+ }
10
+ /** Passed explicitly down the loader's call graph; never a process-global cache mode. */
11
+ export function readOptions(arg: unknown): ReadOptions | undefined {
12
+ return (arg as { __himiRead?: ReadOptions } | undefined)?.__himiRead;
13
+ }
14
+
15
+ /** Explicit request context for loaders with their own presentation state (e.g. IAP). */
16
+ export async function policyRead<T>(cache: ReadOptions['cache'], load: (options: ReadOptions) => Promise<T>, localResult?: (data: T) => boolean) {
17
+ let metadata: ReadMetadata | undefined;
18
+ const data = await load({ cache, onCacheResult(value) {
19
+ metadata = metadata ? { ...value, source: metadata.source === 'cache' || value.source === 'cache' ? 'cache' : 'network',
20
+ cacheable: metadata.cacheable && value.cacheable,
21
+ fetchedAt: Math.min(metadata.fetchedAt, value.fetchedAt),
22
+ expiresAt: Math.min(metadata.expiresAt ?? 0, value.expiresAt ?? 0) } : value;
23
+ } });
24
+ if (!metadata && localResult?.(data)) metadata = { source: 'local', cacheable: false, fetchedAt: Date.now() };
25
+ if (!metadata) throw new Error('Read loader did not provide response provenance');
26
+ return { data, metadata };
27
+ }
28
+
29
+ type State = Record<string, unknown>;
30
+ type Action = (state: State, arg?: unknown) => State | undefined | Promise<State | undefined>;
31
+ type ReadFailure = Error & { userMessage?: string; signInRequired?: boolean };
32
+ // A record Wix answers 404 for is gone (a delete whose reply was lost): its retained snapshot must
33
+ // not outlive it. Unlike `denied`, it leaves drafts and local controls alone.
34
+ const gone = (error: unknown) => isNotFound(error);
35
+ const denied = (error: unknown) => /HTTP (401|403)\b|forbidden|principal.changed|identity.changed|member access changed/i.test(String(error));
36
+
37
+ /** Adapts a read-only load reducer. Callers forward readOptions(arg) to every
38
+ * primary SDK read and let errors reject. Other actions remain ordinary reducers.
39
+ * The retained snapshot is in memory; persistence belongs exclusively to the
40
+ * policy-controlled Wix cache, never to generic worker hydration or local-db rows.
41
+ */
42
+ export function withReadLifecycle(registerService: typeof register = register, loadAction = 'load', options: {
43
+ errorMessage?(error: unknown): string;
44
+ /** Only an authored create form or guest shell; never a server-derived fallback. */
45
+ localResult?(state: State): boolean;
46
+ /** Additional host-owned scope when a read explicitly addresses another site. */
47
+ scopeKey?(): string;
48
+ /** Clear module-owned read sidecars on identity loss, access denial, or disposal. */
49
+ onInvalidate?(): void;
50
+ /** Editable fields survive recovery once loaded, and edits during an initial read. */
51
+ preserveFields?: readonly string[];
52
+ unavailable?(state: State, status: ReadStatus): State;
53
+ /** Authored source content only; never return a network mirror or demo fallback here. */
54
+ authoredFallback?(state: State, status: ReadStatus): State | undefined;
55
+ } = {}): typeof register {
56
+ if (!offlineReadsEnabled) return registerService;
57
+ return ((name: string, definition: ServiceDefinition<State>) => {
58
+ if ((globalThis as { himalaya?: { input?: Record<string, unknown> } }).himalaya?.input?.__himiOwnerPreview === 'sample') return registerService(name, definition);
59
+ const load = definition.actions?.[loadAction] as Action | undefined;
60
+ if (!load || definition.readLifecycle) return registerService(name, definition);
61
+ let args: Record<string, unknown> = {}, key = '', page: State | undefined, read = initialReadStatus();
62
+ let replacePage = false;
63
+ let activeLoad: { promise: Promise<void>; revision: number } | undefined;
64
+ let startedState: State | undefined;
65
+ let hadSnapshot = false, accessRevoked = false, accessNeedsSignIn = false;
66
+ const localResult = options.localResult ?? (value => value.notFound === true);
67
+ let projectedState: State | undefined;
68
+ const editedFields = new Set<string>();
69
+ let service: { getState(): State; readChanged(): Promise<unknown> };
70
+ const project = (state: State): State => {
71
+ for (const field of options.preserveFields ?? []) {
72
+ if (projectedState && JSON.stringify(state[field]) !== JSON.stringify(projectedState[field])) editedFields.add(field);
73
+ }
74
+ const value: State = { ...state, ...(replacePage && page ? page : !read.hasSnapshot ? definition.state : {}), readStatus: read };
75
+ replacePage = false;
76
+ if (accessRevoked) for (const field of options.preserveFields ?? []) {
77
+ if (Object.prototype.hasOwnProperty.call(definition.state, field)) value[field] = definition.state[field];
78
+ else delete value[field];
79
+ }
80
+ // Drafts and local controls survive both missing snapshots and pending reads.
81
+ for (const field of accessRevoked ? [] : Object.keys(state)) {
82
+ if (options.preserveFields?.includes(field) && (hadSnapshot || editedFields.has(field))) value[field] = state[field];
83
+ if (/Draft$|^draft$|^quantity$|^selection$|^selected|^expandedIds$|^searchQuery$|^query$|^filter$|^sort$/.test(field) &&
84
+ (!read.hasSnapshot || (startedState && JSON.stringify(state[field]) !== JSON.stringify(startedState[field])))) value[field] = state[field];
85
+ }
86
+ if (!read.hasSnapshot) {
87
+ const authored = options.authoredFallback?.(value, read);
88
+ if (authored) return projectedState = { ...authored, readStatus: { ...read, hasLocalContent: true } };
89
+ const message = read.offline ? 'This content isn’t available offline. Connect to load it.' : read.error;
90
+ const unavailable = { ...value, status: read.refreshing ? 'loading' : 'error', isLoading: read.refreshing,
91
+ notLoading: !read.refreshing, busy: !read.refreshing, hideContent: true, errored: !read.refreshing, isErrored: !read.refreshing,
92
+ notErrored: read.refreshing, found: false, noEntity: true, hideNotFound: true, hideLoading: !read.refreshing, hideLoadError: read.refreshing, error: message, errorText: message, emptyText: message, noEmpty: true };
93
+ if (accessRevoked) Object.assign(unavailable, { readDenied: true, signInRequired: accessNeedsSignIn, noSignIn: !accessNeedsSignIn });
94
+ return projectedState = options.unavailable?.(unavailable, read) ?? unavailable;
95
+ }
96
+ return projectedState = definition.readPresentation?.(value) ?? value;
97
+ };
98
+ const perform = async (cache: ReadOptions['cache'], requestKey: string) => {
99
+ const requestArgs = requestKey ? JSON.parse(requestKey) as Record<string, unknown> : {};
100
+ return policyRead(cache, async readContext => {
101
+ const input = { ...(service?.getState() ?? definition.state), __himiRead: readContext } as State;
102
+ // Unavailable-state flags added by this adapter are presentation, not a failed
103
+ // domain read. A reducer that spreads its input must not inherit those flags
104
+ // and have its successful cached/local result rejected as another failure.
105
+ for (const field of ['errored', 'isErrored']) {
106
+ if (!Object.prototype.hasOwnProperty.call(definition.state, field)) delete input[field];
107
+ }
108
+ // An unsaved create form is authored local content. Its no-op loader must
109
+ // receive its declared status, not the adapter's temporary unavailable state.
110
+ if (!read.hasSnapshot && options.localResult?.(input)) input.status = definition.state.status;
111
+ const data = await load(input, { ...requestArgs, __himiRead: readContext });
112
+ if (!data || data.status === 'error' || data.errored === true || data.isErrored === true) {
113
+ const unauthorized = data?.signInRequired === true || /HTTP 401|unauthori[sz]ed|sign[\s-]?in/i.test(String(data?.error || data?.errorText || ''));
114
+ const failure = new Error(unauthorized ? 'HTTP 401 unauthorized' : data?.readDenied ? 'HTTP 403 forbidden' : 'Read failed') as ReadFailure;
115
+ failure.signInRequired = data?.signInRequired === true;
116
+ const userMessage = data?.error || data?.errorText || data?.emptyText;
117
+ if (typeof userMessage === 'string' && userMessage.trim()) failure.userMessage = userMessage.trim();
118
+ throw failure;
119
+ }
120
+ delete data.__himiRead;
121
+ return data;
122
+ }, localResult);
123
+ };
124
+ const controller = createReadController<State>({
125
+ allowLocal: true, cached: key => perform('only-if-cached', key), fetch: key => perform('reload', key), clearOnError(error) {
126
+ accessRevoked = denied(error);
127
+ accessNeedsSignIn = (error as ReadFailure | null)?.signInRequired === true || /HTTP 401|unauthori[sz]ed|sign[\s-]?in/i.test(String(error));
128
+ const clear = accessRevoked || gone(error);
129
+ if (clear) options.onInvalidate?.();
130
+ return clear;
131
+ }, errorMessage(error) {
132
+ return (error as ReadFailure | null)?.userMessage || options.errorMessage?.(error) || (accessNeedsSignIn ? 'Sign in to view this content.' : '') || '';
133
+ },
134
+ publish(data, status) {
135
+ // A failed revalidation republishes the controller's retained snapshot. The worker
136
+ // state can contain newer local changes (for example, another fetched page or a
137
+ // selected row), so do not replace that state with the older controller page on a
138
+ // stale/error publication. A first cached paint and a successful refresh still
139
+ // replace the visible read data.
140
+ replacePage = status.hasSnapshot && data !== undefined
141
+ && (!page || (!status.refreshing && !status.stale && !status.error));
142
+ page = data;
143
+ read = status;
144
+ void service?.readChanged();
145
+ },
146
+ });
147
+ const host = (globalThis as { himalaya?: { __subscribeReadLifecycle?(listener: (signal: string) => void): () => void } }).himalaya;
148
+ let unsubscribeAdapter: (() => void) | undefined;
149
+ unsubscribeAdapter = host?.__subscribeReadLifecycle?.(signal => {
150
+ if (signal === 'dispose') { options.onInvalidate?.(); unsubscribeAdapter?.(); return; }
151
+ if (signal !== 'invalidate') return;
152
+ options.onInvalidate?.();
153
+ // Identity invalidation clears the controller's private snapshot, so the
154
+ // adapter must also forget that its form fields came from a prior user.
155
+ // Otherwise project() can preserve those fields while the host clears the
156
+ // shared read state.
157
+ hadSnapshot = false;
158
+ accessRevoked = true;
159
+ accessNeedsSignIn = false;
160
+ editedFields.clear();
161
+ page = undefined;
162
+ read = initialReadStatus();
163
+ replacePage = true;
164
+ void service?.readChanged();
165
+ });
166
+ const refresh: Action = async (_state, arg) => {
167
+ stashCopy(arg);
168
+ if (arg && typeof arg === 'object') {
169
+ args = { ...arg as Record<string, unknown> };
170
+ // Localized copy is presentation, not a different backend query.
171
+ delete args.copy;
172
+ }
173
+ const next = JSON.stringify({ ...((globalThis as { himalaya?: { input?: Record<string, unknown> } }).himalaya?.input ?? {}), ...args, ...(options.scopeKey ? { __readScope: options.scopeKey() } : {}) });
174
+ // Coalesce through the final state projection, not only the controller's
175
+ // network task. A module-scope load() and the host's boot/onAppear load()
176
+ // commonly join the same request; returning when the fetch settles lets the
177
+ // host snapshot the worker before the first caller's readChanged() publishes.
178
+ if (key === next && activeLoad && activeLoad.revision === controller.getRevision()) {
179
+ await activeLoad.promise;
180
+ await service?.readChanged();
181
+ return service?.getState();
182
+ }
183
+ hadSnapshot = read.hasSnapshot && key === next;
184
+ key = next;
185
+ startedState = service?.getState() ?? definition.state;
186
+ projectedState = startedState; editedFields.clear(); accessRevoked = false; accessNeedsSignIn = false;
187
+ const load = controller.load(key);
188
+ const revision = controller.getRevision();
189
+ const task = (async () => {
190
+ await load;
191
+ // Controller publications project through this service action. Await its final
192
+ // projection before resolving load/retry so hosts that serialize worker actions
193
+ // cannot render the pre-read state after the request has completed.
194
+ await service?.readChanged();
195
+ })();
196
+ const active = { promise: task, revision };
197
+ activeLoad = active;
198
+ try {
199
+ await task;
200
+ } finally { if (activeLoad === active) activeLoad = undefined; }
201
+ return await service?.getState();
202
+ };
203
+ service = registerService(name, { ...definition, readLifecycle: { refreshAction: 'refreshRead', statusPath: 'readStatus' },
204
+ state: { ...definition.state, readStatus: read },
205
+ actions: { ...definition.actions, ...(loadAction !== "refresh" && definition.actions?.refresh ? { refresh: () => controller.refresh().then(() => undefined) } : {}), [loadAction]: refresh, retry: () => controller.refresh().then(() => undefined),
206
+ refreshRead: refresh, readChanged: project },
207
+ });
208
+ return service;
209
+ }) as typeof register;
210
+ }
211
+
212
+ // Generic services keep their existing behavior. A `load` action is not proof
213
+ // that it is a safe, cache-eligible read: local device state and writes often use
214
+ // that name too. Shared read helpers and opted-in screens register through
215
+ // withReadLifecycle explicitly.
216
+ export const defineService: typeof register = register;
217
+ /** Safe opt-in for a service whose load action is a cache-eligible read. */
218
+ export const readService: typeof register = withReadLifecycle();
219
+
220
+
221
+ /** Raw read escape hatch for an endpoint without a generated SDK binding. */
222
+ export async function rawRead<T>(method: string, path: string, body?: unknown, options?: ReadOptions): Promise<T> {
223
+ const api = (globalThis as any).himalaya?.wixApi;
224
+ if (!api) throw new Error('Wix bridge unavailable');
225
+ if (!options) return api.call(method, path, body) as Promise<T>;
226
+ if (!api.read) throw new Error('READ_LIFECYCLE_UNSUPPORTED');
227
+ const result = await api.read(method, path, body, undefined, { cache: options.cache });
228
+ options.onCacheResult(result.cache);
229
+ return result.data as T;
230
+ }
231
+
232
+ /** Editor recovery refreshes its read eligibility without replacing an open draft
233
+ * or its concurrency revision. Field values remain owned by the form until the
234
+ * user explicitly submits or reopens it. Only a new, unsaved form is local data. */
235
+ export function withFormReadLifecycle(registerService: typeof register = register): typeof register {
236
+ if (!offlineReadsEnabled) return registerService;
237
+ return ((name: string, definition: ServiceDefinition<State>) => withReadLifecycle(registerService, 'load', {
238
+ preserveFields: ['title', 'name', 'price', 'sku', 'visible', 'revision', 'category',
239
+ 'description', 'originalDescription', 'descriptionMode', 'descriptionJson', 'descriptionDirty', 'descriptionEdits',
240
+ 'when', 'location', 'excerpt', 'body', 'richContent', 'bodyJson', 'bodyDirty', 'bodyEdits',
241
+ 'detailsOpen', 'coverUrl', 'originalCoverUrl', 'coverPreview', 'isFeatured',
242
+ 'question', 'answer', 'answerFormat', 'answerMode', 'answerJson', 'answerDirty', 'answerEdits', 'categoryId', 'statusValue'],
243
+ localResult: value => value.mode === 'create' && !value.id,
244
+ })(name, definition)) as typeof register;
245
+ }
246
+
247
+ /** Server-derived state is never a generic worker snapshot; explicit drafts may persist. */
248
+ export function withReadPersistence(registerService: typeof register, draftFields: readonly string[] = []): typeof register {
249
+ if (!offlineReadsEnabled) return registerService;
250
+ return ((name: string, definition: ServiceDefinition<State>) => registerService(name, {
251
+ ...definition,
252
+ notPersisted: [...new Set([...(definition.notPersisted ?? []),
253
+ ...Object.keys(definition.state).filter(key => !draftFields.includes(key))])],
254
+ })) as typeof register;
255
+ }
@@ -250,7 +250,7 @@ interface State {
250
250
  hideFeaturedSkeleton: boolean;
251
251
  welcome: string;
252
252
  refreshedAt: string;
253
- /** The resolved home layout preset (RFC 0045 FragmentSlot discriminator).
253
+ /** The resolved home layout preset (shared slot discriminator).
254
254
  * Plain string, never a proto enum — a `$service:` casePath decodes it as a
255
255
  * JSON string and unknown values fall through to the shipped defaultCase. */
256
256
  effectiveLayout: string;
@@ -161,6 +161,9 @@ function derive(state: State): State {
161
161
  });
162
162
  return {
163
163
  ...state,
164
+ nextCursor: state.nextCursor ?? "",
165
+ filterOptions: state.filterOptions ?? [{ id: "all", label: copyParam("collection.all", "All"), selected: true },
166
+ ...Object.keys(FULFILLMENT).map(label => ({ id: `value:${label}`, label: copyParam(`collection.filter.${label}`, label), selected: false }))],
164
167
  displayRows: rows.map((row, index, rows) => {
165
168
  // Older owned cache rows retain their original date in meta. Group only
166
169
  // adjacent days so pagination and an explicit backend sort remain authoritative.