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

alhambra

v0.1.9

Published

Protect objects/arrays from mutation without deep cloning.

Downloads

15

Readme

Alhambra

Protect objects/arrays from mutation without deep cloning.

Alhambra uses Proxies and shallow clones to protect the "original" object from mutation. The "protected" object has the same behavior as the original object: getters, setters, methods, and the prototype chain act the same.

If you want to remove the Proxy wrappers from the protected object, use alhambra.release() on it.

Comparison to deep cloning

Alhambra

  • Pros:
    • Creation cost does not increase as size increases.
  • Cons:
    • Interaction cost of the clone is more expensive than the original.
    • Interaction cost increases as the nesting depth of properties increase. For example, getting clone.foo.bar is more expensive than getting clone.foo.

Deep clone

  • Pros:
    • Interaction (get, set, etc.) cost of the clone is the same as the original.
    • Interaction cost does not increase as size increases.
  • Cons:
    • Creation cost increases as size of original increases.

In other words

  • Alhambra is better when you "clone big/frequently and interact small/infrequently".
    • For example, if you clone an array of 10 million objects and only interact with a few objects.
  • Deep cloning is better when you "clone small/infrequently and interact big/frequently".
    • For example, if you clone an array of 10 million objects and iterate over the entire array.

Usage

npm install alhambra

Objects

Directly mutating an object's properties keeps the original unchanged.

const obj = { id: 1 };
const p = alhambra.protect(obj);

p.id = 2;

const newObj = alhambra.release(p);

console.log(obj.id === 1); // True
console.log(newObj.id === 2); // True

Nested properties are protected, too.

const obj = { foo: { bar: 1 } };
const p = alhambra.protect(obj);

p.foo.bar = 2;

const newObj = alhambra.release(p);

console.log(obj.foo.bar === 1); // True
console.log(newObj.foo.bar === 2); // True

The original object is returned when the protected object isn't changed.

const obj = { id: 1 };
const p = alhambra.protect(obj);
const newObj = alhambra.release(p);

console.log(obj === newObj); // True

Instantiation.

class Foo {
  constructor() {
    this.id = 1;
  }

  greet() {
    console.log('Hello!');
  }
}

const obj = new Foo();
const p = alhambra.protect(obj);

p.id = 2;
p.greet(); // Still works.

const newObj = alhambra.release(p);

console.log(obj.id === 1); // True
console.log(newObj.id === 2); // True

Arrays

Prototype methods work.

const alhambra = require('alhambra');
const arr = [1, 2, 3];
const p = alhambra.protect(arr);

p.push(4);

const newArr = alhambra.release(p);

console.log(arr.length === 3); // True
console.log(newArr.length === 4); // True

Nested array.

const alhambra = require('alhambra');
const obj = {
  foo: {
    arr: [1, 2, 3],
  },
};
const p = alhambra.protect(obj);

p.foo.bar.arr.push(4);

const newObj = alhambra.release(p);

console.log(obj.foo.arr.length === 3); // True
console.log(newObj.foo.arr.length === 4); // True

Arrays of objects

Unchanged objects keep the same reference.

const alhambra = require('alhambra');
const obj = {
  foo: {
    arr: [{ a: 1 }, { a: 2 }, { a: 3 }],
  },
};
const p = alhambra.protect(obj);

p.foo.arr[1].a = 100;

const reversed = alhambra.release(p);

console.log(reversed.foo.arr[0] === obj.foo.arr[0]); // True
console.log(reversed.foo.arr[1] === obj.foo.arr[1]); // False
console.log(reversed.foo.arr[2] === obj.foo.arr[2]); // True

Caveats

Mutations to the source can still affect the new object. This is because the protect() method is designed to protect the source, rather than new object.

const alhambra = require('alhambra');
const obj = { id: 1 };
const p = alhambra.protect(obj);

obj.id = 2;

const newObj = alhambra.release(p);

console.log(obj.id === 2); // True
console.log(newObj.id === 2); // True