0.0.19 • Published 9 months ago

@jrc03c/js-type-experiments v0.0.19

Weekly downloads
-
License
ISC
Repository
-
Last release
9 months ago

Intro

This is just a little utility for doing realtime type-checking in JS. Obviously, there are already some kinds of typed arrays in JS, but they mostly deal with number types and/or raw binary data. This utility allows for the creation of typed arrays and typed properties of any type.

Installation

npm install --save https://github.com/jrc03c/js-type-experiments

Usage

const {
  createType,
  createTypedArray,
  defineTypedProperty,
  isOfType,
} = require("@jrc03c/js-type-experiments")

const fooType = createType("Foo", v => v === "foo")
console.log("foo" instanceof fooType) // true
console.log("bar" instanceof fooType) // false

const myNumbers = createTypedArray("number")
myNumbers.push(234) // okay
myNumbers.push("Hello, world!") // error

const person = {}
defineTypedProperty(person, "name", "string")
person.name = "Alice" // okay
person.name = true // error

console.log(isOfType(234, "number")) // true
console.log(isOfType(234, "string")) // false

API

createType(name, fn)

Creates a custom type defined by a pass / fail function.

A concrete example might make the purpose of this function a little clearer. Suppose we want to create an array of non-negative integers. Using the createTypedArray function below, we could try something like this:

const x = createTypedArray("number")

But the problem, of course, is that x would accept any number, not just non-negative integers. And that's where the createType function comes to the rescue. It allows us to define a custom type without creating a whole new class (which is especially helpful for primitive values that don't use classes) merely by passing a name and a test function that tests each value to determine whether it's a member of that type or not. So, to create an array of non-negative integers, we could do something like this:

function isANonNegativeInteger(x) {
  return typeof x === "number" && x >= 0 && Math.floor(x) === x
}

const nonNegativeIntegerType = createType("NonNegInt", isANonNegativeInteger)
const x = createTypedArray(nonNegativeIntegerType)
x.push(234) // okay
x.push(-234) // error

createTypedArray(type, allowsSubclassInstances)

The type argument can be a class (like Date or a custom class) or a string representing a primitive type (like "number" or "boolean").

The allowsSubclassInstances argument is a boolean representing whether or not the array will accept subclass instances of type. It is true by default. For example, imagine that we have a class called Person that has a subclass called Employee, and that we want to create a typed array containing only Person instances. Passing true for the allowsSubclassInstances argument would imply that both Person instances and Employee instances could be inserted into the array; whereas passing false for the argument would imply that only Person (but not Employee) instances could be inserted into the array.

class Person {}
class Employee extends Person {}

const personAndSubclassesArray = createTypedArray(Person, true)
const personOnlyArray = createTypedArray(Person, false)

const alice = new Employee()
personAndSubclassesArray.push(alice) // okay
personOnlyArray.push(alice) // error

NOTE: One very important thing to know about the typed arrays created via this function is that new typed arrays of the same type cannot be created using the new keyword! For example, this will throw an error:

const NumberArray = createTypedArray("number").constructor
const x = new NumberArray() // error

The reason for this limitation is that the typed arrays created by this function are actually wrapped in Proxys — and since those can't be returned from within constructors, the constructor and the new keyword must be avoided. However, it's still possible to create new typed arrays using the static from method:

const NumberArray = createTypedArray("number").constructor
const x = NumberArray.from([2, 3, 4])
const y = NumberArray.from([5, 6, 7])
const z = NumberArray.from()

Of course, you can also use the createTypedArray function multiple times in a row to create the same kinds of typed arrays:

const x = createTypedArray("number")
const y = createTypedArray("number")
x.constructor === y.constructor // true

NOTE: Another limitation of typed arrays is that they can't be subclassed. That functionality is on my to-do list, but I haven't gotten to it yet!

defineTypedProperty(object, property, type, options)

The property argument must be a string representing the name of the property to be created.

The type argument works the same as in the createTypedArray function above.

The options argument here is actually the same as the options argument passed into Object.defineProperty (called descriptor in the MDN docs) with only one addition: it can optionally take an "allowsSubclassInstances" property that must be a boolean. That property has the same functionality as in the createTypedArray function above.

By the way, here's a useful little recipe if you need to define a property whose type is a typed array. For example, in a class called Person, there might be a property called nicknames that's supposed to be an array of (only) strings. To define such a property, do this:

const {
  createTypedArray,
  defineTypedProperty,
} = require("@jrc03c/js-type-experiments")

class Person {
  constructor() {
    // ...

    defineTypedProperty(
      this,
      "nicknames",
      createTypedArray("string").constructor,
    )

    // ...
  }
}

In other words, we create a temporary typed array using the createTypedArray function, specifying "string" as the type, and then immediately use the constructor of that array since that's the "type" of value — i.e., the StringArray type — that the property nicknames should accept.

isOfType(value, type, allowsSubclassInstances)

Returns a boolean indicating whether or not value is of type type. Optionally, allowsSubclassInstances can be set to true or false to indicate whether or not value is allowed to be a subclass of type. (This argument makes no difference for primitive types, though, since they can't be subclassed as far as I know.) For example:

isOfType(3, "number") // true
isOfType(3, "string") // false
isOfType(3, Date) // false

class Person {}
class Employee extends Person {}

const alice = new Employee()
isOfType(alice, Person) // true
isOfType(alice, Person, false) // false

NOTE: Be aware that null and undefined values will match any type! For example:

isOfType(undefined, "number") // true
isOfType(null, "string") // true

The reason for this behavior is that it's useful to be able to assign null or undefined values to (e.g.) typed arrays or typed properties such as those created with the createTypedArray and defineTypedProperty functions.

Notes

NaN, null, and undefined values: Arrays and properties of any type will accept null and undefined values without throwing errors. Number arrays and number properties will also accept NaN values.

0.0.19

9 months ago

0.0.17

9 months ago

0.0.18

9 months ago

0.0.14

10 months ago

0.0.15

10 months ago

0.0.16

10 months ago

0.0.13

12 months ago

0.0.12

1 year ago

0.0.10

1 year ago

0.0.11

1 year ago

0.0.9

1 year ago

0.0.7

2 years ago

0.0.6

2 years ago

0.0.5

2 years ago

0.0.4

2 years ago

0.0.3

2 years ago

0.0.2

2 years ago