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

@gialicus/smart-object

v2.0.2

Published

Oggetti TypeScript generati da schemi Zod con getter, set* tipizzati e operazioni JSON Patch

Readme

@gialicus/smart-object

Typed TypeScript objects backed by Zod schemas, with an RFC 6902 operation log for every validated change. Use them when you need mutable, type-safe state plus a portable delta trail for audit, sync, or replay — without bolting on a separate change-tracking layer.

Installation

npm install @gialicus/smart-object zod

Dependencies: Zod ^4.0.0 (peer dependency — schema validation) and fast-json-patch (RFC 6902 patch application, bundled).

Usage

import z from "zod";
import { SmartObject } from "@gialicus/smart-object";

const Person = SmartObject(z.object({
    name: z.string(),
    age: z.number(),
}));

const person = new Person({ name: "Mario", age: 30 });

// Reads expose validated state
console.log(person.name); // "Mario"

// Writes validate first, then append patches to person.operations
person.setName("Luigi");
person.setAge(31);

console.log(person.operations);
// [
//   { op: "replace", path: "/name", value: "Luigi" },
//   { op: "replace", path: "/age", value: 31 },
// ]

person.setName("Luigi"); // Unchanged value — no operation added (keeps sync payloads minimal)

person.clearOperations(); // Drops the audit trail after persist/sync; state is unchanged

// Snapshot for serialization — deep clone, safe for JSON.stringify
console.log(person.toJSON());

// Initial construction is the replay baseline — it never emits operations
console.log(new Person({ name: "Mario", age: 30 }).operations); // []

// Reconstruct from baseline + accumulated deltas
const initial = { name: "Mario", age: 30 };
const person2 = Person.fromOperations(initial, [...person.operations]);

Union root schemas

SmartObject also accepts z.discriminatedUnion(...) and z.union([...]) when every option is a z.object(...):

const Event = SmartObject(z.discriminatedUnion("type", [
    z.object({ type: z.literal("click"), x: z.number(), y: z.number() }),
    z.object({ type: z.literal("scroll"), delta: z.number() }),
]));

const event = new Event({ type: "click", x: 10, y: 20 });
event.setX(15);

// Switch the active union variant atomically
event.switchToScroll({ delta: 5 });
// or: event.switchVariant({ type: "scroll", delta: 5 });

See examples/event.ts and examples/profile.ts for full demos.

Object and array getters always return deep clones — in-place mutation does not affect internal state or the operation log. Only set* methods generate RFC 6902 operations.

API

SmartObject(schema)

Factory that accepts a Zod schema and returns an instantiable class.

| Parameter | Type | Description | |-----------|------|-------------| | schema | z.ZodObject | z.ZodUnion | z.ZodDiscriminatedUnion | z.ZodIntersection | z.ZodLazy | Zod schema defining the object shape |

Members generated for each schema field foo:

| Member | Type | Description | |--------|------|-------------| | foo | getter | Exposes the current validated field value (deep clone for objects and arrays) | | setFoo(value) | (value: T) => void | Validates, updates state, and records patches only when the value actually changes |

set* method names follow camelCase with the field name capitalized (namesetName, userIdsetUserId).

Union root extras (discriminated and generic unions):

| Member | Type | Description | |--------|------|-------------| | switchVariant(value) | (variant) => void | Replaces the entire active variant after full schema validation | | switchTo{Variant}(fields) | (fields) => void | Discriminated unions only — switches to a variant without repeating the discriminator (e.g. switchToScroll({ delta: 5 })) |

Record and Map field extras (for each z.record(...) field or string-key z.map(...) field tags):

| Member | Type | Description | |--------|------|-------------| | getTagsEntry(key) | (key: string) => V \| undefined | Reads a single entry | | setTagsEntry(key, value) | (key: string, value: V) => void | Validates and patches a single entry (/tags/{key}) | | deleteTagsEntry(key) | (key: string) => void | Removes an entry |

Instance members:

| Member | Type | Description | |--------|------|-------------| | operations | readonly Operation[] | Chronological RFC 6902 patch log (defensive copy) | | clearOperations() | () => void | Clears the patch log without rolling back state | | toJSON() | () => T | Deep clone of current state, safe for JSON.stringify |

Static members:

| Member | Type | Description | |--------|------|-------------| | fromOperations(initial, operations) | (initial, Operation[]) => Instance | Builds an instance from a baseline, replays and validates operations, and copies them into the accumulator |

SmartObjectError

Structured error thrown on validation failures, invalid union field access, and failed replay:

| Property | Type | Description | |----------|------|-------------| | code | "InvalidValue" | "InvalidUnionField" | "InvalidReplay" | "UnsupportedSchema" | Error category | | field | string | undefined | Schema field path when applicable | | cause | unknown | Original error (e.g. ZodError) |

Operation

RFC 6902 operation emitted by fast-json-patch when a field actually changes. Examples:

