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

@toolsplus/json-evolutions

v3.1.0

Published

JSON evolutions

Downloads

33

Readme

JSON Evolutions

@toolsplus/json-evolutions evolves stored JSON objects through explicitly versioned changesets while application code works with the latest Effect Schema representation.

Version 3 is Effect 4-native and ESM-only. It deliberately replaces the v1 io-ts/fp-ts interface while preserving the stored _version protocol.

Install

npm install @toolsplus/json-evolutions effect@^4.0.0-rc.109

The Effect peer accepts RC 109 or newer compatible Effect 4 releases. Development and package smoke tests remain pinned to RC 109 as the supported baseline. Node.js 24 or newer is required.

Complete example

This example is compiled and executed against the packed npm artifact during the package smoke test.

import {Effect, Result, Schema} from "effect";
import {
    createChangelog,
    evolve,
    evolveAndDecode,
    immutabilityHelperChangeset,
    jsonPatchChangeset,
    versioned,
} from "@toolsplus/json-evolutions";

const addEnabled = jsonPatchChangeset({
    _version: 1,
    patch: [{op: "add", path: "/enabled", value: true}],
});
const addLabel = immutabilityHelperChangeset({
    _version: 2,
    spec: {$merge: {label: "current"}},
});
const changelog = Result.getOrThrow(createChangelog(addLabel, addEnabled));

const Configuration = Schema.Struct({
    enabled: Schema.Boolean,
    label: Schema.String,
});
const StoredConfiguration = Configuration.pipe(versioned(changelog));

const program = Effect.gen(function* () {
    const stored = yield* Schema.encodeUnknownEffect(StoredConfiguration)({
        enabled: false,
        label: "saved",
    });
    const configuration = yield* evolveAndDecode(StoredConfiguration)({
        _version: 0,
    });
    const evolved = yield* evolve(changelog)({
        _version: 1,
        enabled: false,
    });
    const initialized = yield* evolve(changelog, {
        initializeFromUnversioned: (input) =>
            Effect.succeed({...input, _version: 0 as const}),
    })({});
    return {stored, configuration, evolved, initialized};
});

const fallback = {_version: 2, enabled: false, label: "fallback"} as const;
const recovered = evolve(changelog)({_version: "invalid"}).pipe(
    Effect.catchTags({
        InvalidStoredValue: (error) =>
            Effect.logWarning(error.message).pipe(Effect.as(fallback)),
        UnsupportedFutureVersion: (error) =>
            Effect.fail(
                new Error(`Cannot read stored version ${error.version}`),
            ),
    }),
);

const result = await Effect.runPromise(program);
if (result.stored._version !== 2 || !result.configuration.enabled) {
    throw new Error("JSON evolution example failed");
}
void recovered;

StoredConfiguration.Type is the application value {readonly enabled: boolean; readonly label: string}. Its encoded representation adds _version: number. Runtime decoding accepts only _version: 2, the exact latest version derived from the retained changelog. Field transformations and their service requirements are preserved.

evolveAndDecode evolves historical input, validates the exact current marker, decodes the business representation, and removes _version. Use evolve when business decoding is not wanted. An initializer runs only for a strict JSON root object with no own version marker; its service requirements propagate, typed failures are wrapped, and defects remain defects.

Changeset adapters

jsonPatchChangeset exposes only the six RFC 6902 operations: add, remove, replace, move, copy, and test. JSON Pointer syntax and document-dependent applicability are delegated to fast-json-patch. The root _version path and source are rejected by createChangelog; nested properties such as /settings/_version remain valid.

Changelogs are trusted source declarations. An immutabilityHelperChangeset spec is deliberately opaque and receives only a shallow object-or-function guard during changelog construction. Helper functions, $apply, Map/Set commands, and registered custom commands are not claimed to be serializable. Every delegate result is still strictly validated before evolution continues.

Tagged errors

Evolution uses these schema-backed, yieldable error classes:

  • InvalidStoredValue
  • UnsupportedFutureVersion
  • InitializeFromUnversionedFailed
  • InitializeFromUnversionedReturnedInvalidValue
  • InvalidChangelog
  • JsonPatchEvolutionError
  • ImmutabilityHelperEvolutionError

Match them through _tag, as the complete example does with Effect.catchTags. Business-schema failures from evolveAndDecode remain Schema.SchemaError. Foreign and schema causes are retained through Schema.Defect().

Stored-value guarantees

A stored value must be a genuine JSON root object with an own non-negative safe-integer _version. Nested values may contain only objects, arrays, strings, finite numbers, booleans, and null.

Functions, undefined, symbols, bigint, Date, Map, Set, non-finite numbers, cycles, and root arrays are rejected. Changesets never mutate the original input. The engine owns _version, stamps it after each successful changeset, and validates the newly stamped value before continuing. Business structs may not declare the reserved root marker.

See the v1-to-v3 migration guide for source migration details.