New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@webqit/observer

Package Overview
Dependencies
Maintainers
1
Versions
90
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@webqit/observer

A simple set of functions for intercepting and observing JavaScript objects and arrays.

  • 2.1.1
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
184
increased by736.36%
Maintainers
1
Weekly downloads
 
Created
Source

The Observer API

NPM version NPM downloads

A web-native object observability API!

Observe and intercept operations on any type of JavaScript objects and arrays, using a notably lightweight and predictable utility-first reactivity API!

Motivation

Table of Contents

Download Options

Use as an npm package:

npm i @webqit/observer
// Import
import Observer from '@webqit/observer';;

Use as a script:

<script src="https://unpkg.com/@webqit/observer/dist/main.js"></script>
// Obtain the APIs
const Observer = window.webqit.Observer;

An Overview

Note
This is documentation for Observer@2.x. (Looking for Observer@1.x?)

Method: Observer.observe()

Observe mutations on any object or array!

// Signature 1
Observer.observe( obj, callback[, options = {} ]);
// Signature 2
Observer.observe( obj, props, callback[, options = {} ]);
Concept: Observers

Observe arbitrary objects and arrays:

// An object
const obj = {};
// Mtation observer on an object
const abortController = Observer.observe( obj, handleChanges );
// An array
const arr = [];
// Mtation observer on an array
const abortController = Observer.observe( arr, handleChanges );

Now changes will be delivered synchronously - as they happen. (The sync design is discussed shortly.)

// The change handler
function handleChanges( mutations ) {
    mutations.forEach( mutation => {
        console.log( mutation.type, mutation.key, mutation.value, mutation.oldValue );
    } );
}

--> Stop observing at any time by calling abort() on the returned abortController...

// Remove listener
abortController.abort();

...or by using an Abort Signal instance:

// Providing an AbortSignal
const abortController = new AbortController;
Observer.observe( obj, mutations => {
    // Handle...
}, { signal: abortController.signal } );
// Abort at any time
abortController.abort();
Concept: Mutations

Programmatically mutate properties of an object using the Reflect-like set of operators; each operation will be reported by observers:

// A single "set" operation on an object
Observer.set( obj, 'prop0', 'value0' );
Observer.defineProperty( obj, 'prop1', { get: () => 'value1' } );
Observer.deleteProperty( obj, 'prop2' );
// A single "set" operation on an array
Observer.set( arr, 0, 'item0' ); // Array [ 'item0' ]
Observer.deleteProperty( arr, 0 ); // Array [ <1 empty slot> ]

Beware non-reactive operations:

// Literal object accessors
delete obj.prop0;
obj.prop3 = 'value3';
// Array methods
arr.push( 'item3' );
arr.pop();

--> Enable reactivity on specific properties with literal object accessors - using the Observer.accessorize() method:

// Accessorize all (existing) properties
Observer.accessorize( obj );
// Accessorize specific properties (existing or new)
Observer.accessorize( obj, [ 'prop0', 'prop1', 'prop2' ] );

// Make reactive UPDATES
obj.prop0 = 'value0';
obj.prop1 = 'value1';
obj.prop2 = 'value2';
// Accessorize all (existing) indexes
Observer.accessorize( arr );
// Accessorize specific indexes (existing or new)
Observer.accessorize( arr, [ 0, 1, 2 ] );

// Make reactive UPDATES
arr[ 0 ] = 'item0';
arr[ 1 ] = 'item1';
arr[ 2 ] = 'item2';

// Bonus reactivity: array methods that re-index existing items
arr.unshift( 'new-item0' );
arr.shift();

Beware non-reactive operations:

// The delete operator and object properties that haven't been accessorized
delete obj.prop0;
obj.prop3 = 'value3';
// Array methods that do not re-index existing items
arr.push( 'item0' );
arr.pop();

--> Enable reactivity on arbitray properties with Proxies - using the Observer.proxy() method:

// Obtain a reactive Proxy for an object
const _obj = Observer.proxy( obj );

// Make reactive operations
_obj.prop1 = 'value1';
_obj.prop4 = 'value4';
_obj.prop8 = 'value8';

// With the delete operator
delete _obj.prop0;
// Obtain a reactive Proxy for an array
const _arr = Observer.proxy( arr );

// Make reactive operations
_arr[ 0 ] = 'item0';
_arr[ 1 ] = 'item1';
_arr[ 2 ] = 'item2';

// With an instance method
_arr.push( 'item3' );

And no problem if you end up nesting the approaches.

