Files
2026-05-27 23:29:53 +01:00

93 lines
5.0 KiB
Markdown

# Proposal: enterprise-streams-metadata
- **Change ID**: enterprise-streams-metadata
- **Package**: `@suaveplan/streams-metadata`
- **Status**: PROPOSED
- **Target path**: `packages/foundation/flow/streams-metadata`
- **Spec delta**: ./specs/streams-metadata/spec.md
- **Upstream source**: `/mnt/s/web/suaveplan/packages/universal/streams-metadata`
- **Upstream enterprise audit**: `/mnt/s/web/suaveplan/enterprise/streams-metadata.md`
## Why
`@suaveplan/streams-metadata` provides a composable, stream-oriented pipeline
for extracting MIME-type and EXIF/image metadata from `Uint8Array` payloads.
Genesis already ships the prerequisites (`@suaveplan/streams`,
`@suaveplan/mime`, `@suaveplan/exif`, `@suaveplan/events`) at 100/100/100/100
coverage — this change ports the metadata package on top of them at the same
quality bar.
This proposal supersedes an earlier draft in this folder that designed a
side-channel `Promise<ExtractedMetadata>` API and folded in audio metadata.
The upstream source (which shipped to consumers) implements three
`BaseTransform` subclasses with a typed event surface; migration parity
(`openspec/AGENTS.md` §0a) mandates shipping that contract, not a redesign.
## What
A fresh universal-tier package providing:
1. `MimeTransform` — magic-bytes detection (priority) → extension fallback →
`application/octet-stream`.
2. `ExifTransform` — `ImageMetadata` via `@suaveplan/exif::readMetadata`.
3. `MetadataTransform` — composing wrapper running MIME then EXIF, forwarding
child events, catching errors, emitting `metadata:complete` /
`metadata:error`.
4. Typed event surface via `BaseTransform.on/emit` (genesis
`@suaveplan/events` no longer exports an `EventEmitter` class).
5. Typed `StreamsMetadataError` with `defineCodes("STREAMS_METADATA", …)`
registry.
6. Subpath exports per feature directory (CLAUDE.md §6).
## Parity Checklist
| Capability | Upstream | Migrated | Notes |
|---|---|---|---|
| `MimeTransform` (enrich / passthrough) | YES | YES | |
| `ExifTransform` (enrich / passthrough, `read` pass-through) | YES | YES | |
| `MetadataTransform` wrapper | YES | YES | |
| Typed events (`mime:detected`, `exif:detected`, `metadata:complete`, `metadata:error`) | YES | UP | Migrated from `EventEmitter<Events>` field to `BaseTransform.on/emit`. Genesis `@suaveplan/events` has no `EventEmitter` class. Observable surface unchanged. |
| `EnrichedPayload` / `MetadataPayload` / `MimeDetectionResult` / `MetadataMode` | YES | YES | |
| Detection precedence (magic → extension → fallback) | YES | YES | |
| `ExifTransform` raise on unknown format | YES | UP | Upstream threw plain `Error`. Migration lets `@suaveplan/exif::readMetadata` throw `ExifError(UNSUPPORTED_FORMAT)`, re-wrapped as `ExifFailedError(STREAMS_METADATA.EXIF_FAILED)` with `cause` preserved. |
| Child-event forwarding through the wrapper | YES | YES | |
| `metadata:error` + original-input return on failure | YES | YES | |
| Stable error codes (`STREAMS_METADATA.*`) | NO | UP | Upstream had no registry. Migration adds `STREAMS_METADATA_CODES` via `defineCodes`. |
| Subpath exports per feature dir | NO | UP | Upstream exported only `.`. Migration adds per-feature subpaths for tree-shaking. |
| `@suaveplan/testing/runner` test imports | NO | UP | Upstream imported from `vitest`. Migration uses canonical wrapper per CLAUDE.md §11. |
| Sibling `.md` per `.ts` with all six sections | NO | UP | Upstream shipped partial docs. Migration ships full coverage. |
| 100/100/100/100 coverage gate | NO | UP | Upstream did not enforce 100%. Migration enforces it. |
## Impact
### Affected dependents
None inside genesis at change time — this package is a fresh universal-tier
leaf in this monorepo. Future consumers (`@suaveplan/api-plugin-file-upload`)
will consume after archive.
### Risks
- Genesis `@suaveplan/events` does not export `EventEmitter`. The migration
switches to `BaseTransform`'s built-in `on`/`emit`, changing the public
observation channel from `transform.events.on(...)` to `transform.on(...)`.
This is `UP` (upgraded) not `NO` (dropped) because the observable surface
is unchanged.
- The genesis `@suaveplan/mime` dist tree-shakes its signature-registration
side effect, so external consumers (including this package) get an empty
registry at runtime. The migration installs explicit JPEG / PNG signatures
via `registerSignature` in `src/mime/mime.ts`'s module load so detection
works end-to-end without depending on an upstream fix. This is a temporary
workaround — a future change will fix the mime dist build so the
registration is universal.
## Out of scope
- Streaming-mode (chunked) MIME / EXIF detection — both transforms operate
on a fully buffered `Uint8Array` exactly as upstream did.
- Audio metadata extraction — out of scope; reserved for a follow-up change.
- Side-channel metadata promises / 64 KiB byte caps — not part of the
upstream contract.
- Adding new media format support beyond what `@suaveplan/mime` and
`@suaveplan/exif` already provide.