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

icc-contracts

v0.1.0

Published

Code-contract library for javascript. Supports pre-conditions.

Downloads

8

Readme

🦔 Integrated Code Contracts

icc-contracts, mostly invisible confidence

⚠ Warning

This repository is currently an experimental concept and is subject to heavy change. Not recommended for general use.

Overview

In general, code-contracts allow the specification of pre-conditions and post-conditions.

icc-contracts focuses on providing pre-conditions in pure JavaScript.

Motivation

But why? Are unit-tests not sufficient?

Well written unit-tests cover 100% of the functionality of the unit. Unit-tests don't cover the way that the unit is integrated into the product. For that we have IntegrationTests. However, when you combine your units and something doesn't work, how do you know what went wrong? Code-contracts to the rescue! Code-contracts help you quickly pinpoint the failing interaction. Since they also document the expectations, they help you understand how to resolve the problem too.

icc-contracts

Icc is a way of writing code-contract pre-conditions as JavaScript.

They run when the code does, providing guard rails as you develop. In other words, making sure the input is in the format you expected.

It must be assumed that, given correct input, your code will produce the expected output. If you aren't sure of that, add more unit tests!

Public Only

If you've written good unit-tests, then you are sure the unit is doing the correct thing. That is, it is internally consistent and meets the specifications. Therefore, the only time things can go wrong is when incorrect input is provided. Given that only public methods can provide input, these are the only ones adding uncertainty. So add code-contracts to your public methods (only).

Performance

Icc-contracts always run when the public methods do Clearly this means there is a performance cost to pay. My opinion is: the performance cost is fine when testing, it is not acceptable in production. So, icc is designed to make it super easy to completely eliminate this overhead in production code as part of your build. If you're a Rollup user, simply run the @rollup/plugin-strip to remove all icc: labeled code.

Installing

Requires Node 14 or higher.

At the command line run npm install --save icc-contracts

Within your source files you need to add: For es6 projects: import contract from 'icc-contracts';

For CommonJS projects: const contract = require('icc-contracts');

Usage Explanation

Given a (stupidly) simple public method

function squareRoot(n) {
    // implementation
}

we can intuit that it should be taking a number. Since it's well named, we can also predict it will return the square root of that number. What will it do when it gets a negative number?

One option would be to "just do something" and carry on. Math.sqrt chose to return NaN.

A safer option might be to reject the negative input.

function squareRoot(n) {
    if(n < 0) throw new Error("n must be positive");
    // implementation
}

That, right there, is a code-contract pre-condition. We write them all the time. Let's not forget that we probably want to be sure we are given a number too:

function squareRoot(n) {
    if('number' !== typeof n) throw new Error(`'n' should be a 'number' but was '${typeof n}'`);
    if(n < 0) throw new Error(`'n' should be positive but was '${n}'`);
    // implementation
}

It's getting tedious already, and this is a simple function.

How would this look using icc-contracts?

import contract from 'icc-contracts';

function squareRoot(n) {
    icc:contract(is=>[[{n}, is.number, is.positive]]);
    // implementation
}

Same readable errors, same verification, much more readable. The expressive pattern also reduces the chance for simple typos to trip you up.

And it is expressed as a single JavaScript labelled-statement, which means we can easily remove it during our build process. Something that would be nigh on impossible to do with the guards in the bare code version :)

Multiple arguments

It isn't any harder to handle multiple arguments.

import contract from 'icc-contracts';

function func(n, p) {
    icc:contract(is=>[
		[{n}, is.number, is.positive],
		[{p}, is.string, is.notEmpty],
	]);
    // implementation
}

Objects

So far, so simple. But of course, most parameters are not simple native types. So what to do with objects?

No problem, the 'with' condition allows us to apply all the same logic to individual members of objects too.

import contract from 'icc-contracts';

function func(args) {
    icc:contract(is=>[
		[{args}, is.with({
			n: [is.number, is.positive],
			p: [is.string, is.notEmpty],
		})],
	]);
    // implementation
}

Optionals

Ok, ok, but what about those times when we can reasonably accept two different things through the same argument, like optional parameters? Simple, there's a condition for that too.

import contract from 'icc-contracts';

function func(arg) {
    icc:contract(is=>[
		[{arg}, is.either([
			[is.undefined],
			[is.string, is.notEmpty],
		])],
	]);
    // implementation
}

Conditions

Note: 'argument' refers to the argument of the method for which you are specifying the contract.

Until now, is a limited list of conditions that can be applied to arguments. I'm happy to take suggestions for new ones.

|type|condition|description| |----|---------|-----------| |is.undefined | | checks if the argument is undefined| |is.number | | checks if the argument is a number | | |is.positive | checks if an argument is a positive| | |is.negative | checks if an argument is a negative| | |is.greaterThan(v) | checks if an argument is > v| | |is.lessThan(v) | checks if an argument is < v| | |is.inRange(inclusiveBegin, inclusiveEnd) | checks if an argument is >= inclusiveBegin && <= inclusiveEnd| |is.string | |checks if the argument is a string| | |is.notEmpty | checks if the argument is not ''| |is.object | |checks if the argument is an object| | |is.instanceof(type)| checks if the argument is an instance of the specfied type| | |is.with(obj) |'obj' must have a member for each desired member on the argument. Each member must consist of an Array of conditions.e.g.{a:[is.number], b:[is.string, is.notEmpty]}Only check the properties you care about, the argument may well contain additional properties which your method can safely ignore. | |is.either([...])| |takes an array of condition arrays. If any of the arrays pass, either passes. If none of them pass, a compound error of all the failures is thrown.|

Meta

LICENSE (Mozilla Public License)