12 KiB
12 KiB
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 statusreports nothing) and no in-flight worktrees with uncommitted work (git worktree listthengit statusin 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.mdmatchesfind packages -maxdepth 3 -name package.jsoncount. Drift here means a prior change archived without regenerating the manifest.
1. Scaffold the package
- 1.1 Add
@ffmpeg/ffmpegand@ffmpeg/coreto the rootpackage.jsoncatalogblock 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/withpackage.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,streamsall atworkspace:^, runtime deps on@ffmpeg/ffmpeg,@ffmpeg/core,zod catalog:forms,@opentelemetry/api catalog:observability,tslib catalog:build, andoptionalDependencies: { "@ffmpeg/core-mt": "<pinned>" }. - 1.3 Declare one
exportsentry per feature module plus the root.barrel (see proposal "Module layout"). - 1.4 Add
tsconfig.jsonextending the shared base; addvitest.config.tsusingcreateTestConfig()from@suaveplan/testing/configwithpool: 'forks'andsetupFiles: ['@suaveplan/testing/polyfills']. Configurehappy-domfor any test that touchesWorker. - 1.5 Add
README.mdat the package root only (no category READMEs). - 1.6 Run
bun scripts/folder-reorg/generate-manifest.tsto refreshMANIFEST.md.
2. Types and error codes
- 2.1 Author
src/codec/ffmpeg-wasm-codec.types.tswithFfmpegWasmCodecOptions,LoadedCore,WorkerMessageunion, and internal pipeline types. - 2.2 Author
src/codec/ffmpeg-wasm-codec.schemas.tsexportingFfmpegWasmCodecOptionsSchema(Zod) — usesimport z from "zod"perMEMORY.md. RejectcoreUrlandcorePathset simultaneously with.superRefine. - 2.3 Author
src/error-codes.tsdefiningAudioCodecFfmpegWasmError,AudioCodecFfmpegWasmErrorCode,AUDIO_CODEC_FFMPEG_WASM_ERROR_METADATA, andregisterAudioCodecFfmpegWasmErrorCodes()via@suaveplan/error'sdefineCodes. Cover all 9 codes from the spec. - 2.4 Author
src/error-codes.test.tsasserting 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'sFFmpeginstance, owns theWorkerlifecycle, exposessubmitDecode,submitEncode,terminate, and aprogressevent hook. - 3.2
AbortSignalplumbing: pre-aborted signal short-circuits before any worker message; mid-call abort callsworker.terminate()and rejects pending calls withAUDIO_CODEC_FFMPEG_WASM.ABORTED. - 3.3 Timeout watchdog using injectable
Clock; defaults 60s decode / 120s encode; maps toAUDIO_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-loadworker error →AUDIO_CODEC_FFMPEG_WASM.WORKER_SPAWN_FAILED. - 3.5 Honour
opts.workerFactorytest seam. - 3.6 Tests in
worker-host.test.tscovering 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 === trueandglobalThis.crossOriginIsolated !== true, throwAUDIO_CODEC_FFMPEG_WASM.CROSS_ORIGIN_ISOLATION_REQUIREDbefore attemptingimport("@ffmpeg/core-mt"). - 4.3 Optional-dep fallback: when COI is true but
import("@ffmpeg/core-mt")rejects, log a warning viaopts.loggerand fall back to@ffmpeg/core. Do not throw. - 4.4 Failure path: any load failure throws
AUDIO_CODEC_FFMPEG_WASM.CORE_LOAD_FAILEDwith the resolved URL incontext. - 4.5 Tests in
core-loader.test.tscovering "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 forsampleRate/channels→ run-f f32leextraction →ffmpeg.readFile("output.raw")→ deinterleave toFloat32Array[]. - 5.2 MEMFS cleanup:
deleteFileboth entries on success and on failure (try/finally). - 5.3 ffmpeg non-zero exit →
AUDIO_CODEC_FFMPEG_WASM.DECODE_FAILEDwith stderr tail. - 5.4 Tests in
decode-pipeline.test.tscovering "Decode pipeline" scenarios.
6. Encode pipeline (src/encode/)
- 6.1 Implement
encode-pipeline.ts: per-format command builders forwav,mp3,ogg,opus,flac,aac,webm, honouringbitrate/qualityoptions. - 6.2 Interleave
Float32Array[]→ singleFloat32Array→Uint8Arrayview → 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.tscovering "Encode pipeline" scenarios for at leastwav,mp3, andopus.
7. Streaming emulation (src/streaming/)
- 7.1 Implement
streaming-decoder.ts:write(chunk)appends to internal buffer and returnsnull;flush()runs through the decode pipeline once. - 7.2 Implement
streaming-encoder.tsas the symmetric mirror. - 7.3 Idempotent
flush(): subsequent calls returnnull/ empty bytes. - 7.4 Progress events fire only during
flush(). - 7.5 Tests in
streaming-decoder.test.tsandstreaming-encoder.test.tscovering "Buffered streaming emulation" scenarios.
8. Codec assembly (src/codec/)
- 8.1 Implement
ffmpeg-wasm-codec.ts:FfmpegWasmCodecclass 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.tscovering "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, callsregistry.register(codec), returns the instance. - 9.2 Tests in
register.test.tscovering the install scenario and the Zod rejection scenario.
10. OpenTelemetry instrumentation
- 10.1 Wire
audio-codec-ffmpeg-wasm.decodeandaudio-codec-ffmpeg-wasm.encodespans, withformatandmultithreadattributes. - 10.2 Increment
audio_codec_ffmpeg_wasm_decode_total/audio_codec_ffmpeg_wasm_encode_totalwithformatandoutcomelabels. - 10.3 Record
audio_codec_ffmpeg_wasm_decode_duration_ms/audio_codec_ffmpeg_wasm_encode_duration_msdriven byopts.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/runneronly — nevervitest, neverbun:test(rule 11). - 11.2 Use
happy-domfor tests that touchWorker; supply a hand-rolledWorkershim that lets us script worker messages anderrorevents. - 11.3 Use a
workerFactorytest seam to inject fakes for unit tests; reserve a real-worker smoke test fortests/integration.test.tsunder Bun-runs-in-Node mode. - 11.4 Every
it()/test()carries{ timeout: 5000 }(30000for the integration suite). No mocks of@ffmpeg/ffmpegitself — only the worker boundary is faked. - 11.5 Property test:
decode(encode(pcm)) ≈ pcmfor lossless formats (wav,flac). - 11.6
tests/purity.test.tsgreps builtdist/for forbidden Node API references and for static imports of@ffmpeg/core-mt. - 11.7
tests/exports.test.tsasserts each subpath export resolves and that no test file importsvitestorbun:testdirectly.
12. Benchmarks
- 12.1
src/__benchmarks__/decode.bench.ts— decode a 5-minute WAV and a 5-minute MP3 withmitata. - 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
.tshas 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
.mdrule:.test.ts,.types.ts(inside a feature dir), barrelindex.ts,*.schemas.ts(Zod schemas only). - 13.3
README.mdcovers: install, quick-startregister(registry)usage, the COI requirement forenableMultithread, the 30 MB lazy-load disclaimer, and the buffered-streaming honesty note. - 13.4
bunx turbo docs:check --filter=@suaveplan/audio-codec-ffmpeg-wasmis green.
14. Pre-publish gates
- 14.1
bunx turbo lint typecheck test --filter=@suaveplan/audio-codec-ffmpeg-wasmis green. - 14.2
bunx turbo test:coverage --filter=@suaveplan/audio-codec-ffmpeg-wasmreports 100% on all touched modules withprovider: 'istanbul'. - 14.3
bun scripts/lint/check-test-runner-imports.tspasses for the package. - 14.4 No
biome-ignorecomments anywhere insrc/ortests/(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.tsre-run; resultingMANIFEST.mddiff committed.
15. Fold the spec
- 15.1 Fold
specs/audio-codec-ffmpeg-wasm/spec.mdintoopenspec/specs/audio-codec-ffmpeg-wasm/spec.mdper §14 archival workflow.openspec/specs/is untouched until this step.
16. Archive
- 16.1 Move
openspec/changes/enterprise-audio-codec-ffmpeg-wasm/toopenspec/archive/<yyyy-mm-dd>-enterprise-audio-codec-ffmpeg-wasm/per §14. - 16.2 Run
openspec validateafter the move to confirm the canonical spec parses.