@tokenoftrust/storefront-runner 2.2.117 → 2.2.118

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.
@@ -2,7 +2,7 @@ import type {
2
2
  OpenPublicationArtifactV1,
3
3
  PublicationRouteRenderPlanV1,
4
4
  } from "@tot/public-runtime";
5
- import type { EditorialPage, HomeContent } from "@/lib/storyblok/content-model.js";
5
+ import { hasHomeContentShape, type EditorialPage, type HomeContent } from "@/lib/storyblok/content-model.js";
6
6
  import type { ChromeConfig } from "@/lib/chrome/model.js";
7
7
  import { parseChromeConfig } from "@/lib/chrome/parseConfig.js";
8
8
  import { parseChromeAssignments } from "@/lib/chrome/model.js";
@@ -75,7 +75,7 @@ export class PublicationSpaceContentProvider implements ContentProvider {
75
75
  async getHome(): Promise<HomeContent> {
76
76
  if (!this.ownsRoot) return this.sharedRoot.getHome();
77
77
  const home = parseJson<HomeContent>(this.publication.readContent("home.json"));
78
- if (!home) throw new Error("selected root publication home is unavailable");
78
+ if (!home || !hasHomeContentShape(home)) throw new Error("selected root publication home is unavailable");
79
79
  return home;
80
80
  }
81
81
 
@@ -595,11 +595,9 @@ export class StoryblokContentProvider implements ContentProvider {
595
595
 
596
596
  // ---------------------------------------------------------------------------
597
597
  // PublishedContentProvider — overlays published customization content for the
598
- // resolved environment (store->app wiring). For each content artifact it checks
599
- // the customization store ({live,test} pointer -> versioned JSON); if present it
600
- // serves that, otherwise it delegates to the base provider (Storyblok/local).
601
- // Enumeration also follows the selected immutable manifest, so sitemap/blog/page
602
- // indexes do not need a build-time tenant checkout.
598
+ // resolved environment (store->app wiring). A selected immutable version is
599
+ // authoritative for both individual reads and enumeration. Legacy per-path
600
+ // customization delegates absent files to the base provider (Storyblok/local).
603
601
  // ---------------------------------------------------------------------------
604
602
 
605
603
  export interface ContentCustomization {
@@ -609,13 +607,27 @@ export interface ContentCustomization {
609
607
 
610
608
  export class PublishedContentProvider implements ContentProvider {
611
609
  readonly perRequestArtifactReads = true;
610
+ private versionedPathsPromise: Promise<string[] | null> | null = null;
612
611
  constructor(
613
612
  private readonly base: ContentProvider,
614
613
  private readonly tenantId: string,
615
614
  private readonly cust: ContentCustomization,
616
615
  ) {}
617
616
 
618
- /** Raw published-artifact string for a tenant file, or null to fall back to base. */
617
+ private versionedPaths(): Promise<string[] | null> {
618
+ return this.versionedPathsPromise ??= listPublishedArtifactPaths(
619
+ this.cust.get,
620
+ this.tenantId,
621
+ this.cust.env,
622
+ contentArtifactPath(this.tenantId, ""),
623
+ );
624
+ }
625
+
626
+ private async usesVersionedContent(): Promise<boolean> {
627
+ return (await this.versionedPaths()) !== null;
628
+ }
629
+
630
+ /** Raw published-artifact string for a tenant file, or null when absent. */
619
631
  private publishedRaw(relative: string): Promise<string | null> {
620
632
  return readPublishedArtifact(
621
633
  this.cust.get,
@@ -636,7 +648,10 @@ export class PublishedContentProvider implements ContentProvider {
636
648
  }
637
649
 
638
650
  async getHome(): Promise<HomeContent> {
639
- return (await this.published<HomeContent>("home.json")) ?? this.base.getHome();
651
+ const home = await this.published<HomeContent>("home.json");
652
+ if (home && hasHomeContentShape(home)) return home;
653
+ if (await this.usesVersionedContent()) throw new Error("Published version has no renderable home.json");
654
+ return this.base.getHome();
640
655
  }
641
656
  // Raw whole-page documents (T0 marketing tenants) publish via KV too, so a
642
657
  // vendor edit previews / goes live with NO Worker rebuild. Artifact path mirrors
@@ -650,12 +665,14 @@ export class PublishedContentProvider implements ContentProvider {
650
665
  // is dt-id-writeback's job.
651
666
  async getHomeHtml(): Promise<string | null> {
652
667
  const raw = await this.publishedRaw("home.html");
653
- return raw !== null ? stampTrackingIdsAtPublish(raw) : this.base.getHomeHtml();
668
+ if (raw !== null) return stampTrackingIdsAtPublish(raw);
669
+ return (await this.usesVersionedContent()) ? null : this.base.getHomeHtml();
654
670
  }
655
671
  async getPageHtml(slug: string): Promise<string | null> {
656
672
  const rel = `pages-html/${normalizeRawPageSlug(slug)}.html`;
657
673
  const raw = await this.publishedRaw(rel);
658
- return raw !== null ? stampTrackingIdsAtPublish(raw) : this.base.getPageHtml(slug);
674
+ if (raw !== null) return stampTrackingIdsAtPublish(raw);
675
+ return (await this.usesVersionedContent()) ? null : this.base.getPageHtml(slug);
659
676
  }
660
677
  listPageHtmlSlugs(): Promise<string[]> {
661
678
  return this.listArtifactPaths("pages-html/").then((paths) =>
@@ -682,18 +699,19 @@ export class PublishedContentProvider implements ContentProvider {
682
699
  const assignments = parseChromeAssignments(await this.published<unknown>("chrome-assignments.json"));
683
700
  return renderRawChrome(config, currentPath, assignments);
684
701
  }
685
- // Otherwise fall back to the base chain (bundled chrome.html or config render).
686
- return this.base.getChromeHtml(currentPath);
702
+ // Legacy per-path content may use bundled chrome; a selected version owns its chrome.
703
+ return (await this.usesVersionedContent()) ? null : this.base.getChromeHtml(currentPath);
687
704
  }
688
705
  async getChrome(): Promise<ChromeConfig> {
689
706
  // Runs through the SAME fail-closed schema validator as getChromeHtml's
690
707
  // chrome.json branch (lib/chrome/parseConfig.ts) — a published artifact is
691
708
  // no longer trusted blind (was: JSON.parse + a raw cast to SiteChrome).
692
709
  const config = parseChromeConfig(await this.published<unknown>("chrome.json"));
693
- return config ?? this.base.getChrome();
710
+ return config ?? ((await this.usesVersionedContent()) ? EMPTY_CHROME_CONFIG : this.base.getChrome());
694
711
  }
695
712
  async getPage(slug: string): Promise<EditorialPage | null> {
696
- return (await this.published<EditorialPage>(`pages/${slug}.json`)) ?? this.base.getPage(slug);
713
+ const page = await this.published<EditorialPage>(`pages/${slug}.json`);
714
+ return page ?? ((await this.usesVersionedContent()) ? null : this.base.getPage(slug));
697
715
  }
698
716
  async listPages(): Promise<EditorialPage[]> {
699
717
  const paths = await this.listArtifactPaths("pages/");
@@ -720,13 +738,9 @@ export class PublishedContentProvider implements ContentProvider {
720
738
 
721
739
  async listArtifactPaths(prefix = ""): Promise<string[] | null> {
722
740
  const root = contentArtifactPath(this.tenantId, "");
723
- const paths = await listPublishedArtifactPaths(
724
- this.cust.get,
725
- this.tenantId,
726
- this.cust.env,
727
- `${root}${prefix}`,
728
- );
729
- return paths?.map((path) => path.slice(root.length)) ?? null;
741
+ const paths = await this.versionedPaths();
742
+ return paths?.filter((path) => path.startsWith(`${root}${prefix}`))
743
+ .map((path) => path.slice(root.length)) ?? null;
730
744
  }
731
745
  }
732
746
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokenoftrust/storefront-runner",
3
- "version": "2.2.117",
3
+ "version": "2.2.118",
4
4
  "license": "SEE LICENSE IN LICENSE",
5
5
  "description": "World-shareable storefront runner: multi-tenant renderer on Astro/Cloudflare. No control plane.",
6
6
  "packageManager": "pnpm@11.9.0",