@wix/himalaya-cli 0.1573.0 → 0.1575.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/core/ts/src/message-format.d.ts +59 -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/core.mjs +3 -3
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/l10n.mjs +8 -3
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/sdui.mjs +3 -3
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-bi.mjs +165 -5
- package/dist/cli.mjs +5261 -2937
- 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 +53 -2
- package/guides/himi-mcp.md +1 -1
- package/guides/himi-test.md +3 -2
- package/guides/iap-products.md +1 -1
- package/guides/importing-oneapp-translations.md +115 -0
- package/guides/index.json +6 -0
- package/guides/one-app-section-mappings.md +16 -4
- package/guides/one-app-to-himi-app.md +167 -10
- package/guides/push-cli-onboarding.md +10 -0
- package/guides/qa-builds.md +21 -1
- package/guides/templates.md +3 -2
- package/guides/testing-himi-apps.md +1 -1
- package/package.json +3 -3
- package/templates/beauty-salon/dev/index.ts +96 -133
- 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 +27 -6
- 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 +136 -142
- 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/dev/index.ts +22 -6
- package/templates/community-club-owner/dev/index.ts +27 -6
- 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/dev/index.ts +22 -6
- package/templates/events-meetups-owner/dev/index.ts +25 -6
- 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 +96 -133
- 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 +28 -6
- 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/himi-chat/dev/index.ts +22 -6
- package/templates/nonprofit-cause/dev/index.ts +22 -6
- package/templates/nonprofit-cause-owner/dev/index.ts +26 -6
- 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 +28 -6
- 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/dev/index.ts +22 -6
- package/templates/podcast-creator-owner/dev/index.ts +27 -6
- 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 +96 -133
- 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 +27 -6
- 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/dev/index.ts +22 -6
- package/templates/restaurant-owner/dev/index.ts +28 -6
- 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 +96 -133
- 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 +26 -6
- 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/surface-showcase/dev/index.ts +22 -6
- package/templates/yoga-studio/dev/index.ts +22 -6
- package/templates/yoga-studio-owner/dev/index.ts +26 -6
- 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
|
@@ -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
|
|
@@ -626,15 +626,31 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
626
626
|
|
|
627
627
|
// Live seed-id enumeration: a published release serves only EXACT screens, so
|
|
628
628
|
// `himi push` bakes each templated detail route's CURRENT catalog ids as exact
|
|
629
|
-
// screens via `seedIds`. Anonymous visitor rung against this app's own clientId
|
|
630
|
-
//
|
|
629
|
+
// screens via `seedIds`. Anonymous visitor rung against this app's own clientId — or,
|
|
630
|
+
// for an app bound through the One App visitor identity (`visitorSource: "one-app"`,
|
|
631
|
+
// no client), that site's One App visitor instance. Best-effort (offline build → no
|
|
632
|
+
// baked details, preview still live-resolves).
|
|
633
|
+
async function liveSeedToken(WIX: string): Promise<string | undefined> {
|
|
634
|
+
const auth = (config as { auth?: { config?: { clientId?: string; visitorSource?: string; metaSiteId?: string } } }).auth?.config;
|
|
635
|
+
// A One App binding boots as the site's One App visitor, so the seed reads as that visitor too —
|
|
636
|
+
// even beside a member-login clientId, and never repointed by a stray local WIX_CLIENT_ID.
|
|
637
|
+
const oneApp = auth?.visitorSource === "one-app";
|
|
638
|
+
const clientId = oneApp ? "" : process.env.WIX_CLIENT_ID ?? auth?.clientId ?? "";
|
|
639
|
+
if (clientId && !clientId.startsWith("YOUR_")) {
|
|
640
|
+
const mint = await fetch(WIX + "/oauth2/token", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ clientId, grantType: "anonymous" }) });
|
|
641
|
+
return ((await mint.json()) as { access_token?: string }).access_token;
|
|
642
|
+
}
|
|
643
|
+
const msid = oneApp ? auth?.metaSiteId ?? "" : "";
|
|
644
|
+
if (!/^[0-9a-fA-F-]{10,64}$/.test(msid)) return undefined;
|
|
645
|
+
const mint = await fetch(`https://apps.wix.com/_api/one-app-session-web/v3/businesses/${msid}/signed-instance?signed_instance_type=Visitor`, { headers: { Accept: "application/json" }, redirect: "error" });
|
|
646
|
+
const visitor = (await mint.json()) as { signedInstance?: string; signedInstanceType?: string };
|
|
647
|
+
return visitor.signedInstanceType === "Visitor" ? visitor.signedInstance : undefined;
|
|
648
|
+
}
|
|
649
|
+
|
|
631
650
|
async function liveSeedIds(spec: LiveSeed): Promise<string[]> {
|
|
632
651
|
try {
|
|
633
652
|
const WIX = "https://www.wixapis.com";
|
|
634
|
-
const
|
|
635
|
-
if (!clientId || clientId.startsWith("YOUR_")) return [];
|
|
636
|
-
const mint = await fetch(WIX + "/oauth2/token", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ clientId, grantType: "anonymous" }) });
|
|
637
|
-
const token = ((await mint.json()) as { access_token?: string }).access_token;
|
|
653
|
+
const token = await liveSeedToken(WIX);
|
|
638
654
|
if (!token) return [];
|
|
639
655
|
const res = await fetch(WIX + spec.path, { method: "POST", headers: { Authorization: token, "Content-Type": "application/json" }, body: JSON.stringify(spec.body) });
|
|
640
656
|
const json = (await res.json()) as Record<string, unknown>;
|
|
@@ -37400,15 +37400,31 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
37400
37400
|
|
|
37401
37401
|
// Live seed-id enumeration: a published release serves only EXACT screens, so
|
|
37402
37402
|
// `himi push` bakes each templated detail route's CURRENT catalog ids as exact
|
|
37403
|
-
// screens via `seedIds`. Anonymous visitor rung against this app's own clientId
|
|
37404
|
-
//
|
|
37403
|
+
// screens via `seedIds`. Anonymous visitor rung against this app's own clientId — or,
|
|
37404
|
+
// for an app bound through the One App visitor identity (`visitorSource: "one-app"`,
|
|
37405
|
+
// no client), that site's One App visitor instance. Best-effort (offline build → no
|
|
37406
|
+
// baked details, preview still live-resolves).
|
|
37407
|
+
async function liveSeedToken(WIX: string): Promise<string | undefined> {
|
|
37408
|
+
const auth = (config as { auth?: { config?: { clientId?: string; visitorSource?: string; metaSiteId?: string } } }).auth?.config;
|
|
37409
|
+
// A One App binding boots as the site's One App visitor, so the seed reads as that visitor too —
|
|
37410
|
+
// even beside a member-login clientId, and never repointed by a stray local WIX_CLIENT_ID.
|
|
37411
|
+
const oneApp = auth?.visitorSource === "one-app";
|
|
37412
|
+
const clientId = oneApp ? "" : process.env.WIX_CLIENT_ID ?? auth?.clientId ?? "";
|
|
37413
|
+
if (clientId && !clientId.startsWith("YOUR_")) {
|
|
37414
|
+
const mint = await fetch(WIX + "/oauth2/token", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ clientId, grantType: "anonymous" }) });
|
|
37415
|
+
return ((await mint.json()) as { access_token?: string }).access_token;
|
|
37416
|
+
}
|
|
37417
|
+
const msid = oneApp ? auth?.metaSiteId ?? "" : "";
|
|
37418
|
+
if (!/^[0-9a-fA-F-]{10,64}$/.test(msid)) return undefined;
|
|
37419
|
+
const mint = await fetch(`https://apps.wix.com/_api/one-app-session-web/v3/businesses/${msid}/signed-instance?signed_instance_type=Visitor`, { headers: { Accept: "application/json" }, redirect: "error" });
|
|
37420
|
+
const visitor = (await mint.json()) as { signedInstance?: string; signedInstanceType?: string };
|
|
37421
|
+
return visitor.signedInstanceType === "Visitor" ? visitor.signedInstance : undefined;
|
|
37422
|
+
}
|
|
37423
|
+
|
|
37405
37424
|
async function liveSeedIds(spec: LiveSeed): Promise<string[]> {
|
|
37406
37425
|
try {
|
|
37407
37426
|
const WIX = "https://www.wixapis.com";
|
|
37408
|
-
const
|
|
37409
|
-
if (!clientId || clientId.startsWith("YOUR_")) return [];
|
|
37410
|
-
const mint = await fetch(WIX + "/oauth2/token", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ clientId, grantType: "anonymous" }) });
|
|
37411
|
-
const token = ((await mint.json()) as { access_token?: string }).access_token;
|
|
37427
|
+
const token = await liveSeedToken(WIX);
|
|
37412
37428
|
if (!token) return [];
|
|
37413
37429
|
const res = await fetch(WIX + spec.path, { method: "POST", headers: { Authorization: token, "Content-Type": "application/json" }, body: JSON.stringify(spec.body) });
|
|
37414
37430
|
const json = (await res.json()) as Record<string, unknown>;
|
|
@@ -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
|
}
|
|
@@ -82114,15 +82118,31 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
82114
82118
|
|
|
82115
82119
|
// Live seed-id enumeration: a published release serves only EXACT screens, so
|
|
82116
82120
|
// `himi push` bakes each templated detail route's CURRENT catalog ids as exact
|
|
82117
|
-
// screens via `seedIds`. Anonymous visitor rung against this app's own clientId
|
|
82118
|
-
//
|
|
82121
|
+
// screens via `seedIds`. Anonymous visitor rung against this app's own clientId — or,
|
|
82122
|
+
// for an app bound through the One App visitor identity (`visitorSource: "one-app"`,
|
|
82123
|
+
// no client), that site's One App visitor instance. Best-effort (offline build → no
|
|
82124
|
+
// baked details, preview still live-resolves).
|
|
82125
|
+
async function liveSeedToken(WIX: string): Promise<string | undefined> {
|
|
82126
|
+
const auth = (config as { auth?: { config?: { clientId?: string; visitorSource?: string; metaSiteId?: string } } }).auth?.config;
|
|
82127
|
+
// A One App binding boots as the site's One App visitor, so the seed reads as that visitor too —
|
|
82128
|
+
// even beside a member-login clientId, and never repointed by a stray local WIX_CLIENT_ID.
|
|
82129
|
+
const oneApp = auth?.visitorSource === "one-app";
|
|
82130
|
+
const clientId = oneApp ? "" : process.env.WIX_CLIENT_ID ?? auth?.clientId ?? "";
|
|
82131
|
+
if (clientId && !clientId.startsWith("YOUR_")) {
|
|
82132
|
+
const mint = await fetch(WIX + "/oauth2/token", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ clientId, grantType: "anonymous" }) });
|
|
82133
|
+
return ((await mint.json()) as { access_token?: string }).access_token;
|
|
82134
|
+
}
|
|
82135
|
+
const msid = oneApp ? auth?.metaSiteId ?? "" : "";
|
|
82136
|
+
if (!/^[0-9a-fA-F-]{10,64}$/.test(msid)) return undefined;
|
|
82137
|
+
const mint = await fetch(`https://apps.wix.com/_api/one-app-session-web/v3/businesses/${msid}/signed-instance?signed_instance_type=Visitor`, { headers: { Accept: "application/json" }, redirect: "error" });
|
|
82138
|
+
const visitor = (await mint.json()) as { signedInstance?: string; signedInstanceType?: string };
|
|
82139
|
+
return visitor.signedInstanceType === "Visitor" ? visitor.signedInstance : undefined;
|
|
82140
|
+
}
|
|
82141
|
+
|
|
82119
82142
|
async function liveSeedIds(spec: LiveSeed): Promise<string[]> {
|
|
82120
82143
|
try {
|
|
82121
82144
|
const WIX = "https://www.wixapis.com";
|
|
82122
|
-
const
|
|
82123
|
-
if (!clientId || clientId.startsWith("YOUR_")) return [];
|
|
82124
|
-
const mint = await fetch(WIX + "/oauth2/token", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ clientId, grantType: "anonymous" }) });
|
|
82125
|
-
const token = ((await mint.json()) as { access_token?: string }).access_token;
|
|
82145
|
+
const token = await liveSeedToken(WIX);
|
|
82126
82146
|
if (!token) return [];
|
|
82127
82147
|
const res = await fetch(WIX + spec.path, { method: "POST", headers: { Authorization: token, "Content-Type": "application/json" }, body: JSON.stringify(spec.body) });
|
|
82128
82148
|
const json = (await res.json()) as Record<string, unknown>;
|
|
@@ -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
|
+
}
|
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
// form can't safely collect — so this form only edits an existing event's text
|
|
7
7
|
// (title + description). Bound by buildForm ($service:himiOwnerLiteEventForm.*).
|
|
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 { getEvent, updateEvent, type WixEvent } from "@himalaya/wix-events";
|
|
11
13
|
import { normalizeEvent } from "../_shared/himi-events.js";
|
|
12
14
|
import { postBodyText, plainPostBody } from "../_shared/owner-post-content.js";
|
|
@@ -66,6 +68,7 @@ interface EventFormState {
|
|
|
66
68
|
when: string;
|
|
67
69
|
location: string;
|
|
68
70
|
status: "idle" | "loading" | "saving" | "saved" | "error";
|
|
71
|
+
writeBusy: boolean;
|
|
69
72
|
error: string;
|
|
70
73
|
savedMessage: string;
|
|
71
74
|
/** WS-C — canonical detail target (`himi_owner_lite_event`); `savedId` rides
|
|
@@ -140,6 +143,7 @@ function shape(s: Partial<EventFormState>): EventFormState {
|
|
|
140
143
|
when: s.when ?? "—",
|
|
141
144
|
location: s.location ?? "—",
|
|
142
145
|
status: s.status ?? "idle",
|
|
146
|
+
writeBusy: s.writeBusy ?? false,
|
|
143
147
|
error: s.error ?? "",
|
|
144
148
|
savedMessage: s.savedMessage ?? "",
|
|
145
149
|
// WS-C — canonical detail target + the saved entity's id as a separate nav
|
|
@@ -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
|
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
// updateService. Bound by buildForm ($service:himiOwnerLiteServiceForm.*).
|
|
6
6
|
|
|
7
7
|
import { landsOnLive } from "../_shared/owner-editor-value.js";
|
|
8
|
-
import { defineService } from "@himalaya/state";
|
|
8
|
+
import { defineService as registerService } from "@himalaya/state";
|
|
9
|
+
import { withExclusiveWrites } from "../_shared/exclusive-writes.js";
|
|
10
|
+
const defineService = withExclusiveWrites(registerService, ["submit"]);
|
|
9
11
|
import { getService, updateService, type WixService } from "@himalaya/wix-bookings";
|
|
10
12
|
import { ownerLoadError, isOwnerPreviewDemo, ownerDemoFallback, ownerWriteError } from "../_shared/owner-load-error.js";
|
|
11
13
|
import { DEMO_SERVICES, DEMO_NOTE, DEMO_SAVE_NOTE, demoRefusalPatch } from "../_shared/owner-preview-demo.js";
|
|
@@ -31,6 +33,7 @@ interface ServiceFormState {
|
|
|
31
33
|
description: string;
|
|
32
34
|
category: string;
|
|
33
35
|
status: "idle" | "loading" | "saving" | "saved" | "error";
|
|
36
|
+
writeBusy: boolean;
|
|
34
37
|
error: string;
|
|
35
38
|
savedMessage: string;
|
|
36
39
|
/** WS-C — canonical detail target (`himi_owner_lite_service`); `savedId` rides
|
|
@@ -88,6 +91,7 @@ function shape(s: Partial<ServiceFormState>): ServiceFormState {
|
|
|
88
91
|
description: s.description ?? "",
|
|
89
92
|
category: s.category ?? "",
|
|
90
93
|
status: s.status ?? "idle",
|
|
94
|
+
writeBusy: s.writeBusy ?? false,
|
|
91
95
|
error: s.error ?? "",
|
|
92
96
|
savedMessage: s.savedMessage ?? "",
|
|
93
97
|
// WS-C — canonical detail target + the saved entity's id as a separate nav
|