3.0.3 • Published 7 months ago

prevent-undefined v3.0.3

Weekly downloads
-
License
ISC
Repository
github
Last release
7 months ago

prevent-undefined

prevent-undefined throws an error to let you know that you accidentally refer an undefined propery on your object.

It also displays dump of the refered object for your convenience. See below :

const  { preventUndefined } = require("prevent-undefined" );
// or
import { preventUndefined } from "prevent-undefined";

// foo.js
  const someObj = {
    hello : {
      world : {
        foo : {
          bar : {
            baz : "HELLO",
          },
        }
      }
    }
  };

  preventUndefined( someObj ).hello.world.foo.bar.VAZ;
  //                                 SOME TYPO    ^^^

then, you'll get:

> node foo.js
ReferenceError: obj.hello.world.foo.bar.VAZ is not defined in {
  hello: {
    world: { foo: { bar: { baz: 'HELLO' } } }
  }
}

The Major Usecase of preventUndefined

The major usecase of preventUndefined is checking validity of named parameter values:

function someFunc(args){
  const {foo,bar} = preventUndefined(args);
  console.error("foo:",foo);
}

someFunc({wrongArg1:"foo", wrongArg2:"bar";});

> ReferenceError: obj.foo is not defined in {
>   "wrongArg1": "foo",
>   "wrongArg2": "bar"
> }

unprevent()

Sometimes, you will want to unprevent your objects especially when you want to use optional chaining:

const { preventUndefined } = require('prevent-undefined');
const someObj = {
  hello : {
    world : {
      foo : {
        bar : {
          baz : {
            message : "HELLO",
          }
        },
      }
    }
  }
};
const o = preventUndefined( someObj );

function proc(o){
  return o.hello?.world?.foo?.bar?.baz?.value ?? "hello world";
}
proc(o);

you'll get

ReferenceError: obj.hello.world.foo.bar.baz.value is not defined in {
  hello: {
    world: {
      foo: { bar: { baz: { message: 'HELLO' } } }
    }
  }
}
    at Object.get (/.../common.js:91:17)

which you definitely don't want.

Use unprevent()

const  { unprevent } = require("prevent-undefined");
function proc(o){
  o=unprevent(o);
  return o.hello?.world?.foo?.bar?.baz?.value ?? "hello world";
}
proc(o); // you'll get "hello world" as expected.

recursivelyUnprevent()

recursivelyUnprevent() recursively unprevent the specified object, literally. Currently recursivelyUnprevent() is in the beta state; therefore, please use this with care. Currently, this does not check any circular references.

recursivelyUnprevent() was ADDED v0.2.19.

preventUnusedProperties()

The basic idea is :

// There is a func as :
function someFunc({foo,bar}){
  console.error("foo:",foo);
  console.error("bar:",bar);
  // note that `baz` is not used at all.
}

var foo = 'foo';
var bar = 'bar';
var baz = 'baz';
// `baz` a property which the caller thinks it is valid but actually isn't.
someFunc({foo,bar,baz});

> foo: foo
> bar: bar

// You'll get `foo` `bar` without getting any error that sucks.

In order to prevent such tiredness, use preventUnusedProperties().

const  { preventUnusedProperties } = require("prevent-undefined");

function someFunc(args){
  // Prevent undefined on the `args` object before destructuring it.
  const {foo,bar} = preventUndefined(args);

  // After destructured the `args` object, call `preventUnusedProperties()`
  preventUnusedProperties(args);

  console.error("foo:",foo);
  console.error("bar:",bar);
}

var foo = 'foo';
var bar = 'bar';
var baz = 'baz';
someFunc({foo,bar,baz});


> ReferenceError: the field [baz] was not referred in
> {
>   "foo": "foo",
>   "bar": "bar",
>   "baz": "baz"
> }

preventUnusedProperties() has been added v0.2.20.

isUndefinedPrevented()

isUndefinedPrevented() function returns true if a specified object is protected by preventUndefined() function.

