146 lines
5.7 KiB
Markdown
146 lines
5.7 KiB
Markdown
# 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
|