212 lines
12 KiB
Markdown
212 lines
12 KiB
Markdown
# Tasks: `@suaveplan/audio-codec-ffmpeg-wasm`
|
|
|
|
Ordered implementation checklist for the `enterprise-audio-codec-ffmpeg-wasm` change.
|
|
Every numbered task corresponds to scenarios in
|
|
`./specs/audio-codec-ffmpeg-wasm/spec.md`. Do not start a task whose prerequisites are
|
|
still pending in `STATUS.md`.
|
|
|
|
---
|
|
|
|
## 0. Pre-flight (non-negotiable)
|
|
|
|
- [ ] 0.1 Confirm clean working tree (`git status` reports nothing) and no in-flight worktrees with uncommitted work (`git worktree list` then `git status` in each)
|
|
- [ ] 0.2 If any worktrees exist under `.claude/worktrees/`, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (`git worktree remove`) before opening a new one — code that has been lost twice on main is the failure mode this guards against.
|
|
- [ ] 0.3 Confirm the package set in `MANIFEST.md` matches `find packages -maxdepth 3 -name package.json` count. Drift here means a prior change archived without regenerating the manifest.
|
|
|
|
## 1. Scaffold the package
|
|
|
|
- [ ] 1.1 Add `@ffmpeg/ffmpeg` and `@ffmpeg/core` to the root `package.json` `catalog`
|
|
block at exact pinned versions. Note in the PR description that this is the only
|
|
catalog addition required by this change.
|
|
- [ ] 1.2 Create `packages/media/audio/audio-codec-ffmpeg-wasm/` with `package.json`
|
|
(`"name": "@suaveplan/audio-codec-ffmpeg-wasm"`, `"version": "0.1.0"`,
|
|
`"browser": true`, `"sideEffects": false`, `"type": "module"`), workspace deps on
|
|
`@suaveplan/audio-codecs`, `audio-core`, `error`, `types`, `streams` all at
|
|
`workspace:^`, runtime deps on `@ffmpeg/ffmpeg`, `@ffmpeg/core`, `zod catalog:forms`,
|
|
`@opentelemetry/api catalog:observability`, `tslib catalog:build`, and
|
|
`optionalDependencies: { "@ffmpeg/core-mt": "<pinned>" }`.
|
|
- [ ] 1.3 Declare one `exports` entry per feature module plus the root `.` barrel
|
|
(see proposal "Module layout").
|
|
- [ ] 1.4 Add `tsconfig.json` extending the shared base; add `vitest.config.ts` using
|
|
`createTestConfig()` from `@suaveplan/testing/config` with `pool: 'forks'` and
|
|
`setupFiles: ['@suaveplan/testing/polyfills']`. Configure `happy-dom` for any test
|
|
that touches `Worker`.
|
|
- [ ] 1.5 Add `README.md` at the package root only (no category READMEs).
|
|
- [ ] 1.6 Run `bun scripts/folder-reorg/generate-manifest.ts` to refresh `MANIFEST.md`.
|
|
|
|
## 2. Types and error codes
|
|
|
|
- [ ] 2.1 Author `src/codec/ffmpeg-wasm-codec.types.ts` with `FfmpegWasmCodecOptions`,
|
|
`LoadedCore`, `WorkerMessage` union, and internal pipeline types.
|
|
- [ ] 2.2 Author `src/codec/ffmpeg-wasm-codec.schemas.ts` exporting
|
|
`FfmpegWasmCodecOptionsSchema` (Zod) — uses `import z from "zod"` per
|
|
`MEMORY.md`. Reject `coreUrl` and `corePath` set simultaneously with
|
|
`.superRefine`.
|
|
- [ ] 2.3 Author `src/error-codes.ts` defining `AudioCodecFfmpegWasmError`,
|
|
`AudioCodecFfmpegWasmErrorCode`, `AUDIO_CODEC_FFMPEG_WASM_ERROR_METADATA`, and
|
|
`registerAudioCodecFfmpegWasmErrorCodes()` via `@suaveplan/error`'s `defineCodes`.
|
|
Cover all 9 codes from the spec.
|
|
- [ ] 2.4 Author `src/error-codes.test.ts` asserting every code is reachable from a
|
|
real call site (one assertion per code; cross-references the other test files).
|
|
|
|
## 3. Worker host (`src/worker/`)
|
|
|
|
- [ ] 3.1 Implement `worker-host.ts`: wraps `@ffmpeg/ffmpeg`'s `FFmpeg` instance, owns the
|
|
`Worker` lifecycle, exposes `submitDecode`, `submitEncode`, `terminate`, and a
|
|
`progress` event hook.
|
|
- [ ] 3.2 `AbortSignal` plumbing: pre-aborted signal short-circuits before any worker
|
|
message; mid-call abort calls `worker.terminate()` and rejects pending calls with
|
|
`AUDIO_CODEC_FFMPEG_WASM.ABORTED`.
|
|
- [ ] 3.3 Timeout watchdog using injectable `Clock`; defaults 60s decode / 120s encode;
|
|
maps to `AUDIO_CODEC_FFMPEG_WASM.TIMEOUT`.
|
|
- [ ] 3.4 Crash + OOM detection: parse stderr for `Cannot enlarge memory arrays` /
|
|
`OutOfMemory` → `AUDIO_CODEC_FFMPEG_WASM.MEMORY_LIMIT_EXCEEDED`; pre-`load` worker
|
|
error → `AUDIO_CODEC_FFMPEG_WASM.WORKER_SPAWN_FAILED`.
|
|
- [ ] 3.5 Honour `opts.workerFactory` test seam.
|
|
- [ ] 3.6 Tests in `worker-host.test.ts` covering scenarios from spec Requirements
|
|
"Worker isolation", "AbortSignal propagation", "Timeout watchdog", and
|
|
"Memory-limit detection".
|
|
|
|
## 4. Core loader (`src/core-loader/`)
|
|
|
|
- [ ] 4.1 Implement `core-loader.ts`: resolve core URL (precedence corePath → coreUrl →
|
|
bundled default), memoise the loaded core per codec instance.
|
|
- [ ] 4.2 COI gate: when `enableMultithread === true` and
|
|
`globalThis.crossOriginIsolated !== true`, throw
|
|
`AUDIO_CODEC_FFMPEG_WASM.CROSS_ORIGIN_ISOLATION_REQUIRED` *before* attempting
|
|
`import("@ffmpeg/core-mt")`.
|
|
- [ ] 4.3 Optional-dep fallback: when COI is true but
|
|
`import("@ffmpeg/core-mt")` rejects, log a warning via `opts.logger` and fall back
|
|
to `@ffmpeg/core`. Do not throw.
|
|
- [ ] 4.4 Failure path: any load failure throws
|
|
`AUDIO_CODEC_FFMPEG_WASM.CORE_LOAD_FAILED` with the resolved URL in `context`.
|
|
- [ ] 4.5 Tests in `core-loader.test.ts` covering "Lazy core load on first use" and
|
|
"Cross-origin-isolation gate" scenarios.
|
|
|
|
## 5. Decode pipeline (`src/decode/`)
|
|
|
|
- [ ] 5.1 Implement `decode-pipeline.ts`: sniff format from magic bytes →
|
|
`ffmpeg.writeFile("input.<ext>", bytes)` → run probe pass for `sampleRate`/`channels`
|
|
→ run `-f f32le` extraction → `ffmpeg.readFile("output.raw")` → deinterleave to
|
|
`Float32Array[]`.
|
|
- [ ] 5.2 MEMFS cleanup: `deleteFile` both entries on success and on failure (try/finally).
|
|
- [ ] 5.3 ffmpeg non-zero exit → `AUDIO_CODEC_FFMPEG_WASM.DECODE_FAILED` with stderr tail.
|
|
- [ ] 5.4 Tests in `decode-pipeline.test.ts` covering "Decode pipeline" scenarios.
|
|
|
|
## 6. Encode pipeline (`src/encode/`)
|
|
|
|
- [ ] 6.1 Implement `encode-pipeline.ts`: per-format command builders for `wav`, `mp3`,
|
|
`ogg`, `opus`, `flac`, `aac`, `webm`, honouring `bitrate`/`quality` options.
|
|
- [ ] 6.2 Interleave `Float32Array[]` → single `Float32Array` → `Uint8Array` view →
|
|
MEMFS write.
|
|
- [ ] 6.3 Unsupported format check **before** posting any worker message →
|
|
`AUDIO_CODEC_FFMPEG_WASM.UNSUPPORTED_CODEC`.
|
|
- [ ] 6.4 MEMFS cleanup (try/finally on both entries).
|
|
- [ ] 6.5 ffmpeg non-zero exit → `AUDIO_CODEC_FFMPEG_WASM.ENCODE_FAILED`.
|
|
- [ ] 6.6 Tests in `encode-pipeline.test.ts` covering "Encode pipeline" scenarios for at
|
|
least `wav`, `mp3`, and `opus`.
|
|
|
|
## 7. Streaming emulation (`src/streaming/`)
|
|
|
|
- [ ] 7.1 Implement `streaming-decoder.ts`: `write(chunk)` appends to internal buffer and
|
|
returns `null`; `flush()` runs through the decode pipeline once.
|
|
- [ ] 7.2 Implement `streaming-encoder.ts` as the symmetric mirror.
|
|
- [ ] 7.3 Idempotent `flush()`: subsequent calls return `null` / empty bytes.
|
|
- [ ] 7.4 Progress events fire only during `flush()`.
|
|
- [ ] 7.5 Tests in `streaming-decoder.test.ts` and `streaming-encoder.test.ts` covering
|
|
"Buffered streaming emulation" scenarios.
|
|
|
|
## 8. Codec assembly (`src/codec/`)
|
|
|
|
- [ ] 8.1 Implement `ffmpeg-wasm-codec.ts`: `FfmpegWasmCodec` class wiring constructor
|
|
validation (Zod) → core loader → worker host → decode/encode pipelines →
|
|
`createDecoder` / `createEncoder` → `dispose`.
|
|
- [ ] 8.2 `canDecode(bytes)` uses `@suaveplan/audio-codecs`'s magic sniff plus the
|
|
probed encoder list to filter unsupported variants.
|
|
- [ ] 8.3 Progress callback wiring with try/catch around the user callback; exceptions
|
|
logged via `opts.logger`, never aborts the call.
|
|
- [ ] 8.4 Tests in `ffmpeg-wasm-codec.test.ts` covering "AudioCodec contract
|
|
conformance", "Progress callback wiring", "Zod boundary validation", and OTel
|
|
scenarios.
|
|
|
|
## 9. Register helper (`src/register/`)
|
|
|
|
- [ ] 9.1 Implement `register.ts`: validates options, constructs codec, calls
|
|
`registry.register(codec)`, returns the instance.
|
|
- [ ] 9.2 Tests in `register.test.ts` covering the install scenario and the Zod
|
|
rejection scenario.
|
|
|
|
## 10. OpenTelemetry instrumentation
|
|
|
|
- [ ] 10.1 Wire `audio-codec-ffmpeg-wasm.decode` and `audio-codec-ffmpeg-wasm.encode`
|
|
spans, with `format` and `multithread` attributes.
|
|
- [ ] 10.2 Increment `audio_codec_ffmpeg_wasm_decode_total` /
|
|
`audio_codec_ffmpeg_wasm_encode_total` with `format` and `outcome` labels.
|
|
- [ ] 10.3 Record `audio_codec_ffmpeg_wasm_decode_duration_ms` /
|
|
`audio_codec_ffmpeg_wasm_encode_duration_ms` driven by `opts.clock`.
|
|
- [ ] 10.4 Verify no import-time side effects via `tests/purity.test.ts`.
|
|
|
|
## 11. Tests harness
|
|
|
|
- [ ] 11.1 Every test file imports runner primitives from `@suaveplan/testing/runner`
|
|
only — never `vitest`, never `bun:test` (rule 11).
|
|
- [ ] 11.2 Use `happy-dom` for tests that touch `Worker`; supply a hand-rolled `Worker`
|
|
shim that lets us script worker messages and `error` events.
|
|
- [ ] 11.3 Use a `workerFactory` test seam to inject fakes for unit tests; reserve a
|
|
real-worker smoke test for `tests/integration.test.ts` under Bun-runs-in-Node mode.
|
|
- [ ] 11.4 Every `it()` / `test()` carries `{ timeout: 5000 }` (`30000` for the
|
|
integration suite). No mocks of `@ffmpeg/ffmpeg` itself — only the worker boundary
|
|
is faked.
|
|
- [ ] 11.5 Property test: `decode(encode(pcm)) ≈ pcm` for lossless formats (`wav`,
|
|
`flac`).
|
|
- [ ] 11.6 `tests/purity.test.ts` greps built `dist/` for forbidden Node API references
|
|
and for static imports of `@ffmpeg/core-mt`.
|
|
- [ ] 11.7 `tests/exports.test.ts` asserts each subpath export resolves and that no test
|
|
file imports `vitest` or `bun:test` directly.
|
|
|
|
## 12. Benchmarks
|
|
|
|
- [ ] 12.1 `src/__benchmarks__/decode.bench.ts` — decode a 5-minute WAV and a 5-minute
|
|
MP3 with `mitata`.
|
|
- [ ] 12.2 `src/__benchmarks__/encode.bench.ts` — encode 5-minute PCM to MP3, OPUS, and
|
|
FLAC.
|
|
- [ ] 12.3 Document the multi-thread vs single-thread delta in the benchmark output
|
|
comments.
|
|
|
|
## 13. Documentation
|
|
|
|
- [ ] 13.1 Every implementation `.ts` has a sibling `.md` (Purpose ≥2 sentences,
|
|
Features, Usage: Basic, Usage: Advanced, API Reference, Implementation Notes;
|
|
≥200 words excl. code; both simple AND advanced examples).
|
|
- [ ] 13.2 Excluded from the `.md` rule: `.test.ts`, `.types.ts` (inside a feature
|
|
dir), barrel `index.ts`, `*.schemas.ts` (Zod schemas only).
|
|
- [ ] 13.3 `README.md` covers: install, quick-start `register(registry)` usage, the COI
|
|
requirement for `enableMultithread`, the 30 MB lazy-load disclaimer, and the
|
|
buffered-streaming honesty note.
|
|
- [ ] 13.4 `bunx turbo docs:check --filter=@suaveplan/audio-codec-ffmpeg-wasm` is green.
|
|
|
|
## 14. Pre-publish gates
|
|
|
|
- [ ] 14.1 `bunx turbo lint typecheck test --filter=@suaveplan/audio-codec-ffmpeg-wasm`
|
|
is green.
|
|
- [ ] 14.2 `bunx turbo test:coverage --filter=@suaveplan/audio-codec-ffmpeg-wasm`
|
|
reports 100% on all touched modules with `provider: 'istanbul'`.
|
|
- [ ] 14.3 `bun scripts/lint/check-test-runner-imports.ts` passes for the package.
|
|
- [ ] 14.4 No `biome-ignore` comments anywhere in `src/` or `tests/` (rule 9).
|
|
- [ ] 14.5 No `export *` in any non-root barrel; root barrel uses explicit named exports
|
|
or namespace re-exports (rule 10).
|
|
- [ ] 14.6 `bun scripts/folder-reorg/generate-manifest.ts` re-run; resulting
|
|
`MANIFEST.md` diff committed.
|
|
|
|
## 15. Fold the spec
|
|
|
|
- [ ] 15.1 Fold `specs/audio-codec-ffmpeg-wasm/spec.md` into
|
|
`openspec/specs/audio-codec-ffmpeg-wasm/spec.md` per §14 archival workflow.
|
|
`openspec/specs/` is untouched until this step.
|
|
|
|
## 16. Archive
|
|
|
|
- [ ] 16.1 Move `openspec/changes/enterprise-audio-codec-ffmpeg-wasm/` to
|
|
`openspec/archive/<yyyy-mm-dd>-enterprise-audio-codec-ffmpeg-wasm/` per §14.
|
|
- [ ] 16.2 Run `openspec validate` after the move to confirm the canonical spec parses.
|