isUndefinedPrevented() has been added on v0.2.31

maxRecursiveLevel

preventUndefined() protects the specified object and all of its properties as well. As default, it protects all of descendants recursively. You can limit its maximum recursive level.

const obj = {
  foo: {
    bar : {
      baz :{
        data:1
      },
    },
  },
};

test( 'maxRecursiveLevel ... base ', ()=>{
  const po = preventUndefined( obj );
  expect( isUndefinedPrevented( po.foo         )).toBe( true );
  expect( isUndefinedPrevented( po.foo.bar     )).toBe( true );
  expect( isUndefinedPrevented( po.foo.bar.baz )).toBe( true );
});

test( 'maxRecursiveLevel ...0 ', ()=>{
  const po = preventUndefined( obj, {maxRecursiveLevel:0 }  );
  expect( isUndefinedPrevented( po             )).toBe( false );
  expect( isUndefinedPrevented( po.foo         )).toBe( false );
  expect( isUndefinedPrevented( po.foo.bar     )).toBe( false );
  expect( isUndefinedPrevented( po.foo.bar.baz )).toBe( false );
});

test( 'maxRecursiveLevel ...1 ', ()=>{
  const po = preventUndefined( obj, {maxRecursiveLevel:1 }  );
  expect( isUndefinedPrevented( po             )).toBe( true  );
  expect( isUndefinedPrevented( po.foo         )).toBe( false );
  expect( isUndefinedPrevented( po.foo.bar     )).toBe( false );
  expect( isUndefinedPrevented( po.foo.bar.baz )).toBe( false );
});

test( 'maxRecursiveLevel ...2 ', ()=>{
  const po = preventUndefined( obj, {maxRecursiveLevel:2 }  );
  expect( isUndefinedPrevented( po             )).toBe( true  );
  expect( isUndefinedPrevented( po.foo         )).toBe( true  );
  expect( isUndefinedPrevented( po.foo.bar     )).toBe( false );
  expect( isUndefinedPrevented( po.foo.bar.baz )).toBe( false );
});

test( 'maxRecursiveLevel ...3 ', ()=>{
  const po = preventUndefined( obj, {maxRecursiveLevel:3 }  );
  expect( isUndefinedPrevented( po             )).toBe( true  );
  expect( isUndefinedPrevented( po.foo         )).toBe( true  );
  expect( isUndefinedPrevented( po.foo.bar     )).toBe( true  );
  expect( isUndefinedPrevented( po.foo.bar.baz )).toBe( false );
});

ADDED v0.2.33 (Sat, 07 Jan 2023 17:51:53 +0900)

Validators

If you set a validator to the second parameter of preventUndefined(), it monitors every modification on the object to keep consistency of the object.

const validator = (o)=>typeof o?.foo?.bar?.value === 'number';
const obj = preventUndefined({
  foo : {
    bar : {
      value : 100,
    },
  }
}, validator);

obj.foo.bar.value = 'BUMMER! NOT A NUMBER';

> ReferenceError: detected defining an invalid property value to obj.foo.bar.value on
> {
>   "foo": {
>     "bar": {
>       "value": "BUMMER! NOT A NUMBER"
>     }
>   }
> }

validator is a function which receives an argument and returns a boolean value.

The only argument is the value to be validated. Return true if the target object is valid; otherwise return false.

A validator function should not throw an error; if the validator throws an error, that causes the process that invokes the validation to throw the error.

( You might expect that throwing any error is treated as invalid state of the target object since if the validation process interrupts the main process, it could interrupt entire process; but this behavior is intentional because it is important to let the user know the validator encounters something that the user did not expect. )

I recommend you to use a non-opinionated validator Vanilla Schema Validator; though it is not mandatory.

See Vanilla Schema Validator

Validators have been added in v0.2.23.

onError

