@wix/himalaya-cli 0.1544.0 → 0.1546.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/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 +1002 -806
- 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/himi-json-output.md +29 -0
- package/guides/iap-products.md +49 -4
- package/guides/index.json +6 -0
- package/guides/offline-reading.md +63 -0
- 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/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
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { initialReadStatus, type ReadStatus } from '@himalaya/state';
|
|
2
|
+
|
|
3
|
+
/** Backend collection lifecycle shared by domain workers. The domain supplies
|
|
4
|
+
* the query and row mapper; this layer never calls a domain API or mutates it. */
|
|
5
|
+
export interface CollectionQuery {
|
|
6
|
+
query: string;
|
|
7
|
+
filter: string;
|
|
8
|
+
sort: 'default' | 'az' | 'za';
|
|
9
|
+
savedIds: string[];
|
|
10
|
+
}
|
|
11
|
+
export interface CollectionPage<Row> {
|
|
12
|
+
items: Row[];
|
|
13
|
+
nextCursor: string;
|
|
14
|
+
total?: number;
|
|
15
|
+
metadata?: Record<string, unknown>;
|
|
16
|
+
complete?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface CollectionQueryState<Row> extends CollectionPage<Row> {
|
|
19
|
+
criteria: CollectionQuery;
|
|
20
|
+
status: 'idle' | 'loading' | 'ready' | 'error';
|
|
21
|
+
loadingMore: boolean;
|
|
22
|
+
pageError: boolean;
|
|
23
|
+
complete: boolean;
|
|
24
|
+
error: string;
|
|
25
|
+
read: ReadStatus;
|
|
26
|
+
/** The rows are the source's `cached` page, shown while the live read is still out. */
|
|
27
|
+
provisional?: boolean;
|
|
28
|
+
}
|
|
29
|
+
/** What a collection showed, as `shownPage` hands it out and `seed` takes it back on a restored boot. */
|
|
30
|
+
export interface CollectionShownPage<Row> {
|
|
31
|
+
state: CollectionQueryState<Row>;
|
|
32
|
+
loadedPages: number;
|
|
33
|
+
}
|
|
34
|
+
export interface CollectionQuerySource<Row> {
|
|
35
|
+
fetch(criteria: CollectionQuery, cursor: string): Promise<CollectionPage<Row>>;
|
|
36
|
+
/** Optional public cache for immediate paint; always incomplete, never a paging authority. */
|
|
37
|
+
cached?(criteria: CollectionQuery): Promise<CollectionPage<Row> | undefined>;
|
|
38
|
+
id(row: Row): string;
|
|
39
|
+
errorMessage?(error: unknown): string;
|
|
40
|
+
/** Private collections must discard prior rows when access or identity changes. A revalidation keeps the
|
|
41
|
+
* rows it shows through any other failure: safe because one worker never serves two identities (its pool
|
|
42
|
+
* key carries the account, and a purge tears parked workers down). */
|
|
43
|
+
clearOnError?(error: unknown): boolean;
|
|
44
|
+
}
|
|
45
|
+
const normalize = (q: Partial<CollectionQuery>): CollectionQuery => ({
|
|
46
|
+
query: typeof q.query === 'string' ? q.query.trim().slice(0, 256) : '',
|
|
47
|
+
filter: typeof q.filter === 'string' ? q.filter.slice(0, 256) : 'all',
|
|
48
|
+
sort: q.sort === 'az' || q.sort === 'za' ? q.sort : 'default',
|
|
49
|
+
savedIds: Array.isArray(q.savedIds) ? [...new Set(q.savedIds.filter(id => typeof id === 'string' && id.length > 0 && id.length <= 256))].slice(0, 500) : [],
|
|
50
|
+
});
|
|
51
|
+
const copyQuery = (q: CollectionQuery): CollectionQuery => ({ ...q, savedIds: [...q.savedIds] });
|
|
52
|
+
|
|
53
|
+
/** New criteria cancel the old result logically, including an in-flight next
|
|
54
|
+
* page. Failures preserve the cursor and rows for an explicit retry. Callers
|
|
55
|
+
* publish each snapshot through their ordinary service reducer, so loading and
|
|
56
|
+
* filtered-empty never masquerade as a successfully completed query. */
|
|
57
|
+
export function createLegacyCollectionQuery<Row>(source: CollectionQuerySource<Row>, publish: (state: CollectionQueryState<Row>) => void) {
|
|
58
|
+
let generation = 0, disposed = false;
|
|
59
|
+
let loadedPages = 0, restoringPages = 0;
|
|
60
|
+
let state: CollectionQueryState<Row> = { criteria: normalize({}), items: [], nextCursor: '', status: 'idle', loadingMore: false, pageError: false, complete: false, error: '', read: initialReadStatus() };
|
|
61
|
+
/** The query a revalidation is running under: its snapshots are held back until it is done. */
|
|
62
|
+
let revalidating = -1;
|
|
63
|
+
/** The last page the screen showed whole (rows ready, nothing paging, failing or standing in): what a revalidation
|
|
64
|
+
* keeps on screen and restores when it fails, and what a restored boot shows. A next page loading or failing after
|
|
65
|
+
* it leaves those rows on screen, so it is still this. */
|
|
66
|
+
let lastShown: { state: CollectionQueryState<Row>; loadedPages: number } | undefined;
|
|
67
|
+
const sameCriteria = (a: CollectionQuery, b: CollectionQuery) => JSON.stringify(a) === JSON.stringify(b);
|
|
68
|
+
let clearedByError = false;
|
|
69
|
+
const snapshot = (): CollectionQueryState<Row> => ({ ...state, read: { ...state.read }, criteria: copyQuery(state.criteria), items: [...state.items] });
|
|
70
|
+
const emit = () => {
|
|
71
|
+
if (disposed || revalidating === generation) return;
|
|
72
|
+
// A settled answer replaces it, an empty one included: rows the live query proved gone are never kept.
|
|
73
|
+
if (state.status === 'ready' && !state.pageError && !state.loadingMore && !state.provisional) {
|
|
74
|
+
lastShown = { state: snapshot(), loadedPages };
|
|
75
|
+
}
|
|
76
|
+
publish(snapshot());
|
|
77
|
+
};
|
|
78
|
+
const merge = (old: Row[], page: Row[]): Row[] => {
|
|
79
|
+
const positions = new Map<string, number>(), result: Row[] = [];
|
|
80
|
+
for (const item of [...old, ...page]) {
|
|
81
|
+
const id = source.id(item);
|
|
82
|
+
if (!id) throw new Error('A backend collection row requires an identity');
|
|
83
|
+
const index = positions.get(id);
|
|
84
|
+
if (index === undefined) { positions.set(id, result.length); result.push(item); }
|
|
85
|
+
else result[index] = item;
|
|
86
|
+
}
|
|
87
|
+
return result;
|
|
88
|
+
};
|
|
89
|
+
/** [keepRows]: a search typed over the rows on screen leaves them there until its answer arrives (status stays
|
|
90
|
+
* `loading`, and nothing pages under the old answer), so the list is not blanked on each keystroke. */
|
|
91
|
+
const fetch = async (append: boolean, keepRows = false): Promise<void> => {
|
|
92
|
+
if (disposed || (append && (state.loadingMore || state.status === 'loading' || !state.nextCursor))) return;
|
|
93
|
+
const revision = append ? generation : ++generation;
|
|
94
|
+
const cursor = append ? state.nextCursor : '';
|
|
95
|
+
const criteria = copyQuery(state.criteria);
|
|
96
|
+
if (!append) loadedPages = 0;
|
|
97
|
+
state = { ...state, status: append ? 'ready' : 'loading', loadingMore: append, pageError: false, error: '',
|
|
98
|
+
...(append ? {} : keepRows ? { nextCursor: '', complete: false, provisional: false }
|
|
99
|
+
: { items: [], nextCursor: '', total: undefined, metadata: undefined, complete: false, provisional: false }) };
|
|
100
|
+
emit();
|
|
101
|
+
try {
|
|
102
|
+
if (!append && source.cached) {
|
|
103
|
+
const cached = await source.cached(criteria).catch(() => undefined);
|
|
104
|
+
if (disposed || generation !== revision) return;
|
|
105
|
+
if (cached?.items.length) {
|
|
106
|
+
state = { ...state, items: merge([], cached.items), metadata: cached.metadata, status: 'ready', complete: false, provisional: true };
|
|
107
|
+
emit();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const page = await source.fetch(criteria, cursor);
|
|
111
|
+
if (disposed || generation !== revision) return;
|
|
112
|
+
if (!Array.isArray(page.items) || typeof page.nextCursor !== 'string' || (cursor && page.nextCursor === cursor)) throw new Error('Invalid collection page');
|
|
113
|
+
state = { ...state, items: merge(append ? state.items : [], page.items), nextCursor: page.nextCursor,
|
|
114
|
+
total: typeof page.total === 'number' && Number.isFinite(page.total) && page.total >= 0 ? page.total : (append ? state.total : undefined),
|
|
115
|
+
metadata: page.metadata ?? (append ? state.metadata : undefined),
|
|
116
|
+
status: 'ready', loadingMore: false, pageError: false, complete: page.complete ?? !page.nextCursor, provisional: false };
|
|
117
|
+
loadedPages++;
|
|
118
|
+
} catch (error) {
|
|
119
|
+
if (disposed || generation !== revision) return;
|
|
120
|
+
const clear = source.clearOnError?.(error) === true;
|
|
121
|
+
// Revoked access always ends what a revalidation showed, whether or not the source clears its rows.
|
|
122
|
+
clearedByError = clear || /HTTP 40[13]\b/.test(String(error));
|
|
123
|
+
if (clearedByError) lastShown = undefined;
|
|
124
|
+
if (clear) loadedPages = 0;
|
|
125
|
+
// The rows a typed search kept on screen while it ran were never its answer: a failed one shows none of them.
|
|
126
|
+
const drop = clear || keepRows;
|
|
127
|
+
state = { ...state, ...(drop ? { items: [], nextCursor: '', total: undefined, metadata: undefined } : {}),
|
|
128
|
+
status: append && !clear ? 'ready' : 'error', loadingMore: false, pageError: append && !clear, complete: false,
|
|
129
|
+
provisional: false, error: source.errorMessage?.(error) ?? '' };
|
|
130
|
+
}
|
|
131
|
+
emit();
|
|
132
|
+
};
|
|
133
|
+
return {
|
|
134
|
+
getState: snapshot,
|
|
135
|
+
/** [revalidate]: a screen coming back re-queries the rows it shows. They stay on screen until every
|
|
136
|
+
* page is in, and a failure keeps them; revoked access still clears them, and other criteria reset. */
|
|
137
|
+
async query(criteria: Partial<CollectionQuery>, restorePages = false, revalidate = false): Promise<void> {
|
|
138
|
+
if (disposed) return Promise.resolve();
|
|
139
|
+
const next = normalize(criteria);
|
|
140
|
+
const same = JSON.stringify(next) === JSON.stringify(state.criteria);
|
|
141
|
+
const count = restorePages && same ? Math.max(loadedPages, restoringPages, 1) : 1;
|
|
142
|
+
const showing = lastShown && sameCriteria(lastShown.state.criteria, next) ? lastShown : undefined;
|
|
143
|
+
restoringPages = count;
|
|
144
|
+
// Only the search text changed: the user is typing over the rows on screen, also while the last keystroke's
|
|
145
|
+
// search is still out.
|
|
146
|
+
const typing = !same && (state.status === 'ready' || state.status === 'loading')
|
|
147
|
+
&& sameCriteria({ ...state.criteria, query: '' }, { ...next, query: '' });
|
|
148
|
+
state = { ...state, criteria: next };
|
|
149
|
+
if (revalidate && showing) { revalidating = generation + 1; clearedByError = false; }
|
|
150
|
+
const pending = fetch(false, typing);
|
|
151
|
+
const revision = generation;
|
|
152
|
+
await pending;
|
|
153
|
+
// Re-appearance revalidates every previously opened page using fresh
|
|
154
|
+
// cursors. A changed query, error or revoked access stops the restoration.
|
|
155
|
+
while (!disposed && generation === revision && loadedPages < count && state.status === 'ready'
|
|
156
|
+
&& !state.pageError && !state.loadingMore && state.nextCursor) await fetch(true);
|
|
157
|
+
if (generation === revision) restoringPages = 0;
|
|
158
|
+
if (revalidating === revision) {
|
|
159
|
+
revalidating = -1;
|
|
160
|
+
if (generation !== revision) return;
|
|
161
|
+
if ((state.status !== 'ready' || state.pageError) && showing && !clearedByError) {
|
|
162
|
+
state = { ...showing.state, criteria: next };
|
|
163
|
+
loadedPages = showing.loadedPages;
|
|
164
|
+
}
|
|
165
|
+
emit();
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
/** The page the screen shows (rows ready, nothing failing or still paging), for the service to keep with its
|
|
169
|
+
* state; undefined when it shows none. */
|
|
170
|
+
shownPage(): CollectionShownPage<Row> | undefined {
|
|
171
|
+
if (!lastShown || !sameCriteria(lastShown.state.criteria, state.criteria)) return undefined;
|
|
172
|
+
const page = lastShown.state;
|
|
173
|
+
return { state: { ...page, criteria: copyQuery(page.criteria), items: [...page.items] }, loadedPages: lastShown.loadedPages };
|
|
174
|
+
},
|
|
175
|
+
/** A cold boot restored from disk: what the last run showed becomes what this one shows, so its first `load`
|
|
176
|
+
* revalidates behind it instead of starting empty. Only before the first query, and only a well-formed page. */
|
|
177
|
+
seed(page: unknown): boolean {
|
|
178
|
+
const candidate = page && typeof page === 'object' ? page as Partial<CollectionShownPage<Row>> : undefined;
|
|
179
|
+
const restored = candidate?.state;
|
|
180
|
+
if (generation !== 0 || state.status !== 'idle' || !restored || restored.status !== 'ready'
|
|
181
|
+
|| !Array.isArray(restored.items) || typeof restored.nextCursor !== 'string') return false;
|
|
182
|
+
try { restored.items.forEach(item => { if (!source.id(item)) throw new Error('A backend collection row requires an identity'); }); } catch { return false; }
|
|
183
|
+
state = { ...state, ...restored, criteria: normalize(restored.criteria ?? {}), items: [...restored.items], status: 'ready',
|
|
184
|
+
loadingMore: false, pageError: false, provisional: false, error: '', read: restored.read ? { ...restored.read } : initialReadStatus() };
|
|
185
|
+
loadedPages = Math.max(1, Math.floor(Number(candidate?.loadedPages) || 1));
|
|
186
|
+
lastShown = { state: snapshot(), loadedPages };
|
|
187
|
+
return true;
|
|
188
|
+
},
|
|
189
|
+
refresh: () => { restoringPages = 0; return fetch(false); },
|
|
190
|
+
loadMore: () => fetch(true),
|
|
191
|
+
retry: () => fetch(state.pageError),
|
|
192
|
+
dispose() { disposed = true; generation++; },
|
|
193
|
+
};
|
|
194
|
+
}
|
|
@@ -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
|
}
|