@wix/himalaya-cli 0.1321.0 → 0.1322.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (21) hide show
  1. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/webview/src/index.d.ts +4 -0
  2. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/webview-page/src/index.d.ts +16 -1
  3. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/stores-v1/src/display.d.ts +21 -0
  4. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/webview-page.mjs +78 -0
  5. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/webview.mjs +16 -0
  6. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-stores.mjs +13 -4
  7. package/dist/cli.mjs +25 -0
  8. package/guides/sdk-reference.json +33 -3
  9. package/guides/web-page-native-sdk.md +154 -33
  10. package/guides/webview-page-sdk.md +11 -0
  11. package/package.json +2 -2
  12. package/templates/beauty-salon/dev/index.ts +492 -0
  13. package/templates/beauty-salon/tier5-src/wix-spaces-collection-detail/index.ts +146 -30
  14. package/templates/branded-lite/dev/index.ts +492 -0
  15. package/templates/branded-lite/tier5-src/wix-spaces-collection-detail/index.ts +146 -30
  16. package/templates/gym-fitness/dev/index.ts +492 -0
  17. package/templates/gym-fitness/tier5-src/wix-spaces-collection-detail/index.ts +146 -30
  18. package/templates/portfolio-creative/dev/index.ts +492 -0
  19. package/templates/portfolio-creative/tier5-src/wix-spaces-collection-detail/index.ts +146 -30
  20. package/templates/retail-store/dev/index.ts +492 -0
  21. package/templates/retail-store/tier5-src/wix-spaces-collection-detail/index.ts +146 -30
@@ -28,6 +28,10 @@ export interface PageCapabilityResponse {
28
28
  code?: string;
29
29
  };
30
30
  }
31
+ /** `call("himalaya", "capabilities")` answers `{ v: 1, capabilities: { <id>: [<method>…] } }`:
32
+ * exactly the allowlist this worker was given. Reserved, so no real capability can take it. */
33
+ export declare const PAGE_HANDSHAKE_CAPABILITY = "himalaya";
34
+ export declare const PAGE_HANDSHAKE_METHOD = "capabilities";
31
35
  export interface ExposedPageCapability {
32
36
  module: Record<string, unknown>;
33
37
  methods: readonly string[];
@@ -18,8 +18,23 @@ export interface WebViewPageResponse {
18
18
  result?: unknown;
19
19
  error?: WebViewPageError;
20
20
  }
21
- /** Invoke one method through the companion worker. Primarily useful to generated bindings. */
21
+ /** Invoke one method through the companion worker. Primarily useful to generated bindings.
22
+ * A call that shows native UI (a permission prompt, the share sheet) answers only when the user
23
+ * is done with it: pass a long `timeoutMs` (e.g. 120_000) rather than the 30-second default. */
22
24
  export declare function call<T>(capability: string, method: string, args?: unknown[], timeoutMs?: number): Promise<T>;
25
+ /**
26
+ * The capabilities and methods this page's companion worker answers, as `{ <id>: [<method>…] }`;
27
+ * `null` outside the app, when the app is older than the handshake, or nothing answered in
28
+ * `timeoutMs`. A list is remembered (and returned at once, whatever `timeoutMs`); a miss is not,
29
+ * because a worker that is still booting drops the first messages.
30
+ */
31
+ export declare function enabledCapabilities(timeoutMs?: number): Promise<Record<string, string[]> | null>;
32
+ /** Whether the companion worker lets this page call `capability.method`, read from
33
+ * enabledCapabilities() — nothing is called. `false` when there is no list. */
34
+ export declare function isEnabled(capability: string, method: string, timeoutMs?: number): Promise<boolean>;
35
+ /** For an app older than the handshake: CALL `capability.method` and report whether it answered
36
+ * (even with an error). It runs the method, so pass only a read-only one. */
37
+ export declare function probe(capability: string, method: string, args?: unknown[], timeoutMs?: number): Promise<boolean>;
23
38
  /**
24
39
  * Typed SDK proxy for every request/response capability in CAPABILITY_REGISTRY.
25
40
  * A namespace is usable only when this screen's companion worker explicitly imported and
@@ -106,11 +106,32 @@ export interface StoreProductSearch {
106
106
  query?: string;
107
107
  sort?: 'default' | 'az' | 'za';
108
108
  ids?: string[];
109
+ /**
110
+ * Only products in this category (V3) / collection (V1) — a category screen's
111
+ * "products in X". ANDed with `ids` when both are given.
112
+ *
113
+ * V3 matches `allCategoriesInfo.categories`, so a parent category also lists its
114
+ * subcategories' products, as the site does. Only Search Products can filter by
115
+ * category — Query Products answers `not declared as filterable` — so a
116
+ * `categoryId` always routes the V3 read to `/products/search`. V1 matches
117
+ * `collections.id`.
118
+ */
119
+ categoryId?: string;
109
120
  limit?: number;
110
121
  cursor?: string;
111
122
  /** Extra v3 fieldsets. Additive — widens the default, never replaces it. */
112
123
  fields?: readonly string[];
113
124
  }
