Files
suaveplan/openspec/archive/2026-04-19-enterprise-serialize/proposal.md
2026-05-27 23:29:53 +01:00

5.7 KiB
Raw Blame History

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:

    "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