Initial Commit
This commit is contained in:
145
openspec/archive/2026-04-19-enterprise-serialize/proposal.md
Normal file
145
openspec/archive/2026-04-19-enterprise-serialize/proposal.md
Normal file
@@ -0,0 +1,145 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user