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

@dworthen/bycontract

v2.0.8

Published

argument validation library based on JSDOC syntax

Downloads

9

Readme

ByContract 2

NPM Build Status Join the chat at https://gitter.im/dsheiko/bycontract

byContract is a small argument validation library based on JSDOC syntax. The library is available as a UMD-compatible module. Besides, it exposes byContract function globally when window object available, meaning you can still use it in non-modular programming.

Highlights

  • Validation syntax based on JSDoc expressions
  • Entry and exit point contract validation
  • Explanatory exceptions in the style of aproba
  • Recursive structure (object) validation
  • Interface validation
  • Template tag flavor
  • Property decorators flavor
  • Can be disabled or completely cut off for production

Table of contents

Welcome ByContract

Main flavor

function pdf( path, w, h, options, callback ) {
  validate( arguments, [
    "string",
    "!number",
    "!number",
    PdfOptionsType,
    "function=" ] );
}

Template tag flavor

function pdf( path, w, h, options, callback ) {
  validateContract`
    {string}          ${ path }
    {!number}         ${ w }
    {!number}         ${ h }
    {#PdfOptionsType} ${ options }
    {function=}       ${ callback }
    `;
}

Property decorator flavor

class Page {
  @validateJsdoc(`
    @param {string}          path
    @param {!number}         w
    @param {!number}         h
    @param {#PdfOptionsType} options
    @param {function=}       callback
    @returns {Promise}
  `)
  pdf( path, w, h, options, callback ) {
    return Promise.resolve();
  }
}

Where to use it

Node.js

npm install bycontract
const { validate } = require( "bycontract" );
validate( 1, "number|string" );

Browser

<script src="dist/byContract.min.js"></script>
<script>
  const { validate } =  byContract;
  validate( 1, "number|string" );
</script>

ES6 Module / Webpack

npm install bycontract
import { validate } from "bycontract";
validate( 1, "number|string" );

Syntax Overview

Main flavor

Validate arguments
validate( arguments, [ "JSDOC-EXPRESSION", "JSDOC-EXPRESSION" ] );  // ok or exception
Validate a single value (e.g. return value)
validate( value, "JSDOC-EXPRESSION" ); // ok or exception
Example
import { validate } from "bycontract";

const PdfOptionsType = {
  scale: "?number"
}

/**
 * Example
 * @param {string} path
 * @param {!number} w
 * @param {!number} h
 * @param {PdfOptionsType} options
 * @param {function=} callback
 */
function pdf( path, w, h, options, callback ) {
  validate( arguments, [
    "string",
    "!number",
    "!number",
    PdfOptionsType,
    "function=" ] );
  //...
  const returnValue = Promise.resolve();
  return validate( returnValue, "Promise" );
}

pdf( "/tmp/test.pdf", 1, 1, { scale: 1 } );

// Test it

pdf( "/tmp/test.pdf", "1", 1, { scale: 1 } ); // ByContractError: Argument #1: expected non-nullable but got string

Template Tag flavor

validateContract`
    {JSDOC-EXPRESSION} ${ var1 }
    {JSDOC-EXPRESSION} ${ var2 }
`;
Example
import { validate, typedef } from "bycontract";

typedef("#PdfOptionsType", {
  scale: "number"
});

function pdf( path, w, h, options, callback ) {
  validateContract`
    {string}          ${ path }
    {!number}         ${ w }
    {!number}         ${ h }
    {#PdfOptionsType} ${ options }
    {function=}       ${ callback }
    `;
}

or you can copy/paste from JSDoc:

function pdf( path, w, h, options, callback ) {
  validateContract`
    @param {string}          ${ path }
    @param {!number}         ${ w }
    @param {!number}         ${ h }
    @param {#PdfOptionsType} ${ options }
    @param {function=}       ${ callback }
    `;
}

Property Decorator flavor

@validateJsdoc`
    @param {JSDOC-EXPRESSION} param1
    @param {JSDOC-EXPRESSION} param2
`;
Example
import { validate, typedef } from "bycontract";

typedef("#PdfOptionsType", {
  scale: "number"
});

class Page {
  @validateJsdoc(`
    @param {string}          path
    @param {!number}         w
    @param {!number}         h
    @param {#PdfOptionsType} options
    @param {function=}       callback
    @returns {Promise}
  `)
  pdf( path, w, h, options, callback ) {
    return Promise.resolve();
  }
}
const page = new Page();
page.pdf( "/tmp/test.pdf", "1", 1, { scale: 1 } );
// ByContractError:
// Method: pdf, parameter w: expected non-nullable but got string

This solution requires legacy decorators proposal support. You can get it with following Babel configuration

{
  presets: [
    [ "@babel/preset-env" ]
  ],
  plugins: [
    [ "@babel/plugin-proposal-decorators", { "legacy": true } ]
  ]
}

Types

Primitive Types

You can use one of primitive types: *, array, string, undefined, boolean, function, nan, null, number, object, regexp

validate( true, "boolean" );
// or
validate( true, "Boolean" );
validate( null, "boolean" ); // ByContractError: expected boolean but got null

const fn = () => validate( arguments, [ "boolean", "*" ]);
fn( null, "any" ); // ByContractError: Argument #0: expected boolean but got null

Union Types

validate( 100, "string|number|boolean" ); // ok
validate( "foo", "string|number|boolean" ); // ok
validate( true, "string|number|boolean" ); // ok
validate( [], "string|number|boolean" );
// ByContractError: expected string|number|boolean but failed on each:
// expected string but got array, expected number but got array, expected boolean but got array

Optional Parameters