// 'value1'-->obj
Observer.accessorize( obj, [ 'prop0', 'prop1', 'prop2', ] );
obj.prop1 = 'value1';

// 'value1'-->_obj-->obj
let _obj = Observer.proxy( obj );
_obj.prop1 = 'value1';

// 'value1'-->set()-->_obj-->obj
Observer.set( _obj, 'prop1', 'value1' );

--> "Restore" accessorized properties to their normal state by using the unaccessorize() method:

Observer.unaccessorize( obj, [ 'prop1', 'prop6', 'prop10' ] );

--> "Reproduce" original objects from Proxies obtained via Observer.proxy() by using the unproxy() method:

obj = Observer.unproxy( _obj );
Concept: Batch Mutations

Make multiple mutations at a go, and they'll be correctly delivered in batch to observers!

// Batch operations on an object
Observer.set( obj, {
    prop0: 'value0',
    prop1: 'value1',
    prop2: 'value2',
} );
Observer.defineProperties( obj, {
    prop0: { value: 'value0' },
    prop1: { value: 'value1' },
    prop2: { get: () => 'value2' },
} );
Observer.deleteProperties( obj, [ 'prop0', 'prop1', 'prop2' ] );
// Batch operations on an array
Observer.set( arr, {
    '0': 'item0',
    '1': 'item1',
    '2': 'item2',
} );
Object.proxy( arr ).push( 'item3', 'item4', 'item5', );
Object.proxy( arr ).unshift( 'new-item0' );
Object.proxy( arr ).splice( 0 );

--> Use the Observer.batch() to batch multiple arbitrary mutations - whether related or not:

Observer.batch( arr, async () => {
    Observer.set( arr, 0, 'item0' ); // Array [ 'item0' ]
    await somePromise();
    Observer.set( arr, 2, 'item2' ); // Array [ 'item0', <1 empty slot>, 'item2' ]
} );

Method calls on a proxied instance - e.g. Object.proxy( arr ).splice( 0 ) - also follow this strategy.

Concept: Custom Details

Pass some custom detail - an arbitrary value - to observers via a params.detail property.

// A set operation with detail
Observer.set( obj, {
    prop2: 'value2',
    prop3: 'value3',
}, { detail: 'Certain detail' } );

Observers recieve this value on their mutation.detail property.

// An observer with detail
Observer.observe( obj, 'prop1', mutation => {
    console.log( 'A mutation has been made with detail:' + mutation.detail );
} );
Concept: Diffing

Receive notifications only for mutations that actually change property state, and ignore those that don't.

// Responding to state changes only
Observer.observe( obj, handleChanges, { diff: true } );
// Recieved
Observer.set( obj, 'prop0', 'value' );
// Ignored
Observer.set( obj, 'prop0', 'value' );

Method: Observer.intercept()

Intercept operations on any object or array before they happen!

// Signature 1
Observer.intercept( obj, prop, handler[, options = {} ]);
// Signature 2
Observer.intercept( obj, traps[, options = {} ]);
Concept: Traps

Extend standard operations on an object - Observer.set(), Observer.deleteProperty(), etc - with custom traps using the Observer.intercept() method!

Below, we intercept all "set" operations for an HTTP URL then transform it to an HTTPS URL.

const setTrap = ( operation, previous, next ) => {
    if ( operation.key === 'url' && operation.value.startsWith( 'http:' ) ) {
        operation.value = operation.value.replace( 'http:', 'https:' );
    }
    return next();
};
Observer.intercept( obj, 'set', setTrap );

Now, only the first of the following will fly as-is.

// Not transformed
Observer.set( obj, 'url', 'https://webqit.io' );

// Transformed
Observer.set( obj, 'url', 'http://webqit.io' );

And below, we intercept all "get" operations for a certain value to trigger a network fetch behind the scenes.

const getTrap = ( operation, previous, next ) => {
    if ( operation.key === 'token' ) {
        return next( fetch( tokenUrl ) );
    }
    return next();
};
Observer.intercept( obj, 'get', getTrap );

And all of that can go into one "traps" object:

Observer.intercept( obj, {
    get: getTrap,
    set: setTrap,
    deleteProperty: deletePropertyTrap,
    defineProperty: definePropertyTrap,
    ownKeys: ownKeysTrap,
    has: hasTrap,
    // etc
} );

The End?

Certainly not! But this rundown should be a good start. Next:

Issues

To report bugs or request features, please submit an issue.

License

MIT.

Keywords

FAQs

Package last updated on 11 May 2023

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc