@wix/himalaya-cli 0.877.0 → 0.879.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.
@@ -37,7 +37,18 @@ export interface WixGetPostRequest {
37
37
  * filter-by-id — a graceful degrade to the LIST projection (no body) that keeps
38
38
  * blog detail + saved screens rendering instead of erroring out. `queryPosts`'s
39
39
  * request type has no `fieldsets` field, so that fallback path cannot carry the
40
- * body. */
40
+ * body.
41
+ *
42
+ * When that fallback ALSO finds nothing (#2658) — a deleted post, or one this
43
+ * visitor may not see — we reject rather than resolve. `WixGetPostResponse.post`
44
+ * is non-optional, so returning `{ post: resp.posts?.[0] }` would hand callers an
45
+ * `undefined` the type promises is present, and the `post.title` access that type
46
+ * licenses throws a `TypeError` deep inside the caller — the exact hard error
47
+ * this fallback exists to prevent, and harder to diagnose than a clean rejection.
48
+ * Every call site rides its own rejection path (cached-post state, draft-only
49
+ * `catch`, or an `allSettled` filter), so rejecting is what routes them into the
50
+ * graceful degrade they already implement. The originating failure travels as
51
+ * `cause`, so the 404-vs-network distinction is not lost. */
41
52
  export declare function getPost(postId: string, req?: WixGetPostRequest): Promise<WixGetPostResponse>;
42
53
  export interface WixBlogCategory {
43
54
  id: string;
@@ -12,9 +12,13 @@ function getPost(postId, req = {}) {
12
12
  return wixCall(
13
13
  "GET",
14
14
  `/blog/v3/posts/${encodeURIComponent(postId)}` + query
15
- ).catch(async () => {
15
+ ).catch(async (cause) => {
16
16
  const resp = await queryPosts({ query: { filter: { id: postId } } });
17
- return { post: resp.posts?.[0] };
17
+ const post = resp.posts?.[0];
18
+ if (!post) {
19
+ throw new Error(`Blog post not found: ${postId}`, { cause });
20
+ }
21
+ return { post };
18
22
  });
19
23
  }
20
24
  function queryCategories(req = {}) {
package/dist/cli.mjs CHANGED
@@ -37687,6 +37687,8 @@ var init_kits = __esm({
37687
37687
  // ../../core/dev-server/src/widgets.ts
37688
37688
  var widgets_exports = {};
37689
37689
  __export(widgets_exports, {
37690
+ MAX_PRODUCE_CACHE_ENTRIES: () => MAX_PRODUCE_CACHE_ENTRIES,
37691
+ PRODUCE_CACHE_TTL_MS: () => PRODUCE_CACHE_TTL_MS,
37690
37692
  WIDGET_BUDGET_MS: () => WIDGET_BUDGET_MS,
37691
37693
  buildViewSnapshot: () => buildViewSnapshot,
37692
37694
  buildWidgetSnapshot: () => buildWidgetSnapshot,
@@ -37694,6 +37696,7 @@ __export(widgets_exports, {
37694
37696
  extractWidgets: () => extractWidgets,
37695
37697
  freezeOverrideSnapshot: () => freezeOverrideSnapshot,
37696
37698
  frozenViewManifestEntries: () => frozenViewManifestEntries,
37699
+ resetWidgetProduceCache: () => resetWidgetProduceCache,
37697
37700
  resetWidgetRealm: () => resetWidgetRealm,
37698
37701
  viewWidgetId: () => viewWidgetId,
37699
37702
  widgetRealm: () => widgetRealm
@@ -37722,6 +37725,84 @@ async function widgetRealm() {
37722
37725
  function resetWidgetRealm() {
37723
37726
  realmPromise = null;
37724
37727
  }
37728
+ function guestClock(clock) {
37729
+ return clock === void 0 ? void 0 : Math.trunc(clock);
37730
+ }
37731
+ function serializedServices(produced) {
37732
+ try {
37733
+ return JSON.stringify(produced.services) ?? void 0;
37734
+ } catch {
37735
+ return void 0;
37736
+ }
37737
+ }
37738
+ function resetWidgetProduceCache() {
37739
+ produceCacheGeneration++;
37740
+ }
37741
+ function realmProduceCache(evaluator) {
37742
+ const existing = produceCache.get(evaluator);
37743
+ if (!existing) {
37744
+ const fresh = { generation: produceCacheGeneration, entries: /* @__PURE__ */ new Map(), bytes: 0 };
37745
+ produceCache.set(evaluator, fresh);
37746
+ return fresh;
37747
+ }
37748
+ if (existing.generation !== produceCacheGeneration) {
37749
+ existing.entries.clear();
37750
+ existing.bytes = 0;
37751
+ existing.generation = produceCacheGeneration;
37752
+ }
37753
+ return existing;
37754
+ }
37755
+ function dropEntry(cache, key2) {
37756
+ const entry = cache.entries.get(key2);
37757
+ if (!entry) return;
37758
+ cache.entries.delete(key2);
37759
+ cache.bytes -= entry.bytes;
37760
+ }
37761
+ function produceCacheKey(bundleJs, timeoutMs, clock) {
37762
+ const digest2 = createHash14("sha256").update(bundleJs).digest("base64");
37763
+ return `${timeoutMs}|${clock === void 0 ? "live" : Math.trunc(clock)}|${digest2}`;
37764
+ }
37765
+ function cachedProduce(evaluator, key2) {
37766
+ const cache = realmProduceCache(evaluator);
37767
+ const hit = cache.entries.get(key2);
37768
+ if (!hit) return void 0;
37769
+ if (Date.now() >= hit.expiresAt) {
37770
+ dropEntry(cache, key2);
37771
+ return void 0;
37772
+ }
37773
+ cache.entries.delete(key2);
37774
+ cache.entries.set(key2, hit);
37775
+ return { services: JSON.parse(hit.servicesJson), envelope: hit.envelope };
37776
+ }
37777
+ function rememberProduce(evaluator, key2, servicesJson, envelope, expiresAt) {
37778
+ const bytes2 = envelope.length + servicesJson.length;
37779
+ if (bytes2 > MAX_PRODUCE_ENTRY_BYTES) return;
37780
+ const cache = realmProduceCache(evaluator);
37781
+ dropEntry(cache, key2);
37782
+ cache.entries.set(key2, { servicesJson, envelope, expiresAt, bytes: bytes2 });
37783
+ cache.bytes += bytes2;
37784
+ const over = () => cache.entries.size > MAX_PRODUCE_CACHE_ENTRIES || cache.bytes > MAX_PRODUCE_CACHE_BYTES;
37785
+ if (!over()) return;
37786
+ const now = Date.now();
37787
+ for (const [k, entry] of [...cache.entries]) {
37788
+ if (!over()) break;
37789
+ if (k !== key2 && now >= entry.expiresAt) dropEntry(cache, k);
37790
+ }
37791
+ while (over()) {
37792
+ const oldest = cache.entries.keys().next();
37793
+ if (oldest.done || oldest.value === key2) break;
37794
+ dropEntry(cache, oldest.value);
37795
+ }
37796
+ }
37797
+ function reuseWindowMs(parsed) {
37798
+ let window2 = PRODUCE_CACHE_TTL_MS;
37799
+ for (const raw of [...parsed.widgets ?? [], ...parsed.views ?? []]) {
37800
+ const ttl2 = parseJsonField(raw.refresh)?.ttlSeconds;
37801
+ if (typeof ttl2 !== "number" || !Number.isFinite(ttl2) || ttl2 <= 0) continue;
37802
+ window2 = Math.min(window2, ttl2 * 1e3);
37803
+ }
37804
+ return window2;
37805
+ }
37725
37806
  function viewRenderModes() {
37726
37807
  const out = {};
37727
37808
  for (const target of [...SURFACE_GROUPS, ...PREVIEW_SURFACES.map((s) => s.id)]) {
@@ -37924,7 +38005,30 @@ function parseJsonField(value) {
37924
38005
  }
37925
38006
  }
37926
38007
  async function extractWidgets(bundleJs, opts = {}) {
37927
- const services = {};
38008
+ const evaluator = opts.evaluator ?? await widgetRealm();
38009
+ const timeoutMs = opts.timeoutMs ?? WIDGET_BUDGET_MS;
38010
+ const clock = guestClock(opts.clock);
38011
+ const key2 = produceCacheKey(bundleJs, timeoutMs, clock);
38012
+ const cached3 = cachedProduce(evaluator, key2);
38013
+ const produced = cached3 ?? runProduce(evaluator, bundleJs, timeoutMs, clock);
38014
+ let parsed;
38015
+ try {
38016
+ parsed = JSON.parse(produced.envelope);
38017
+ } catch (err) {
38018
+ throw new Error(`the widget producer's realm returned a malformed envelope: ${describeThrown(err)}`);
38019
+ }
38020
+ let services = produced.services;
38021
+ if (!cached3) {
38022
+ const servicesJson = serializedServices(produced);
38023
+ if (servicesJson !== void 0) {
38024
+ services = JSON.parse(servicesJson);
38025
+ rememberProduce(evaluator, key2, servicesJson, produced.envelope, Date.now() + reuseWindowMs(parsed));
38026
+ }
38027
+ }
38028
+ return interpretProduce(services, parsed);
38029
+ }
38030
+ function runProduce(evaluator, bundleJs, timeoutMs, clock) {
38031
+ const services = /* @__PURE__ */ Object.create(null);
37928
38032
  let envelope;
37929
38033
  const inert = makeInert2();
37930
38034
  const himalayaTarget = {
@@ -37966,21 +38070,17 @@ async function extractWidgets(bundleJs, opts = {}) {
37966
38070
  }
37967
38071
  };
37968
38072
  sandbox.globalThis = sandbox;
37969
- const evaluator = opts.evaluator ?? await widgetRealm();
37970
- evaluator.run(widgetProgram(bundleJs, opts.clock), sandbox, {
37971
- timeoutMs: opts.timeoutMs ?? WIDGET_BUDGET_MS,
38073
+ evaluator.run(widgetProgram(bundleJs, clock), sandbox, {
38074
+ timeoutMs,
37972
38075
  filename: "widget-bundle.js",
37973
38076
  captureConsole: false
37974
38077
  });
37975
38078
  if (envelope === void 0) {
37976
38079
  throw new Error("the widget producer's realm returned no registrations envelope");
37977
38080
  }
37978
- let parsed;
37979
- try {
37980
- parsed = JSON.parse(envelope);
37981
- } catch (err) {
37982
- throw new Error(`the widget producer's realm returned a malformed envelope: ${describeThrown(err)}`);
37983
- }
38081
+ return { services, envelope };
38082
+ }
38083
+ function interpretProduce(services, parsed) {
37984
38084
  const widgets = {};
37985
38085
  for (const raw of parsed.widgets ?? []) {
37986
38086
  widgets[String(raw.id)] = {
@@ -38239,7 +38339,7 @@ function freezeOverrideSnapshot(input, opts = {}) {
38239
38339
  }
38240
38340
  return snapshot2;
38241
38341
  }
38242
- var MAX_TIMELINE_FRAMES, WIDGET_BUDGET_MS, realmPromise, CAPTURE_SLOT, READBACK_SERVICE;
38342
+ var MAX_TIMELINE_FRAMES, WIDGET_BUDGET_MS, realmPromise, PRODUCE_CACHE_TTL_MS, MAX_PRODUCE_CACHE_ENTRIES, MAX_PRODUCE_ENTRY_BYTES, MAX_PRODUCE_CACHE_BYTES, produceCache, produceCacheGeneration, CAPTURE_SLOT, READBACK_SERVICE;
38243
38343
  var init_widgets = __esm({
38244
38344
  "../../core/dev-server/src/widgets.ts"() {
38245
38345
  init_validator();
@@ -38247,6 +38347,12 @@ var init_widgets = __esm({
38247
38347
  MAX_TIMELINE_FRAMES = 48;
38248
38348
  WIDGET_BUDGET_MS = 5e3;
38249
38349
  realmPromise = null;
38350
+ PRODUCE_CACHE_TTL_MS = 6e4;
38351
+ MAX_PRODUCE_CACHE_ENTRIES = 2048;
38352
+ MAX_PRODUCE_ENTRY_BYTES = 4 * 1024 * 1024;
38353
+ MAX_PRODUCE_CACHE_BYTES = 64 * 1024 * 1024;
38354
+ produceCache = /* @__PURE__ */ new WeakMap();
38355
+ produceCacheGeneration = 0;
38250
38356
  CAPTURE_SLOT = "himi.widget.capture";
38251
38357
  READBACK_SERVICE = "himi.widget.readback";
38252
38358
  }
@@ -6590,7 +6590,7 @@
6590
6590
  {
6591
6591
  "name": "getPost",
6592
6592
  "kind": "function",
6593
- "jsdoc": "/** Fetch a single PUBLISHED post by id — GET /blog/v3/posts/{id}. The list\n * (queryPosts) returns published posts, so the detail reads them here (draft\n * ids are a different space — getDraftPost is for the edit/create flow).\n *\n * Body fieldsets (#826): Blog v3 `GET /posts/{id}` returns 200 but only the\n * LIST projection (title / excerpt / meta) unless the caller asks for the body —\n * the bare GET has NO `richContent` / `contentText`. So we request\n * RICH_CONTENT + CONTENT_TEXT by default and the article body loads on the happy\n * path; callers can override via `req.fieldsets`. (Supersedes the old #507 note:\n * the 502 this GET once returned for anonymous visitors is gone — it 200s now —\n * but a body-less 200 was never the \"body preserved whenever the backend is\n * healthy\" the earlier comment claimed; the body was simply never requested.)\n *\n * On any rejection (network / backend error) we still fall back to a queryPosts\n * filter-by-id — a graceful degrade to the LIST projection (no body) that keeps\n * blog detail + saved screens rendering instead of erroring out. `queryPosts`'s\n * request type has no `fieldsets` field, so that fallback path cannot carry the\n * body. */",
6593
+ "jsdoc": "/** Fetch a single PUBLISHED post by id — GET /blog/v3/posts/{id}. The list\n * (queryPosts) returns published posts, so the detail reads them here (draft\n * ids are a different space — getDraftPost is for the edit/create flow).\n *\n * Body fieldsets (#826): Blog v3 `GET /posts/{id}` returns 200 but only the\n * LIST projection (title / excerpt / meta) unless the caller asks for the body —\n * the bare GET has NO `richContent` / `contentText`. So we request\n * RICH_CONTENT + CONTENT_TEXT by default and the article body loads on the happy\n * path; callers can override via `req.fieldsets`. (Supersedes the old #507 note:\n * the 502 this GET once returned for anonymous visitors is gone — it 200s now —\n * but a body-less 200 was never the \"body preserved whenever the backend is\n * healthy\" the earlier comment claimed; the body was simply never requested.)\n *\n * On any rejection (network / backend error) we still fall back to a queryPosts\n * filter-by-id — a graceful degrade to the LIST projection (no body) that keeps\n * blog detail + saved screens rendering instead of erroring out. `queryPosts`'s\n * request type has no `fieldsets` field, so that fallback path cannot carry the\n * body.\n *\n * When that fallback ALSO finds nothing (#2658) — a deleted post, or one this\n * visitor may not see — we reject rather than resolve. `WixGetPostResponse.post`\n * is non-optional, so returning `{ post: resp.posts?.[0] }` would hand callers an\n * `undefined` the type promises is present, and the `post.title` access that type\n * licenses throws a `TypeError` deep inside the caller — the exact hard error\n * this fallback exists to prevent, and harder to diagnose than a clean rejection.\n * Every call site rides its own rejection path (cached-post state, draft-only\n * `catch`, or an `allSettled` filter), so rejecting is what routes them into the\n * graceful degrade they already implement. The originating failure travels as\n * `cause`, so the 404-vs-network distinction is not lost. */",
6594
6594
  "decl": "export function getPost(\n postId: string,\n req: WixGetPostRequest = {},\n): Promise<WixGetPostResponse>;",
6595
6595
  "http": "GET /blog/v3/posts/{…}"
6596
6596
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/himalaya-cli",
3
- "version": "0.877.0",
3
+ "version": "0.879.0",
4
4
  "type": "module",
5
5
  "description": "himi — the Himalaya authoring CLI. Build a Tier-5 content package on your own machine, push it to the production serve as a draft, preview it on web + Himi Player, and read worker logs back — the remote dev loop. Wraps @himalaya/serve-cli for transport.",
6
6
  "keywords": [
@@ -63,5 +63,5 @@
63
63
  "artifactId": "himalaya-cli"
64
64
  }
65
65
  },
66
- "falconPackageHash": "165e76dc8fe6c80270e34398c2070b203234e75c6e6b748b88abb286"
66
+ "falconPackageHash": "cadb5d525c1bd00dfb5a1b90aeae24a17d6b94eb560176b0cb962516"
67
67
  }