# content-address _(no submodules)_ ## Purpose Content-addressed URL builder and cache-invalidation helpers for packed archives. Each archive's canonical URL is derived from a BLAKE3 Merkle root over its entries plus the format-version byte, so any payload or wire-format change produces a different URL and the Service Worker can cleanly invalidate the prior version on next install. This module is the foundation for the `/packed/.bundle.asset` URL form and the SW `activate`-time cache pruning. ## Features - `computeArchiveContentHash(entries, formatVersion)` — deterministic 32-byte BLAKE3 root that mixes the format-version byte into the leaf set so a wire-format bump is reflected in the URL. - `contentHashHex(hash)` — lowercase-hex serialiser used by URL builders and cache-key emitters. - `buildContentAddressedUrl(prefix, hash, ext?)` — emits the canonical `/.` URL (default ext `bundle.asset` matches the Cluster A SW pattern). - `cleanupOutdatedCaches(caches, prefix, keep)` — Service Worker helper that deletes every cache name with a given prefix not in `keep`. - `PACKER_BROWSER_FLAGS.URL_CONTENT_ADDRESSED` — stable feature-flag string registered against the manager's `FlagProvider`. ## Usage: Basic Example ```ts import { buildContentAddressedUrl, computeArchiveContentHash, contentHashHex, } from "@suaveplan/packer-browser"; const entries = [new Uint8Array([1, 2, 3]), new Uint8Array([4, 5, 6])]; const hash = computeArchiveContentHash(entries, /* formatVersion */ 1); const url = buildContentAddressedUrl("/packed", contentHashHex(hash)); // → "/packed/<64-hex>.bundle.asset" ``` ## Usage: Advanced Example ```ts import { cleanupOutdatedCaches, runPackedResourceServiceWorker, } from "@suaveplan/packer-browser"; // Inside the Service Worker bootstrap: runPackedResourceServiceWorker(self as unknown as ServiceWorkerGlobalScope, { cacheName: `packed-resources-${currentHash}`, contentAddressed: true, }); // `cleanupOutdatedCaches` is invoked by the SW's activate handler // automatically when `contentAddressed` is on. You can also call it // from a maintenance message handler: self.addEventListener("message", async (ev) => { if ((ev as MessageEvent).data?.type === "GC") { const deleted = await cleanupOutdatedCaches( self.caches, "packed-resources-", [`packed-resources-${currentHash}`] ); console.info("Pruned", deleted); } }); ``` ## API Reference - `computeArchiveContentHash(entries: ReadonlyArray, formatVersion: number): Uint8Array` Throws `RangeError` if `formatVersion` is not an integer in [0, 255]. - `contentHashHex(hash: Uint8Array): string` Throws `RangeError` on non-32-byte input. - `buildContentAddressedUrl(prefix: string, hash: string, ext?: string): string` - `cleanupOutdatedCaches(caches: CacheStorageLike, prefix: string, keep: ReadonlyArray): Promise` Returns the list of cache names that were deleted. - `PACKER_BROWSER_FLAGS.URL_CONTENT_ADDRESSED: string` — `"packer-browser.url.content-addressed"`. ## Implementation Notes - The format-version byte is appended as a synthetic trailing entry rather than mixed via HKDF so the implementation stays simple and every transformation is auditable via the existing `entryDigestBlake3` + `merkleRootBlake3` primitives. - `cleanupOutdatedCaches` accepts any object with `keys()` and `delete()` methods; both the production `globalThis.caches` and the in-memory `ShimCacheStorage` satisfy the contract, which keeps the SW unit tests honest. - `buildContentAddressedUrl` trims a trailing slash from the prefix so callers can pass `/packed` or `/packed/` interchangeably. - The Cluster A SW pattern `/\.packed$/` continues to match the canonical content-addressed form; the Cluster B `RunServiceWorkerOptions.legacyUrlPattern` slot accepts any other RegExp callers need for backwards compatibility (e.g. legacy named-archive URLs).