104 lines
4.0 KiB
Markdown
104 lines
4.0 KiB
Markdown
# content-address
|
|
|
|
<!-- begin:auto-docs -->
|
|
_(no submodules)_
|
|
<!-- end:auto-docs -->
|
|
|
|
## 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/<hash>.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
|
|
`<prefix>/<hash>.<ext>` 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<Uint8Array>, 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<string>): Promise<string[]>`
|
|
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).
|