@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
@@ -10,28 +10,33 @@
10
10
  //
11
11
  // Opened with `category` = a category id or name; absent ⇒ All.
12
12
  import { getCurrentCart } from "@himalaya/wix-ecom";
13
- import { getAll, remove, upsert, type DbEntity } from "@himalaya/local-db";
13
+ import { createReadController, initialReadStatus, connectivity, type ReadMetadata, type ReadStatus } from "@himalaya/state";
14
+ import type { WixCallOptions } from "@himalaya/wix-stores";
14
15
  import { searchStoreProducts, type StoreProduct } from "../_shared/himi-store.js";
15
16
  import { withIapPresentation } from "../_shared/iap-presentation.js";
16
17
  import { stashCopy, copyParam, copyTemplate } from "../_shared/himi-copy.js";
17
18
  import { loadStoreCategories, type StoreCategoryCard } from "../_shared/himi-store-categories.js";
18
19
  import {
19
20
  FACETS_BOOT, PAGE_SIZE, applyFacets, countText, currencyOf, facetActions, idOf, option, readViewPrefs, selected, toRows, toggleSaved,
20
- type FacetState, type Option, type ProductCard, type Row,
21
+ type FacetState, type Option, type Row,
21
22
  } from "../_shared/himi-store-facets.js";
22
23
 
23
24
  const ALL = "all";
24
25
  const PAGE = 100;
25
26
  const MAX_PRODUCTS = 500;
26
- const ENTITY = "products";
27
+
27
28
 
