SkillAgentSearch skills...

Offensive.js

:facepunch: Human-readable, fast and boilerplate-free contract programming (precondition checks) for JavaScript.

Install / Use

/learn @mchalapuk/Offensive.js

README

offensive :facepunch: js

NPM version

A human-readable, fast and boilerplate-free contract programming library for JavaScript.

Why would I want it?

  1. It reduces the boilerplate of writing assertion messsages to zero,
  2. Provides very intuitive and extensible DSL for writing assertions,
  3. Low core bundle size (22.5kB minified) and a way of bundling only needed assertions,
  4. Has zero runtime dependencies which greatly increases package security,
  5. It's TypeScript-friendly (contains its own .d.ts files).

Installation

npm install --save offensive

Loading The Library

// node-style require
const { contract } = require('offensive');
// or (with all assertions pre-loaded)
const { contract } = require('offensive/all');

// es6-style default import
import { contract } from 'offensive';
// or (with all assertions pre-loaded)
import { contract } from 'offensive/all';

Loading Assertions

In order to minimize the bundle payload, each assertion can be imported separately, either during application bootup or in each file where the specific assertions are used. The same assertion can be safely imported multiple times (importing an assertion second time is a no op).

// node-style require
require('offensive/assertions/aString/register');

// es6-style import
import 'offensive/assertions/aString/register';

When using the library on server-side, the bundle size is typically of no concern. For those situations, the library supports loading all assertions at once.

import 'offensive/assertions/register';
import { contract } from 'offensive';

// or even shorter
import { contract } from 'offensive/all';

Usage Examples

Precondition Checks A.K.A. Offensive Programming

Programming offensively is about throwing exceptions a lot. As soon as corrupted state or illegal parameter is detected, program is crashed with a descriptive error message. This technique greatly helps in finding bugs at their cause.

import 'offensive/assertions/fieldThat/register';
import 'offensive/assertions/aNumber/register';

import { contract } from 'offensive';

class Point2D {
  /**
   * @param init initializer object containing `x` and `y` properties.
   */
  constructor(init) {
    // Contract is satisfied if init contains
    // `x` and `y` property of type number.
    contract('init', init)
      .has.fieldThat('x', x => x.is.aNumber)
      .and.fieldThat('y', y => y.is.aNumber)
      .check();
    this.x = init.x;
    this.y = init.y;
  }
}

Now, following erroneus call...

const point = new Point2D({ x: 'a', y: null });

...will result in throwing following exception.

ContractError: init.x must be a number (got 'a') and init.y be a number (got null)
  at operatorContext (offensives/ContextFactory.js:34:33)
  at new Point2D (example.js:16:7)
  at Object.<anonymous> (example.js:22:15)

Alternatively, above contract could be implemented using multiple checks, but the error would only contain information about first failed check.

contract('init', init).is.anObject.check();
contract('init.x', init.x).is.aNumber.check();
contract('init.y', init.y).is.aNumber.check();

Above examples use only [.anObject][object], [.aNumber][number] and [.fieldThat][field-that] assertions.

See full list of offensive.js built-in assertions.

Defensive Programming

Offensive programming is not applicable when collaborating with external components. A program should not crash in response to a bug in another program. Logging an error and trying to correct it by using default value or simply ignoring erroneus input would be a better way of handling such cases.

Ping Server

Following example is a fully functional HTTP-based ping server implemented using express.js with defensive checks on HTTP request implemented using offensive.js.

import * as express from 'express';
import * as bodyParser from 'body-parser';

import 'offensive/assertions/aString/register';
import 'offensive/assertions/fieldThat/register';
import { contract } from 'offensive';

const app = express();
app.use(bodyParser.json());

// A simple ping service which reflects messages sent to it.
app.post('/ping', function (req, res, next) {
  // Contract is satisfied if body has a message which is a string
  // (.propertyThat is an alias of .fieldThat assertion)
  const error = contract('req.body', req.body)
    .contains.propertyThat('message', message => message.is.aString)
    .getError();
  if (error) {
    res.status(400).json({ error });
    return;
  }

  const { message } = body;
  res.json({ message });
});

Above code presents defensive programming on the server side, but the same technique is applicable in the client. Client-server contract should be tested both, after receiving request from the client, and after receiving response from the server.

API Reference

Table of Contents

  1. Contract Function
  2. .check()
  3. .getError()
  4. Assertions
  5. [Boolean Operators][operators]
  6. [Legacy Call Operator][legacy-call-operator]

<a id=contract-function></a>