Started from v0.2.27, you can specify an event handler when you call preventUndefined():

  const obj = preventUndefined({
    foo: 'foo',
    bar: 'bar',
  }, {
    onError: (info)=>{
      console.error( 'called ' + info.propPath  );
    }
  });

  console.error( "peekaboo! ",  obj.wrongProp );

> "called wrongProp"
>
> ReferenceError: [prevent-undefined] obj.wrongProp is not defined in {
>   "foo": "foo",
>   "bar": "bar"
> }
> [stacktrace]
> ...

Some information about the error which has just occured is available as the properties on the first argument of the event handler.

propPath

propPath property is an array object which consists the property names.

  const onError(info)=>{
    console.error(info.propPath); //
  };
  const someObj = {
    hello : {
      world : {
        foo : {
          bar : {
            baz : "HELLO",
          },
        }
      }
    }
  };

  preventUndefined( someObj,{onError}).hello.world.foo.bar.VAZ;
  //                                      SOME TYPO AGAIN  ^^^

then, you'll get

> hello.world.foo.bar.VAZ

target

target property is the target object which you specify as the first argument when you call preventUndefined().

message

message property is the string value on the message property on the error object which is thrown.

stackOnCreated

stackOnCreated is an array object which consists the stacktrace when preventUndefined() is called.

stackOnOccured

stackOnOccured is an array object which consists the stacktrace when the illegal access on the target object is occured.

event handler started from v0.2.27

errorIfUndefined()

errorIfUndefined() is a perfect fancy mascot to implement ''prevent-undefined way'' of named parameters. When you call errorIfUndefined(), it simply throws ReferenceError(). Its only use case is as following :

function strictFunc({foo=errorIfUndefined('foo')}) {
  console.error(foo);
}
strictFunc({ foo_WITH_TYPO: 'foo' });

> ReferenceError: the parameter value of foo was undefined; any reference to an undefined value is strictly prohibited on this object.

errorIfUndefined takes only one parameter : name which is to specify the name of the parameter.

errorIfUndefined() has been added in v0.2.19.

DEPRECATED v0.2.33 This feature will be removed in near future.

The Basic Idea of prevent-undefined

IMHO, undefined is always evil:

  // you defined some procedures :
  function showName(name){
    if (name.length<10) {
      console.error( "a short name", name );
    } else {
      console.error( "a long name", name );
    }
  }
  function foo(guy) {
    showName(guy.name);
  }

  // then, you have some data:
  const guy1 = getYourDataFromResource('john');
  const guy2 = getYourDataFromResource('paul');

  // then, you want to exec the procedures with your data:
  foo(guy1)
  foo(guy2);

  // and if you misteriously get ReferenceError as

  > TypeError: Cannot read properties of undefined (reading 'length')
  > at showName (..) file.js:17:14
  > ...

At this point, you have no clue where the problem came from. At this point, you will never notice that some specific unenthusiastic programmer made a function as following:

const JOHN ={
  name:'John',
  age: 18,
};
const PAUL ={
  name:'Paul',
  age: 23,
};

function getYourDataFromResource(id) {
  if ( id === 'john' ) {
    return {
      name : JOHN.nameeeeeeeeeeeeeeeeee;
    }
  } else if ( id ==='paul' ) {
    return {
      name : PAUL.naaaaaaaaaaaame;
    }
  } else {
    // wat
  }
}

Even though the problem is obviously occured inside the function getYourDataFromResource() you will only get the error from a totaly irrelevant function showName() which is very confusing and annoying.

The language should actually throw an Error where the undefined comes out.

IMHO, undefined is always poisoneous; the language system should have never allow the existence of undefined. In spite of the difficulty, the current specification of ECMA Script cannot detect undefined properties even if you enable 'use strict'.

This is where prevent-undefined come in.

const JOHN = preventUndefined({
  NAME:'John',
  AGE: 18,
});

const PAUL = preventUndefined({
  NAME:'Paul',
  AGE: 23,
});