28
29
  interface S extends FacetState {
30
+ readStatus: ReadStatus;
31
+ readMessage: string;
32
+ noReadMessage: boolean;
33
+ hasReadMessage: boolean;
29
34
  status: "loading" | "ready" | "error";
30
35
  isLoading: boolean;
31
36
  notLoading: boolean;
32
37
  isErrored: boolean;
33
38
  notErrored: boolean;
34
- /** The rows are the last synced catalog, read from the device while offline. */
39
+ /** Response provenance comes from the shared policy-controlled read adapter. */
35
40
  fromCache: boolean;
36
41
  noOffline: boolean;
37
42
  categorySelected: string;
@@ -58,14 +63,20 @@ let pendingCategoriesRead: Promise<StoreCategoryCard[]> | undefined;
58
63
  /** Cart count can land while the first product scope is still pending. */
59
64
  let loadedCartCount: number | undefined;
60
65
  /** Each category's fetched rows, so switching back to a chip is instant. */
61
- const scopes = new Map<string, { rows: Row[]; complete: boolean; loading: boolean; fromCache?: boolean; currency?: string; total?: number }>();
66
+ interface ScopeSnapshot {
67
+ rows: Row[]; complete: boolean; loading: boolean; fromCache?: boolean;
68
+ currency?: string; total?: number; cursor?: string; metadata: ReadMetadata;
69
+ }
70
+ const scopes = new Map<string, ScopeSnapshot>();
71
+ const readers = new Map<string, ReturnType<typeof createReadController<ScopeSnapshot>>>();
72
+ const publications = new Map<string, Promise<void>>();
73
+ const publicationVersions = new Map<string, number>();
74
+ const restFlights = new WeakSet<ScopeSnapshot>();
62
75
  const pendingScopes = new Map<string, number>();
63
76
  // A pull-to-refresh that loses the foreground generation still represents fresh data the
64
77
  // shopper asked for. Keep that intent until a current request for the scope lands, so a
65
78
  // quick trip away and back cannot present the old cached scope as the refresh result.
66
79
  const pendingRefreshScopes = new Map<string, number>();
67
- let cacheWriteGeneration = 0;
68
- let cacheWriteQueue: Promise<void> = Promise.resolve();
69
80
  /** Bumped on every scope change; a stale first page must never write over a newer scope. */
70
81
  let generation = 0;
71
82
  /** The category the screen shows. Module state, not action state: the boot load and the
@@ -79,20 +90,6 @@ let pendingAuxiliary: { cartCount: number; version: number } | undefined;
79
90
  let auxiliaryVersion = 0;
80
91
  let lastRouteActivation: string | undefined;
81
92
  let routeCategoryConsumed = false;
82
- let legacyCacheFacetResetDone = false;
83
- const LEGACY_CACHE_FACETS_KEY = "himi-store:legacy-cache-facets-reset:v1";
84
-
85
- function shouldResetLegacyCacheFacets(): boolean {
86
- if (legacyCacheFacetResetDone) return false;
87
- legacyCacheFacetResetDone = true;
88
- try {
89
- const store = (globalThis as { himalaya?: { storage?: { getItem(key: string): string | null; setItem(key: string, value: string): void } } }).himalaya?.storage;
90
- if (store?.getItem(LEGACY_CACHE_FACETS_KEY) === "1") return false;
91
- store?.setItem(LEGACY_CACHE_FACETS_KEY, "1");
92
- } catch { /* one reset this boot even when storage is unavailable */ }
93
- return true;
94
- }
95
-
96
93
  function input(arg?: unknown): { category?: string; categoryId?: string; categoryActivation?: string } {
97
94
  const fromArg = arg as { category?: unknown; categoryId?: unknown; categoryActivation?: unknown } | undefined;
98
95
  const route = (fromArg && typeof fromArg === "object" && (
@@ -113,6 +110,7 @@ function matchCategory(want: string): string | undefined {
113
110
  }
114
111
 
115
112
  async function cartItemCount(): Promise<number | undefined> {
113
+ if (!connectivity().online) return undefined;
116
114
  try {
117
115
  const cart = await getCurrentCart();
118
116
  return (cart.lineItems ?? []).reduce((n, li) => n + (li.quantity ?? 1), 0);
@@ -125,6 +123,7 @@ async function cartItemCount(): Promise<number | undefined> {
125
123
  * supersede that refresh's category discovery. Cache reads share an in-flight catalog read;
126
124
  * only the newest network read can replace the worker's current categories. */
127
125
  function readCategories(fresh: boolean): Promise<StoreCategoryCard[]> {
126
+ if (!connectivity().online) { categoriesAuthoritative = false; return Promise.resolve(categories); }
128
127
  // A network read already in flight is fresh enough for a refresh that starts before it
129
128
  // lands. Sharing it avoids discarding a good older response when a redundant newer read
130
129
  // fails, and lets a route load consume the same authoritative list.
@@ -169,52 +168,6 @@ function reconcileCategories(): { invalidatedSelection: boolean; intentVersion:
169
168
  return { invalidatedSelection, intentVersion: categoryIntentVersion };
170
169
  }
171
170
 
172
- type CachedCard = ProductCard & {
173
- cachePrice?: number; cacheCreated?: string; cacheName?: string;
174
- cacheOptions?: Record<string, string[]>; cacheCurrency?: string; legacyCache?: boolean;
175
- };
176
-
177
- /** Migrate the former himi-store-shop row shape in memory: the entity name stayed the same. */
178
- function cachedCard(value: unknown): CachedCard | undefined {
179
- const raw = value as (ProductCard & { subtitle?: string; initial?: string }) | undefined;
180
- if (!raw?.id || typeof raw.name !== "string") return undefined;
181
- if (typeof raw.noTagRow === "boolean" && typeof raw.notOnSale === "boolean") return raw as CachedCard;
182
- const image = typeof raw.image === "string" ? raw.image : "";
183
- const price = typeof raw.commercePrice === "string" && raw.commercePrice ? raw.commercePrice : raw.subtitle ?? "";
184
- return {
185
- id: raw.id, name: raw.name, image, hasImage: typeof raw.hasImage === "boolean" ? raw.hasImage : !!image,
186
- noImage: typeof raw.noImage === "boolean" ? raw.noImage : !image, initial: raw.initial ?? raw.name.slice(0, 1).toUpperCase(),
187
- price, wasPrice: "", onSale: false, notOnSale: true, saved: false, notSaved: true,
188
- swatch1: "", swatch2: "", swatch3: "", swatch4: "", swatch5: "",
189
- swatch1Hidden: true, swatch2Hidden: true, swatch3Hidden: true, swatch4Hidden: true, swatch5Hidden: true,
190
- noSwatches: true, noTagRow: true, commercePrice: raw.commercePrice ?? price,
191
- detailScreenId: raw.detailScreenId || "himi_product", legacyCache: true,
192
- };
193
- }
194
-
195
- async function readCachedCards(): Promise<CachedCard[]> {
196
- try {
197
- return ((await getAll(ENTITY)) as unknown[]).map(cachedCard).filter((c): c is CachedCard => !!c);
198
- } catch { return []; }
199
- }
200
- function writeCachedCards(list: Row[], snapshotCurrency?: string): void {
201
- const writeGeneration = ++cacheWriteGeneration;
202
- const snapshot = list.map(r => ({
203
- ...r.card, subtitle: r.card.price,
204
- cachePrice: r.price, cacheCreated: r.created, cacheName: r.name, cacheOptions: r.options, cacheCurrency: snapshotCurrency,
205
- } as unknown as DbEntity));
206
- cacheWriteQueue = cacheWriteQueue.then(async () => {
207
- if (writeGeneration !== cacheWriteGeneration) return;
208
- const cached = await getAll(ENTITY);
209
- if (writeGeneration !== cacheWriteGeneration) return;
210
- // Once a snapshot starts writing, finish it. A later complete snapshot queues behind
211
- // it; switching or refreshing categories cannot strand a half-replaced catalog.
212
- const keep = new Set(snapshot.map(row => row.id));
213
- for (const row of snapshot) await upsert(ENTITY, row);
214
- for (const item of cached) if (!keep.has(item.id)) await remove(ENTITY, item.id);
215
- }).catch(() => { /* capability not granted → online-only */ });
216
- }
217
-
218
171
  function derive(s: S): S {
219
172
  // The field keeps exactly what the shopper typed (a trailing space included); the match trims.
220
173
  const typed = s.query.trim();
@@ -222,6 +175,9 @@ function derive(s: S): S {
222
175
  const next = applyFacets(s, q ? rows.filter((r) => r.name.includes(q)) : rows, currency);
223
176
  const n = next.matched;
224
177
  const ready = s.status === "ready";
178
+ const readMessage = !s.readStatus.hasSnapshot && s.readStatus.offline
179
+ ? "This content isn’t available offline. Connect to load it."
180
+ : s.readStatus.error || "";
225
181
  const choices = [{ id: ALL, label: copyParam("shop.allChip", "All") }, ...categories.map((c) => ({ id: c.id, label: c.name }))];
226
182
  return {
227
183
  ...next,
@@ -229,7 +185,9 @@ function derive(s: S): S {
229
185
  notLoading: s.status !== "loading",
230
186
  isErrored: s.status === "error",
231
187
  notErrored: s.status !== "error",
232
- noOffline: !s.fromCache,
188
+ // The shared shell owns offline/stale messages; this former local-DB note stays hidden.
189
+ noOffline: true,
190
+ readMessage, noReadMessage: !readMessage, hasReadMessage: !!readMessage,
233
191
  categoryOptions: choices.map((c) => option(c, c.id === s.categorySelected)),
234
192
  hideCategories: categories.length === 0,
235
193
  countLabel: ready ? countText(n) : "",
@@ -244,8 +202,94 @@ function derive(s: S): S {
244
202
  };
245
203
  }
246
204
 
247
- async function fetchPage(category: string, cursor?: string) {
248
- return searchStoreProducts({ limit: PAGE, ...(category !== ALL ? { categoryId: category } : {}), ...(cursor ? { cursor } : {}) });
205
+ /** Every content/price projection reads the active controller, not an older load's skeleton. */
206
+ function currentReadState(state: S): S {
207
+ const controller = readers.get(selectedCategory);
208
+ if (!controller) return derive(state);
209
+ const readStatus = controller.getStatus();
210
+ return derive({ ...state, readStatus, categorySelected: selectedCategory, scopeGen: generation,
211
+ fromCache: readStatus.fromCache || readStatus.offline && readStatus.hasSnapshot,
212
+ status: readStatus.hasSnapshot ? "ready" : readStatus.error || readStatus.offline ? "error" : "loading" });
213
+ }
214
+
215
+ async function fetchPage(category: string, cursor?: string, options?: WixCallOptions) {
216
+ return searchStoreProducts({ limit: PAGE, ...(category !== ALL ? { categoryId: category } : {}), ...(cursor ? { cursor } : {}) }, options);
217
+ }
218
+
219
+ function combinedMetadata(a: ReadMetadata | undefined, b: ReadMetadata): ReadMetadata {
220
+ return !a ? b : { source: b.source, fetchedAt: Math.min(a.fetchedAt, b.fetchedAt),
221
+ cacheable: a.cacheable && b.cacheable,
222
+ expiresAt: a.expiresAt === undefined || b.expiresAt === undefined ? undefined : Math.min(a.expiresAt, b.expiresAt) };
223
+ }
224
+
225
+ async function readPolicyScope(category: string, cache: 'only-if-cached' | 'reload', depth: number): Promise<{ data: ScopeSnapshot; metadata: ReadMetadata }> {
226
+ const list: Row[] = [], seen = new Set<string>(), cursors = new Set<string>();
227
+ let metadata: ReadMetadata | undefined, cursor: string | undefined, scopeCurrency: string | undefined, total: number | undefined;
228
+ let hasPage = false;
229
+ do {
230
+ let pageMetadata: ReadMetadata | undefined;
231
+ let res: Awaited<ReturnType<typeof fetchPage>>;
232
+ try { res = await fetchPage(category, cursor, { cache, onCacheResult: value => { pageMetadata = value; } }); }
233
+ catch (error) {
234
+ // A later cache miss is an honest partial snapshot; it never sends a request.
235
+ if (cache === 'only-if-cached' && hasPage && /OFFLINE_CACHE_MISS/.test(String(error))) break;
236
+ throw error;
237
+ }
238
+ if (!pageMetadata) throw new Error('READ_LIFECYCLE_UNSUPPORTED: update the Himalaya runtime');
239
+ hasPage = true; metadata = combinedMetadata(metadata, pageMetadata);
240
+ const products = res.products ?? [];
241
+ scopeCurrency ??= products.map(currencyOf).find(Boolean);
242
+ total = Math.max(total ?? 0, res.pagingMetadata?.total ?? 0);
243
+ const added = toRows(products).filter(row => { if (seen.has(row.card.id)) return false; seen.add(row.card.id); return true; });
244
+ list.push(...added.slice(0, MAX_PRODUCTS - list.length));
245
+ cursor = res.pagingMetadata?.cursors?.next || undefined;
246
+ if (cursor && (!added.length || cursors.has(cursor) || cursors.size >= 100)) throw new Error('Product pagination did not advance');
247
+ if (cursor) cursors.add(cursor);
248
+ // A refresh stages fresh cursors to the depth the shopper has already loaded.
249
+ depth = Math.max(depth, cache === 'reload' ? scopes.get(category)?.rows.length ?? 0 : 0);
250
+ } while (cursor && list.length < Math.min(depth, MAX_PRODUCTS));
251
+ const data: ScopeSnapshot = { rows: list, complete: !cursor && list.length >= Math.min(total ?? 0, MAX_PRODUCTS),
252
+ loading: !!cursor, cursor, currency: scopeCurrency, total, fromCache: cache === 'only-if-cached', metadata: metadata! };
253
+ return { data, metadata: metadata! };
254
+ }
255
+
256
+ function publishRead(category: string, data: ScopeSnapshot | undefined, status: ReadStatus): void {
257
+ if (data) { data.fromCache = status.fromCache || status.offline; scopes.set(category, data); }
258
+ else scopes.delete(category);
259
+ if (category !== selectedCategory) return;
260
+ rows = data?.rows ?? []; currency = data?.currency;
261
+ const gen = generation, version = (publicationVersions.get(category) ?? 0) + 1;
262
+ publicationVersions.set(category, version);
263
+ const publication = (async () => {
264
+ await committed(gen);
265
+ if (gen !== generation || category !== selectedCategory || publicationVersions.get(category) !== version) return;
266
+ await svc.readChanged({ gen, category, readStatus: status });
267
+ })();
268
+ publications.set(category, publication);
269
+ }
270
+
271
+ function clearOnReadDenial(error: unknown, category: string): boolean {
272
+ if (!/HTTP (401|403)|PRINCIPAL_CHANGED/.test(String(error))) return false;
273
+ auxiliaryVersion++; categoryReadVersion++;
274
+ categories = []; categoriesAuthoritative = false; pendingCategoriesRead = undefined;
275
+ loadedCartCount = 0; pendingAuxiliary = undefined;
276
+ pendingScopes.clear(); pendingRefreshScopes.clear();
277
+ for (const [key, controller] of readers) if (key !== category) controller.invalidate();
278
+ return true;
279
+ }
280
+
281
+ function reader(category: string) {
282
+ let value = readers.get(category);
283
+ if (!value) {
284
+ value = createReadController<ScopeSnapshot>({
285
+ cached: key => readPolicyScope(key, 'only-if-cached', MAX_PRODUCTS),
286
+ fetch: key => readPolicyScope(key, 'reload', scopes.get(key)?.rows.length ?? 0),
287
+ publish: (data, status) => publishRead(category, data, status),
288
+ clearOnError: error => clearOnReadDenial(error, category),
289
+ });
290
+ readers.set(category, value);
291
+ }
292
+ return value;
249
293
  }
250
294
 
251
295
  /** Fetch the rest of a scope in the background, a page at a time. A page that lands while
@@ -254,16 +298,22 @@ async function fetchRest(category: string, cursor: string): Promise<void> {
254
298
  let next: string | undefined = cursor;
255
299
  let failed = false;
256
300
  const scope = scopes.get(category);
257
- if (!scope) return;
301
+ if (!scope || restFlights.has(scope)) return;
302
+ restFlights.add(scope);
303
+ const controller = reader(category);
304
+ let revision = controller.getRevision();
305
+ const current = () => scopes.get(category) === scope && controller.getRevision() === revision;
258
306
  const seenCursors = new Set<string>();
259
307
  const seenProducts = new Set(scope.rows.map(row => row.card.id));
260
308
  try {
261
309
  while (next && scope.rows.length < MAX_PRODUCTS) {
262
- if (scopes.get(category) !== scope) return;
310
+ if (!current()) return;
263
311
  if (seenCursors.has(next) || seenCursors.size >= 100) throw new Error("Product pagination did not advance");
264
312
  seenCursors.add(next);
265
- const res = await fetchPage(category, next);
266
- if (scopes.get(category) !== scope) return;
313
+ let metadata: ReadMetadata | undefined;
314
+ const res = await fetchPage(category, next, { cache: 'reload', onCacheResult: value => { metadata = value; } });
315
+ if (!metadata) throw new Error('READ_LIFECYCLE_UNSUPPORTED');
316
+ if (!current()) return;
267
317
  const added = toRows(res.products ?? []).filter(row => {
268
318
  if (seenProducts.has(row.card.id)) return false;
269
319
  seenProducts.add(row.card.id);
@@ -273,14 +323,28 @@ async function fetchRest(category: string, cursor: string): Promise<void> {
273
323
  next = res.pagingMetadata?.cursors?.next ?? undefined;
274
324
  if (!added.length && next) throw new Error("Product pagination made no progress");
275
325
  scope.rows.push(...added.slice(0, MAX_PRODUCTS - scope.rows.length));
276
- if (category === selectedCategory) await svc.scopeGrew();
326
+ scope.metadata = combinedMetadata(scope.metadata, metadata);
327
+ scope.cursor = next;
328
+ controller.replace({ data: scope, metadata: scope.metadata }, category);
329
+ revision = controller.getRevision();
330
+ if (category === selectedCategory) await publications.get(category);
277
331
  if (!next && seenProducts.size < Math.min(scope.total, MAX_PRODUCTS)) throw new Error("Product pagination is incomplete");
278
332
  }
279
- } catch { failed = true; /* keep what arrived; pull to refresh tries again */ }
280
- if (scopes.get(category) !== scope) return;
333
+ } catch (error) {
334
+ if (!current()) return;
335
+ failed = true;
336
+ if (clearOnReadDenial(error, category)) reader(category).invalidate();
337
+ else if (scopes.get(category) === scope) publishRead(category, scope, {
338
+ ...reader(category).getStatus(), stale: true, refreshing: false, error: "Couldn’t refresh. Content may be outdated.",
339
+ });
340
+ } finally {
341
+ restFlights.delete(scope);
342
+ if (!current() && scopes.get(category) === scope) scope.loading = false;
343
+ }
344
+ if (!current()) return;
281
345
  scope.complete = !failed;
282
346
  scope.loading = false;
283
- if (!failed && category === ALL) writeCachedCards(scope.rows, scope.currency);
347
+
284
348
  }
285
349
 
286
350
  /** Wait, a bounded number of turns, until the action that started fetch `gen` has committed
@@ -311,77 +375,17 @@ async function publishAuxiliaryIfReady(): Promise<void> {
311
375
  /** Fetch `category`'s first page and hand it to `scopeLanded`. The action that started the
312
376
  * fetch has already returned: a search, a sort or another chip the shopper picks meanwhile
313
377
  * is current state, and a state captured before this await must never overwrite it. */
314
- async function fetchScope(category: string, gen: number, refresh = false): Promise<void> {
315
- // A pull to refresh keeps what is on screen until the new page is in hand.
316
- const shown = refresh ? scopes.get(category) : undefined;
378
+ async function fetchScope(category: string, gen: number, _refresh = false): Promise<void> {
317
379
  try {
318
- const res = await fetchPage(category);
319
- if (gen !== generation) return;
320
- if (pendingRefreshScopes.get(category) === gen) pendingRefreshScopes.delete(category);
321
- const products: StoreProduct[] = res.products ?? [];
322
- const cursor = res.pagingMetadata?.cursors?.next;
323
- const loaded = toRows(products);
324
- const total = res.pagingMetadata?.total;
325
- // Keep partial live rows visible, but neither cache them nor suppress a later retry.
326
- const complete = !cursor && new Set(loaded.map(row => row.card.id)).size >= Math.min(total ?? 0, MAX_PRODUCTS);
327
- const scope = { rows: loaded, complete, loading: !!cursor, fromCache: false, currency: products.map(currencyOf).find(Boolean), total };
328
- // Fresh data for this scope: every other scope refetches when the shopper next opens it.
329
- if (refresh) scopes.clear();
330
- scopes.set(category, scope);
331
- rows = scope.rows;
332
- currency = scope.currency ?? currency;
333
- if (cursor) void fetchRest(category, cursor);
334
- else if (scope.complete && category === ALL) writeCachedCards(scope.rows, scope.currency);
335
- await committed(gen);
336
- if (gen === generation) {
337
- await svc.scopeLanded({ gen, category, status: "ready", fromCache: false });
338
- await publishAuxiliaryIfReady();
339
- }
340
- } catch (error) {
341
- if (gen !== generation) return;
342
- if (pendingRefreshScopes.get(category) === gen) pendingRefreshScopes.delete(category);
343
- if (shown) {
344
- // A failed refresh is not a failed screen: the cards the shopper was looking at stay.
345
- await committed(gen);
346
- if (gen === generation) {
347
- await svc.scopeLanded({ gen, category, status: "ready", fromCache: !!shown?.fromCache });
348
- await publishAuxiliaryIfReady();
349
- }
350
- return;
351
- }
352
- // Offline: paint the last synced catalog — only for All, and never over an auth refusal.
353
- const status = /HTTP (\d{3})/.exec(String(error))?.[1];
354
- if (category === ALL && status !== "401" && status !== "403") {
355
- const cached = await readCachedCards();
356
- if (gen !== generation) return;
357
- if (cached.length) {
358
- const legacyCache = cached.some((card) => card.legacyCache || typeof card.cachePrice !== "number" || !card.cacheOptions);
359
- rows = cached.map((card) => {
360
- const stored = card;
361
- currency ??= stored.cacheCurrency;
362
- return {
363
- card, price: Number.isFinite(stored.cachePrice) ? stored.cachePrice! : 0,
364
- created: stored.cacheCreated ?? "", name: stored.cacheName ?? card.name.toLowerCase(), options: stored.cacheOptions ?? {},
365
- };
366
- });
367
- scopes.set(category, { rows, complete: true, loading: false, fromCache: true, currency });
368
- await committed(gen);
369
- if (gen === generation) {
370
- await svc.scopeLanded({ gen, category, status: "ready", fromCache: true, resetFacets: legacyCache && shouldResetLegacyCacheFacets() });
371
- await publishAuxiliaryIfReady();
372
- }
373
- return;
374
- }
375
- }
376
- rows = [];
377
- await committed(gen);
378
- if (gen === generation) {
379
- await svc.scopeLanded({ gen, category, status: "error", fromCache: false });
380
- await publishAuxiliaryIfReady();
381
- }
380
+ const controller = reader(category);
381
+ await controller.load(category);
382
+ await publications.get(category);
383
+ const scope = scopes.get(category), status = controller.getStatus();
384
+ if (scope?.cursor && !status.fromCache && !status.error && !status.offline) void fetchRest(category, scope.cursor);
385
+ if (gen === generation) await publishAuxiliaryIfReady();
382
386
  } finally {
383
387
  if (pendingScopes.get(category) === gen) pendingScopes.delete(category);
384
- if (gen === generation && pendingRefreshScopes.get(category) === gen) pendingRefreshScopes.delete(category);
388
+ if (pendingRefreshScopes.get(category) === gen) pendingRefreshScopes.delete(category);
385
389
  }
386
390
  }
387
391
 
@@ -390,11 +394,19 @@ async function fetchScope(category: string, gen: number, refresh = false): Promi
390
394
  * refresh) fetches again but leaves the current cards up until the new page is in hand. */
391
395
  function showScope(s: S, category: string, refresh = false): S {
392
396
  const pending = pendingScopes.get(category);
393
- if (!refresh && category === selectedCategory && pending === generation) {
394
- return derive({ ...s, status: "loading", fromCache: false, categorySelected: category, scopeGen: generation });
397
+ const reactivating = category !== selectedCategory;
398
+ if (!refresh && !reactivating && pending === generation) {
399
+ const readStatus = reader(category).getStatus();
400
+ return derive({ ...s, readStatus, status: readStatus.hasSnapshot ? "ready" : readStatus.error || readStatus.offline ? "error" : "loading",
401
+ fromCache: readStatus.fromCache, categorySelected: category, scopeGen: generation });
395
402
  }
396
403
  const known = scopes.get(category);
397
- const needsRefresh = refresh || pendingRefreshScopes.has(category) || (!!known && !known.complete && !known.loading);
404
+ const status = known ? reader(category).getStatus() : undefined;
405
+ const needsRefresh = refresh || pendingRefreshScopes.has(category) || (!!known && (
406
+ known.fromCache || status?.stale || !!status?.error ||
407
+ (known.metadata.expiresAt !== undefined && known.metadata.expiresAt <= Date.now()) ||
408
+ (!known.complete && !known.loading)
409
+ ));
398
410
  selectedCategory = category;
399
411
  const gen = ++generation;
400
412
  if (known) {
@@ -402,15 +414,22 @@ function showScope(s: S, category: string, refresh = false): S {
402
414
  currency = known.currency ?? currency;
403
415
  }
404
416
  if (known && !needsRefresh) {
405
- return derive({ ...s, status: "ready", fromCache: !!known.fromCache, categorySelected: category, scopeGen: gen,
417
+ return derive({ ...s, readStatus: reader(category).getStatus(), status: "ready", fromCache: !!known.fromCache, categorySelected: category, scopeGen: gen,
406
418
  visible: PAGE_SIZE, cursorEpoch: s.cursorEpoch + 1 });
407
419
  }
408
420
  pendingScopes.set(category, gen);
409
421
  if (needsRefresh) pendingRefreshScopes.set(category, gen);
422
+ if (reactivating && pending !== undefined) {
423
+ // An earlier visit's hung refresh must not own this visit. Keep its snapshot while
424
+ // fencing that flight, then start a current request with fresh pagination cursors.
425
+ const controller = reader(category);
426
+ if (known) controller.replace({ data: known, metadata: known.metadata }, category);
427
+ else controller.invalidate();
428
+ }
410
429
  void fetchScope(category, gen, needsRefresh);
411
430
  const keep = needsRefresh && !!known && s.status === "ready";
412
- return derive({ ...s, status: keep ? "ready" : "loading", categorySelected: category, scopeGen: gen,
413
- ...(keep ? {} : { visible: PAGE_SIZE, cursorEpoch: s.cursorEpoch + 1 }) });
431
+ return derive({ ...s, readStatus: reader(category).getStatus(), status: keep ? "ready" : "loading", categorySelected: category, scopeGen: gen,
432
+ ...(keep && category === s.categorySelected ? {} : { visible: PAGE_SIZE, cursorEpoch: s.cursorEpoch + 1 }) });
414
433
  }
415
434
 
416
435
  /** The categories and the cart count, then `loaded` — which reads the state as it is when
@@ -584,27 +603,30 @@ async function load(s: S, arg?: unknown, fresh = false): Promise<S | undefined>
584
603
  const view = readViewPrefs();
585
604
  const BOOT: S = derive({
586
605
  ...FACETS_BOOT, ...view,
606
+ readStatus: initialReadStatus(), readMessage: "", noReadMessage: true, hasReadMessage: false,
587
607
  status: "loading", isLoading: true, notLoading: false, isErrored: false, notErrored: true, fromCache: false, noOffline: true,
588
608
  categorySelected: ALL, scopeGen: 0, query: "", categoryOptions: [], hideCategories: true,
589
609
  countLabel: "", noEmpty: true, emptyText: "", cartCount: 0,
590
610
  });
591
611
 
592
612
  const facets = facetActions<S>(derive);
593
- const CONTENT = ["load", "refresh", "retry", "loaded", "scopeLanded", "scopeGrew", "selectCategory", "search", "toggleSave", "loadMore", ...Object.keys(facets)];
613
+ const CONTENT = ["readChanged", "load", "refresh", "retry", "loaded", "scopeLanded", "scopeGrew", "selectCategory", "search", "toggleSave", "loadMore", ...Object.keys(facets)];
594
614
  const register = withIapPresentation<S>({
595
615
  // Restored from the last run, these painted a state this boot has not reached: a count over
596
616
  // no cards, an open sheet, a stale scope generation that defeats `committed`, a search
597
617
  // narrowing the list under an empty field. The shopper's view, sort and filters do persist.
598
618
  prefix: "shop",
599
- transientKeys: ["results", "query", "scopeGen", "status", "isLoading", "notLoading", "isErrored", "notErrored", "fromCache", "noOffline",
619
+ transientKeys: ["readStatus", "readMessage", "noReadMessage", "hasReadMessage", "results", "query", "scopeGen", "status", "isLoading", "notLoading", "isErrored", "notErrored", "fromCache", "noOffline",
600
620
  "countLabel", "noEmpty", "emptyText", "cartCount", "panel", "filtersOpen", "sortOpen", "categorySelected", "categoryOptions", "hideCategories",
601
621
  "sortOptions", "sortLabel", "filterLabel", "activeFilters", "noActiveFilters", "priceChips", "priceSelected", "hidePrice",
602
622
  "opt1Name", "opt1Chips", "opt1Selected", "hideOpt1", "opt2Name", "opt2Chips", "opt2Selected", "hideOpt2", "opt3Name", "opt3Chips", "opt3Selected", "hideOpt3",
603
623
  "visible", "cursorEpoch", "matched", "nextCursor", "noMore", "showLabel", "hideGrid", "hideList", "hideComfortable", "hideCompact"],
604
624
  contentActions: CONTENT,
605
625
  forcePriceRefreshActions: ["load", "refresh", "retry"],
606
- items: (state) => state.results.map((row) => ({ reference: { kind: "storesProduct", id: row.id }, commerceDisplayPrice: row.commercePrice })),
607
- present: (state, price, _allowed, appleIapOrResolving) => ({ ...state, results: state.results.map((row) => {
626
+ items: (state) => currentReadState(state).results.map((row) => ({ reference: { kind: "storesProduct", id: row.id }, commerceDisplayPrice: row.commercePrice })),
627
+ present: (state, price, _allowed, appleIapOrResolving) => {
628
+ const current = currentReadState(state);
629
+ return { ...current, results: current.results.map((row) => {
608
630
  const stored = row as typeof row & { commerceWasPrice?: string; commerceOnSale?: boolean };
609
631
  const wasPrice = stored.commerceWasPrice ?? row.wasPrice;
610
632
  const onSale = stored.commerceOnSale ?? row.onSale;
@@ -626,17 +648,30 @@ const register = withIapPresentation<S>({
626
648
  notOnSale: !onSale,
627
649
  noTagRow: !onSale && row.noSwatches,
628
650
  };
629
- }) }),
651
+ }) };
652
+ },
630
653
  });
631
654
 
632
655
  type A = (s: S, arg?: unknown) => S | undefined | Promise<S | undefined>;
633
656
  const svc = register<S, Record<string, A>>("himiBrandedShop", {
634
657
  state: BOOT,
658
+ readLifecycle: { refreshAction: "recover", statusPath: "readStatus" },
635
659
  actions: {
636
660
  load: (s, arg) => load(s, arg),
637
661
  // Pull to refresh: the categories and the scope again, from Wix.
638
662
  refresh: (s, arg) => load(s, arg, true),
639
- retry: (s) => showScope(s, s.categorySelected),
663
+ retry: (s) => showScope(s, s.categorySelected, true),
664
+ recover: async () => { await fetchScope(selectedCategory, generation, true); return undefined; },
665
+ readChanged: (s, arg) => {
666
+ if (!arg || typeof arg !== 'object') return undefined;
667
+ const value = arg as { gen: number; category: string; readStatus: ReadStatus };
668
+ if (value.gen !== generation || value.category !== selectedCategory) return undefined;
669
+ const status = value.readStatus;
670
+ return derive({ ...s, ...(loadedCartCount === undefined ? {} : { cartCount: loadedCartCount }), readStatus: status, fromCache: status.fromCache || status.offline && status.hasSnapshot,
671
+ status: status.hasSnapshot ? "ready" : status.error || status.offline ? "error" : "loading",
672
+ categorySelected: value.category, scopeGen: value.gen });
673
+ },
674
+ identityChanged: (s) => derive({ ...s, ...FACETS_BOOT, readStatus: initialReadStatus(), scopeGen: generation, cartCount: 0, status: "loading" }),
640
675
  // The categories and the cart count landed (load / refresh).
641
676
  loaded: (s, arg) => loadedState(s, arg),
642
677
  // The first page of the scope on screen landed, or failed.
@@ -657,7 +692,7 @@ const svc = register<S, Record<string, A>>("himiBrandedShop", {
657
692
  categoryIntentVersion++;
658
693
  pendingLink = undefined;
659
694
  if (id !== ALL && !categories.some((c) => c.id === id)) return s;
660
- selectedCategory = id;
695
+ // showScope compares the prior selection before committing the requested category.
661
696
  // categorySelected is committed with the tap (showScope), so a tap back to the chip
662
697
  // on screen while another category loads is a no-op on the right scope.
663
698
  if (id === s.categorySelected) return cancelsPendingRoute || refresh ? showScope(s, id, refresh) : s;
@@ -687,5 +722,13 @@ const svc = register<S, Record<string, A>>("himiBrandedShop", {
687
722
  },
688
723
  });
689
724
 
725
+ (globalThis as { himalaya?: { __subscribeReadLifecycle?(listener: (signal: string) => void): () => void } }).himalaya?.__subscribeReadLifecycle?.(signal => {
726
+ if (signal !== 'invalidate') return;
727
+ generation++; auxiliaryVersion++; categoryReadVersion++;
728
+ rows = []; currency = undefined; categories = []; categoriesAuthoritative = false;
729
+ scopes.clear(); pendingScopes.clear(); pendingRefreshScopes.clear();
730
+ loadedCartCount = 0; pendingAuxiliary = undefined; pendingCategoriesRead = undefined;
731
+ void svc.identityChanged();
732
+ });
690
733
  svc.load();
691
734
  export { svc };