I am supporting CandidateX

CandidateX is a startup that focuses on creating inclusion-focused hiring solutions, designed to increase access to job opportunities for underestimated talent. Check them out if you have a few minutes to spare. They need visibility!

Accidentally accessing non-existent properties and tracking down where that is happening is a big time waster that a JavaScript proxy can help you track down.

In Fix Apps Script file order problems with Exports I described how to organize your Apps Script code better using an Exports object to describe and modify the classes and namespaces in your project, or in libraries your project uses. Now that you have this centralized view, it’s easy to add checks on attempts to access properties that don’t exist in your target object.

Proxy and Reflect

I’ve done a few articles on the usefulness of JavaScript Proxy – for example (SuperFetch – a proxy enhancement to Apps Script UrlFetch, JavaScript proxy, fiddling with Apps Script objects and custom errors, Proxy implementation of Apps Script GraphQL Class, Resuscitating the Apps Script execution transcript – JavaScript Proxy and Reflect to the rescue).

This is a very basic one, and will show how to modify your Exports object to validate and report on accidental attempts to access missing properties in an object.

Example

Let’s say we have an Exports object that contains this reference to a class

  /**
* Store class
* @implements {Store}
*/
get Store() {
return Store
},
Exports

We’d create an instance like this

const store = new Exports.Store (constructor arguments)
create an instance of a class

So far so good, but let’s say that you later try to access a non existent property

const first = store.badProperty
// or
const second = store.getBadly (key)
Accessing a non existent property

The first would fail silently and the second would complain about ‘undefined not a function’. It would be much handier if instead it warned you that you were trying to access ‘badProperty’ and ‘getBadly’. That’s where a JavaScript proxy comes in.

Applying a Proxy

Let’s now enhance the Exports to generate a new instance of the store class

  /**
* Store class
* @implements {Store}
*/
get Store() {
return Store
},

/**
* Store instance with validation
* @param {...*} args
* @return {Store} instance of store
*/
newStore (...args) {
return new this.Store(...args)
},
enhanced Exports

And create an instance like this

const store = Exports.newStore (constructor arguments)
create an instance

We’ll get a new instance of the Store class, but still with no protection for invalid property access. Let’s add a proxy to that.

  /**
* Store class
* @implements {Store}
*/
get Store() {
return Store
},

/**
* Store instance with validation
* @param {...*} args
* @return {Store} a proxied instance of store
*/
newStore (...args) {
return new Proxy (new this.Store(...args))
},
proxy that does nothing

Now, instead of an instance of the Store, we get an instance of Proxy, which has a Store instance as its target. However, we still haven’t done anything about detecting invalid property accesses. The second argument to Proxy() allows you to set up an intercept to property accesses.

var Exports = {

get validateProperties () {
return {
get(target, prop, receiver) {
if (!Reflect.has(target, prop)) throw `attempt to get property ${prop} that doesn't exist`
return Reflect.get(target, prop, receiver)
},

set(target, prop, value, receiver) {
if (!Reflect.has(target, prop)) throw `attempt to set property ${prop} that doesn't exist`
return Reflect.set(target, prop, value, receiver)
}
}
},
/**
* Store class
* @implements {Store}
*/
get Store() {
return Store
},

/**
* Store instance with validation
* @param {...*} args
* @return {Store} a proxied instance of store with property checking enabled
*/
newStore (...args) {
return new Proxy (new this.Store(...args), this.validateProperties)
},
validate property accesses

The same ‘validateProperty’ object in Exports can now be applied to any other of your Exported classes or namespaces in the same way to check that any property you try to access.

If you do it will throw an error and explain which property was invalid – like this.

const value = store.badProperty
//throws - attempt to get property badProperty that doesn't exist
throws error on bad property access

Unit testing

When developing a project I always do unit testing as I go along – right from the start. See Simple but powerful Apps Script Unit Test library for the library I use. Here’s a small clip on how to use it to test the proxy implementation.


unit.section (()=> {

unit.not (null, unit.threw (()=> user.badProp), {
description: 'throws on non existnt property'
})
unit.is (null, unit.threw (()=>user.get('x')), {
description: 'doesnt throw on existing property'
})
}, {
description: 'check proxy installed'
})
unit.report()
//
12:29:20 PM Info Starting section check proxy installed
12:29:21 PM Info Finished section check proxy installed passes: 2 failures: 0 elapsed ms 77
12:29:21 PM Info Section summary
12:29:21 PM Info check proxy installed passes 2 failures 0
12:29:21 PM Info Total passes 2 (100.0%) Total failures 0 (0.0%)
12:29:21 PM Info ALL TESTS PASSED
12:29:21 PM Info Total elapsed ms 93
12:29:21 PM Notice Execution completed
unit testing proxy test

Summary

I recommend adding this kind of validation from the beginning. It’ll make life easier.