function getYourDataFromResource(id) {
  if ( id === 'john' ) {
    return {
      name : JOHN.namee; // this throws error.
    }
  } else if ( id ==='PaUL' ) {
    return {
      name : PAUL.naame; // this throws error, too.
    }
  }
}

When you applied prevent-undefined in this way, you can easily indicate where the problematic undefined comes from.

IMHO, you should always avoid referencing undefined properties for any form.

Write not like this:

if ( o.foo ) {
  console.error( 'foo exists' );
}

Write like this:

if ( 'foo' in o  ) {
  console.error( 'foo exists' );
}

Because the conditional check o.foo implicitly references undefined which makes detecting undefined very difficult.

// if the o was get preventUndefined()ed somewhere before:
if ( o.foo ) { // this throws ReferenceError()
  console.error( 'foo exists' );
}

Unfortunately some functions from the standard JavaScript library use implicit referencing undefined; for example,

const o = preventUndefined( {hello:1})

// The following code will end up ReferenceError which led our `preventUndefined()` to
// ignore `then` with some hard-coding.
Promise.resolve( o );

That is, if you have found some standard functions that implicitly refers undefined it is necessary to add the name of the property to the ignore list which is hard-coded in prevent-undefined module.

History

v0.2.6

This didn't work; it didn't support common.js.

v0.2.7

(may) supports both common.js and es6 module.

Special thanks goes to How to Create a Hybrid NPM Module for ESM and CommonJS. This site helped a lot. Thank you very much.

v0.2.8

(Fri, 14 Oct 2022 19:53:18 +0900)

Supported unpreventing null

unprevent( null ) == null

v0.2.9

Fixed an issue that it throws TypeError when a symbol property is accessed.

v0.2.10

Added keywords to ignore such as toJSON, Symbol.toStringTag, Symbol.toPrimitive , etc.

v0.2.11

Added $$typeof to the ignore keyword list to let prevent-undefined work cooperatively with React.js.

This very informative article Why Do React Elements Have a $$typeof Property? helped me to understand the problem. Thank you very much.

v0.2.12

v0.2.11 does not work as an ES6 module due to my carelessness. The problem was fixed n this version.

v0.2.13

Skipped the version v0.2.13 via fear of Friday the 13th. This version have never been released.

v0.2.14

In the version v0.2.11, I documented, commented, and tested it without modifying the actual code. Believe me, this time I actually modified.

v0.2.15

  • Added standard well-known symbols to ignore list; they are to added since they are likely to be checked as
const i = a[Symbol.iterator];
if (i === undefined ) {
  // ...
}

which is not a preferable behavior for this module.

  • Applied automated code generation for tests.

v0.2.16

Changed the way to check if the object is a symbol or not.

v0.2.17

(Mon, 24 Oct 2022 17:40:53 +0900)

Added '@@iterator' to the ignore list in order to avoid an issue that will occur when use this module with React.js.

v0.2.18

(Thu, 03 Nov 2022 17:11:29 +0900) Removed a unnecessarily left unremoved debug logging output.

v0.2.19

(Mon, 07 Nov 2022 16:17:06 +0900) Added recursivelyUnprevent() and errorIfUndefined()

v0.2.20

(Wed, 09 Nov 2022 15:52:32 +0900) Added preventUnusedProperties() Updated README.md; added a section The Basic Idea of preventUndefined().

v0.2.21

(Wed, 09 Nov 2022 16:01:54 +0900) Updated README.md.

v0.2.22

(Wed, 09 Nov 2022 16:11:21 +0900) Updated README.md.

v0.2.23

(Mon, 14 Nov 2022 17:25:30 +0900) Supported validators.

v0.2.24

(Tue, 15 Nov 2022 16:03:18 +0900) Supported rtti.

v0.2.25

(Tue, 15 Nov 2022 16:08:53 +0900) rtti is renamed to typesafe.

v0.2.26

(Wed, 23 Nov 2022 19:15:59 +0900) Now preventUndefined displays the stacktrace not only where the illegal access is occured but where the object is protected by preventUndefined(), too. Hopefully this will let you know where your problematic object comes from.