Contract Function

function contract<T>(varName : string, testedValue : T) : AssertionBuilder<T>;

Creates an instance of AssertionBuilder. Methods of returned instance add assertions to the builder. Requested assertions will be checked against given testedValue after [executing assertion expression][call-operator]. In case some assertions fail, given name will be used as part of error message.

import { contract } from 'offensive';
...

contract('arg', arg)...

<a id=check></a>

.check() aliases: throwIfUnmet()

interface AssertionBuilder<T> {
  check(errorName?: string = 'ContractError') : T;
}

Executes built assert expression. Returns testedValue if assertion succeeds. Throws ContractError in case it fails. intended for offensive programming.

import 'offensive/assertions/length';
import { contract } from 'offensive';

contract('arg', arg)
  .has.length(10)
  .check(); // <- executes built assert expression

NOTE: Assertion will not be run unless this method or .getError() is invoked.

<a id=get-error></a>

.getError()

interface AssertionBuilder<T> {
  getError(errorName?: string = 'ContractError') : string | null;
}

Executes built assert expression. Returns error message if assertion fails. Returns null in case it succeeds. Intended for defensive programming.

import 'offensive/assertions/length';
import { contract } from 'offensive';

const error = contract('arg', arg)
  .has.length(10)
  .getError(); // <- executes built assert expression

NOTE: Assertion will not be run unless this method or .check() is invoked.

Assertions

offensive.js contains following built-in assertions.

Table of Contents

  1. .Null
  2. .Undefined
  3. .Empty
  4. .ofType(requiredType)
  5. [.aBoolean][boolean]
  6. [.aNumber][number]
  7. [.anInteger][integer]
  8. [.aString][string]
  9. [.anObject][object]
  10. [.aFunction][function]
  11. [.anArray][array]
  12. [.anInstanceOf(RequiredClass)][instance-of]
  13. [.aDate][date]
  14. [.aRegExp][regexp]
  15. [.True][true]
  16. [.False][false]
  17. [.truthy][truthy]
  18. [.falsy][falsy]
  19. [.matches(regexp)][matches]
  20. [.anEmail][email]
  21. [.aUUID][uuid]
  22. [.anEmptyString][empty-string]
  23. [.aNonEmptyString][non-empty-string]
  24. [.anIntegerString][integer-string]
  25. [.startsWith(substring)][starts-with]
  26. [.endsWith(substring)][ends-with]
  27. [.substring(substring)][substring]
  28. [.equalTo][equal-to]
  29. [.exactly][exactly]
  30. [.lessThan(rightBounds)][less-than]
  31. [.lessThanOrEqualTo(rightBounds)][less-than-or-equal-to]
  32. [.greaterThan(leftBounds)][greater-than]
  33. [.greaterThanOrEqualTo(leftBounds)][greater-than-or-equal-to]
  34. [.inRange(leftBounds, rightBounds)][in-range]
  35. [.before(rightBounds, boundsVarName?)][before]
  36. [.after(leftBounds, boundsVarName?)][after]
  37. [.field(fieldName)][field]
  38. [.fieldThat(fieldName, condition)][field-that]
  39. [.allFieldsThat(condition)][all-fields-that]
  40. [.method(methodName)][method]
  41. [.length(requiredLength)][length]
  42. [.oneOf(set, name)][one-of]
  43. [.elementThat(index, assertName, condition)][element-that]
  44. [.allElementsThat(assertName, condition)][all-elements-that]
  45. [.includes(element)][includes]
  46. [.includesAllOf(element)][includes-all-of]
  47. [.includesElementThat(condition)][includes-element-that]

<a id=null-assertion></a>

.Null aliases: .null, .Nil, .nil

Asserts that checked value is null using ===. Typically used in combination with [.not][not] operator.

contract('arg', arg).is.not.Null.check();

<a id=undefined-assertion></a>

.Undefined aliases: .undefined

Asserts that checked value is undefined. Typically used in combination with [.not][not] operator.

contract('arg', arg).is.not.Undefined.check();

<a id=empty-assertion></a>

.Empty aliases: .empty

Asserts that checked value is null or undefined. Typically used in combination with [.not][not] operator.

contract('arg', arg).is.not.Empty.check();

<a id=of-type-assertion></a>

.ofType(requiredType : string) aliases: .type

Asserts that checked value is of *

View on GitHub
GitHub Stars9
CategoryLegal
Updated7mo ago
Forks2

Languages

TypeScript

Security Score

82/100

Audited on Aug 28, 2025

No findings