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

@jsheaven/observed

v1.0.4

Published

Nano library to transparently observe and/or intercept JavaScript Object/Array mutations via Proxy and Reflect at any depth.

Downloads

23

Readme

Transparently observe and/or intercept JavaScropt Object and Array mutations (data changes) at any level (depth)

  1. As a developer, I want to define a store as an object at one location in code, but want hooks to be called somewhere else, when values in that object change at any level I'm interested in, atomically (e.g. foo.bar.xyz[1]).

  2. As a developer, I want the ability to intercept mutations and change the value of the mutation before it happens (meta-programming, indirection).

  3. As a developer, I want to observe mutations before and after they happen. I want the ability to listen for mutation globally and/or atomically to specific locations in the object tree.

  4. As a developer, I want the library to act transparently, so that every mutation (set) comes thru, even though the value may be the same.

  5. As a developer, I expect great performance without unnecessary overhead/dependencies. I expect O(1) runtime complexity on average.

  • ✅ Observes mutations (value changes) in JavaScript Objects and Arrays at any level
  • ✅ Available as a simple, functional API: const myObservedObj = observed({ foo: { test: 123, bar: [true] } });
  • ✅ Observe changes at any level foo.test etc.
  • ✅ Allows to stop observing at any level using offSet(myObservedObj.foo)
  • ✅ Allows to register interceptors to change mutation values
  • ✅ Allows to register global listeners and interceptors
  • ✅ Allows to control depth of observation
  • ✅ Just 720 byte nano sized (ESM, gizpped)
  • ✅ 0 dependencies
  • ✅ Tree-shakable and side-effect free
  • ✅ First class TypeScript support
  • ✅ 100% Unit Test coverage
  • yarn: yarn add @jsheaven/observed
  • npm: npm install @jsheaven/observed
import { observed, onSet, offSet } from '@jsheaven/observed'

const someObject = { foo: 'X', cool: { test: 123 } }

// is now observed, by default deeply
const someObjectObserved = observed(someObject)

const onSomethingInCoolChange = (prop, value, prevValue) => {
  console.log(prop, 'changed from', prevValue, 'to', value)
}

// listen to changes, by default in 'after' phase, when it already happened
onSet(someObjectObserved.cool, onSomethingInCoolChange)

someObjectObserved.cool.test = 456 // prints to console now

// lets set an interceptor, so we can change the values before the change happens
const interceptAndChangeWhenSomethingInCoolChanges = (prop, value, prevValue) => {
  console.log(prop, 'changed from', prevValue, 'to', value)
  if (prop === 'test' && typeof value === 'number') {
    return value * 2 // always double
  }
  return value // original change
}
onSet(someObjectObserved.cool, interceptAndChangeWhenSomethingInCoolChanges, 'before')

someObjectObserved.cool.test = 456 // prints 912 to console now because the doubling of the value happens first

someObjectObserved.lala = 234 // nothing happens, there is no listener on someObject directly

someObjectObserved.foo = 'Y' // nothing happens, also here there is no listener

// you can also test an object to be observed (if Proxy the proxy is set)
isObserved(someObjectObserved) // true
isObserved(someObject) // false

// and also fetch the callback references from an observed object
getObservers(someObjectObserved.cool) // [ ... ]

// to stop listenting/intercepting specifically:
offSet(someObjectObserved.cool, onSomethingInCoolChange)

// to remove all callbacks on that level at once:
offSet(someObjectObserved.cool)
const { observed } = require('@jsheaven/observed')

// same API like ESM variant
import { observed } from '@jsheaven/observed'

// original object always stays untouched (never mutated from observed objects mutations)
const someObject = { foo: 'X', cool: { test: 123 } }

// now lets say we that we want to observe any change at any level
const someObjectObserved = observed(someObject, {
  onSet: (prop, value, prevValue) => {
    console.log(prop, 'changed from', prevValue, 'to', value)
  },
  onGet: (prop, value, target, receiver) => {
    console.log(prop, 'has been read/accessed with value', value, 'in target object', target, 'receiver', receiver)
  },
  onBeforeSet: (prop, value, prevValue) => {
    console.log(prop, 'changed from', prevValue, 'to', value)
    if (typeof value === 'number') {
      return value * 2 // always double
    }
    return value // original change
  },
})

// both listeners are registered now and act before and after all atomic listeners
// therefore atomic listeners in 'before' phase, can override global listeners in 'before' phase
// and global listeners in 'after' phase always receive the "final" result and are reading last

someObjectObserved.cool.test = 456 // prints 912 to console now because the doubling of the value happens first

someObjectObserved.lala = 468 // prints 468

someObjectObserved.foo = 'Y' // prints to the console

// has no effect, because we're talking global listeners and they can't be removed
offSet(someObjectObserved.cool)
const { observed } = require('@jsheaven/observed')

// same API like ESM variant

TL;DR: On average, the time complexity of this library is O(1).

Because this library supports infinite depth mutation tracking, and because you could add as much hook listeners on every property of an object as you like, you can, theoretical, run into O(n) where n is the number of properties on the object being observed. This is because the observed function loop over all the properties of the object to observe them. The time complexity of the onSet function is O(n) as it adds a callback to the list of callbacks of the observed object, which has a length of n, but as explained before, in the average case, it's O(1).