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

@iosio/obi

v0.7.12

Published

mini observable

Downloads

37

Readme

@iosio/obi

mini observable object

Obi extends the functionality of objects to provide change detection.

The goal of this module is to provide a straight forward and interoperable option for state management without the bloat. This module aims to provide a minimal and focused approach with the least amount of magic behind the scenes.

Installation

npm install @iosio/obi --save

Usage

To create state who's properties you want to observe, pass an object to obi and store the returned value to a variable. This adds non enumerable properties to the object, and its nested objects, which can be used to subscribe to changes.

import {obi} from '@iosio/obi';

let $state = obi({
    foo: 'bar',
    arr: ['a', 'b', 'c'],
    $i: 'will not trigger update when i change', 
    some: {
        nested: 'value',
        other: 'nested value'
    },
});

Root level changes

$onChange is a property that exists on all objects nested within the state tree. To subscribe to changes on the root properties of an object, pass a callback to $onChange.

const unsubscribe = $state.$onChange((state, paths) => {
    console.log('state changed: ', paths)
});
$state.foo = 'baz';
//logs: state changed: ['foo']
$state.some.nested = '*'; // does not trigger callback - see: Nested object property changes
//unsubscribe() //option to cancel subscription

Nested object property changes

To subscribe to a specific object layer within a state tree, utilize its dedicated $onChange property.

$state.some.$onChange((state, paths) => {
    console.log('state changed: ', paths)
});
$state.some.nested = 'update';
//logs: state changed: ['some.nested']
$state.foo = 'hello';
//value is updated but does not trigger change on the 'some' branch

To subscribe to changes on values nested any layer deep within the state tree, pass a callback to $onAnyChange.

$state.$onAnyChange((state, paths) => {
    console.log('state changed: ', paths)
});
// *note* destructuring works here because the 'nested' property is still dot-walked out to.
// see 'Notes to be mindfu'l of section
let {some} = $state;
some.nested = 'newValue'; 
//logs: state changed: ['some.nested']

Selecting specific properties to detect changes on

pass an array containing string values that represent the property paths you want to be notified about, 'dot-walking' out to any nested value if need be;



const watchNested = $state.$select(['foo','some.nested']);

watchNested.$onChange((state, paths) => {
    console.log('state changed: ', paths)
});
$state.foo = 'bang';
//logs: state changed: ['foo']
$state.some.nested = 'only I shall trigger the update!'; 
//logs: state changed: ['some.nested']

Batching multiple updates at once

In addition to the available $onChange property, $merge also exists as a non enumerable property that allows for batching multiple updates.

$state.some.$onChange((state, paths) => {
    console.log('state changed: ', paths)
});
//pass an object to merge into the nested branch
$state.some.$merge({
    nested: 'updated',
    other: 'updated'
});
//will only trigger one update
//logs: state changed: ['some.nested', 'some.other']
//or a function to perform any operations with a single subsequent update trigger
$state.some.$merge(() => {
    $state.some.nested = 'updated again';
    $state.some.other = 'updated again';
});
//will only trigger one update
//logs: state changed: ['some.nested', 'some.other']

Notes to be mindful of

In order to ensure the callback is triggered, you must 'dot-walk' out at least once to the property when making assignments.

//DON'T DO THIS
// destructuring to a single property will *not trigger the $onChange callback when assigning a new value.
let {foo} = $state; 
foo = 'baz'; // value is updated but no callback triggered;

//DO THIS 
$state.foo = 'baz'; // will trigger change detection

Updating arrays

$state.arr.push('d'); // mutations on arrays will not trigger updates
$state.arr = [...$state.arr]; //use the spread operator to trigger the update.

Properties that begin with '$' will not trigger change callbacks when updated.

$state.$i = 'updated';
//value is updated but does not trigger change callback.

Functions may be included on the state object.

let $state = obi({
    foo: 'bar',
    getSetFoo: () =>{
        api.getFoo().then(data=>{
            $state.foo = data
        })
    }
});

Call $getState to retrieve the object state as a whole (for example: console logging or form submission purposes) while excluding any non stateful properties like functions.

console.log($state.$getState());
/*
    logs: 
{
    foo: 'bar',
    arr: ['a', 'b', 'c'],
    $i: 'will not trigger update when i change', 
    some: {
        nested: 'value',
        other: 'nested value'
    },
}
*/

Under the hood

Obi uses Object.defineProperty over Proxy for a few reasons. Given that Proxy provides suitable functionality, there are still some small nuances that exist with it that end up not setting its benefits far apart from what Object.defineProperty provides when utilizing it solely for detecting object property changes. Not to mention there is only a partial polyfill for Proxy that does not cover all bases for legacy browsers.

TODO

  • add tests

License

MIT