@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.
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/agent/src/index.d.ts +5 -2
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/state/src/index.d.ts +14 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/state/src/read.d.ts +60 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/auth-providers/wix/src/index.d.ts +3 -2
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/activity-counters-v1/src/index.d.ts +19 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/ai-site-chat/src/index.d.ts +19 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/analytics/src/index.d.ts +42 -2
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/async-jobs/src/index.d.ts +19 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/automations-v1/src/index.d.ts +19 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/badges-v2/src/index.d.ts +19 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/billing-tax-v1/src/index.d.ts +19 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/blog-v3/src/extensions.d.ts +5 -4
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/blog-v3/src/index.d.ts +19 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/bookings-v2/src/extensions.d.ts +4 -4
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/bookings-v2/src/index.d.ts +19 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/business-feed/src/index.d.ts +11 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/business-tools-v1/src/index.d.ts +19 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/chat-web/src/index.d.ts +22 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/comments-v1/src/index.d.ts +19 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/contacts/src/index.d.ts +19 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/coupons/src/index.d.ts +24 -2
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/crm-tasks/src/index.d.ts +11 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/dashboard-setup/src/index.d.ts +11 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/data-collections-v1/src/index.d.ts +19 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/donations/src/index.d.ts +19 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/ecom/src/extensions.d.ts +6 -5
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/ecom/src/index.d.ts +19 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/email-marketing-v1/src/index.d.ts +19 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/events/src/index.d.ts +24 -2
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/faq-v2/src/index.d.ts +19 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/forms/src/index.d.ts +19 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/forum/src/index.d.ts +19 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/genie-chat/src/index.d.ts +11 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/gift-cards/src/index.d.ts +19 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/groups/src/index.d.ts +19 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/inbox/src/extensions.d.ts +2 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/inbox/src/index.d.ts +19 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/invoices/src/index.d.ts +24 -2
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/loyalty/src/index.d.ts +28 -4
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/marketing-v1/src/index.d.ts +19 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/members-feed-viewer/src/index.d.ts +23 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/members-v1/src/index.d.ts +25 -3
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/new-releases/src/index.d.ts +23 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/notification-preferences/src/index.d.ts +23 -2
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/notifications-v3/src/index.d.ts +19 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/online-programs/src/index.d.ts +25 -3
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/portfolio/src/index.d.ts +25 -3
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/pricing-plans/src/index.d.ts +28 -6
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/redirects-v1/src/index.d.ts +19 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/restaurants/src/extensions.d.ts +2 -2
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/restaurants/src/index.d.ts +19 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/reviews/src/index.d.ts +24 -2
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/seo-v1/src/index.d.ts +19 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/site-list/src/index.d.ts +19 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/site-media/src/index.d.ts +11 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/social-feed/src/index.d.ts +23 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/stores-collections/src/display.d.ts +3 -2
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/stores-collections/src/index.d.ts +27 -3
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/stores-v1/src/catalog.d.ts +7 -7
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/stores-v1/src/display.d.ts +4 -3
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/stores-v1/src/extensions.d.ts +2 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/stores-v1/src/index.d.ts +19 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/table-reservations/src/index.d.ts +25 -3
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/users/src/index.d.ts +23 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/video/src/index.d.ts +19 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/wix-data/src/index.d.ts +27 -3
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/workflows-v1/src/index.d.ts +19 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk-auto/_shared/runtime.generated.d.ts +8 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/checkout.mjs +12 -4
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/screens.mjs +36 -11
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/state.mjs +206 -12
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-activity-counters.mjs +19 -5
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-ai-site-chat.mjs +19 -5
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-analytics.mjs +21 -7
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-async-jobs.mjs +19 -5
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-automations.mjs +19 -5
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-badges.mjs +19 -5
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-billing-tax.mjs +19 -5
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-blog.mjs +31 -14
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-bookings.mjs +26 -12
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-business-tools.mjs +20 -6
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-chat-web.mjs +29 -7
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-comments.mjs +28 -5
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-contacts.mjs +19 -5
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-coupons.mjs +14 -6
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-data-collections.mjs +19 -5
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-data.mjs +15 -5
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-donations.mjs +19 -5
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-ecom.mjs +33 -16
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-email-marketing.mjs +19 -5
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-events.mjs +14 -6
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-faq.mjs +20 -6
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-forms.mjs +19 -5
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-forum.mjs +19 -5
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-gift-cards.mjs +19 -5
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-groups.mjs +19 -5
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-inbox.mjs +21 -7
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-invoices.mjs +14 -6
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-loyalty.mjs +16 -8
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-marketing.mjs +19 -5
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-members-feed-viewer.mjs +21 -10
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-members.mjs +17 -8
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-new-releases.mjs +17 -5
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-notification-preferences.mjs +20 -6
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-notifications.mjs +19 -5
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-online-programs.mjs +16 -8
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-portfolio.mjs +16 -8
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-pricing-plans.mjs +22 -14
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-redirects.mjs +19 -5
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-restaurants.mjs +22 -7
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-reviews.mjs +14 -6
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-rich-content-editor.mjs +3 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-seo.mjs +19 -5
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-site-list.mjs +19 -5
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-social-feed.mjs +15 -5
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-stores-collections.mjs +20 -10
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-stores.mjs +116 -83
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-table-reservations.mjs +16 -8
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-users.mjs +15 -5
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-video.mjs +9 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-workflows.mjs +19 -5
- package/dist/cli.mjs +90 -57
- package/dist/preview-assets.generated.bin +0 -0
- package/fixtures/wix-canned.json +8 -8
- package/guides/dynamic-list.service.ts +38 -29
- package/guides/form-submit.service.ts +2 -1
- package/guides/index.json +6 -0
- package/guides/offline-reading.md +63 -0
- package/guides/one-app-to-himi-app.md +5 -3
- package/guides/recipe-dynamic-list.md +2 -0
- package/guides/recipe-four-state.md +1 -0
- package/guides/sdk-reference.json +225 -99
- package/guides/testing-himi-apps.md +2 -0
- package/guides/tier-5-add-a-screen.md +2 -0
- package/guides/wix-api-usage.md +9 -2
- package/package.json +2 -2
- package/templates/beauty-salon/dev/index.ts +4 -3
- package/templates/beauty-salon/tier5-src/_shared/collection-service.ts +6 -9
- package/templates/beauty-salon/tier5-src/_shared/iap-presentation.ts +7 -3
- package/templates/beauty-salon/tier5-src/himi-branded-lite-home/index.ts +1 -1
- package/templates/beauty-salon/tier5-src/himi-branded-lite-orders/index.ts +3 -0
- package/templates/beauty-salon-owner/dev/index.ts +19 -19
- package/templates/beauty-salon-owner/tier5-src/_shared/collection-service.ts +6 -9
- package/templates/beauty-salon-owner/tier5-src/_shared/owner-module-data.ts +1 -1
- package/templates/branded-lite/dev/index.ts +38 -20
- package/templates/branded-lite/tier5-src/_shared/collection-query-legacy.ts +194 -0
- package/templates/branded-lite/tier5-src/_shared/collection-query.ts +158 -99
- package/templates/branded-lite/tier5-src/_shared/collection-service.ts +31 -11
- package/templates/branded-lite/tier5-src/_shared/iap-presentation.ts +7 -3
- package/templates/branded-lite/tier5-src/_shared/read-service.ts +255 -0
- package/templates/branded-lite/tier5-src/himi-branded-lite-home/index.ts +1 -1
- package/templates/branded-lite/tier5-src/himi-branded-lite-orders/index.ts +3 -0
- package/templates/branded-lite/tier5-src/himi-branded-lite-shop/index.ts +202 -159
- package/templates/branded-lite/tier5-src/himi-store-product/index.ts +130 -201
- package/templates/community-club/dev/index.ts +4 -4
- package/templates/community-club/tier5-src/_shared/collection-service.ts +6 -9
- package/templates/community-club/tier5-src/_shared/iap-presentation.ts +7 -3
- package/templates/community-club/tier5-src/himi-branded-lite-home/index.ts +1 -1
- package/templates/community-club-owner/dev/index.ts +19 -19
- package/templates/community-club-owner/tier5-src/_shared/collection-service.ts +6 -9
- package/templates/community-club-owner/tier5-src/_shared/owner-module-data.ts +1 -1
- package/templates/events-meetups/dev/index.ts +3 -3
- package/templates/events-meetups/tier5-src/_shared/collection-service.ts +6 -9
- package/templates/events-meetups/tier5-src/_shared/iap-presentation.ts +7 -3
- package/templates/events-meetups/tier5-src/himi-branded-lite-home/index.ts +1 -1
- package/templates/events-meetups-owner/dev/index.ts +17 -17
- package/templates/events-meetups-owner/tier5-src/_shared/collection-service.ts +6 -9
- package/templates/events-meetups-owner/tier5-src/_shared/owner-module-data.ts +1 -1
- package/templates/gym-fitness/dev/index.ts +5 -4
- package/templates/gym-fitness/tier5-src/_shared/collection-service.ts +6 -9
- package/templates/gym-fitness/tier5-src/_shared/iap-presentation.ts +7 -3
- package/templates/gym-fitness/tier5-src/himi-branded-lite-home/index.ts +1 -1
- package/templates/gym-fitness/tier5-src/himi-branded-lite-orders/index.ts +3 -0
- package/templates/gym-fitness-owner/dev/index.ts +20 -20
- package/templates/gym-fitness-owner/tier5-src/_shared/collection-service.ts +6 -9
- package/templates/gym-fitness-owner/tier5-src/_shared/owner-module-data.ts +1 -1
- package/templates/nonprofit-cause/dev/index.ts +4 -4
- package/templates/nonprofit-cause/tier5-src/_shared/collection-service.ts +6 -9
- package/templates/nonprofit-cause/tier5-src/_shared/iap-presentation.ts +7 -3
- package/templates/nonprofit-cause/tier5-src/himi-branded-lite-home/index.ts +1 -1
- package/templates/nonprofit-cause-owner/dev/index.ts +18 -18
- package/templates/nonprofit-cause-owner/tier5-src/_shared/collection-service.ts +6 -9
- package/templates/nonprofit-cause-owner/tier5-src/_shared/owner-module-data.ts +1 -1
- package/templates/owner-lite/dev/index.ts +20 -20
- package/templates/owner-lite/tier5-src/_shared/collection-service.ts +6 -9
- package/templates/owner-lite/tier5-src/_shared/owner-module-data.ts +1 -1
- package/templates/podcast-creator/dev/index.ts +4 -4
- package/templates/podcast-creator/tier5-src/_shared/collection-service.ts +6 -9
- package/templates/podcast-creator/tier5-src/_shared/iap-presentation.ts +7 -3
- package/templates/podcast-creator/tier5-src/himi-branded-lite-home/index.ts +1 -1
- package/templates/podcast-creator-owner/dev/index.ts +19 -19
- package/templates/podcast-creator-owner/tier5-src/_shared/collection-service.ts +6 -9
- package/templates/podcast-creator-owner/tier5-src/_shared/owner-module-data.ts +1 -1
- package/templates/portfolio-creative/dev/index.ts +5 -4
- package/templates/portfolio-creative/tier5-src/_shared/collection-service.ts +6 -9
- package/templates/portfolio-creative/tier5-src/_shared/iap-presentation.ts +7 -3
- package/templates/portfolio-creative/tier5-src/himi-branded-lite-home/index.ts +1 -1
- package/templates/portfolio-creative/tier5-src/himi-branded-lite-orders/index.ts +3 -0
- package/templates/portfolio-creative-owner/dev/index.ts +19 -19
- package/templates/portfolio-creative-owner/tier5-src/_shared/collection-service.ts +6 -9
- package/templates/portfolio-creative-owner/tier5-src/_shared/owner-module-data.ts +1 -1
- package/templates/restaurant/dev/index.ts +3 -3
- package/templates/restaurant/tier5-src/_shared/collection-service.ts +6 -9
- package/templates/restaurant/tier5-src/_shared/iap-presentation.ts +7 -3
- package/templates/restaurant/tier5-src/himi-branded-lite-home/index.ts +1 -1
- package/templates/restaurant-owner/dev/index.ts +20 -20
- package/templates/restaurant-owner/tier5-src/_shared/collection-service.ts +6 -9
- package/templates/restaurant-owner/tier5-src/_shared/owner-module-data.ts +1 -1
- package/templates/retail-store/dev/index.ts +19 -1
- package/templates/retail-store/tier5-src/_shared/collection-query-legacy.ts +194 -0
- package/templates/retail-store/tier5-src/_shared/collection-query.ts +158 -99
- package/templates/retail-store/tier5-src/_shared/collection-service.ts +31 -11
- package/templates/retail-store/tier5-src/_shared/iap-presentation.ts +7 -3
- package/templates/retail-store/tier5-src/_shared/read-service.ts +255 -0
- package/templates/retail-store/tier5-src/himi-branded-lite-home/index.ts +1 -1
- package/templates/retail-store/tier5-src/himi-branded-lite-orders/index.ts +3 -0
- package/templates/retail-store/tier5-src/himi-branded-lite-shop/index.ts +202 -159
- package/templates/retail-store/tier5-src/himi-store-product/index.ts +130 -201
- package/templates/retail-store-owner/dev/index.ts +18 -18
- package/templates/retail-store-owner/tier5-src/_shared/collection-service.ts +6 -9
- package/templates/retail-store-owner/tier5-src/_shared/owner-module-data.ts +1 -1
- package/templates/yoga-studio/dev/index.ts +4 -4
- package/templates/yoga-studio/tier5-src/_shared/collection-service.ts +6 -9
- package/templates/yoga-studio/tier5-src/_shared/iap-presentation.ts +7 -3
- package/templates/yoga-studio/tier5-src/himi-branded-lite-home/index.ts +1 -1
- package/templates/yoga-studio-owner/dev/index.ts +18 -18
- package/templates/yoga-studio-owner/tier5-src/_shared/collection-service.ts +6 -9
- package/templates/yoga-studio-owner/tier5-src/_shared/owner-module-data.ts +1 -1
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { createReadController, initialReadStatus, type ReadMetadata, type ReadStatus } from '@himalaya/state';
|
|
2
|
+
import { createLegacyCollectionQuery } from './collection-query-legacy';
|
|
3
|
+
|
|
1
4
|
/** Backend collection lifecycle shared by domain workers. The domain supplies
|
|
2
5
|
* the query and row mapper; this layer never calls a domain API or mutates it. */
|
|
3
6
|
export interface CollectionQuery {
|
|
@@ -12,6 +15,9 @@ export interface CollectionPage<Row> {
|
|
|
12
15
|
total?: number;
|
|
13
16
|
metadata?: Record<string, unknown>;
|
|
14
17
|
complete?: boolean;
|
|
18
|
+
provenance?: ReadMetadata;
|
|
19
|
+
/** A result derived entirely from local criteria/authored configuration (e.g. no saved IDs). */
|
|
20
|
+
local?: boolean;
|
|
15
21
|
}
|
|
16
22
|
export interface CollectionQueryState<Row> extends CollectionPage<Row> {
|
|
17
23
|
criteria: CollectionQuery;
|
|
@@ -20,6 +26,7 @@ export interface CollectionQueryState<Row> extends CollectionPage<Row> {
|
|
|
20
26
|
pageError: boolean;
|
|
21
27
|
complete: boolean;
|
|
22
28
|
error: string;
|
|
29
|
+
read: ReadStatus;
|
|
23
30
|
/** The rows are the source's `cached` page, shown while the live read is still out. */
|
|
24
31
|
provisional?: boolean;
|
|
25
32
|
}
|
|
@@ -29,7 +36,9 @@ export interface CollectionShownPage<Row> {
|
|
|
29
36
|
loadedPages: number;
|
|
30
37
|
}
|
|
31
38
|
export interface CollectionQuerySource<Row> {
|
|
32
|
-
fetch(criteria: CollectionQuery, cursor: string): Promise<CollectionPage<Row>>;
|
|
39
|
+
fetch(criteria: CollectionQuery, cursor: string, options?: CollectionReadOptions): Promise<CollectionPage<Row>>;
|
|
40
|
+
/** Source forwards cache mode and metadata to every SDK read. */
|
|
41
|
+
cacheRead?: boolean;
|
|
33
42
|
/** Optional public cache for immediate paint; always incomplete, never a paging authority. */
|
|
34
43
|
cached?(criteria: CollectionQuery): Promise<CollectionPage<Row> | undefined>;
|
|
35
44
|
id(row: Row): string;
|
|
@@ -39,6 +48,10 @@ export interface CollectionQuerySource<Row> {
|
|
|
39
48
|
* key carries the account, and a purge tears parked workers down). */
|
|
40
49
|
clearOnError?(error: unknown): boolean;
|
|
41
50
|
}
|
|
51
|
+
export interface CollectionReadOptions {
|
|
52
|
+
cache: 'only-if-cached' | 'reload';
|
|
53
|
+
onCacheResult(metadata: ReadMetadata): void;
|
|
54
|
+
}
|
|
42
55
|
const normalize = (q: Partial<CollectionQuery>): CollectionQuery => ({
|
|
43
56
|
query: typeof q.query === 'string' ? q.query.trim().slice(0, 256) : '',
|
|
44
57
|
filter: typeof q.filter === 'string' ? q.filter.slice(0, 256) : 'all',
|
|
@@ -47,30 +60,33 @@ const normalize = (q: Partial<CollectionQuery>): CollectionQuery => ({
|
|
|
47
60
|
});
|
|
48
61
|
const copyQuery = (q: CollectionQuery): CollectionQuery => ({ ...q, savedIds: [...q.savedIds] });
|
|
49
62
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
*
|
|
53
|
-
*
|
|
63
|
+
|
|
64
|
+
/** Refreshes stage a complete replacement at the currently loaded depth. Old rows
|
|
65
|
+
* and cursors remain usable until every replacement page succeeds. Query changes
|
|
66
|
+
* start a separate snapshot; they never reuse a subset from another query. */
|
|
54
67
|
export function createCollectionQuery<Row>(source: CollectionQuerySource<Row>, publish: (state: CollectionQueryState<Row>) => void) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
let
|
|
68
|
+
// Only opted-in reads use the new snapshot/recovery controller. Other template collections
|
|
69
|
+
// keep their established query, pagination, and warm-return behavior.
|
|
70
|
+
if (!source.cacheRead) return createLegacyCollectionQuery(source, publish);
|
|
71
|
+
const sample = (globalThis as { himalaya?: { input?: Record<string, unknown> } }).himalaya?.input?.__himiOwnerPreview === 'sample';
|
|
72
|
+
const cacheRead = source.cacheRead && !sample;
|
|
73
|
+
const shouldClearOnError = (error: unknown) => source.clearOnError?.(error) === true
|
|
74
|
+
|| /HTTP 40[13]\b|forbidden|principal.changed|identity.changed/i.test(String(error));
|
|
75
|
+
let generation = 0, disposed = false, loadedPages = 1, wantedPages = 1, restoringPages = 1, key = '';
|
|
76
|
+
let pending: Promise<void> | undefined;
|
|
77
|
+
let suppressPublish = false;
|
|
78
|
+
let state: CollectionQueryState<Row> = { criteria: normalize({}), items: [], nextCursor: '', status: 'idle',
|
|
79
|
+
loadingMore: false, pageError: false, complete: false, error: '', read: initialReadStatus() };
|
|
80
|
+
let lastShown: CollectionShownPage<Row> | undefined;
|
|
64
81
|
const sameCriteria = (a: CollectionQuery, b: CollectionQuery) => JSON.stringify(a) === JSON.stringify(b);
|
|
65
|
-
|
|
66
|
-
const snapshot = (): CollectionQueryState<Row> => ({ ...state, criteria: copyQuery(state.criteria), items: [...state.items] });
|
|
82
|
+
const snapshot = (): CollectionQueryState<Row> => ({ ...state, read: { ...state.read }, criteria: copyQuery(state.criteria), items: [...state.items] });
|
|
67
83
|
const emit = () => {
|
|
68
|
-
if (disposed
|
|
69
|
-
// A settled answer replaces it, an empty one included: rows the live query proved gone are never kept
|
|
70
|
-
// with nothing in it comes back as its empty state, never its loading look.
|
|
84
|
+
if (disposed) return;
|
|
85
|
+
// A settled answer replaces it, an empty one included: rows the live query proved gone are never kept.
|
|
71
86
|
if (state.status === 'ready' && !state.pageError && !state.loadingMore && !state.provisional) {
|
|
72
87
|
lastShown = { state: snapshot(), loadedPages };
|
|
73
88
|
}
|
|
89
|
+
if (suppressPublish) return;
|
|
74
90
|
publish(snapshot());
|
|
75
91
|
};
|
|
76
92
|
const merge = (old: Row[], page: Row[]): Row[] => {
|
|
@@ -84,91 +100,124 @@ export function createCollectionQuery<Row>(source: CollectionQuerySource<Row>, p
|
|
|
84
100
|
}
|
|
85
101
|
return result;
|
|
86
102
|
};
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
103
|
+
const combine = (a: ReadMetadata, b: ReadMetadata): ReadMetadata => ({
|
|
104
|
+
source: a.source === 'cache' || b.source === 'cache' ? 'cache' : 'network',
|
|
105
|
+
fetchedAt: Math.min(a.fetchedAt, b.fetchedAt), cacheable: a.cacheable && b.cacheable,
|
|
106
|
+
expiresAt: Math.min(a.expiresAt ?? 0, b.expiresAt ?? 0),
|
|
107
|
+
});
|
|
108
|
+
const readPage = async (criteria: CollectionQuery, cursor: string, cache: 'only-if-cached' | 'reload') => {
|
|
109
|
+
let provenance: ReadMetadata | undefined;
|
|
110
|
+
const page = await source.fetch(criteria, cursor, { cache, onCacheResult(value) { provenance = provenance ? combine(provenance, value) : value; } });
|
|
111
|
+
if (!Array.isArray(page.items) || typeof page.nextCursor !== 'string' || (cursor && page.nextCursor === cursor))
|
|
112
|
+
throw new Error('Invalid collection page');
|
|
113
|
+
if (page.local === true) provenance = { source: 'local', fetchedAt: 0, cacheable: false };
|
|
114
|
+
if (cacheRead && !provenance && !page.provenance) throw new Error('Read source did not provide response provenance');
|
|
115
|
+
// A legacy/error fallback is never a successful server refresh.
|
|
116
|
+
if (page.metadata?.fromCache && !provenance) throw new Error('Cache response has no policy provenance');
|
|
117
|
+
return { ...page, items: merge([], page.items), provenance: provenance ?? page.provenance ??
|
|
118
|
+
{ source: 'network' as const, fetchedAt: Date.now(), cacheable: false } };
|
|
119
|
+
};
|
|
120
|
+
const reader = createReadController<CollectionPage<Row>>({
|
|
121
|
+
allowLocal: true,
|
|
122
|
+
...(sample ? { online: () => true } : {}),
|
|
123
|
+
...(!sample && (source.cached || cacheRead) ? { cached: async (readKey: string) => {
|
|
124
|
+
const criteria = JSON.parse(readKey) as CollectionQuery;
|
|
125
|
+
const page = cacheRead ? await readPage(criteria, '', 'only-if-cached') : await source.cached?.(criteria);
|
|
126
|
+
if (!page?.provenance || !['cache', 'local'].includes(page.provenance.source)) return undefined;
|
|
127
|
+
return { data: { ...page, nextCursor: '', complete: !page.nextCursor }, metadata: page.provenance };
|
|
128
|
+
} } : {}),
|
|
129
|
+
fetch: async (readKey) => {
|
|
130
|
+
const criteria = JSON.parse(readKey) as CollectionQuery, revision = generation, depth = wantedPages;
|
|
131
|
+
let page = await readPage(criteria, '', 'reload'), count = 1;
|
|
132
|
+
const seen = new Set<string>();
|
|
133
|
+
while (page.nextCursor && count < depth) {
|
|
134
|
+
if (disposed || revision !== generation) throw new Error('Read disposed');
|
|
135
|
+
if (seen.has(page.nextCursor)) throw new Error('Repeated collection cursor');
|
|
136
|
+
seen.add(page.nextCursor);
|
|
137
|
+
const next = await readPage(criteria, page.nextCursor, 'reload');
|
|
138
|
+
if (next.provenance.source !== 'network') throw new Error('Refresh returned cached data');
|
|
139
|
+
page = { ...page, items: merge(page.items, next.items), nextCursor: next.nextCursor, complete: next.complete, provenance: combine(page.provenance, next.provenance) };
|
|
140
|
+
count++;
|
|
107
141
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
142
|
+
if (!disposed && revision === generation) loadedPages = count;
|
|
143
|
+
return { data: page, metadata: page.provenance };
|
|
144
|
+
},
|
|
145
|
+
clearOnError: error => {
|
|
146
|
+
const clear = shouldClearOnError(error);
|
|
147
|
+
if (clear) lastShown = undefined;
|
|
148
|
+
return clear;
|
|
149
|
+
},
|
|
150
|
+
errorMessage: source.errorMessage,
|
|
151
|
+
publish: (page, read) => {
|
|
152
|
+
state = { ...state, ...(page ?? { items: [], nextCursor: '', total: undefined, metadata: undefined }),
|
|
153
|
+
metadata: page ? { ...page.metadata, fromCache: read.fromCache } : undefined,
|
|
154
|
+
read, status: read.hasSnapshot ? 'ready' : read.refreshing ? 'loading' : 'error',
|
|
155
|
+
complete: !!page && (page.complete ?? !page.nextCursor), loadingMore: false, pageError: false,
|
|
156
|
+
provisional: read.fromCache && read.refreshing,
|
|
157
|
+
error: read.offline && !read.hasSnapshot ? "This content isn’t available offline. Connect to load it." : read.error };
|
|
158
|
+
emit();
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
const query = (criteria: Partial<CollectionQuery>, restorePages = false, _revalidate = false): Promise<void> => {
|
|
162
|
+
if (disposed) return Promise.resolve();
|
|
163
|
+
const next = normalize(criteria), nextKey = JSON.stringify(next);
|
|
164
|
+
if (key === nextKey && pending) return pending;
|
|
165
|
+
const same = key === nextKey;
|
|
166
|
+
if (!same) { loadedPages = 1; restoringPages = 1; }
|
|
167
|
+
key = nextKey; generation++; wantedPages = same ? Math.max(1, loadedPages, restorePages ? restoringPages : 1) : 1;
|
|
168
|
+
state = { ...state, criteria: next };
|
|
169
|
+
const revision = generation;
|
|
170
|
+
pending = reader.load(key).finally(() => { if (revision === generation) pending = undefined; });
|
|
171
|
+
return pending;
|
|
172
|
+
};
|
|
173
|
+
const loadMore = async () => {
|
|
174
|
+
if (disposed || pending || state.loadingMore || !state.nextCursor) return;
|
|
175
|
+
const revision = generation, criteria = copyQuery(state.criteria), cursor = state.nextCursor;
|
|
176
|
+
state = { ...state, loadingMore: true, pageError: false }; emit();
|
|
177
|
+
try {
|
|
178
|
+
const page = await readPage(criteria, cursor, 'reload');
|
|
179
|
+
if (disposed || revision !== generation) return;
|
|
180
|
+
if (page.provenance.source !== 'network') throw new Error('Page returned cached data');
|
|
181
|
+
const provenance = state.provenance ? combine(state.provenance, page.provenance) : page.provenance;
|
|
182
|
+
state = { ...state, ...page, provenance, items: merge(state.items, page.items), total: page.total ?? state.total,
|
|
183
|
+
metadata: page.metadata ?? state.metadata, loadingMore: false, pageError: false, complete: page.complete ?? !page.nextCursor };
|
|
115
184
|
loadedPages++;
|
|
185
|
+
wantedPages = loadedPages;
|
|
186
|
+
// Keep the read controller's retained snapshot aligned with appended pages.
|
|
187
|
+
reader.replace({ data: state, metadata: provenance });
|
|
116
188
|
} catch (error) {
|
|
117
|
-
if (disposed ||
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
189
|
+
if (disposed || revision !== generation) return;
|
|
190
|
+
if (shouldClearOnError(error)) {
|
|
191
|
+
loadedPages = 1; wantedPages = 1; lastShown = undefined; reader.invalidate();
|
|
192
|
+
const message = source.errorMessage?.(error) || 'This content is unavailable for this account. Try signing in again.';
|
|
193
|
+
state = { ...state, status: 'error', loadingMore: false, pageError: false,
|
|
194
|
+
error: message, read: { ...state.read, error: message } };
|
|
195
|
+
emit();
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
state = { ...state, loadingMore: false, pageError: true, complete: false,
|
|
199
|
+
error: source.errorMessage?.(error) || 'The next page could not load. Try again.' };
|
|
200
|
+
emit();
|
|
128
201
|
}
|
|
129
|
-
emit();
|
|
130
202
|
};
|
|
203
|
+
// A late page must not repopulate a cleared/private snapshot after logout or disconnect.
|
|
204
|
+
const host = (globalThis as { himalaya?: { __subscribeReadLifecycle?(listener: (signal: string) => void): () => void } }).himalaya;
|
|
205
|
+
const stop = host?.__subscribeReadLifecycle?.(signal => {
|
|
206
|
+
if (signal === 'invalidate' || signal === 'offline' || signal === 'dispose') {
|
|
207
|
+
generation++; pending = undefined;
|
|
208
|
+
if (signal !== 'offline') { loadedPages = 1; wantedPages = 1; lastShown = undefined; }
|
|
209
|
+
if (signal === 'dispose') disposed = true;
|
|
210
|
+
}
|
|
211
|
+
});
|
|
131
212
|
return {
|
|
132
213
|
getState: snapshot,
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if (disposed) return Promise.resolve();
|
|
137
|
-
const next = normalize(criteria);
|
|
138
|
-
const same = JSON.stringify(next) === JSON.stringify(state.criteria);
|
|
139
|
-
const count = restorePages && same ? Math.max(loadedPages, restoringPages, 1) : 1;
|
|
140
|
-
const showing = lastShown && sameCriteria(lastShown.state.criteria, next) ? lastShown : undefined;
|
|
141
|
-
restoringPages = count;
|
|
142
|
-
// Only the search text changed: the user is typing over the rows on screen, also while the last keystroke's
|
|
143
|
-
// search is still out.
|
|
144
|
-
const typing = !same && (state.status === 'ready' || state.status === 'loading')
|
|
145
|
-
&& sameCriteria({ ...state.criteria, query: '' }, { ...next, query: '' });
|
|
146
|
-
state = { ...state, criteria: next };
|
|
147
|
-
if (revalidate && showing) { revalidating = generation + 1; clearedByError = false; }
|
|
148
|
-
const pending = fetch(false, typing);
|
|
149
|
-
const revision = generation;
|
|
150
|
-
await pending;
|
|
151
|
-
// Re-appearance revalidates every previously opened page using fresh
|
|
152
|
-
// cursors. A changed query, error or revoked access stops the restoration.
|
|
153
|
-
while (!disposed && generation === revision && loadedPages < count && state.status === 'ready'
|
|
154
|
-
&& !state.pageError && !state.loadingMore && state.nextCursor) await fetch(true);
|
|
155
|
-
if (generation === revision) restoringPages = 0;
|
|
156
|
-
if (revalidating === revision) {
|
|
157
|
-
revalidating = -1;
|
|
158
|
-
if (generation !== revision) return;
|
|
159
|
-
if ((state.status !== 'ready' || state.pageError) && showing && !clearedByError) {
|
|
160
|
-
state = { ...showing.state, criteria: next };
|
|
161
|
-
loadedPages = showing.loadedPages;
|
|
162
|
-
}
|
|
163
|
-
emit();
|
|
164
|
-
}
|
|
214
|
+
query: (criteria: Partial<CollectionQuery>, restorePages = false, revalidate = false) => {
|
|
215
|
+
restoringPages = restorePages ? Math.max(1, loadedPages) : 1;
|
|
216
|
+
return query(criteria, restorePages, revalidate);
|
|
165
217
|
},
|
|
166
|
-
/** The page the screen shows (rows ready, nothing failing or still paging), for the service to keep with its
|
|
167
|
-
* state; undefined when it shows none. */
|
|
168
218
|
shownPage(): CollectionShownPage<Row> | undefined {
|
|
169
219
|
if (!lastShown || !sameCriteria(lastShown.state.criteria, state.criteria)) return undefined;
|
|
170
|
-
|
|
171
|
-
return { state: { ...page, criteria: copyQuery(page.criteria), items: [...page.items] }, loadedPages: lastShown.loadedPages };
|
|
220
|
+
return { state: { ...lastShown.state, read: { ...lastShown.state.read }, criteria: copyQuery(lastShown.state.criteria), items: [...lastShown.state.items] }, loadedPages: lastShown.loadedPages };
|
|
172
221
|
},
|
|
173
222
|
/** A cold boot restored from disk: what the last run showed becomes what this one shows, so its first `load`
|
|
174
223
|
* revalidates behind it instead of starting empty. Only before the first query, and only a well-formed page; an
|
|
@@ -176,18 +225,28 @@ export function createCollectionQuery<Row>(source: CollectionQuerySource<Row>, p
|
|
|
176
225
|
seed(page: unknown): boolean {
|
|
177
226
|
const candidate = page && typeof page === 'object' ? page as Partial<CollectionShownPage<Row>> : undefined;
|
|
178
227
|
const restored = candidate?.state;
|
|
228
|
+
const provenance = restored?.provenance;
|
|
179
229
|
if (generation !== 0 || state.status !== 'idle' || !restored || restored.status !== 'ready'
|
|
180
|
-
|| !Array.isArray(restored.items) || typeof restored.nextCursor !== 'string'
|
|
230
|
+
|| !Array.isArray(restored.items) || typeof restored.nextCursor !== 'string'
|
|
231
|
+
|| !provenance || !['cache', 'network'].includes(provenance.source) || !provenance.cacheable
|
|
232
|
+
|| provenance.expiresAt === undefined || provenance.expiresAt <= Date.now()) return false;
|
|
181
233
|
try { restored.items.forEach(item => { if (!source.id(item)) throw new Error('A backend collection row requires an identity'); }); } catch { return false; }
|
|
182
234
|
state = { ...state, ...restored, criteria: normalize(restored.criteria ?? {}), items: [...restored.items], status: 'ready',
|
|
183
235
|
loadingMore: false, pageError: false, provisional: false, error: '' };
|
|
184
236
|
loadedPages = Math.max(1, Math.floor(Number(candidate?.loadedPages) || 1));
|
|
237
|
+
wantedPages = loadedPages;
|
|
238
|
+
const saved = state.provenance;
|
|
239
|
+
if (!saved) return false;
|
|
240
|
+
suppressPublish = true;
|
|
241
|
+
key = JSON.stringify(state.criteria);
|
|
242
|
+
reader.replace({ data: state, metadata: saved }, key);
|
|
243
|
+
suppressPublish = false;
|
|
185
244
|
lastShown = { state: snapshot(), loadedPages };
|
|
186
245
|
return true;
|
|
187
246
|
},
|
|
188
|
-
refresh: () =>
|
|
189
|
-
loadMore
|
|
190
|
-
retry: () =>
|
|
191
|
-
dispose() { disposed = true; generation++; },
|
|
247
|
+
refresh: () => query(state.criteria, true, true),
|
|
248
|
+
loadMore,
|
|
249
|
+
retry: () => state.pageError ? loadMore() : query(state.criteria, true, true),
|
|
250
|
+
dispose() { disposed = true; generation++; stop?.(); reader.dispose(); },
|
|
192
251
|
};
|
|
193
252
|
}
|
|
@@ -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
|
|
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
|
|
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
|
-
|
|
206
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
//
|
|
134
|
-
//
|
|
135
|
-
|
|
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(); }
|