@wix/himalaya-cli 0.1574.0 → 0.1576.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/dev-server/src/app-config.d.ts +16 -5
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/define-app.d.ts +6 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/telemetry/wix-bi/src/index.d.ts +15 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/telemetry/wix-bi/src/oneapp.d.ts +90 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-bi.mjs +165 -5
- package/dist/cli.mjs +3489 -2601
- package/dist/preview-assets.generated.bin +0 -0
- package/guides/designing-himi-screens.md +7 -1
- package/guides/hhp.md +2 -2
- package/guides/himi-json-output.md +20 -0
- package/guides/push-cli-onboarding.md +10 -0
- package/package.json +2 -2
- package/templates/beauty-salon/dev/index.ts +74 -127
- package/templates/beauty-salon/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/beauty-salon/tier5-src/himi-branded-lite-cart/index.ts +18 -5
- package/templates/beauty-salon-owner/dev/index.ts +5 -0
- package/templates/beauty-salon-owner/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-post-form/index.ts +5 -1
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-product-form/index.ts +5 -1
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-service-form/index.ts +5 -1
- package/templates/branded-lite/dev/index.ts +114 -136
- package/templates/branded-lite/l10n/en.json +570 -0
- package/templates/branded-lite/l10n/he.json +200 -0
- package/templates/branded-lite/tier5-src/_shared/business-chat-copy.ts +40 -0
- package/templates/branded-lite/tier5-src/_shared/business-chat-message-model.ts +1 -1
- package/templates/branded-lite/tier5-src/_shared/business-chat-model.ts +3 -2
- package/templates/branded-lite/tier5-src/_shared/business-chat-presenter.ts +26 -21
- package/templates/branded-lite/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/branded-lite/tier5-src/business-chat/index.ts +184 -63
- package/templates/branded-lite/tier5-src/himi-branded-lite-cart/index.ts +18 -5
- package/templates/community-club-owner/dev/index.ts +5 -0
- package/templates/community-club-owner/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/community-club-owner/tier5-src/himi-owner-lite-event-form/index.ts +5 -1
- package/templates/community-club-owner/tier5-src/himi-owner-lite-post-form/index.ts +5 -1
- package/templates/community-club-owner/tier5-src/himi-owner-lite-product-form/index.ts +5 -1
- package/templates/events-meetups-owner/dev/index.ts +3 -0
- package/templates/events-meetups-owner/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-event-form/index.ts +5 -1
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-post-form/index.ts +5 -1
- package/templates/gym-fitness/dev/index.ts +74 -127
- package/templates/gym-fitness/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/gym-fitness/tier5-src/himi-branded-lite-cart/index.ts +18 -5
- package/templates/gym-fitness-owner/dev/index.ts +6 -0
- package/templates/gym-fitness-owner/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-event-form/index.ts +5 -1
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-post-form/index.ts +5 -1
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-product-form/index.ts +5 -1
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-service-form/index.ts +5 -1
- package/templates/nonprofit-cause-owner/dev/index.ts +4 -0
- package/templates/nonprofit-cause-owner/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-event-form/index.ts +5 -1
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-post-form/index.ts +5 -1
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-service-form/index.ts +5 -1
- package/templates/owner-lite/dev/index.ts +6 -0
- package/templates/owner-lite/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/owner-lite/tier5-src/himi-owner-lite-event-form/index.ts +5 -1
- package/templates/owner-lite/tier5-src/himi-owner-lite-post-form/index.ts +5 -1
- package/templates/owner-lite/tier5-src/himi-owner-lite-product-form/index.ts +5 -1
- package/templates/owner-lite/tier5-src/himi-owner-lite-service-form/index.ts +5 -1
- package/templates/podcast-creator-owner/dev/index.ts +5 -0
- package/templates/podcast-creator-owner/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-event-form/index.ts +5 -1
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-post-form/index.ts +5 -1
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-product-form/index.ts +5 -1
- package/templates/portfolio-creative/dev/index.ts +74 -127
- package/templates/portfolio-creative/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/portfolio-creative/tier5-src/himi-branded-lite-cart/index.ts +18 -5
- package/templates/portfolio-creative-owner/dev/index.ts +5 -0
- package/templates/portfolio-creative-owner/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-post-form/index.ts +5 -1
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-product-form/index.ts +5 -1
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-service-form/index.ts +5 -1
- package/templates/restaurant-owner/dev/index.ts +6 -0
- package/templates/restaurant-owner/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-event-form/index.ts +5 -1
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-post-form/index.ts +5 -1
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-product-form/index.ts +5 -1
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-service-form/index.ts +5 -1
- package/templates/retail-store/dev/index.ts +74 -127
- package/templates/retail-store/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/retail-store/tier5-src/himi-branded-lite-cart/index.ts +18 -5
- package/templates/retail-store-owner/dev/index.ts +4 -0
- package/templates/retail-store-owner/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-post-form/index.ts +5 -1
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-product-form/index.ts +5 -1
- package/templates/yoga-studio-owner/dev/index.ts +4 -0
- package/templates/yoga-studio-owner/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-event-form/index.ts +5 -1
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-post-form/index.ts +5 -1
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-service-form/index.ts +5 -1
|
@@ -14543,39 +14543,28 @@ const SCREENS = runtimeSetting({
|
|
|
14543
14543
|
"id": "himi_cart.qty_minus",
|
|
14544
14544
|
"body": {
|
|
14545
14545
|
"package": "himalaya",
|
|
14546
|
-
"type": "
|
|
14546
|
+
"type": "Button",
|
|
14547
14547
|
"props": {
|
|
14548
|
-
"
|
|
14549
|
-
"
|
|
14550
|
-
|
|
14551
|
-
|
|
14552
|
-
"
|
|
14553
|
-
"package": "himalaya",
|
|
14554
|
-
"type": "Icon",
|
|
14555
|
-
"props": {
|
|
14556
|
-
"name": "minus",
|
|
14557
|
-
"size": 24,
|
|
14558
|
-
"tint": "textPrimary"
|
|
14559
|
-
}
|
|
14560
|
-
},
|
|
14561
|
-
"modifiers": {
|
|
14562
|
-
"accessibilityRole": "decorative"
|
|
14563
|
-
}
|
|
14548
|
+
"title": "Decrease quantity",
|
|
14549
|
+
"action": {
|
|
14550
|
+
"actionId": "tier5.himiStoreCart.decrement",
|
|
14551
|
+
"params": {
|
|
14552
|
+
"id": "$item.id"
|
|
14564
14553
|
}
|
|
14565
|
-
|
|
14554
|
+
},
|
|
14555
|
+
"style": "tertiary",
|
|
14556
|
+
"iconOnly": true,
|
|
14557
|
+
"leadingIcon": "minus",
|
|
14558
|
+
"cornerRadius": 22,
|
|
14559
|
+
"minHeight": 44,
|
|
14560
|
+
"disabledPath": "$service:himiStoreCart.writeBusy"
|
|
14566
14561
|
}
|
|
14567
14562
|
},
|
|
14568
14563
|
"modifiers": {
|
|
14569
14564
|
"width": 44,
|
|
14570
14565
|
"height": 44,
|
|
14571
|
-
"onTap": {
|
|
14572
|
-
"actionId": "tier5.himiStoreCart.decrement",
|
|
14573
|
-
"params": {
|
|
14574
|
-
"id": "$item.id"
|
|
14575
|
-
}
|
|
14576
|
-
},
|
|
14577
|
-
"accessibilityRole": "button",
|
|
14578
14566
|
"accessibilityLabel": "Decrease quantity",
|
|
14567
|
+
"accessibilityRole": "button",
|
|
14579
14568
|
"haptic": "light"
|
|
14580
14569
|
}
|
|
14581
14570
|
},
|
|
@@ -14598,39 +14587,28 @@ const SCREENS = runtimeSetting({
|
|
|
14598
14587
|
"id": "himi_cart.qty_plus",
|
|
14599
14588
|
"body": {
|
|
14600
14589
|
"package": "himalaya",
|
|
14601
|
-
"type": "
|
|
14590
|
+
"type": "Button",
|
|
14602
14591
|
"props": {
|
|
14603
|
-
"
|
|
14604
|
-
"
|
|
14605
|
-
|
|
14606
|
-
|
|
14607
|
-
"
|
|
14608
|
-
"package": "himalaya",
|
|
14609
|
-
"type": "Icon",
|
|
14610
|
-
"props": {
|
|
14611
|
-
"name": "plus",
|
|
14612
|
-
"size": 24,
|
|
14613
|
-
"tint": "textPrimary"
|
|
14614
|
-
}
|
|
14615
|
-
},
|
|
14616
|
-
"modifiers": {
|
|
14617
|
-
"accessibilityRole": "decorative"
|
|
14618
|
-
}
|
|
14592
|
+
"title": "Increase quantity",
|
|
14593
|
+
"action": {
|
|
14594
|
+
"actionId": "tier5.himiStoreCart.increment",
|
|
14595
|
+
"params": {
|
|
14596
|
+
"id": "$item.id"
|
|
14619
14597
|
}
|
|
14620
|
-
|
|
14598
|
+
},
|
|
14599
|
+
"style": "tertiary",
|
|
14600
|
+
"iconOnly": true,
|
|
14601
|
+
"leadingIcon": "plus",
|
|
14602
|
+
"cornerRadius": 22,
|
|
14603
|
+
"minHeight": 44,
|
|
14604
|
+
"disabledPath": "$service:himiStoreCart.writeBusy"
|
|
14621
14605
|
}
|
|
14622
14606
|
},
|
|
14623
14607
|
"modifiers": {
|
|
14624
14608
|
"width": 44,
|
|
14625
14609
|
"height": 44,
|
|
14626
|
-
"onTap": {
|
|
14627
|
-
"actionId": "tier5.himiStoreCart.increment",
|
|
14628
|
-
"params": {
|
|
14629
|
-
"id": "$item.id"
|
|
14630
|
-
}
|
|
14631
|
-
},
|
|
14632
|
-
"accessibilityRole": "button",
|
|
14633
14610
|
"accessibilityLabel": "Increase quantity",
|
|
14611
|
+
"accessibilityRole": "button",
|
|
14634
14612
|
"haptic": "light"
|
|
14635
14613
|
}
|
|
14636
14614
|
},
|
|
@@ -14646,39 +14624,28 @@ const SCREENS = runtimeSetting({
|
|
|
14646
14624
|
"id": "himi_cart.save_filled",
|
|
14647
14625
|
"body": {
|
|
14648
14626
|
"package": "himalaya",
|
|
14649
|
-
"type": "
|
|
14627
|
+
"type": "Button",
|
|
14650
14628
|
"props": {
|
|
14651
|
-
"
|
|
14652
|
-
"
|
|
14653
|
-
|
|
14654
|
-
|
|
14655
|
-
"
|
|
14656
|
-
"package": "himalaya",
|
|
14657
|
-
"type": "Icon",
|
|
14658
|
-
"props": {
|
|
14659
|
-
"name": "heart_fill",
|
|
14660
|
-
"size": 24,
|
|
14661
|
-
"tint": "textPrimary"
|
|
14662
|
-
}
|
|
14663
|
-
},
|
|
14664
|
-
"modifiers": {
|
|
14665
|
-
"accessibilityRole": "decorative"
|
|
14666
|
-
}
|
|
14629
|
+
"title": "Remove from wishlist",
|
|
14630
|
+
"action": {
|
|
14631
|
+
"actionId": "tier5.himiStoreCart.save",
|
|
14632
|
+
"params": {
|
|
14633
|
+
"id": "$item.id"
|
|
14667
14634
|
}
|
|
14668
|
-
|
|
14635
|
+
},
|
|
14636
|
+
"style": "tertiary",
|
|
14637
|
+
"iconOnly": true,
|
|
14638
|
+
"leadingIcon": "heart_fill",
|
|
14639
|
+
"cornerRadius": 22,
|
|
14640
|
+
"minHeight": 44,
|
|
14641
|
+
"disabledPath": "$service:himiStoreCart.writeBusy"
|
|
14669
14642
|
}
|
|
14670
14643
|
},
|
|
14671
14644
|
"modifiers": {
|
|
14672
14645
|
"width": 44,
|
|
14673
14646
|
"height": 44,
|
|
14674
|
-
"onTap": {
|
|
14675
|
-
"actionId": "tier5.himiStoreCart.save",
|
|
14676
|
-
"params": {
|
|
14677
|
-
"id": "$item.id"
|
|
14678
|
-
}
|
|
14679
|
-
},
|
|
14680
|
-
"accessibilityRole": "button",
|
|
14681
14647
|
"accessibilityLabel": "Remove from wishlist",
|
|
14648
|
+
"accessibilityRole": "button",
|
|
14682
14649
|
"haptic": "light",
|
|
14683
14650
|
"hiddenPath": "$item.notSaved"
|
|
14684
14651
|
}
|
|
@@ -14687,39 +14654,28 @@ const SCREENS = runtimeSetting({
|
|
|
14687
14654
|
"id": "himi_cart.save",
|
|
14688
14655
|
"body": {
|
|
14689
14656
|
"package": "himalaya",
|
|
14690
|
-
"type": "
|
|
14657
|
+
"type": "Button",
|
|
14691
14658
|
"props": {
|
|
14692
|
-
"
|
|
14693
|
-
"
|
|
14694
|
-
|
|
14695
|
-
|
|
14696
|
-
"
|
|
14697
|
-
"package": "himalaya",
|
|
14698
|
-
"type": "Icon",
|
|
14699
|
-
"props": {
|
|
14700
|
-
"name": "heart",
|
|
14701
|
-
"size": 24,
|
|
14702
|
-
"tint": "textPrimary"
|
|
14703
|
-
}
|
|
14704
|
-
},
|
|
14705
|
-
"modifiers": {
|
|
14706
|
-
"accessibilityRole": "decorative"
|
|
14707
|
-
}
|
|
14659
|
+
"title": "Add to wishlist",
|
|
14660
|
+
"action": {
|
|
14661
|
+
"actionId": "tier5.himiStoreCart.save",
|
|
14662
|
+
"params": {
|
|
14663
|
+
"id": "$item.id"
|
|
14708
14664
|
}
|
|
14709
|
-
|
|
14665
|
+
},
|
|
14666
|
+
"style": "tertiary",
|
|
14667
|
+
"iconOnly": true,
|
|
14668
|
+
"leadingIcon": "heart",
|
|
14669
|
+
"cornerRadius": 22,
|
|
14670
|
+
"minHeight": 44,
|
|
14671
|
+
"disabledPath": "$service:himiStoreCart.writeBusy"
|
|
14710
14672
|
}
|
|
14711
14673
|
},
|
|
14712
14674
|
"modifiers": {
|
|
14713
14675
|
"width": 44,
|
|
14714
14676
|
"height": 44,
|
|
14715
|
-
"onTap": {
|
|
14716
|
-
"actionId": "tier5.himiStoreCart.save",
|
|
14717
|
-
"params": {
|
|
14718
|
-
"id": "$item.id"
|
|
14719
|
-
}
|
|
14720
|
-
},
|
|
14721
|
-
"accessibilityRole": "button",
|
|
14722
14677
|
"accessibilityLabel": "Add to wishlist",
|
|
14678
|
+
"accessibilityRole": "button",
|
|
14723
14679
|
"haptic": "light",
|
|
14724
14680
|
"hiddenPath": "$item.saved"
|
|
14725
14681
|
}
|
|
@@ -14728,39 +14684,28 @@ const SCREENS = runtimeSetting({
|
|
|
14728
14684
|
"id": "himi_cart.remove",
|
|
14729
14685
|
"body": {
|
|
14730
14686
|
"package": "himalaya",
|
|
14731
|
-
"type": "
|
|
14687
|
+
"type": "Button",
|
|
14732
14688
|
"props": {
|
|
14733
|
-
"
|
|
14734
|
-
"
|
|
14735
|
-
|
|
14736
|
-
|
|
14737
|
-
"
|
|
14738
|
-
"package": "himalaya",
|
|
14739
|
-
"type": "Icon",
|
|
14740
|
-
"props": {
|
|
14741
|
-
"name": "trash",
|
|
14742
|
-
"size": 24,
|
|
14743
|
-
"tint": "textPrimary"
|
|
14744
|
-
}
|
|
14745
|
-
},
|
|
14746
|
-
"modifiers": {
|
|
14747
|
-
"accessibilityRole": "decorative"
|
|
14748
|
-
}
|
|
14689
|
+
"title": "Remove from cart",
|
|
14690
|
+
"action": {
|
|
14691
|
+
"actionId": "tier5.himiStoreCart.remove",
|
|
14692
|
+
"params": {
|
|
14693
|
+
"id": "$item.id"
|
|
14749
14694
|
}
|
|
14750
|
-
|
|
14695
|
+
},
|
|
14696
|
+
"style": "tertiary",
|
|
14697
|
+
"iconOnly": true,
|
|
14698
|
+
"leadingIcon": "trash",
|
|
14699
|
+
"cornerRadius": 22,
|
|
14700
|
+
"minHeight": 44,
|
|
14701
|
+
"disabledPath": "$service:himiStoreCart.writeBusy"
|
|
14751
14702
|
}
|
|
14752
14703
|
},
|
|
14753
14704
|
"modifiers": {
|
|
14754
14705
|
"width": 44,
|
|
14755
14706
|
"height": 44,
|
|
14756
|
-
"onTap": {
|
|
14757
|
-
"actionId": "tier5.himiStoreCart.remove",
|
|
14758
|
-
"params": {
|
|
14759
|
-
"id": "$item.id"
|
|
14760
|
-
}
|
|
14761
|
-
},
|
|
14762
|
-
"accessibilityRole": "button",
|
|
14763
14707
|
"accessibilityLabel": "Remove from cart",
|
|
14708
|
+
"accessibilityRole": "button",
|
|
14764
14709
|
"haptic": "light"
|
|
14765
14710
|
}
|
|
14766
14711
|
}
|
|
@@ -15009,7 +14954,7 @@ const SCREENS = runtimeSetting({
|
|
|
15009
14954
|
"action": {
|
|
15010
14955
|
"actionId": "tier5.himiStoreCart.applyPromo"
|
|
15011
14956
|
},
|
|
15012
|
-
"disabledPath": "$service:himiStoreCart.
|
|
14957
|
+
"disabledPath": "$service:himiStoreCart.writeBusy"
|
|
15013
14958
|
}
|
|
15014
14959
|
},
|
|
15015
14960
|
"modifiers": {
|
|
@@ -15066,7 +15011,8 @@ const SCREENS = runtimeSetting({
|
|
|
15066
15011
|
"iconOnly": true,
|
|
15067
15012
|
"leadingIcon": "close",
|
|
15068
15013
|
"cornerRadius": 22,
|
|
15069
|
-
"minHeight": 44
|
|
15014
|
+
"minHeight": 44,
|
|
15015
|
+
"disabledPath": "$service:himiStoreCart.writeBusy"
|
|
15070
15016
|
}
|
|
15071
15017
|
},
|
|
15072
15018
|
"modifiers": {
|
|
@@ -15200,6 +15146,7 @@ const SCREENS = runtimeSetting({
|
|
|
15200
15146
|
"title": "Checkout",
|
|
15201
15147
|
"style": "primary",
|
|
15202
15148
|
"fullWidth": true,
|
|
15149
|
+
"disabledPath": "$service:himiStoreCart.writeBusy",
|
|
15203
15150
|
"action": {
|
|
15204
15151
|
"actionId": "tier5.himiStoreCart.checkout"
|
|
15205
15152
|
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { defineService as register, type ServiceDefinition } from '@himalaya/state';
|
|
2
|
+
|
|
3
|
+
/** Reject repeated taps until the accepted write's result is committed. A promise
|
|
4
|
+
* queue alone releases before the host applies the reducer result, so the next
|
|
5
|
+
* write can still read an old revision/id. The private synchronous reducer below
|
|
6
|
+
* publishes busy and commits completion through the ordinary service handle;
|
|
7
|
+
* awaiting that handle guarantees the host has stored the result before unlock.
|
|
8
|
+
* Skipped actions return undefined, so they cannot roll back the accepted write.
|
|
9
|
+
*/
|
|
10
|
+
export function withExclusiveWrites<S extends object = Record<string, unknown>>(
|
|
11
|
+
registerService: typeof register, names: readonly string[],
|
|
12
|
+
options: { prepare?: (action: string, args: readonly unknown[]) => void; coalesce?: readonly string[]; settle?: (before: S, next: S, live: S) => S } = {},
|
|
13
|
+
): typeof register {
|
|
14
|
+
return ((name: string, definition: ServiceDefinition<Record<string, unknown>>) => {
|
|
15
|
+
let busy = false;
|
|
16
|
+
let activeAction = "";
|
|
17
|
+
let completion = Promise.resolve();
|
|
18
|
+
let complete = () => {};
|
|
19
|
+
let handle: { getState(): Record<string, unknown>; exclusiveWriteState(next: Record<string, unknown>): Promise<unknown> };
|
|
20
|
+
const wrapped: ServiceDefinition<Record<string, unknown>> = {
|
|
21
|
+
...definition,
|
|
22
|
+
state: { ...definition.state, writeBusy: false },
|
|
23
|
+
notHydrated: [...(definition.notHydrated ?? []), 'writeBusy'],
|
|
24
|
+
actions: {
|
|
25
|
+
...Object.fromEntries(Object.entries(definition.actions ?? {}).map(([action, reduce]) => [action,
|
|
26
|
+
names.includes(action) ? async (state: Record<string, unknown>, ...args: never[]) => {
|
|
27
|
+
// Intake request metadata even when its state replacement is skipped.
|
|
28
|
+
options.prepare?.(action, args);
|
|
29
|
+
if (busy) {
|
|
30
|
+
// Repeated initial loads must await the accepted load, so callers
|
|
31
|
+
// observe its committed rows rather than the initial skeleton.
|
|
32
|
+
if (activeAction === action && options.coalesce?.includes(action)) await completion;
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
busy = true;
|
|
36
|
+
activeAction = action;
|
|
37
|
+
completion = new Promise<void>(resolve => { complete = resolve; });
|
|
38
|
+
try {
|
|
39
|
+
await handle.exclusiveWriteState({ ...handle.getState(), writeBusy: true });
|
|
40
|
+
const next = await reduce(state, ...args);
|
|
41
|
+
const live = handle.getState();
|
|
42
|
+
// Non-write actions (for example editing a draft field) can still run
|
|
43
|
+
// during I/O. Let the owning service preserve those newer edits.
|
|
44
|
+
const settled = options.settle ? options.settle(state as S, (next ?? live) as S, live as S) : next ?? live;
|
|
45
|
+
await handle.exclusiveWriteState({ ...settled, writeBusy: false });
|
|
46
|
+
} catch (error) {
|
|
47
|
+
await handle.exclusiveWriteState({ ...handle.getState(), writeBusy: false });
|
|
48
|
+
throw error;
|
|
49
|
+
} finally {
|
|
50
|
+
busy = false;
|
|
51
|
+
complete();
|
|
52
|
+
}
|
|
53
|
+
} : reduce,
|
|
54
|
+
])),
|
|
55
|
+
exclusiveWriteState: (_state, next: Record<string, unknown>) => next,
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
const registered = registerService(name, wrapped);
|
|
59
|
+
// Registration preserves this private action, but the dynamic action map
|
|
60
|
+
// cannot express its payload to the generic service handle.
|
|
61
|
+
handle = registered as unknown as typeof handle;
|
|
62
|
+
// Older hosts ignore notHydrated. A fresh worker has no pending write.
|
|
63
|
+
handle.getState().writeBusy = false;
|
|
64
|
+
return registered;
|
|
65
|
+
}) as typeof register;
|
|
66
|
+
}
|
|
@@ -115,6 +115,7 @@ interface State {
|
|
|
115
115
|
checkoutHint: string;
|
|
116
116
|
itemCount: number;
|
|
117
117
|
promoCode: string;
|
|
118
|
+
promoEditRevision: number;
|
|
118
119
|
appliedCode: string;
|
|
119
120
|
appliedLabel: string;
|
|
120
121
|
couponId: string;
|
|
@@ -380,11 +381,24 @@ function couponDetails(result: unknown): { id: string; code: string } {
|
|
|
380
381
|
return { id: coupon?.coupon?.id ?? coupon?.id ?? "", code: coupon?.coupon?.code ?? coupon?.code ?? "" };
|
|
381
382
|
}
|
|
382
383
|
|
|
384
|
+
import { withExclusiveWrites } from "../_shared/exclusive-writes.js";
|
|
385
|
+
|
|
383
386
|
let cartPricing: IapCommerce;
|
|
384
387
|
const defineService = withIapPresentation<State>({
|
|
385
|
-
register: withOnlineWrites(registerState, ["remove", "increment", "decrement", "checkout", "beginPromo", "applyPromo", "removePromo"], { derive, messageFields: action => action.includes("Promo") ? ["promoError"] : ["checkoutHint"] }),
|
|
388
|
+
register: withExclusiveWrites<State>(withOnlineWrites(registerState, ["remove", "increment", "decrement", "checkout", "beginPromo", "applyPromo", "removePromo"], { derive, messageFields: action => action.includes("Promo") ? ["promoError"] : ["checkoutHint"] }), ["load", "remove", "increment", "decrement", "checkout", "applyPromo", "removePromo", "save"], {
|
|
389
|
+
prepare: (action, args) => { if (action === "load") stashCopy(args[0]); },
|
|
390
|
+
coalesce: ["load"],
|
|
391
|
+
// Full cart reloads share the transaction, since they also replace rows and
|
|
392
|
+
// reset promo fields. Only setPromo increments the edit revision: beginPromo's internal error reset
|
|
393
|
+
// must not masquerade as a user edit when a rejected code is retried.
|
|
394
|
+
settle: (before, next, live) => {
|
|
395
|
+
if ((before.promoEditRevision ?? 0) === (live.promoEditRevision ?? 0)) return next;
|
|
396
|
+
return derive({ ...next, promoEditRevision: live.promoEditRevision,
|
|
397
|
+
...(next.items.length ? { promoCode: live.promoCode, promoError: live.promoError } : {}) });
|
|
398
|
+
},
|
|
399
|
+
}),
|
|
386
400
|
onCreate: commerce => { cartPricing = commerce; },
|
|
387
|
-
prefix: "cart", transientKeys: ["items", "subtotal", "promoBusy"], contentActions: ["load", "remove", "increment", "decrement"],
|
|
401
|
+
prefix: "cart", transientKeys: ["items", "subtotal", "promoBusy", "promoEditRevision"], contentActions: ["load", "remove", "increment", "decrement"],
|
|
388
402
|
items: state => state.items.flatMap(row => row.catalogReference?.catalogItemId ? [{ reference: { kind: "storesProduct", id: row.catalogReference.catalogItemId }, commerceDisplayPrice: row.commerceTotal ?? row.total }] : []),
|
|
389
403
|
present: (state, price, allowed) => ({ ...state,
|
|
390
404
|
items: state.items.map(row => ({ ...row, total: row.catalogReference?.catalogItemId ? price(row.catalogReference.catalogItemId, row.commerceTotal ?? row.total) : row.total })),
|
|
@@ -404,10 +418,9 @@ const svc = defineService<State, {
|
|
|
404
418
|
decrement: (state: State, arg?: unknown) => Promise<State>;
|
|
405
419
|
checkout: (state: State) => Promise<State>;
|
|
406
420
|
}>("himiStoreCart", {
|
|
407
|
-
state: derive({ status: "loading", errorKind: "", items: [], subtotal: "", subtotalLabel: "", emptyText: "", checkoutHint: "", showPaidToast: false, noPaidToast: true, itemCount: 0, promoCode: "", appliedCode: "", appliedLabel: "", couponId: "", promoError: "", promoBusy: false, noPromoError: true, hasCoupon: false, discount: "", noDiscount: true, noCheckoutHint: true, noCoupon: true, isLoading: true, notLoading: false, isErrored: false, notErrored: true, noOfflineError: true, noServerError: true, hideContent: true, noEmpty: true, noItems: true }),
|
|
421
|
+
state: derive({ status: "loading", errorKind: "", items: [], subtotal: "", subtotalLabel: "", emptyText: "", checkoutHint: "", showPaidToast: false, noPaidToast: true, itemCount: 0, promoCode: "", promoEditRevision: 0, appliedCode: "", appliedLabel: "", couponId: "", promoError: "", promoBusy: false, noPromoError: true, hasCoupon: false, discount: "", noDiscount: true, noCheckoutHint: true, noCoupon: true, isLoading: true, notLoading: false, isErrored: false, notErrored: true, noOfflineError: true, noServerError: true, hideContent: true, noEmpty: true, noItems: true }),
|
|
408
422
|
actions: {
|
|
409
423
|
load: async (state, arg) => {
|
|
410
|
-
stashCopy(arg);
|
|
411
424
|
// 1) Instant paint from the on-device cache (offline-safe; empty on first-ever run).
|
|
412
425
|
const cached = await readCachedItems();
|
|
413
426
|
const base = cached.length
|
|
@@ -423,7 +436,7 @@ const svc = defineService<State, {
|
|
|
423
436
|
: derive({ ...state, status: "error", errorKind: isOfflineFailure(error) ? "offline" : "server", items: [], subtotal: "", emptyText: copyParam("cart.loadErrorText", "Cart could not load."), checkoutHint: "" });
|
|
424
437
|
}
|
|
425
438
|
},
|
|
426
|
-
setPromo: (state, arg) => derive({ ...state, promoCode: (typeof arg === "string" ? arg : String((arg as { value?: unknown } | undefined)?.value ?? "")).slice(0, 100), promoError: "" }),
|
|
439
|
+
setPromo: (state, arg) => derive({ ...state, promoEditRevision: (state.promoEditRevision ?? 0) + 1, promoCode: (typeof arg === "string" ? arg : String((arg as { value?: unknown } | undefined)?.value ?? "")).slice(0, 100), promoError: "" }),
|
|
427
440
|
beginPromo: state => derive({ ...state, promoBusy: true, promoError: "" }),
|
|
428
441
|
applyPromo: async (state): Promise<State> => {
|
|
429
442
|
const code = state.promoCode.trim();
|
|
@@ -75838,6 +75838,7 @@ const SCREENS = runtimeSetting({
|
|
|
75838
75838
|
"title": "Create product",
|
|
75839
75839
|
"style": "primary",
|
|
75840
75840
|
"fullWidth": true,
|
|
75841
|
+
"disabledPath": "$service:himiOwnerLiteProductForm.writeBusy",
|
|
75841
75842
|
"action": {
|
|
75842
75843
|
"actionId": "tier5.himiOwnerLiteProductForm.submit"
|
|
75843
75844
|
}
|
|
@@ -76158,6 +76159,7 @@ const SCREENS = runtimeSetting({
|
|
|
76158
76159
|
"title": "Create post",
|
|
76159
76160
|
"style": "primary",
|
|
76160
76161
|
"fullWidth": true,
|
|
76162
|
+
"disabledPath": "$service:himiOwnerLitePostForm.writeBusy",
|
|
76161
76163
|
"action": {
|
|
76162
76164
|
"actionId": "tier5.himiOwnerLitePostForm.submit"
|
|
76163
76165
|
}
|
|
@@ -76673,6 +76675,7 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
76673
76675
|
"title": "Save changes",
|
|
76674
76676
|
"style": "primary",
|
|
76675
76677
|
"fullWidth": true,
|
|
76678
|
+
"disabledPath": "$service:himiOwnerLiteProductForm.writeBusy",
|
|
76676
76679
|
"action": {
|
|
76677
76680
|
"actionId": "tier5.himiOwnerLiteProductForm.submit"
|
|
76678
76681
|
}
|
|
@@ -76997,6 +77000,7 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
76997
77000
|
"title": "Save changes",
|
|
76998
77001
|
"style": "primary",
|
|
76999
77002
|
"fullWidth": true,
|
|
77003
|
+
"disabledPath": "$service:himiOwnerLitePostForm.writeBusy",
|
|
77000
77004
|
"action": {
|
|
77001
77005
|
"actionId": "tier5.himiOwnerLitePostForm.submit"
|
|
77002
77006
|
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { defineService as register, type ServiceDefinition } from '@himalaya/state';
|
|
2
|
+
|
|
3
|
+
/** Reject repeated taps until the accepted write's result is committed. A promise
|
|
4
|
+
* queue alone releases before the host applies the reducer result, so the next
|
|
5
|
+
* write can still read an old revision/id. The private synchronous reducer below
|
|
6
|
+
* publishes busy and commits completion through the ordinary service handle;
|
|
7
|
+
* awaiting that handle guarantees the host has stored the result before unlock.
|
|
8
|
+
* Skipped actions return undefined, so they cannot roll back the accepted write.
|
|
9
|
+
*/
|
|
10
|
+
export function withExclusiveWrites<S extends object = Record<string, unknown>>(
|
|
11
|
+
registerService: typeof register, names: readonly string[],
|
|
12
|
+
options: { prepare?: (action: string, args: readonly unknown[]) => void; coalesce?: readonly string[]; settle?: (before: S, next: S, live: S) => S } = {},
|
|
13
|
+
): typeof register {
|
|
14
|
+
return ((name: string, definition: ServiceDefinition<Record<string, unknown>>) => {
|
|
15
|
+
let busy = false;
|
|
16
|
+
let activeAction = "";
|
|
17
|
+
let completion = Promise.resolve();
|
|
18
|
+
let complete = () => {};
|
|
19
|
+
let handle: { getState(): Record<string, unknown>; exclusiveWriteState(next: Record<string, unknown>): Promise<unknown> };
|
|
20
|
+
const wrapped: ServiceDefinition<Record<string, unknown>> = {
|
|
21
|
+
...definition,
|
|
22
|
+
state: { ...definition.state, writeBusy: false },
|
|
23
|
+
notHydrated: [...(definition.notHydrated ?? []), 'writeBusy'],
|
|
24
|
+
actions: {
|
|
25
|
+
...Object.fromEntries(Object.entries(definition.actions ?? {}).map(([action, reduce]) => [action,
|
|
26
|
+
names.includes(action) ? async (state: Record<string, unknown>, ...args: never[]) => {
|
|
27
|
+
// Intake request metadata even when its state replacement is skipped.
|
|
28
|
+
options.prepare?.(action, args);
|
|
29
|
+
if (busy) {
|
|
30
|
+
// Repeated initial loads must await the accepted load, so callers
|
|
31
|
+
// observe its committed rows rather than the initial skeleton.
|
|
32
|
+
if (activeAction === action && options.coalesce?.includes(action)) await completion;
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
busy = true;
|
|
36
|
+
activeAction = action;
|
|
37
|
+
completion = new Promise<void>(resolve => { complete = resolve; });
|
|
38
|
+
try {
|
|
39
|
+
await handle.exclusiveWriteState({ ...handle.getState(), writeBusy: true });
|
|
40
|
+
const next = await reduce(state, ...args);
|
|
41
|
+
const live = handle.getState();
|
|
42
|
+
// Non-write actions (for example editing a draft field) can still run
|
|
43
|
+
// during I/O. Let the owning service preserve those newer edits.
|
|
44
|
+
const settled = options.settle ? options.settle(state as S, (next ?? live) as S, live as S) : next ?? live;
|
|
45
|
+
await handle.exclusiveWriteState({ ...settled, writeBusy: false });
|
|
46
|
+
} catch (error) {
|
|
47
|
+
await handle.exclusiveWriteState({ ...handle.getState(), writeBusy: false });
|
|
48
|
+
throw error;
|
|
49
|
+
} finally {
|
|
50
|
+
busy = false;
|
|
51
|
+
complete();
|
|
52
|
+
}
|
|
53
|
+
} : reduce,
|
|
54
|
+
])),
|
|
55
|
+
exclusiveWriteState: (_state, next: Record<string, unknown>) => next,
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
const registered = registerService(name, wrapped);
|
|
59
|
+
// Registration preserves this private action, but the dynamic action map
|
|
60
|
+
// cannot express its payload to the generic service handle.
|
|
61
|
+
handle = registered as unknown as typeof handle;
|
|
62
|
+
// Older hosts ignore notHydrated. A fresh worker has no pending write.
|
|
63
|
+
handle.getState().writeBusy = false;
|
|
64
|
+
return registered;
|
|
65
|
+
}) as typeof register;
|
|
66
|
+
}
|
|
@@ -8,7 +8,9 @@ import { editorSubmitValue, queuedDocument, editorChanged, editorRunKeys, resetH
|
|
|
8
8
|
import { ricosUploadService } from "@himalaya/wix-rich-content-editor";
|
|
9
9
|
import { wixCall } from "@himalaya/wix-site-media";
|
|
10
10
|
import { wixMediaUrl, WIX_DETAIL_IMAGE } from "../_shared/himi-store.js";
|
|
11
|
-
import { defineService } from "@himalaya/state";
|
|
11
|
+
import { defineService as registerService } from "@himalaya/state";
|
|
12
|
+
import { withExclusiveWrites } from "../_shared/exclusive-writes.js";
|
|
13
|
+
const defineService = withExclusiveWrites(registerService, ["submit"]);
|
|
12
14
|
import { getDraftPost, createDraftPost, updateDraftPost, type WixDraftPostWrite, type WixRichContent } from "@himalaya/wix-blog";
|
|
13
15
|
import { ownerLoadError, isOwnerPreviewDemo, ownerDemoFallback, ownerWriteError } from "../_shared/owner-load-error.js";
|
|
14
16
|
import { DEMO_POSTS, DEMO_NOTE, DEMO_SAVE_NOTE, demoRefusalPatch } from "../_shared/owner-preview-demo.js";
|
|
@@ -53,6 +55,7 @@ interface PostFormState {
|
|
|
53
55
|
noCover: boolean;
|
|
54
56
|
isFeatured: string;
|
|
55
57
|
status: "idle" | "loading" | "saving" | "saved" | "error";
|
|
58
|
+
writeBusy: boolean;
|
|
56
59
|
error: string;
|
|
57
60
|
savedMessage: string;
|
|
58
61
|
/** WS-C — canonical detail target (`himi_owner_lite_post`); `savedId` rides
|
|
@@ -128,6 +131,7 @@ function shape(s: Partial<PostFormState>): PostFormState {
|
|
|
128
131
|
noCover: !s.coverUrl,
|
|
129
132
|
isFeatured: s.isFeatured ?? "false",
|
|
130
133
|
status: s.status ?? "idle",
|
|
134
|
+
writeBusy: s.writeBusy ?? false,
|
|
131
135
|
error: s.error ?? "",
|
|
132
136
|
savedMessage: s.savedMessage ?? "",
|
|
133
137
|
// WS-C — canonical detail target + the saved entity's id as a separate nav
|
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
// the WRITE shape needs `priceData: { price: <number>, currency }`. Bound by the
|
|
7
7
|
// generic buildForm descriptor ($service:himiOwnerLiteProductForm.*).
|
|
8
8
|
|
|
9
|
-
import { defineService } from "@himalaya/state";
|
|
9
|
+
import { defineService as registerService } from "@himalaya/state";
|
|
10
|
+
import { withExclusiveWrites } from "../_shared/exclusive-writes.js";
|
|
11
|
+
const defineService = withExclusiveWrites(registerService, ["submit"]);
|
|
10
12
|
import {
|
|
11
13
|
getProduct,
|
|
12
14
|
createProduct,
|
|
@@ -76,6 +78,7 @@ interface ProductFormState {
|
|
|
76
78
|
descriptionNote: string;
|
|
77
79
|
visible: string;
|
|
78
80
|
status: "idle" | "loading" | "saving" | "saved" | "error";
|
|
81
|
+
writeBusy: boolean;
|
|
79
82
|
error: string;
|
|
80
83
|
savedMessage: string;
|
|
81
84
|
/** WS-C — canonical detail target (`himi_owner_lite_product`); `savedId` rides
|
|
@@ -161,6 +164,7 @@ function shape(s: Partial<ProductFormState>): ProductFormState {
|
|
|
161
164
|
descriptionNote: dMode === "locked" ? copyParam("ownerProductForm.richDescriptionNote", "This description has formatting or media. Edit it in your site dashboard to keep that content intact; name, price and SKU can be changed here.") : "",
|
|
162
165
|
visible: s.visible ?? "true",
|
|
163
166
|
status: s.status ?? "idle",
|
|
167
|
+
writeBusy: s.writeBusy ?? false,
|
|
164
168
|
error: s.error ?? "",
|
|
165
169
|
savedMessage: s.savedMessage ?? "",
|
|
166
170
|
// WS-C — canonical detail target + the saved entity's id as a separate nav
|
|
@@ -76564,6 +76564,7 @@ const SCREENS = runtimeSetting({
|
|
|
76564
76564
|
"title": "Create post",
|
|
76565
76565
|
"style": "primary",
|
|
76566
76566
|
"fullWidth": true,
|
|
76567
|
+
"disabledPath": "$service:himiOwnerLitePostForm.writeBusy",
|
|
76567
76568
|
"action": {
|
|
76568
76569
|
"actionId": "tier5.himiOwnerLitePostForm.submit"
|
|
76569
76570
|
}
|
|
@@ -76922,6 +76923,7 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
76922
76923
|
"title": "Save changes",
|
|
76923
76924
|
"style": "primary",
|
|
76924
76925
|
"fullWidth": true,
|
|
76926
|
+
"disabledPath": "$service:himiOwnerLiteServiceForm.writeBusy",
|
|
76925
76927
|
"action": {
|
|
76926
76928
|
"actionId": "tier5.himiOwnerLiteServiceForm.submit"
|
|
76927
76929
|
}
|
|
@@ -77285,6 +77287,7 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
77285
77287
|
"title": "Save changes",
|
|
77286
77288
|
"style": "primary",
|
|
77287
77289
|
"fullWidth": true,
|
|
77290
|
+
"disabledPath": "$service:himiOwnerLiteEventForm.writeBusy",
|
|
77288
77291
|
"action": {
|
|
77289
77292
|
"actionId": "tier5.himiOwnerLiteEventForm.submit"
|
|
77290
77293
|
}
|
|
@@ -77608,6 +77611,7 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
77608
77611
|
"title": "Save changes",
|
|
77609
77612
|
"style": "primary",
|
|
77610
77613
|
"fullWidth": true,
|
|
77614
|
+
"disabledPath": "$service:himiOwnerLitePostForm.writeBusy",
|
|
77611
77615
|
"action": {
|
|
77612
77616
|
"actionId": "tier5.himiOwnerLitePostForm.submit"
|
|
77613
77617
|
}
|