function foo( bar, baz ) {
  validate( arguments, [ "number=", "string=" ] );
}
foo(); // ok
foo( 100 ); // ok
foo( 100, "baz" ); // ok
foo( 100, 100 ); // ByContractError: Argument #1: expected string but got number
foo( "bar", "baz" ); // ByContractError: Argument #0: expected number but got string

Array Expression

validate( [ 1, 1 ], "Array.<number>" ); // ok
validate( [ 1, "1" ], "Array.<number>" );
// ByContractError: array element 1: expected number but got string
// or
validate( [ 1, 1 ], "number[]" ); // ok
validate( [ 1, "1" ], "number[]" );
// ByContractError: array element 1: expected number but got string

Object Expression

validate( { foo: "foo", bar: "bar" }, "Object.<string, string>" ); // ok
validate( { foo: "foo", bar: 100 }, "Object.<string, string>" );
// ByContractError: object property bar: expected string but got number

Structure

validate({
  foo: "foo",
  bar: 10
}, {
  foo: "string",
  bar: "number"
}); // ok

validate({
  foo: "foo",
  bar: {
    quiz: [10]
  }
}, {
  foo: "string",
  bar: {
    quiz: "number[]"
  }
}); // ok

validate({
  foo: "foo",
  bar: 10
}, {
  foo: "string",
  bar: "number"
}); // ByContractError:  property #bar expected number but got null

Interface validation

You can validate if a supplied value is an instance of a declared interface:

class MyClass {}
const instance = new MyClass();

validate( instance, MyClass ); // ok
class MyClass {}
class Bar {}
const instance = new MyClass();

validate( instance, Bar );
// ByContractError: expected instance of Bar but got instance of MyClass

When the interface is globally available you can set contract as a string:

const instance = new Date();
validate( instance, "Date" ); // ok

//..
validate( node, "HTMLElement" ); // ok
//..
validate( ev, "Event" ); // ok

Globally available interfaces can also be used in Array/Object expressions:

validate( [ new Date(), new Date(), new Date() ], "Array.<Date>" ); // ok

Nullable Type

validate( 100, "?number" ); // ok
validate( null, "?number" ); // ok

Validation Exceptions

import { validate, Exception } from "bycontract";
try {
  validate( 1, "NaN" );
} catch( err ) {
  console.log( err instanceof Error ); // true
  console.log( err instanceof TypeError ); // true
  console.log( err instanceof Exception ); // true
  console.log( err.name ); // ByContractError
  console.log( err.message ); // expected nan but got number
}

Combinations

Sometimes we allow function to accept different sequences of types. Let’s take an example:

function andLogAndFinish( spec, tracker, done ) {
  validate( "SOF|SZF|OOF|OZF", [ spec, tracker, done ] )
  //...
}

Where the following sequences of types valid:

  • string, object, function
  • string, null, function
  • object, object, function
  • object, null, function
import { validateCombo } from "bycontract";

const CASE1 = [ "string", TRACKER_OPTIONS, "function" ],
      CASE2 = [ "string", null, "function" ],
      CASE3 = [ SPEC_OPTIONS, TRACKER_OPTIONS, "function" ],
      CASE4 = [ SPEC_OPTIONS, null, "function" ];

validateCombo( arguments, [ CASE1, CASE2, CASE3, CASE4 ] );

Function validateCombo throws exception when none of the cases is valid

Custom Types

Pretty much like with JSDoc @typedef one can declare a custom type and use it as a contract.

Validating against a Union Type

Here we define a union type for values that can contain either numbers or strings that represent numbers.

import { validate, typedef } from "bycontract";
typedef( "NumberLike", "number|string" );
validate( 10, "NumberLike" ); // OK
validate( null, "NumberLike" ); // ByContractError: expected number|string but got null

Validating against a Complex Type

This example defines a type Hero that represents an object/namespace required to have properties hasSuperhumanStrength and hasWaterbreathing both of boolean type.

import { validate, typedef } from "bycontract";
typedef( "#Hero", {
  hasSuperhumanStrength: "boolean",
  hasWaterbreathing: "boolean"
});
var superman = {
  hasSuperhumanStrength: true,
  hasWaterbreathing: false
};
validate( superman, "#Hero" ); // OK

When any of properties violates the specified contract an exception thrown

var superman = {
  hasSuperhumanStrength: 42,
  hasWaterbreathing: null
};
validate( superman, "#Hero" ); // ByContractError:  property #hasSuperhumanStrength expected boolean but got number

If value misses a property of the complex type an exception thrown

var auqaman = {
  hasWaterbreathing: true
};
validate( superman, "#Hero" ); // ByContractError: missing required property #hasSuperhumanStrength

Custom Validators

Basic type validators exposed exported as is object. So you can extend it:

import { validate, is } from "bycontract";
is.email = function( val ){
  var re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
  return re.test( val );
}
validate( "[email protected]", "email" ); // ok
validate( "bla-bla", "email" ); // ByContractError: expected email but got string

Production Environment

You can disable validation logic for production env like

import { validate, config } from "bycontract";
if ( process.env.NODE_ENV === "production" ) {
  config({ enable: false });
}

Alternatively you can fully remove the library from the production codebase with Webpack:

webpack config

const webpack = require( "webpack" ),
      TerserPlugin = require( "terser-webpack-plugin" );

module.exports = {
  mode: process.env.NODE_ENV || "development",
  ...
  optimization: {
     minimizer: [
         new TerserPlugin(),
         new webpack.NormalModuleReplacementPlugin(
          /dist\/bycontract\.dev\.js/,
          ".\/bycontract.prod.js"
        )
     ]
  }
};

building for development

npx NODE_ENV=development webpack

building for production

npx NODE_ENV=production webpack

Analytics