125
+ /**
126
+ * Search, sort, and filter products on whichever catalog this site runs — the read
127
+ * behind a shop list, a saved-products list, and a category screen.
128
+ *
129
+ * `categoryId` narrows to one category's products (V3 `allCategoriesInfo`, so a parent
130
+ * category includes its subcategories; V1 `collections.id`), ANDed with `ids`. An
131
+ * empty `ids` array resolves to an empty page WITHOUT a request. Paging is one
132
+ * contract on both catalogs: pass back `pagingMetadata.cursors.next` as `cursor`.
133
+ * A non-catalog failure (401/403, offline) rejects; it is never retried on V1.
134
+ */
114
135
  export declare function searchStoreProducts(options?: StoreProductSearch): Promise<StoreProductsResponse>;
115
136
  /** Read one product from whichever catalog this site runs. `fields` is additive —
116
137
  * it WIDENS the default v3 display fieldset rather than replacing it. */
@@ -51,6 +51,81 @@ function call(capability, method, args = [], timeoutMs = 3e4) {
51
51
  }
52
52
  });
53
53
  }
54
+ var manifest;
55
+ function currentBridge() {
56
+ try {
57
+ return workerBridge();
58
+ } catch {
59
+ return void 0;
60
+ }
61
+ }
62
+ function timedOut(error) {
63
+ return error instanceof Error && error.message.startsWith("Himalaya capability call timed out");
64
+ }
65
+ var asking;
66
+ function askShared(target, budget) {
67
+ const until = Date.now() + budget;
68
+ if (asking?.on === target) {
69
+ asking.until = Math.max(asking.until, until);
70
+ return asking.answer;
71
+ }
72
+ const state = { on: target, until, answer: Promise.resolve(null) };
73
+ asking = state;
74
+ state.answer = new Promise((resolve) => {
75
+ let done = false;
76
+ const finish = (list) => {
77
+ if (done) return;
78
+ done = true;
79
+ clearInterval(again);
80
+ if (asking === state) asking = void 0;
81
+ if (list) manifest = { on: target, list };
82
+ resolve(list);
83
+ };
84
+ const ask = () => {
85
+ if (done) return;
86
+ const left = state.until - Date.now();
87
+ if (left <= 0 || currentBridge() !== target) return finish(null);
88
+ call("himalaya", "capabilities", [], Math.max(1, Math.min(2e3, left))).then(
89
+ (reply) => finish(reply?.capabilities ?? null),
90
+ // A timed-out question is not an answer; the deadline decides. Anything else is final.
91
+ (error) => {
92
+ if (!timedOut(error)) finish(null);
93
+ }
94
+ );
95
+ };
96
+ const again = setInterval(ask, 500);
97
+ ask();
98
+ });
99
+ return state.answer;
100
+ }
101
+ async function enabledCapabilities(timeoutMs = 3e3) {
102
+ const bridge = currentBridge();
103
+ if (!bridge) return null;
104
+ if (manifest?.on === bridge) return manifest.list;
105
+ if (!(timeoutMs > 0)) return null;
106
+ const budget = Math.min(timeoutMs, 2147483647);
107
+ const answer = askShared(bridge, budget);
108
+ return new Promise((resolve) => {
109
+ const stop = setTimeout(() => resolve(manifest?.on === bridge ? manifest.list : null), budget);
110
+ void answer.then((list) => {
111
+ clearTimeout(stop);
112
+ resolve(list);
113
+ });
114
+ });
115
+ }
116
+ async function isEnabled(capability, method, timeoutMs = 3e3) {
117
+ const enabled = await enabledCapabilities(timeoutMs);
118
+ return enabled?.[capability]?.includes(method) ?? false;
119
+ }
120
+ async function probe(capability, method, args = [], timeoutMs = 3e3) {
121
+ if (!currentBridge()) return false;
122
+ try {
123
+ await call(capability, method, args, timeoutMs);
124
+ return true;
125
+ } catch (error) {
126
+ return !(timedOut(error) || /refused the request/.test(error.message));
127
+ }
128
+ }
54
129
  var members = /* @__PURE__ */ new Map();