v0.2.27

Added the error handler onError().

v0.2.28

(Fri, 30 Dec 2022 11:44:22 +0900) Removed unnecessary files from the package.

v0.2.29

(Fri, 30 Dec 2022 14:02:41 +0900) Deprecated typesafe.

v0.2.30

v0.2.31

  • If a specified validator has script propety, show its content on validation fail.
  • Fixed unexpected behavior when the parent class of a class is protected by preventUndefined().
  • Added isUndefinedPrevented()

v0.2.32

  • A Workaround issue that using spread syntax causes following errors :

1.

'ownKeys' on proxy: trap returned duplicate entries`

2.

This is caused by either a bug in Node.js or incorrect usage of Node.js internals.
Please open an issue with this stack trace at https://github.com/nodejs/node/issues`

Intercepting getOwnPropertyDescriptor() and ownKeys() by using Proxy sometimes makes Node.js crash; I am not sure what exactly causes the problem but it surely causes it to crash.

v0.2.33

(Sat, 07 Jan 2023 17:51:53 +0900)

  • Added maxRecursiveLevel feature

v0.2.34

(Fri, 20 Jan 2023 19:40:35 +0900)

  • improved more informative error messages
  • removed unnecessary instances of debugger directive

v0.2.35

(Fri, 20 Jan 2023 19:58:25 +0900) fixed the issue that the package could not be loaded; v0.2.34 was broken.

v0.2.36

(Wed, 25 Jan 2023 14:42:54 +0900)

  • Check the specified validator if it returns a function. If so, throw an error to let the user know that it could be a validator factory, not a validator itself.

v0.2.37

(Wed, 25 Jan 2023 16:31:47 +0900)

  • validators should be called with explicitly specified thisArg via call() / apply().

v3.0.0

(Tue, 21 Feb 2023 10:35:08 +0900)

  • Added a new error message system.
    • Quit replacing stacktrace of throwing errors.
    • Set an error object which is created when prevent-undefined is configured on every throwing error object.
    • Output the trace of validators when a validation session was failed.

v3.0.1

(Tue, 21 Feb 2023 17:27:43 +0900)

  • Updated the document.

v3.0.2

(Mon, 08 May 2023 13:50:28 +0900) (Though, actual modification was done on May 2 2023)

  • Convert symbol properties without throwing errors.

v3.0.3

(Mon, 31 Jul 2023 19:15:26 +0900)

  • Added Node.js Buffer to the excluding class list.
  • Applied peerDependencies.

    Conclusion


That's all. Thank you very much.

3.0.3

7 months ago

3.0.2

1 year ago

3.0.1

1 year ago

0.2.37

1 year ago

0.2.36

1 year ago

0.2.35

1 year ago

3.0.0

1 year ago

0.2.34

1 year ago

0.2.33

1 year ago

0.2.32

1 year ago

0.2.31

1 year ago

0.2.30

1 year ago

0.2.29

1 year ago

0.2.28

1 year ago

0.2.27

1 year ago

0.2.26

1 year ago

0.2.25

1 year ago

0.2.24

1 year ago

0.2.23

2 years ago

0.2.22

2 years ago

0.2.21

2 years ago

0.2.20

2 years ago

0.2.19

2 years ago

0.2.18

2 years ago

0.2.17

2 years ago

0.2.16

2 years ago

0.2.15

2 years ago

0.2.14

2 years ago

0.2.12

2 years ago

0.2.11

2 years ago

0.2.10

2 years ago

0.2.9

2 years ago

0.2.8

2 years ago

0.2.7

2 years ago

0.2.6

2 years ago

0.2.5

2 years ago

0.2.4

2 years ago

0.2.3

2 years ago

0.2.2

2 years ago

0.2.1

2 years ago

0.2.0

2 years ago

0.1.1

2 years ago

0.1.0

2 years ago