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 🙏

© 2024 – Pkg Stats / Ryan Hefner

@asmartbear/augmented

v1.1.14

Published

Objects that look and act exactly like native Javascript types, but augmented with validation of type and value, hooks on changes, and synchronization with other replicas

Downloads

7

Readme

Augmented Objects

Objects that look and act exactly like native JavaScript types ("POJO"), but augmented with validation of type and value, callbacks before and after changes, and synchronization with other replicas (peer-to-peer or hub-and-poke to a server).

Usage

Install through npm, or get the source from Github.

Creating objects and using objects normally

createManager() and so on....

Use createObject() to create augmented objects. They behave just like regular JavaScript objects, both with scalar types (null, boolean, number, string) and nested objects and arrays, including reflection and common algorithms like conversion to JSON:

import * as A from '@asmartbear/augmented'

const mgr = A.createManager();
const x = mgr.createObject();
x.a = 1;             // supports all scalar types
x.b = { c:2, d:4 };  // supports nested objects and arrays
x.b.d = "hi";        // change data types
console.log(x);
/// {a:1,b:{c:2,d:"hi"}}
console.log(Object.keys(x));
/// a,b
console.log(JSON.stringify(x));
/// {"a":1,"b":{"c":2,"d":"hi"}}

Serialization & Cloning

You can serialize objects into a compressed binary form suitable for storage or communication. The state includes not only the content of the object, but synchronization state as well.

Cloning is like serializing and unserializing in succession, except faster (because we skip data compression).

You can serialize and clone native objects and augmented objects with the same functions.

import * as A from '@asmartbear/augmented'

const mgr = A.createManager();
const data = {a:1,b:{c:2,d:"hi"}};
const x = Object.assign(mgr.createObject(), data);
console.log(x);
/// {a:1,b:{c:2,d:"hi"}}
const ser = mgr.serialize(x);       // convert to compressed byte array
console.log(ser.byteLength < 150);  // even with sync data, it's small!
/// true
const y = mgr.unserialize(ser);     // recreate the object
console.log(y);
/// {a:1,b:{c:2,d:"hi"}}
}   // omit

Synchronization

The algorithm of synchronization is described further below. Augmented objects can merge their state with any number of replicas, no matter how out-of-date, without tombstones that grow without bound (like classic CRDTs), and without a central server or ever-growing log (like operation/OTs). Merge conflicts resolve automatically. Synchronization state is included in serialize()/unserialize(), so you can move objects between processes and synchronize them with a single line of code.

import * as A from '@asmartbear/augmented'

// Create an object on Replica 1
const replica1 = A.createManager("R1");
const x = replica1.createObject();
Object.assign(x, {a:1,b:2,c:3});
console.log(x);
/// {a:1,b:2,c:3}

// Create an object on Replica 2, and "synchronize" it.
// In this (working!) sample code, it's another replica in the same
// process space, but in practice you would presumably `serialize()`
// the object, send to Replica 2, then `unserialize()` there.
const replica2 = A.createManager("R2");
const y = replica2.createObject();
A.synchronize(y, x);    // synchronizes y <-- x
console.log(y);
/// {a:1,b:2,c:3}
console.log(x === y);   // they are distinct objects!
/// false

// Modify the first object `x`.  If you attempt to synchronize `y`
// back to `x`, nothing happens, because x's changes are newer, and
// synchronization is idempotent.
delete x.a;
x.b = 222;
x.d = 4;
console.log(x);
/// {b:222,c:3,d:4}
A.synchronize(x, y);
console.log(x);         // no change!
/// {b:222,c:3,d:4}

// Modify `y` also, then synchronize `x`.  If one changes a key that
// the other deletes, the result is a deletion. But if two changes
// conflict, it's last-write-wins.
y.a = 111;          // the delete from `x` will win
y.b = "twotwotwo";  // the change in `y` will win, because it's later
y.d = 4;            // both made the same change here; no problem
y.e = 5;            // a new key
console.log(y);
/// {a:111,b:"twotwotwo",c:3,d:4,e:5}
A.synchronize(y, x);        // y <-- x
console.log(y);
/// {b:"twotwotwo",c:3,d:4,e:5}
A.synchronize(x, y);        // x <-- y
console.log(x);             // now both are the same
/// {b:"twotwotwo",c:3,d:4,e:5}

Observed-Remove semantics

Yadda yadda!

import * as A from '@asmartbear/augmented'

// Initial setup, three replicas, with no synchronization yet.
//
// Notice `x` and `y` both create field `a` with different values.  If we
// synchronize now, they'll all converge to `{a:111,b:2,c:3,d:4}`, merging
// the fields, taking the latest copy of `a`.
const x = Object.assign(A.createManager("X").createObject(), {a:1,b:2});
const y = Object.assign(A.createManager("Y").createObject(), {a:111,c:3});
const z = Object.assign(A.createManager("Z").createObject(), {d:4});

// Synchronize `z` <-- `y`, then `z` deletes field `a`.
A.synchronize(z,y);
console.log(z);
/// {a:111,c:3,d:4}
delete z.a;
console.log(z);
/// {c:3,d:4}

// If we synchronize this back to `y`, the deletion is synchronized, and
// both objects are identical, as you would always expect with eventual
// consistency.
A.synchronize(y,z);
console.log(y);
/// {c:3,d:4}

// However, the `a` field in `x` is a different field!  It's the same
// name by coincidence, but when `z` deleted `y.a`, it didn't know about
// `x.a` yet; we say `z` didn't "observe" `x.a`.  Therefore, synchronizing
// `x` <-- `z` will _not_ result in a deletion!
A.synchronize(x,z);
console.log(x);
/// {a:1,b:2,c:3,d:4}

// Even synchronizing `x` <-- `y` will not result in a deletion, because
// `y` also has not "observed" `x.a` yet.
A.synchronize(x,y);
console.log(x);
/// {a:1,b:2,c:3,d:4}

// However, synchronizing the other direction will succeed; both `y` and
// `z` will take this previously-unobserved field `a`.  Thus, eventual
// consistency has been achieved, even with "observation" semantics.
A.synchronize(y,x);
A.synchronize(z,x);
console.log(y);
/// {a:1,b:2,c:3,d:4}
console.log(z);
/// {a:1,b:2,c:3,d:4}

// On the other hand, if we create `x.n` and `y.n`, and first synchronize
// both to `z`, and _then_ delete `z.n`, then the deletion will synchronize
// back to both, because both were "observed" by `z` this time.
x.n = 123;
y.n = 321;
A.synchronize(z,x);
A.synchronize(z,y);
console.log(z);             // the later (321) value will win
/// {a:1,b:2,c:3,d:4,n:321}
delete z.n;
console.log(z);
/// {a:1,b:2,c:3,d:4}
A.synchronize(x,z);
console.log(x);
/// {a:1,b:2,c:3,d:4}
A.synchronize(y,z);
console.log(y);
/// {a:1,b:2,c:3,d:4}

Synchronization Algorithm

Tacos are great.