55
130
  function pageMember(capability, path) {
56
131
  const key = `${capability}:${path.join(".")}`;
@@ -81,8 +156,11 @@ var health = himalaya.health;
81
156
  var version = 1;
82
157
  export {
83
158
  call,
159
+ enabledCapabilities,
84
160
  health,
85
161
  himalaya,
162
+ isEnabled,
163
+ probe,
86
164
  version
87
165
  };
88
166
  //# sourceMappingURL=webview-page.mjs.map
@@ -14,6 +14,8 @@ function postToPage(msg) {
14
14
  function onPageMessage(cb) {
15
15
  runtime().onPageMessage(cb);
16
16
  }
17
+ var PAGE_HANDSHAKE_CAPABILITY = "himalaya";
18
+ var PAGE_HANDSHAKE_METHOD = "capabilities";
17
19
  function pageRequest(value) {
18
20
  if (!value || typeof value !== "object") return null;
19
21
  const request = value;
@@ -29,15 +31,27 @@ function pageError(value) {
29
31
  };
30
32
  }
31
33
  function exposeCapabilitiesToPage(capabilities) {
34
+ if (Object.prototype.hasOwnProperty.call(capabilities, PAGE_HANDSHAKE_CAPABILITY)) {
35
+ throw new Error(`"${PAGE_HANDSHAKE_CAPABILITY}" is reserved for the page handshake and cannot be exposed`);
36
+ }
32
37
  const allowed = new Map(
33
38
  Object.entries(capabilities).map(([id, entry]) => [id, {
34
39
  module: entry.module,
35
40
  methods: new Set(entry.methods)
36
41
  }])
37
42
  );
43
+ const manifest = Object.fromEntries(
44
+ [...allowed].map(([id, entry]) => [id, [...entry.methods].sort()])
45
+ );
38
46
  onPageMessage((message) => {
39
47
  const request = pageRequest(message);
40
48
  if (!request) return;
49
+ if (request.capability === PAGE_HANDSHAKE_CAPABILITY) {
50
+ if (request.method === PAGE_HANDSHAKE_METHOD) {
51
+ postToPage({ v: 1, id: request.id, ok: true, result: { v: 1, capabilities: manifest } });
52
+ }
53
+ return;
54
+ }
41
55
  const capability = allowed.get(request.capability);
42
56
  let candidate = capability?.module;
43
57
  let receiver = void 0;
@@ -58,6 +72,8 @@ function exposeCapabilitiesToPage(capabilities) {
58
72
  }
59
73
  var version = 1;
60
74
  export {
75
+ PAGE_HANDSHAKE_CAPABILITY,
76
+ PAGE_HANDSHAKE_METHOD,
61
77
  exposeCapabilitiesToPage,
62
78
  onPageMessage,
63
79
  postToPage,
@@ -369,21 +369,30 @@ async function searchStoreProducts(options = {}) {
369
369
  const limit = Math.max(1, Math.min(100, Math.floor(options.limit ?? 50) || 50));
370
370
  const ids = options.ids === void 0 ? void 0 : [...new Set(options.ids.filter((id) => typeof id === "string" && !!id))].slice(0, 500);
371
371
  if (ids && !ids.length) return { products: [], pagingMetadata: { total: 0, count: 0, cursors: { next: null }, hasNext: false } };
372
+ const categoryId = typeof options.categoryId === "string" ? options.categoryId.trim() : "";
372
373
  const ascending = options.sort === "az", sorted = ascending || options.sort === "za";
373
374
  return readWithCatalogFallback((version) => {
374
375
  if (version === "v1") {
375
376
  const offset = v1Offset(options.cursor);
376
- const filter = { ...query ? { name: { $contains: query } } : {}, ...ids ? { id: { $hasSome: ids } } : {} };
377
+ const filter2 = {
378
+ ...query ? { name: { $contains: query } } : {},
379
+ ...ids ? { id: { $hasSome: ids } } : {},
380
+ ...categoryId ? { "collections.id": { $hasSome: [categoryId] } } : {}
381
+ };
377
382
  return wixCall("POST", "/stores/v1/products/query", { query: {
378
383
  paging: { limit, offset },
379
- ...Object.keys(filter).length ? { filter: JSON.stringify(filter) } : {},
384
+ ...Object.keys(filter2).length ? { filter: JSON.stringify(filter2) } : {},
380
385
  ...sorted ? { sort: JSON.stringify([{ name: ascending ? "asc" : "desc" }]) } : {}
381
386
  } }).then((res) => fromV1(res, offset));
382
387
  }
383
- const search = !!query || sorted;
388
+ const filter = {
389
+ ...ids ? { id: { $in: ids } } : {},
390
+ ...categoryId ? { "allCategoriesInfo.categories": { $matchItems: [{ id: categoryId }] } } : {}
391
+ };
392
+ const search = !!query || sorted || !!categoryId;
384
393
  const criteria = {
385
394
  cursorPaging: { limit, ...options.cursor ? { cursor: options.cursor } : {} },
386
- ...ids ? { filter: { id: { $in: ids } } } : {},
395
+ ...Object.keys(filter).length ? { filter } : {},
387
396
  ...query ? { search: { expression: query, fields: ["name"] } } : {},
388
397
  ...sorted ? { sort: [{ fieldName: "name", order: ascending ? "ASC" : "DESC" }] } : {}
389
398
  };
package/dist/cli.mjs CHANGED
@@ -149133,6 +149133,7 @@ __export(nav_lint_exports, {
149133
149133
  NAV_KINDS: () => NAV_KINDS,
149134
149134
  SERVED_ONLY_NAV_KEYS: () => SERVED_ONLY_NAV_KEYS,
149135
149135
  navIssues: () => navIssues,
149136
+ navSharedIdErrors: () => navSharedIdErrors,
149136
149137
  pushRouteAdvisories: () => pushRouteAdvisories
149137
149138
  });
149138
149139
  async function navIssues(dir, strict) {
@@ -149209,6 +149210,7 @@ async function navIssues(dir, strict) {
149209
149210
  for (const message of navConfigPathErrors({ ...nav, homeScreenId: servedHome })) {
149210
149211
  error("nav-path-malformed", `navigation.${message}`);
149211
149212
  }
149213
+ for (const message of navSharedIdErrors(nav)) error("nav-row-id-shadows-tab", `navigation.${message}`);
149212
149214
  const navKind = nav.navKind;
149213
149215
  const effectiveBottomTabs = navKind === void 0 || navKind === "bottomTabs" || typeof navKind !== "string" || !NAV_KINDS.has(navKind);
149214
149216
  const declaresOtherChrome = itemCount(nav.drawer) > 0 || itemCount(nav.sidebar) > 0;
@@ -149316,6 +149318,29 @@ function unmappedAdvisory({ destination, vertical, reason, screenId, paramKey })
149316
149318
  }
149317
149319
  return `push-route-unmapped: the site has ${vertical}, but no screen opens "${destination}" notifications. Name that screen "${canonical4}", or map it: push.screens: { "${destination}": "<screenId>" }.`;
149318
149320
  }
149321
+ function navSharedIdErrors(nav) {
149322
+ const items = (key2) => {
149323
+ const block = nav[key2];
149324
+ const list = isPlainObject2(block) ? block.items : void 0;
149325
+ return Array.isArray(list) ? list.flatMap((x2, i2) => isPlainObject2(x2) ? [[x2, i2]] : []) : [];
149326
+ };
149327
+ const tabScreens = /* @__PURE__ */ new Map();
149328
+ for (const [tab] of items("tabBar")) if (typeof tab.id === "string" && !tabScreens.has(tab.id)) tabScreens.set(tab.id, tab.screenId);
149329
+ const out2 = [];
149330
+ for (const key2 of ["drawer", "sidebar"]) {
149331
+ for (const [row, i2] of items(key2)) {
149332
+ if (typeof row.id !== "string" || !tabScreens.has(row.id)) continue;
149333
+ const tabScreen = tabScreens.get(row.id);
149334
+ if (typeof tabScreen !== "string") continue;
149335
+ if (key2 === "drawer" && (row.role === "section" || row.role === "note")) continue;
149336
+ if (typeof row.screenId !== "string" || row.screenId === "" || row.screenId === tabScreen) continue;
149337
+ out2.push(
149338
+ `${key2}.items[${i2}] "${row.id}": a tab has this id but opens ${JSON.stringify(tabScreen)}, not "${row.screenId}" \u2014 every shell hands its host one id and the host resolves tabs first, so this row would open the tab's screen. Give the row its own id.`
149339
+ );
149340
+ }
149341
+ }
149342
+ return out2;
149343
+ }
149319
149344
  var NAVIGATION_KEYS, NAV_KINDS, ITEMS_BLOCKS, SERVED_ONLY_NAV_KEYS, MISPLACED_TOP_LEVEL_KEYS, isPlainObject2, itemCount;
149320
149345
  var init_nav_lint = __esm({
149321
149346
  "src/nav-lint.ts"() {
@@ -4188,6 +4188,18 @@
4188
4188
  "jsdoc": "",
4189
4189
  "decl": "export interface PageCapabilityResponse {\n v: 1;\n id: string;\n ok: boolean;\n result?: unknown;\n error?: { message: string; code?: string };\n}"
4190
4190
  },
4191
+ {
4192
+ "name": "PAGE_HANDSHAKE_CAPABILITY",
4193
+ "kind": "const",
4194
+ "jsdoc": "/** `call(\"himalaya\", \"capabilities\")` answers `{ v: 1, capabilities: { <id>: [<method>…] } }`:\n * exactly the allowlist this worker was given. Reserved, so no real capability can take it. */",
4195
+ "decl": "export const PAGE_HANDSHAKE_CAPABILITY = \"himalaya\";"
4196
+ },
4197
+ {
4198
+ "name": "PAGE_HANDSHAKE_METHOD",
4199
+ "kind": "const",
4200
+ "jsdoc": "",
4201
+ "decl": "export const PAGE_HANDSHAKE_METHOD = \"capabilities\";"
4202
+ },
4191
4203
  {
4192
4204
  "name": "ExposedPageCapability",
4193
4205
  "kind": "interface",
@@ -4240,9 +4252,27 @@
4240
4252
  {
4241
4253
  "name": "call",
4242
4254
  "kind": "function",
4243
- "jsdoc": "/** Invoke one method through the companion worker. Primarily useful to generated bindings. */",
4255
+ "jsdoc": "/** Invoke one method through the companion worker. Primarily useful to generated bindings.\n * A call that shows native UI (a permission prompt, the share sheet) answers only when the user\n * is done with it: pass a long `timeoutMs` (e.g. 120_000) rather than the 30-second default. */",
4244
4256
  "decl": "export function call<T>(\n capability: string,\n method: string,\n args: unknown[] = [],\n timeoutMs = 30_000,\n): Promise<T>;"
4245
4257
  },
4258
+ {
4259
+ "name": "enabledCapabilities",
4260
+ "kind": "function",
4261
+ "jsdoc": "/**\n * The capabilities and methods this page's companion worker answers, as `{ <id>: [<method>…] }`;\n * `null` outside the app, when the app is older than the handshake, or nothing answered in\n * `timeoutMs`. A list is remembered (and returned at once, whatever `timeoutMs`); a miss is not,\n * because a worker that is still booting drops the first messages.\n */",
4262
+ "decl": "export async function enabledCapabilities(timeoutMs = 3_000): Promise<Record<string, string[]> | null>;"
4263
+ },
4264
+ {
4265
+ "name": "isEnabled",
4266
+ "kind": "function",
4267
+ "jsdoc": "/** Whether the companion worker lets this page call `capability.method`, read from\n * enabledCapabilities() — nothing is called. `false` when there is no list. */",
4268
+ "decl": "export async function isEnabled(capability: string, method: string, timeoutMs = 3_000): Promise<boolean>;"
4269
+ },
4270
+ {
4271
+ "name": "probe",
4272
+ "kind": "function",
4273
+ "jsdoc": "/** For an app older than the handshake: CALL `capability.method` and report whether it answered\n * (even with an error). It runs the method, so pass only a read-only one. */",
4274
+ "decl": "export async function probe(capability: string, method: string, args: unknown[] = [], timeoutMs = 3_000): Promise<boolean>;"
4275
+ },
4246
4276
  {
4247
4277
  "name": "himalaya",
4248
4278
  "kind": "const",
@@ -23108,12 +23138,12 @@
23108
23138
  "name": "StoreProductSearch",
23109
23139
  "kind": "interface",
23110
23140
  "jsdoc": "/** User-facing product discovery. V3 name search/sorting belong to Search\n * Products; Query Products deliberately does not support the name field.\n * V1 uses its documented JSON-string filter/sort dialect. */",
23111
- "decl": "export interface StoreProductSearch {\n query?: string;\n sort?: 'default' | 'az' | 'za';\n ids?: string[];\n limit?: number;\n cursor?: string;\n /** Extra v3 fieldsets. Additive — widens the default, never replaces it. */\n fields?: readonly string[];\n}"
23141
+ "decl": "export interface StoreProductSearch {\n query?: string;\n sort?: 'default' | 'az' | 'za';\n ids?: string[];\n /**\n * Only products in this category (V3) / collection (V1) — a category screen's\n * \"products in X\". ANDed with `ids` when both are given.\n *\n * V3 matches `allCategoriesInfo.categories`, so a parent category also lists its\n * subcategories' products, as the site does. Only Search Products can filter by\n * category — Query Products answers `not declared as filterable` — so a\n * `categoryId` always routes the V3 read to `/products/search`. V1 matches\n * `collections.id`.\n */\n categoryId?: string;\n limit?: number;\n cursor?: string;\n /** Extra v3 fieldsets. Additive — widens the default, never replaces it. */\n fields?: readonly string[];\n}"
23112
23142
  },
23113
23143
  {
23114
23144
  "name": "searchStoreProducts",
23115
23145
  "kind": "function",
23116
- "jsdoc": "",
23146
+ "jsdoc": "/**\n * Search, sort, and filter products on whichever catalog this site runs — the read\n * behind a shop list, a saved-products list, and a category screen.\n *\n * `categoryId` narrows to one category's products (V3 `allCategoriesInfo`, so a parent\n * category includes its subcategories; V1 `collections.id`), ANDed with `ids`. An\n * empty `ids` array resolves to an empty page WITHOUT a request. Paging is one\n * contract on both catalogs: pass back `pagingMetadata.cursors.next` as `cursor`.\n * A non-catalog failure (401/403, offline) rejects; it is never retried on V1.\n */",
23117
23147
  "decl": "export async function searchStoreProducts(options: StoreProductSearch = {}): Promise<StoreProductsResponse>;",
23118
23148
  "http": "POST /stores/v1/products/query"
23119
23149
  },