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

146 lines
5.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# OpenSpec Change: enterprise-serialize
- **Change ID**: enterprise-serialize
- **Package**: `@suaveplan/serialize`
- **Category**: universal
- **Target path**: `packages/universal/serialize`
- **Status**: IN PROGRESS
- **Date**: 2026-04-18
- **Reference spec**: `/mnt/s/web/suaveplan/enterprise/serialize.md`
## Summary
Greenfield implementation of `@suaveplan/serialize` for the genesis branch. The existing main-branch serialize package provides the baseline public-API surface to preserve; this change delivers round-trip-preserving structured serialization — tagged JSON (preserving `Date`, `Map`, `Set`, `BigInt`, `Uint8Array`, typed arrays, `URL`, `RegExp`, user-defined tags) plus MessagePack binary profile and streaming variants. Eval-safe by default; opt-in legacy adapters isolated under `@suaveplan/serialize/unsafe`.
## Current-state audit (genesis)
The package does not exist on genesis yet — this is a fresh build.
## Available workspace deps on genesis
| Package | Available |
|---------|-----------|
| `@suaveplan/error` | ✅ |
| `@suaveplan/types` | ✅ |
| `@suaveplan/streams` | ❌ not yet built — implement chunked stream via Web Streams API internally |
| `@suaveplan/opentelemetry` | ❌ not yet built — use `@opentelemetry/api` directly |
| `@suaveplan/feature-flags` | ❌ not yet built — skip |
| `@suaveplan/schema-registry` | ❌ not yet built — export Zod schemas directly |
External deps:
- `zod` → `catalog:forms`
- `@opentelemetry/api` → `catalog:observability`
- `@msgpack/msgpack` → add to `catalog:serialization` (new catalog group) at `^3.0.0`
- `tslib` → `catalog:build`
Unsafe-subpath-only (isolated, not in main bundle):
- `serialize-javascript` → add to `catalog:serialization` at `^6.0.0` — only imported by `src/adapters/eval-safe/` which lives under the `/unsafe` subpath export
Dev deps (via catalog):
- `vitest`, `@vitest/coverage-istanbul` → `catalog:testing`
- `fast-check` → `catalog:testing`
- `mitata` → `catalog:testing`
## Preserved public API surface (from main branch)
### Main export (`@suaveplan/serialize`)
#### Codec
- `serialize(value, opts?)` → `string` — tagged JSON serialization
- `deserialize(json, opts?)` → `unknown` — tagged JSON deserialization
- `encodeBinary(value, opts?)` → `Uint8Array` — MessagePack encoding
- `decodeBinary(bytes, opts?)` → `unknown` — MessagePack decoding
- `encodeStream(value, opts?)` → `ReadableStream<Uint8Array>` — streaming MessagePack
- `decodeStream(stream, opts?)` → `ReadableStream<unknown>` — streaming decode
- `deepClone(value)` → `unknown` — structured clone via serialize/deserialize round-trip
- `SerializeOptions`, `DeserializeOptions`, `Codec` (types)
#### Type registry
- `registerType(def)` — register a custom type tag
- `unregisterType(tag)` — remove a custom type tag
- `listTypes()` → `TypeDefinition[]` — all registered types
- `getType(tag)` → `TypeDefinition | undefined`
- `getTypeByCtor(ctor)` → `TypeDefinition | undefined`
- `TypeDefinition` — `{ tag: string; ctor: Function; encode: (v) => unknown; decode: (v) => unknown }`
#### Errors
- `SerializationError` extends `SuaveplanError`
- `CircularReferenceError` extends `SerializationError`
- `MaxDepthExceededError` extends `SerializationError`
- `MaxSizeExceededError` extends `SerializationError`
- `UnsupportedTypeError` extends `SerializationError`
- `SerializationErrorCode`, `SerializationErrorContext`, `SerializationErrorOptions` (types)
#### Error codes (re-exported)
- `registerSerializeErrorCodes()`, `SERIALIZE_ERROR_METADATA`, `SerializeErrorCode`
#### Legacy
- `legacySerialize(value)` → `string` — old format compat (no new tags; for migration only)
- `legacyDeserialize(json)` → `unknown` — old format compat
### Unsafe subpath (`@suaveplan/serialize/unsafe`)
- `serializeEvalSafe(value, opts?)` → `string` — uses `serialize-javascript` under the hood; emits a security warning in dev
- `deserializeEvalSafe(json, opts?)` → `unknown`
- `EvalSafeOptions` (type)
## New additions for genesis
### Catalog entry
- Add `serialization` catalog group to root `package.json`:
```json
"serialization": {
"@msgpack/msgpack": "^3.0.0",
"serialize-javascript": "^6.0.0"
}
```
### Structured errors
- `SerializationError` extends `SuaveplanError` with codes under `SERIALIZE.*` namespace
- Codes: `SERIALIZE.CODEC.CIRCULAR_REFERENCE`, `SERIALIZE.CODEC.MAX_DEPTH`, `SERIALIZE.CODEC.MAX_SIZE`, `SERIALIZE.CODEC.UNKNOWN_TAG`, `SERIALIZE.REGISTRY.DUPLICATE_TAG`, `SERIALIZE.LEGACY.REFUSED`
### Zod schemas
- `SerializeOptionsSchema`, `DeserializeOptionsSchema`, `TypeDefinitionSchema` — all public config types validated at boundary
### AbortSignal support
- `encodeStream` and `decodeStream` honour `AbortSignal` passed via options
### OTel instrumentation
- Histogram: `serialize.payload_bytes` (encode + decode, by format label json/msgpack)
- Counter: `serialize.error.total` (by error code label)
### Property-based tests (fast-check)
- Round-trip for every built-in type: `deserialize(serialize(x)) deep-equals x`
- Binary round-trip: `decodeBinary(encodeBinary(x)) deep-equals x`
- JSON–binary structural equivalence: both formats produce semantically equal output
- Cycle detection: any circular graph always throws `CircularReferenceError`
- Custom tag round-trip: registered tag encodes and decodes without data loss
### Benchmarks (mitata)
- `serialize` / `deserialize` 10 KB nested object
- `encodeBinary` / `decodeBinary` 10 KB nested object
- `deepClone` 10 KB object vs `structuredClone` baseline
## Impact
- Adds one new package: `packages/universal/serialize`
- Adds `serialization` catalog group to root `package.json`
- Adds one new spec capability: `serialization`
- No existing package consumers on genesis yet