npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@oh-just-another/serialization

v0.60.0

Published

Versioned scene (de)serialization with forward migrations and zod validation.

Readme

@oh-just-another/serialization

npm version

L2 wire format for @oh-just-another/scene. Validates and round-trips a scene through a versioned JSON document. Pure logic — no DOM, no Node API.

Quick start

import { serializeScene, stringifyScene, parseScene } from "@oh-just-another/serialization";

// Save
localStorage.setItem("scene", stringifyScene(editor.scene));

// Load
const scene = parseScene(localStorage.getItem("scene")!);
editor.loadScene(scene);

Wire format

{
  "format": "oh-just-another/scene",
  "version": 1,
  "elements": [...],
  "links": [...],
  "layers": [...],
  "viewport": { "pan": {...}, "zoom": 1, "rotation": 0, "size": {...} }
}
  • format — magic constant, lets a file be recognized without sniffing.
  • version — incremented on any breaking schema change. Migrations live in migrations.ts.
  • The element sub-schema is a z.discriminatedUnion over every built-in type, plus a passthrough arm for plugin-registered types.

API

| Name | Purpose | | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | | CURRENT_VERSION, SceneDocument, SceneDocumentZ | Wire-format constants and types. | | serializeScene(scene) / stringifyScene(scene, indent?) | In-memory Scene → wire document / JSON string. | | deserializeScene(raw, options?) / parseScene(json, options?) | Wire document / JSON string → typed Scene. Runs migrations + zod validation + brand re-application. | | DeserializationError | Thrown on validation failure. reason carries the original ZodError. | | sceneJsonSchema() | JSON Schema (draft-07) of the wire document, generated from the zod schema. For LLM structured output / external validators. | | registerMigration(fromVersion, fn) / runMigrations(doc, from, to) | Forward migrations between schema versions. | | MissingMigrationError | Thrown when an intermediate version has no migration registered. | | serializeFiles(files) / stringifyFiles(files, indent?) | Scene.files binary sidecar → wire document / JSON string. | | parseFiles(json) | JSON string → typed Scene.files. | | SerializedFilesDocument | Wire-format type for the file sidecar. |

Design notes

  • One version field, forward-only migrations. No back-migrations — exporting always lands at CURRENT_VERSION. Loading an older version walks through every migration in order.
  • Strict schemas with a passthrough arm for unknown shape types. Plugins that register a custom shape can persist it without modifying the wire schema; the kernel just hands the raw object to the bounder/renderer registry on load.
  • Deserialise re-brands ids. ElementId / LinkId / LayerId are branded strings in the kernel but plain strings in JSON; hydrate casts them back through elementId() / linkId() / layerId().
  • exactOptionalPropertyTypes workaround. Zod's parsed output exposes optional fields as T | undefined, which the kernel rejects. The hydrate path strips undefined-valued keys before constructing the typed shape.