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

contextual-proxy

v0.2.2

Published

An enhanced JavaScript Proxy that not only wraps a JavaScript object, but also adds additional contextual variables and parent chain.

Downloads

4

Readme

Contextual Proxy Node.js CI

An enhanced JavaScript Proxy that not only wraps a JavaScript object, but also adds additional contextual variables and parent chain.

Using Proxy, this is a simpler implementation of Aurelia's scope (binding context and override context) (Aurelia 1, Aurelia 2).

Not only implementation is simplified, but also the usage. Now this kind of proxy can be used as if it's a plain JavaScript object.

| Contextual Proxy | Aurelia Scope | | ---------------- | ------- | | The proxied object | Binding context | | Contextual variables | Override context | | Parent object | Parent Scope |

Proxy also means IE is not supported.

Aurelia needs a full AST to implement a subset of JavaScript in order to execute an expression utilising scope (binding context and override context), because it's not transparent to access through parent chain. With contextual proxy, it's now transparent. There is no need of an implementation of AST at runtime to execute an expression utilising this proxy, see scoped-eval for more details.

Import the contextual proxy

npm install contextual-proxy
import proxy from "contextual-proxy";
// Or in CommonJS:
// const proxy = require("contextual-proxy").default;

API

It exposes just one API.

JavaScript Proxy class can not be extended (Proxy is special because of no prototype), so we have to design it as a function.

export default function proxy(target: any, parent?: any, context?: {
  [key: string]: any;
}): any;

Create a proxy without any parent object or contextual variable

const obj = {a: 1, b: 2};
const wrapped = proxy(obj);

This is almost same as the plain Proxy created by const wrapped = new Proxy(obj, {});.

Note Proxy can only wrap object, not primitive value (string, number, boolean) or null (which is an object).

Create a proxy with parent object

const parent = {b: 1, c: 2};
const obj = {a: 1, b: 2};
const wrapped = proxy(obj, parent);

Now the parent object is somehow behave like a back-store (think about prototype chain), it can be explicitly accessed by $parent property.

Note we didn't change anything on obj and parent objects themselves, contextual proxy chained them together without touching the original objects.

wrapped.a; // 1
wrapped.b; // 2
wrapped.c; // 2

wrapped.$parent.a; // undefined
wrapped.$parent.b; // 1
wrapped.$parent.c; // 2

Create a proxy with a chain of parent objects

If parent contexts are all prepared by contextual proxy, it can support chain of parent objects.

Use $parent, $parent.$parent... or $parents to access the chain of parent objects.

const grandParent = {foo: 'Foo'};
const wrappedGP = proxy(grandParent);
const parent = {b: 1, c: 2};
const wrappedP = proxy(parent, wrappedGP);
const obj = {a: 1, b: 2};
const wrapped = proxy(obj, wrappedP);

wrapped.foo; // 'Foo'

wrapped.$parent.foo; // 'Foo'
wrapped.$parent.$parent.foo; // 'Foo'
// Same as above two lines
wrapped.$parents[0].foo; // 'Foo'
wrapped.$parents[1].foo; // 'Foo'

wrapped.$parents.length; // 2
wrapped.$parent.$parents.length; // 1

Add contextual variables

When wrapping target object, you can add contextual variables which only live in the proxy, not in the original target object.

By convention, contextual variables starts with $ such as $index.

The following example is the kind of usage of contextual proxy in a view rendering or model validation.

const classRoom = {
  year: 2,
  name: 'Courageous Kingfishers',
  students: [
    { name: 'Michael' },
    { name: 'Emma' }
  ]
];

const wrappedClassRoom = proxy(classRoom);

classRoom.students.forEach((student, i) => {
  const wrapped = proxy(
    student,
    wrappedClassRoom,
    {
      $index: i,
      $first: i === 0,
      $last: i === classRoom.students.length - 1
    }
  );
  wrapped.year; // 2
  wrapped.name; // Michael or Emma
  wrapped.$index: // 0 or 1
  wrapped.$parent.name; // Courageous Kingfishers
});

Assignment operation

const parent = {b: 1, c: 2};
const obj = {a: 1, b: 2};
const wrapped = proxy(obj, proxy(parent), {$index: 1});

wrapped.b = 3; // obj is now {a: 1, b: 3}
wrapped.c = 3; // parent is now {b: 1, c: 3}
wrapped.$parent.b = 2; // parent is now {b: 2, c: 3}

// obj and parent are unchanged, but contextual
// variable $index is changed from 1 to 2;
wrapped.$index = 2;

When you assign some variable not exists in wrapped object, parent object and contextual variables, the proxy will create a new property on the object or contextual variables.

// Create contextual variable $foo with value 1.
// This doesn't mutate the original object at all.
wrapped.$foo = 1;

// Add a new property to original obj.
wrapped.foo = 2;
// Original obj is now {a: 1, b: 3, foo: 2}

Order of property accessing

To access property foo with wrapped.foo, contextual-proxy first checks contextual variables, then the original object itself, finally checks the parent object. If the parent object is another contextual-proxy wrapper, it will do the same checks and go up the chain to the next level of parent object if there is any.

Explicit access

If the original object has a property $length, and the wrapped proxy also has a contextual variable $length, the original $length will be hidden behind the contextual variable of the same name.

// This will access the contextual variable.
wrapped.$length;
// Use $this to explicitly access original object.
wrapped.$this.$length;

Note $this behaves differently from Aurelia's $this. In Aurelia, $this.foo can still access property foo in the parent objects chain. Here in contextual proxy, $this locks the access to just the original object, we think this is the less surprising behaviour.

$parent can access the parent object and deeper parent objects chain. To lock the access to that parent object, there is a trick: use $this on the parent proxy wrapped.$parent.$this.foo.

Same story goes for accessing the hidden property on parent objects chain. If both wrapped object and parent object has same name property foo, you can use $parent to explicitly skip original object.

// This will access foo on the original object.
wrapped.foo;
// use $parent to explicitly access property on
// parent context (or deeper in the chain).
wrapped.$parent.foo;

There is also $contextual to lock the access to the contextual variables.

// Only access contextual variable.
wrapped.$contextual.$foo;
// If parent context is also a contextual proxy.
wrapped.$parent.$contextual.$foo;

License

MIT.