Rxova
Skip to content

Serialization: Dates, Maps, and the text paths

BroadcastChannel carries structured clone. A Date arrives a Date, a Map arrives a Map.

Two paths in this library carry text instead — the storage-event transport (browsers without BroadcastChannel) and disk persistence — and JSON is a strictly poorer format:

You storeJSON gives back
new Date()a string
new Map() / new Set(){}
undefined propertythe key is gone
/regex/{}
Uint8Arrayan object of indices

So the same call had two different answers depending on which transport the browser happened to offer. That is the kind of difference discovered in production, on the one browser you didn’t test.

The default serializer refuses every value JSON would silently change:

store.set('createdAt', new Date());
// TypeError: use-everywhere: "createdAt" is a Date (JSON makes it a string),
// which JSON cannot round-trip. Pass a serializer (devalue, superjson) or keep
// the value JSON-shaped.

Same call store.set() already makes for values structured clone rejects: one actionable error naming the key beats two replicas that quietly disagree.

On the persistence path it reports through onError rather than throwing — persistence is best-effort and must never break your page — but it is no longer silent.

BigInt and circular references need no special handling: JSON.stringify already throws on both.

Pass a Serializer. Two methods, no dependency:

import * as devalue from 'devalue';
const settings = defineStore('settings', {
persist: localStorageAdapter('app:settings', {
serializer: { stringify: devalue.stringify, parse: devalue.parse },
}),
});

superjson works the same way. So does anything of your own — the interface is { stringify(value): string; parse(text): unknown }.

The StorageTransport takes one too, as its third argument, so the wire and the disk can be given matching fidelity.

Measured, brotlied, bundled as a production app would:

Size
@ungap/structured-clone1.0 kB
devalue3.4 kB
superjson3.6 kB
seroval7.4 kB

The whole of @use-everywhere/core is 7.3 kB. Bundling devalue would add 47% to every user’s bundle for a fidelity most applications don’t need, because most state is already JSON-shaped.

So the seam is the answer, not the dependency — the same call as payload schemas, where the library accepts any Standard Schema without depending on Zod.