128 lines
7.0 KiB
Markdown
128 lines
7.0 KiB
Markdown
# enhance-packer-browser-streaming — Cluster B §4 streaming enhancements
|
|
|
|
**Package**: `@suaveplan/packer-browser`
|
|
**Status**: PROPOSED
|
|
**Type**: feature
|
|
**Design**: not applicable (Cluster B; architecture captured inline in `tasks.md` and the spec delta, mirroring Cluster A).
|
|
**Spec delta**: ./specs/packer-browser/spec.md
|
|
|
|
## Why
|
|
|
|
Cluster A (`2026-05-19-add-packer-browser-core`) landed the production-quality
|
|
core (PACKER-BROWSER-1..12, 153 tests, 100/100/100/100 coverage). The upstream
|
|
audit (`../suaveplan/enterprise/packer-browser.md` §4) enumerates ten
|
|
feature-rich enhancements; Cluster B picks up the four "streaming" items so
|
|
the package can serve real CDN-backed packed media: progressive (entry-by-
|
|
entry) unpack, Range-request support inside the Service Worker, encrypted
|
|
archives with a pluggable keyring, and content-addressed versioned archive
|
|
URLs with automatic SW cache invalidation. These four are foundational for
|
|
large media (audio/video) and multi-tenant deployments; remaining §4 items
|
|
(adaptive cache, `<link rel=preload>`, multi-origin SW, BackgroundFetch,
|
|
diagnostics overlay, additional subpath splits, full Playwright sweep) ship in
|
|
Cluster C.
|
|
|
|
## What changes
|
|
|
|
Pure additions. Cluster A's public surface (PACKER-BROWSER-1..12) remains
|
|
intact. The Cluster A entrypoint (default `.` export) gains five new exports;
|
|
no removals.
|
|
|
|
- ADDED: `PackedResourceLoader.loadStream(inputs, opts?)` — async iterator
|
|
that yields each `LoadedResource` as soon as its packed bytes are
|
|
decoded, honouring `AbortSignal` at every yield.
|
|
- ADDED: `PackedResourceManager.loadStream(inputs, opts?)` — thin pass-through
|
|
that also writes each decoded entry into the cache as it arrives.
|
|
- ADDED: `serveRangeResponse(request, body, opts?)` — RFC 7233 §2.1
|
|
single-range helper returning `Response` with `206`/`200`/`412`/`416`
|
|
semantics (`Content-Range`, `Accept-Ranges: bytes`, `Content-Length`,
|
|
`If-Range` ETag handling).
|
|
- ADDED: `RunServiceWorkerOptions.rangeThreshold` (default `262144` —
|
|
256 KiB) — fetch handler emits a `206` for matching requests whose cached
|
|
body exceeds the threshold; multi-range requests fall back to `416`.
|
|
- ADDED: `KeyringProvider` contract — `resolveKey(keyId): Promise<Uint8Array>`
|
|
with a default `emptyKeyring()` factory that rejects all key ids.
|
|
- ADDED: `encodeEncryptedArchive(plaintext, keyId, key, nonce, opts?)` /
|
|
`decodeEncryptedArchiveHeader(bytes)` — wire format with stable
|
|
`SP-ENC1\x01` magic prefix, framed JSON header, and AES-256-GCM
|
|
ciphertext+tag. Header carries `{ alg: "AES-256-GCM", keyId, nonce, aad? }`.
|
|
- ADDED: `PackedResourceLoaderOptions.keyring` — when supplied, the loader
|
|
detects the `SP-ENC1` prefix, resolves the key, AES-GCM decrypts, then
|
|
proceeds with the standard verify+unpack pipeline.
|
|
- ADDED: `PACKER_BROWSER_CODES.KEY_UNAVAILABLE` (new error code) +
|
|
matching i18n keys (`packer-browser.error.KEY_UNAVAILABLE.no-key`,
|
|
`packer-browser.error.KEY_UNAVAILABLE.decrypt-failed`).
|
|
- ADDED: `computeArchiveContentHash(entries, formatVersion)` —
|
|
BLAKE3 Merkle root over canonical entry digests with the format-version
|
|
byte appended; reuses `@suaveplan/packer`'s
|
|
`entryDigestBlake3`/`merkleRootBlake3`.
|
|
- ADDED: `buildContentAddressedUrl(prefix, hash, ext?)` — content-addressed
|
|
URL builder; defaults to `/packed/<hash>.bundle.asset`.
|
|
- ADDED: `RunServiceWorkerOptions.contentAddressed` (default `true`) +
|
|
`RunServiceWorkerOptions.legacyUrlPattern` — when content-addressed mode
|
|
is on, SW caches under the hash; the legacy `/packed/<name>` pattern is
|
|
still recognised when `legacyUrlPattern` is supplied.
|
|
- ADDED: `cleanupOutdatedCaches(scope, prefix, keep)` — SW helper that
|
|
deletes every cache whose name starts with `prefix` and is not in `keep`;
|
|
invoked from the `activate` handler when content-addressed mode is on so
|
|
a new manifest hash invalidates old cache entries.
|
|
- ADDED: `PACKER_BROWSER_FLAGS.URL_CONTENT_ADDRESSED` — registered against
|
|
the manager's injected `FlagProvider` so adopters can flip the URL form
|
|
at runtime; default-on for new manifests, default-off honours
|
|
legacy `/packed/<name>` requests transparently.
|
|
|
|
## Out of scope
|
|
|
|
- Adaptive cache sizing via `navigator.storage.estimate()` (§4.3) — Cluster C.
|
|
- `<link rel="preload">` generation (§4.5) — Cluster C.
|
|
- Multi-origin SW helper (§4.7) — Cluster C.
|
|
- BackgroundFetch API integration (§4.8) — Cluster C.
|
|
- Diagnostics dev overlay (§4.9) — Cluster C.
|
|
- Subpath splits beyond Cluster A's five (`.`, `/manager`, `/service-worker`,
|
|
`/utils`, `/singleton`) (§4.10) — Cluster C.
|
|
- Full Playwright E2E coverage across all features — Cluster C (Cluster A's
|
|
Playwright stub remains; new tests are vitest unit only).
|
|
|
|
## Source mapping
|
|
|
|
This change derives from `../suaveplan/enterprise/packer-browser.md`.
|
|
|
|
| Requirement | Source lines | Notes |
|
|
|---|---|---|
|
|
| PACKER-BROWSER-13 — Progressive streaming unpack | L69-L69 | §4.1 progressive streaming; per-entry yield with `AbortSignal` honouring. |
|
|
| PACKER-BROWSER-14 — Range-request SW support | L70-L70 | §4.2 range requests; single-range 206 with `Content-Range`/`Accept-Ranges`/`If-Range`. |
|
|
| PACKER-BROWSER-15 — Encrypted archives with pluggable keyring | L72-L72 | §4.4 encrypted archives; pluggable `KeyringProvider`, AES-256-GCM via `@suaveplan/crypto`, new `KEY_UNAVAILABLE` error code. |
|
|
| PACKER-BROWSER-16 — Content-addressed versioned archive URLs | L74-L74 | §4.6 versioned archives; BLAKE3 hash over entry digests + format-version byte; `cleanupOutdatedCaches` from SW activate; flag-gated URL form. |
|
|
|
|
## Verification approach
|
|
|
|
Every requirement carries `**Verified by:**` entries pointing at concrete
|
|
`it(...)` titles in the listed test files. Tests are co-located with
|
|
implementation (`src/<feature>/<feature>.test.ts`).
|
|
|
|
- `src/loader/loader-stream.test.ts` — covers PACKER-BROWSER-13.
|
|
- `src/manager/manager.test.ts` — covers PACKER-BROWSER-13 (manager pass-through).
|
|
- `src/range-response/range-response.test.ts` — covers PACKER-BROWSER-14.
|
|
- `src/service-worker/service-worker.test.ts` — covers PACKER-BROWSER-14 (SW wiring).
|
|
- `src/keyring/keyring.test.ts` — covers PACKER-BROWSER-15.
|
|
- `src/encrypted-archive/encrypted-archive.test.ts` — covers PACKER-BROWSER-15 (wire format).
|
|
- `src/loader/loader.test.ts` — covers PACKER-BROWSER-15 (decrypt-then-unpack path).
|
|
- `src/content-address/content-address.test.ts` — covers PACKER-BROWSER-16.
|
|
- `src/service-worker/service-worker.test.ts` — covers PACKER-BROWSER-16 (cleanupOutdatedCaches).
|
|
- `src/error-codes.test.ts` — covers PACKER-BROWSER-15 (new code reachability).
|
|
|
|
## Impact
|
|
|
|
### Affected dependents
|
|
|
|
Run `bunx turbo ls --filter=...@suaveplan/packer-browser` to enumerate.
|
|
|
|
- No dependents (verified by
|
|
`grep -rln "@suaveplan/packer-browser" packages/ apps/` — the package is
|
|
still pre-1.0 with no in-tree consumers, per Cluster A archive). All
|
|
additions are additive; no consumer migration required.
|
|
|
|
### Migration
|
|
|
|
Not applicable — additive changes only. Existing Cluster A consumers
|
|
continue to work unchanged; the new APIs are opt-in via additional options.
|