@tokenoftrust/storefront-runner 6.0.0 → 6.0.2

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.
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * Tenant binaries never live in the tenant repo. The platform's content-addressed `/_a/<sha256>`
3
3
  * pool owns them and the store's repo holds references only. Publication captures source images;
4
- * go-live requires zero uncaptured images in every type and sampled live owned-image responses
5
- * before the source store is shut down. This decides,
4
+ * go-live requires zero uncaptured images in every type, or an owner acknowledgement of the exact
5
+ * remaining count, and sampled live owned-image responses. Acknowledged images still load from the
6
+ * source store, so it is never shut down while any remain. This decides,
6
7
  * from a listing of the store's `public/` tree, whether the repo is carrying binaries it must not.
7
8
  *
8
9
  * Findings, each naming its remedy:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokenoftrust/storefront-runner",
3
- "version": "6.0.0",
3
+ "version": "6.0.2",
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",
@@ -553,6 +553,9 @@ export function applyCaptureRewrites(
553
553
  return artifacts.map(createCaptureArtifactRewriter(rewrites));
554
554
  }
555
555
 
556
+ /** Characters of a key's file name used to find which rewrites an artifact can need. */
557
+ const REWRITE_ANCHOR_LENGTH = 12;
558
+
556
559
  /** Prepare locator matching once while allowing callers to persist one rewritten artifact at a time. */
557
560
  export function createCaptureArtifactRewriter(rewrites: readonly CaptureRewrite[]): (artifact: ImageDependencyArtifact) => ImageDependencyArtifact {
558
561
  if (rewrites.length === 0) return (artifact) => ({ ...artifact });
@@ -570,9 +573,43 @@ export function createCaptureArtifactRewriter(rewrites: readonly CaptureRewrite[
570
573
  const exact = new Set((rewrite.exactAliases ?? []).flatMap((n) => [n, ...htmlAmpVariants(n)]));
571
574
  return { rewrite, needles, key, exact };
572
575
  });
576
+ // Trying every rewrite against every artifact is rewrites × artifacts searches: a 10k-image
577
+ // catalog over 3.5k files is ~37M searches, about a minute of CPU, past a Worker's limit. Instead
578
+ // each needle is indexed by the ANCHOR characters that follow its last `/`, and an artifact is read
579
+ // once, at each `/`, to find the only rewrites with a needle that can occur in it: wherever a needle
580
+ // occurs, a `/` is followed by its anchor. A rewrite with a needle lacking an anchor (no slash, or
581
+ // fewer characters after it) is always tried.
582
+ const anchored = new Map<string, number[]>();
583
+ const always: number[] = [];
584
+ prepared.forEach(({ needles }, index) => {
585
+ const anchors = new Set<string>();
586
+ for (const needle of needles) {
587
+ const slash = needle.lastIndexOf("/");
588
+ if (slash < 0 || needle.length - slash - 1 < REWRITE_ANCHOR_LENGTH) {
589
+ always.push(index);
590
+ return;
591
+ }
592
+ anchors.add(needle.substr(slash + 1, REWRITE_ANCHOR_LENGTH));
593
+ }
594
+ for (const anchor of anchors) {
595
+ const list = anchored.get(anchor);
596
+ if (list) list.push(index);
597
+ else anchored.set(anchor, [index]);
598
+ }
599
+ });
600
+ const candidatesIn = (content: string): number[] => {
601
+ if (anchored.size === 0) return always;
602
+ const found = new Set<number>(always);
603
+ for (let at = content.indexOf("/"); at !== -1; at = content.indexOf("/", at + 1)) {
604
+ const list = anchored.get(content.substr(at + 1, REWRITE_ANCHOR_LENGTH));
605
+ if (list) for (const index of list) found.add(index);
606
+ }
607
+ return [...found].sort((a, b) => a - b);
608
+ };
573
609
  return (artifact) => {
574
610
  let content = artifact.content;
575
- for (const { rewrite, needles, key, exact } of prepared) {
611
+ for (const index of candidatesIn(content)) {
612
+ const { rewrite, needles, key, exact } = prepared[index]!;
576
613
  if (key !== null && !content.includes(key)) continue;
577
614
  for (const needle of needles) {
578
615
  content = replaceAllLocatorTokens(content, needle, rewrite.platformUrl, exact.has(needle));