{ op: "replace", path: "/name", value: "Luigi" }
{ op: "add", path: "/age", value: 30 }

Exported types

  • Operation — JSON Patch operation (re-export from fast-json-patch)
  • SmartObjectSchema — union of Zod schema types accepted by SmartObject()
  • SmartObjectErrorCode"InvalidValue" | "InvalidUnionField" | "InvalidReplay" | "UnsupportedSchema"
  • SetMethods<T> — mapped type of inferred set* methods for shape T
  • SetMethodsUnion<T>set* methods for union root schemas
  • AllKeys<T> — all keys across union members
  • UnionDataShape<U> — flattened data shape for union roots
  • VariantSwitchMethods<T>switchVariant for union roots
  • DiscriminatedVariantSwitchMethods<T, D>switchVariant plus generated switchTo* methods
  • RecordFieldMethods<T> — dynamic entry accessors for z.record and string-key z.map fields
  • OperationsAccessoroperations and clearOperations()
  • SnapshotAccessor<T>toJSON()
  • SmartObjectConstructor<T> — constructor type including fromOperations
  • SmartObjectInstance<T> — full instance type (getters + set* + operations + toJSON)

Limitations

  • Partial discriminator write — Changing a discriminated union discriminator alone via setType(...) without providing the new variant fields throws SmartObjectError. Use switchVariant(...) or switchTo{Variant}(...) instead.
  • Union field on wrong variant — Setting a field that does not exist on the active variant throws SmartObjectError.
  • Date fieldsz.date() and z.coerce.date() are supported; operations store ISO 8601 strings while getters return Date instances.
  • Map, Set, and bigintz.map (string keys), z.set, and z.bigint() are supported with explicit codecs; operations use JSON-safe plain objects, arrays, and decimal strings respectively. String-key z.map fields also expose per-entry get/set/delete*Entry methods; whole-field set* is used for z.set and for map fields with non-string keys (not supported). Non-string map keys are not supported.
  • Transformsz.transform / z.pipe with preprocessing work at runtime; operations store the output value after validation. TypeScript setter input types may not reflect transforms.

Design rationale

  1. Constructionnew Person(initial) validates and seeds internal state without emitting operations, because that snapshot is the baseline every later patch is measured against.
  2. Validation — Each write is validated against the schema so the operation log only records structurally valid changes.
  3. No-op writes — Identical values are skipped to keep the patch log minimal and suitable for network sync.
  4. Patch-based updates — Changes are expressed as RFC 6902 operations so deltas are standard, composable, and replayable.
  5. Operation accumulation — Patches from compare are appended in order, preserving causality for audit and replay.
  6. ReplayfromOperations(initial, operations) replays patches, re-validates with Zod, and requires the same baseline used when the operations were produced.

Examples

Project structure

smart-object/
├── src/
│   ├── index.ts              # Public API barrel export
│   ├── types.ts              # Operation and inferred types
│   ├── errors.ts             # SmartObjectError
│   ├── zod-introspect.ts     # Zod schema introspection
│   └── smart-object/
│       ├── index.ts          # Re-export SmartObject
│       ├── factory.ts        # Public SmartObject() factory
│       ├── build-class.ts    # Class generation orchestration
│       ├── instance-state.ts # WeakMap-backed instance storage
│       ├── read-field.ts     # Defensive getter reads
│       ├── json-patch.ts     # fast-json-patch wrapper + Date-safe deepClone
│       ├── codecs.ts         # JSON-safe codecs for Date, Map, Set, and bigint
│       ├── apply-operations.ts # Replay and rollback
│       ├── union-variant.ts  # Union variant matching
│       ├── define-prototype.ts # Getter/setter prototype setup
│       └── setters/
│           ├── object-field.ts
│           ├── union-field.ts
│           ├── variant-switch.ts
│           └── record-field.ts
├── examples/
│   ├── person.ts
│   ├── event.ts
│   ├── profile.ts
│   └── record.ts
├── tests/
│   ├── fixtures/
│   │   ├── person.ts
│   │   ├── entity.ts
│   │   ├── event.ts
│   │   └── profile.ts
│   ├── smart-object/
│   │   ├── construction.test.ts
│   │   ├── getters.test.ts
│   │   ├── setters.test.ts
│   │   ├── clear-operations.test.ts
│   │   ├── from-operations.test.ts
│   │   ├── union-fields.test.ts
│   │   ├── discriminated-union-root.test.ts
│   │   ├── generic-union-root.test.ts
│   │   ├── robustness.test.ts
│   │   ├── setter-naming.test.ts
│   │   ├── to-json.test.ts
│   │   ├── schema-variants.test.ts
│   │   ├── record-fields.test.ts
│   │   ├── date-codec.test.ts
│   │   ├── complex-schema-types.test.ts
│   │   ├── intersection-lazy.test.ts
│   │   └── types.test.ts
│   └── zod-introspect.test.ts
└── dist/                     # Build output (generated)