Research
Security News
Malicious npm Packages Inject SSH Backdoors via Typosquatted Libraries
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
babel-plugin-spectypes
Advanced tools
Fast, compiled, eval-free data validator/transformer
ajv
fast-check
powered testsbabel
to compile validators, so no eval
or new Function
involvedThere are two packages to install - spectypes
, which contains type definitions and small set of runtime helpers and babel-plugin-spectypes
, which parses and compiles validators into functions:
npm i spectypes
npm i babel-plugin-spectypes -D
Add babel-plugin-spectypes
to plugins section in your babel
config:
"plugins": [
+ "babel-plugin-spectypes"
]
Original code:
import { array, number } from 'spectypes'
const check = array(number)
The plugin will search for named imports like import { ... } from 'spectypes'
or const { ... } = require('spectypes')
and get all imported identifiers (aliases also supported). All variable declarations which include these identifiers will be converted into validating functions.
Transformed code:
const check = (value) => {
let err
if (!Array.isArray(value)) {
;(err = err || []).push({
issue: 'not an array',
path: []
})
} else {
for (let index = 0; index < value.length; index++) {
const value_index = value[index]
if (typeof value_index !== 'number') {
;(err = err || []).push({
issue: 'not a number',
path: [index]
})
}
}
}
return err
? { tag: 'failure', failure: { value, errors: err } }
: { tag: 'success', success: value }
}
Primitive validators
Complex validators
Utilities
Validates a boolean value
import { boolean } from 'spectypes'
const check = boolean
expect(check(true)).toEqual({
tag: 'success',
success: true
})
expect(check('false')).toEqual({
tag: 'failure',
failure: {
value: 'false',
errors: [{ issue: 'not a boolean', path: [] }]
}
})
Transformed code
|
Creates a literal validator spec. literal
can validate strings, numbers, booleans, undefined and null. literal(undefined)
is treated specially when used as a property validator inside object
or struct
.
import { literal } from 'spectypes'
const check = literal('test')
expect(check('test')).toEqual({
tag: 'success',
success: 'test'
})
expect(check('temp')).toEqual({
tag: 'failure',
failure: {
value: 'temp',
errors: [{ issue: "not a 'test' string literal", path: [] }]
}
})
Transformed code
|
Transformer spec, that accepts undefined
and null
values and maps them to undefined
.
nullish
is treated specially when used as a property validator inside object
or struct
.
import { nullish } from 'spectypes'
const check = nullish
expect(check(undefined)).toEqual({
tag: 'success'
success: undefined
})
expect(check(null)).toEqual({
tag: 'success'
success: undefined
})
expect(check(123)).toEqual({
tag: 'failure',
failure: {
value: 'temp',
errors: [{ issue: "not 'null' or 'undefined'", path: [] }]
}
})
Transformed code
|
Validates a number value.
import { number } from 'spectypes'
const check = number
expect(check(0)).toEqual({
tag: 'success',
success: 0
})
expect(check({})).toEqual({
tag: 'failure',
failure: {
value: {},
errors: [{ issue: 'not a number', path: [] }]
}
})
Transformed code
|
Validates a string value.
import { string } from 'spectypes'
const check = string
expect(check('')).toEqual({
tag: 'success',
success: ''
})
expect(check(null)).toEqual({
tag: 'failure',
failure: {
value: null,
errors: [{ issue: 'not a string', path: [] }]
}
})
Transformed code
|
Empty validator spec. unknown
is treated specially when used as a property validator inside object
or struct
.
import { unknown } from 'spectypes'
const check = unknown
expect(check('anything')).toEqual({
tag: 'success',
success: 'anything'
})
Transformed code
|
Creates an array validator spec. Takes a spec to validate each item of an array.
import { array, number } from 'spectypes'
const check = array(number)
expect(check([1, 2, 3])).toEqual({
tag: 'success',
success: [1, 2, 3]
})
expect(check({ 0: 1 })).toEqual({
tag: 'failure',
failure: {
value: { 0: 1 },
errors: [{ issue: 'not an array', path: [] }]
}
})
expect(check([1, 2, '3', false])).toEqual({
tag: 'failure',
failure: {
value: [1, 2, '3', false],
errors: [
{ issue: 'not a number', path: [2] },
{ issue: 'not a number', path: [3] }
]
}
})
Transformed code
|
Can be used only as an argument for array
and record
to create filtered transformer specs. Filtering happens after each item or key validation. Takes a spec to validate each item or key of a collection and filter predicate.
import { array, number, filter } from 'spectypes'
const check = array(filter(number, (x) => x > 1))
expect(check([1, 2, 3])).toEqual({
tag: 'success',
success: [2, 3]
})
expect(check([1, 2, null])).toEqual({
tag: 'failure',
failure: {
value: [1, 2, null],
errors: [{ issue: 'not a number', path: [2] }]
}
})
Transformed code
|
Type predicate will be taken into account if provided
import { array, string, filter } from 'spectypes'
const check = array(filter(string, (x): x is 'test' => x === 'test'))
expect(check(['hello', 'test', 'world'])).toEqual({
tag: 'success',
success: ['test'] // readonly 'test'[]
})
Creates a spec with custom constraint. Takes a basis spec and a function to perform additinal validation.
import { number, limit } from 'spectypes'
const check = limit(number, (x) => x > 1)
expect(check(5)).toEqual({
tag: 'success',
success: 5
})
expect(check(-5)).toEqual({
tag: 'failure',
failure: {
value: -5,
errors: [{ issue: 'does not fit the limit', path: [] }]
}
})
expect(check('5')).toEqual({
tag: 'failure',
failure: {
value: '5',
errors: [{ issue: 'not a number', path: [] }]
}
})
Transformed code
|
Type predicate will be taken into account if provided
import { array, string, limit } from 'spectypes'
const check = array(limit(string, (x): x is 'test' => x === 'test'))
expect(check(['test', 'test', 'test'])).toEqual({
tag: 'success',
success: ['test', 'test', 'test'] // readonly 'test'[]
})
Creates a spec that transforms the result of successful validation. Takes basis spec and mapping function.
import { number, map } from 'spectypes'
const check = map(number, (x) => x + 1)
expect(check(10)).toEqual({
tag: 'success',
success: 11
})
expect(check(undefined)).toEqual({
tag: 'failure',
failure: {
value: undefined,
errors: [{ issue: 'not a number', path: [] }]
}
})
Transformed code
|
Can combine tuple
with array
or object
with record
into single spec.
import { tuple, array, string, boolean, merge } from 'spectypes'
const check = merge(tuple(string, string), array(boolean))
expect(check(['hello', 'world', true])).toEqual({
tag: 'success',
success: ['hello', 'world', true]
})
expect(check(['hello', 'world', '!'])).toEqual({
tag: 'failure',
failure: {
value: ['hello', 'world', '!'],
errors: [{ issue: 'not a string', path: [2] }]
}
})
expect(check(['hello'])).toEqual({
tag: 'failure',
failure: {
value: ['hello'],
errors: [{ issue: 'length is less than 2', path: [] }]
}
})
Transformed code
|
import { object, record, number, string, boolean, merge } from 'spectypes'
const check = merge(object({ x: number }), record(string, boolean))
expect(check({ x: 123, y: true })).toEqual({
tag: 'success',
success: { x: 123, y: true }
})
expect(check({ x: true, y: 123 })).toEqual({
tag: 'failure',
failure: {
value: { x: true, y: 123 },
errors: [
{ issue: 'not a number', path: ['x'] },
{ issue: 'not a boolean', path: ['y'] }
]
}
})
Transformed code
|
Creates an object validator spec. Validation will fail if validated object has a property set different from the one specified. Takes an object with specs to validate object properties. literal(undefined)
, nullish
and unknown
are treated specially when used as a property validator inside object
.
import { object, number, string, boolean } from 'spectypes'
const check = object({ x: number, y: string, z: boolean })
expect(check({ x: 1, y: '2', z: false })).toEqual({
tag: 'success',
success: { x: 1, y: '2', z: false }
})
expect(check({ x: 1, y: '2', z: false, xyz: [] })).toEqual({
tag: 'failure',
failure: {
value: { x: 1, y: '2', z: false, xyz: [] },
errors: [{ issue: 'excess key - xyz', path: [] }]
}
})
expect(check({})).toEqual({
tag: 'failure',
failure: {
value: {},
errors: [
{ issue: 'not a number', path: ['x'] },
{ issue: 'not a string', path: ['y'] },
{ issue: 'not a boolean', path: ['z'] }
]
}
})
expect(check([])).toEqual({
tag: 'failure',
failure: {
value: [],
errors: [{ issue: 'not an object', path: [] }]
}
})
Transformed code
|
Creates an optional object property validator spec. Can be used only inside object
and struct
arguments. Will not produce any validation errors if property equals undefined
or is not present in the validated object.
import { optional, struct, number } from 'spectypes'
const check = struct({ x: optional(number) })
expect(check({ x: 5 })).toEqual({
tag: 'success',
success: { x: 5 }
})
expect(check({ x: undefined })).toEqual({
tag: 'success',
success: { x: undefined }
})
expect(check({})).toEqual({
tag: 'success',
success: {}
})
expect(check({ x: 'x' })).toEqual({
tag: 'failure',
failure: {
value: { x: 'x' },
errors: [{ issue: 'not a number', path: ['x'] }]
}
})
Transformed code
|
Creates a record validator spec. This validator is protected from prototype pollution and validation will fail if validated object contains properties that override Object.proptotype
methods. This function has two signatures - one takes a spec to validate each key of a record and a spec to validate each item, another takes only item spec and treats all keys as strings. Key spec can be a string
, template
, string literal
or union
of these specs.
import { record, boolean } from 'spectypes'
const check = record(boolean)
expect(check({ foo: false, bar: true })).toEqual({
tag: 'success',
success: { foo: false, bar: true }
})
expect(check(true)).toEqual({
tag: 'failure',
failure: {
value: true,
errors: [{ issue: 'not an object', path: [] }]
}
})
expect(check({ toString: true })).toEqual({
tag: 'failure',
failure: {
value: { toString: true },
errors: [{ issue: "includes banned 'toString' key", path: [] }]
}
})
Transformed code
|
Creates an object transformer spec. All properties of validated object that are not present in passed param will be removed from the result of successful validation. Takes an object with specs to validate object properties. literal(undefined)
, nullish
and unknown
are treated specially when used as a property validator inside struct
.
import { struct, number, string, boolean } from 'spectypes'
const check = struct({ x: number, y: string, z: boolean })
expect(check({ x: 1, y: '2', z: false })).toEqual({
tag: 'success',
success: { x: 1, y: '2', z: false }
})
expect(check({ x: 1, y: '2', z: false, xyz: [] })).toEqual({
tag: 'success',
success: { x: 1, y: '2', z: false }
})
expect(check({})).toEqual({
tag: 'failure',
failure: {
value: {},
errors: [
{ issue: 'not a number', path: ['x'] },
{ issue: 'not a string', path: ['y'] },
{ issue: 'not a boolean', path: ['z'] }
]
}
})
expect(check([])).toEqual({
tag: 'failure',
failure: {
value: [],
errors: [{ issue: 'not an object', path: [] }]
}
})
Transformed code
|
Creates a template string validator spec. Takes number
, string
, boolean
, literal
specs and their union
s to validate parts of the validated string.
import { template, literal, number, string, boolean } from 'spectypes'
const check = template(literal('test'), string, number, boolean)
expect(check('test___123false')).toEqual({
tag: 'success',
success: 'test___123false'
})
expect(check('test___false')).toEqual({
tag: 'failure',
failure: {
value: 'test___false',
errors: [{ issue: 'template literal mismatch', path: [] }]
}
})
Transformed code
|
Creates a tuple validator spec. Takes specs to validate tuple parts.
import { tuple, number, string, boolean } from 'spectypes'
const check = tuple(number, string, boolean)
expect(check([1, '2', false])).toEqual({
tag: 'success',
success: [1, '2', false]
})
expect(check([])).toEqual({
tag: 'failure',
failure: {
value: [],
errors: [{ issue: 'length is not 3', path: [] }]
}
})
expect(check([1, '2', false, 1000])).toEqual({
tag: 'failure',
failure: {
value: [1, '2', false, 1000],
errors: [{ issue: 'length is not 3', path: [] }]
}
})
expect(check(['1', '2', 'false'])).toEqual({
tag: 'failure',
failure: {
value: ['1', '2', 'false'],
errors: [
{ issue: 'not a number', path: [0] },
{ issue: 'not a boolean', path: [2] }
]
}
})
Transformed code
|
Creates a union validator spec. Takes specs to validate union cases.
import { union, number, string, boolean } from 'spectypes'
const check = union(number, string, boolean)
expect(check('temp')).toEqual({
tag: 'success',
success: 'temp'
})
expect(check(true)).toEqual({
tag: 'success',
success: true
})
expect(check(null)).toEqual({
tag: 'failure',
failure: {
value: null,
errors: [
{ issue: 'union case #0 mismatch: not a number', path: [] },
{ issue: 'union case #1 mismatch: not a string', path: [] },
{ issue: 'union case #2 mismatch: not a boolean', path: [] }
]
}
})
Transformed code
|
Spec that tells babel
plugin to generate a wrapper for an external transformer spec. Any spec containing struct
, nullish
, map
, filter
and transformer
specs will create and return new object on successful validation. Such spec has to be wrapped with transformer
when used inside another spec.
import { array, transformer, map, number } from 'spectypes'
const negated = map(number, (x) => -x)
const check = array(transformer(negated))
// Incorrect usage !!!
// const negated = transformer(map(number, (x) => -x))
// const check = array(negated)
expect(check([1, 2, -3])).toEqual({
tag: 'success',
success: [-1, -2, 3]
})
expect(check([1, 2, 'abc'])).toEqual({
tag: 'failure',
failure: {
value: [1, 2, 'abc'],
errors: [{ issue: 'not a number', path: [2] }]
}
})
Transformed code
|
Spec that tells babel
plugin to generate a wrapper for an external validator spec. Any spec not containing struct
, nullish
, map
, filter
and transformer
specs on successful validation will return validated object. Such spec has to be wrapped with validator
when used inside another spec.
import { array, validator, limit, number } from 'spectypes'
const positive = limit(number, (x) => x >= 0)
const check = array(validator(positive))
// Incorrect usage !!!
// const positive = validator(limit(number, (x) => x >= 0))
// const check = array(positive)
expect(check([0, 1, 2])).toEqual({
tag: 'success',
success: [0, 1, 2]
})
expect(check([-1, -2, -3])).toEqual({
tag: 'failure',
failure: {
value: [-1, -2, -3],
errors: [
{ issue: 'does not fit the limit', path: [0] },
{ issue: 'does not fit the limit', path: [1] },
{ issue: 'does not fit the limit', path: [2] }
]
}
})
Transformed code
|
Creates a spec to validate a value with recursive type. But data that recursively references itself is not supported. LazyTransformerSpec
type should be used when spec contains struct
, nullish
,
map
, filter
and transformer
specs, and LazyValidatorSpec
otherwise.
import { lazy, string, object, array, validator, LazyValidatorSpec } from 'spectypes'
type Person = {
readonly name: string
readonly likes: readonly Person[]
}
const person: LazyValidatorSpec<Person> = lazy(() =>
object({ name: string, likes: array(validator(person)) })
)
expect(person({ name: 'Bob', likes: [{ name: 'Alice', likes: [] }] })).toEqual({
tag: 'success',
{ name: 'Bob', likes: [{ name: 'Alice', likes: [] }] }
})
expect(person({ name: 'Alice', likes: [{ name: 'Bob', likes: 'cats' }] })).toEqual({
tag: 'failure',
failure: {
value: { name: 'Alice', likes: [{ name: 'Bob', likes: 'cats' }] },
errors: [{ issue: 'not an array', path: ['likes', 0, 'likes'] }]
}
})
Transformed code
|
Creates an empty validator that removes readonly
modifiers from the result of validation
import { object, number, string, boolean, writable } from 'spectypes'
const check = writable(object({ x: number, y: string, z: boolean }))
expect(check({ x: 1, y: '2', z: true })).toEqual({
tag: 'success',
success: { x: 1, y: '2', z: true } // { x: number, y: string, z: true }
})
Type to infer success
value
import { object, number, string, boolean, Spectype } from 'spectypes'
const check = object({ x: number, y: string, z: boolean })
// { readonly x: number; readonly y: string; readonly z: boolean }
type Value = Spectype<typeof check>
literal(undefined)
or unknown
is used as a property validator inside object
or struct
and that property is not present in the validated object the validation will fail.nullish
is used as a property validator inside object
or struct
and that property is not present in the validated object the result will still contain that property set to undefined
.import { struct, nullish, literal, unknown } from 'spectypes'
const check = struct({ nullish, unknown, literal: literal(undefined) })
expect(check({ unknown: 1, literal: undefined })).toEqual({
tag: 'success',
success: { nullish: undefined, unknown: 1, literal: undefined }
})
expect(check({ literal: undefined })).toEqual({
tag: 'failure',
failure: {
value: { literal: undefined },
errors: [{ issue: 'missing key - unknown', path: [] }]
}
})
expect(check({ unknown: undefined })).toEqual({
tag: 'failure',
failure: {
value: { unknown: undefined },
errors: [{ issue: 'missing key - literal', path: [] }]
}
})
Transformed code
|
Validators return their results as 'success or failure' wrapped values and does not throw any exceptions (other than those thrown by the functions passed to map
, limit
or filter
). This library does not include any functions to process validation results, but a compatible handy package exists - ts-railway
There is no specific APIs to create custom validators, usually just unknown
, map
and limit
are enough to create a validator for arbitrary data. For example, lets create a validator that checks if some value is a representation of a date and converts that value to Date
object:
import { unknown, map, limit } from 'spectypes'
const check = map(
limit(unknown, (x) => !isNaN(Date.parse(x))),
(x) => new Date(x)
)
const date = new Date('Sun Apr 24 2022 12:51:57')
expect(check('Sun Apr 24 2022 12:51:57')).toEqual({
tag: 'success',
success: date
})
expect(check([1, 2, 'abc'])).toEqual({
tag: 'failure',
failure: {
value: [1, 2, 'abc'],
errors: [{ issue: 'does not fit the limit', path: [] }]
}
})
Transformed code
|
Having 100% of the code covered with tests reflects only the coverage of generative code, not the generated one. It says little about the amount of potential bugs in this package. Because of that most of the test cases are randomly generated. When testing valid data validation it will generate spectypes
validator and corresponding fast-check
arbitrary, then validator will ensure that values provided by arbitrary are valid. When testing invalid data validation it will also generate an expected error, then validator will ensure that values provided by arbitrary are invalid and lead to expected error.
FAQs
Babel plugin that compiles spectypes validators
The npm package babel-plugin-spectypes receives a total of 133 weekly downloads. As such, babel-plugin-spectypes popularity was classified as not popular.
We found that babel-plugin-spectypes demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
Security News
MITRE's 2024 CWE Top 25 highlights critical software vulnerabilities like XSS, SQL Injection, and CSRF, reflecting shifts due to a refined ranking methodology.
Security News
In this segment of the Risky Business podcast, Feross Aboukhadijeh and Patrick Gray discuss the challenges of tracking malware discovered in open source softare.