jdes v0.2.14
Destructured JS
A type safe JS runtime.
TODO for CLI
- target JS (cleanup)
- target another PL that can export WASM or compile natively
API
Allow the definition of enums, structs, unions, or any other arbitrary type.
The type parameter can be either a string or an array of strings, in case of multiple types aliases.
The definition is either the returned value of enums, and struct, the union reference itself, or an object that exposes at least 2 methods: check(value, asArray) and cast(value).
define('special', {
check(value, asArray) {
// true when the type is in squared brackets
return asArray ?
value.map(v => this.check(v, false)) :
value instanceof Special;
},
cast(value) {
return this.check(value, false) ? value : new Special(value);
}
});
const {special: single} = new Special;
const {[special]: multi} = [new Special, new Special];It verifies that a specific value is an expected type, passing through the definition check(value, false) when type is not in square brackets, and check(value, true) when it is.
const value = 'test';
if (!is({string: value}))
throw new TypeError(`unexpected ${value}`);
const values = ['a', 'b', 'c'];
if (!is({[string]: values}))
throw new TypeError(`unexpected ${values}`);It performs a cast through the definition cast(value) method, and it's responsibility of such method to understand what kind of value need to be casted, and throw in case there's no way to cast it.
// the following throws a TypeError
const {string: test} = 123;
// the following works as expected
const {string: test} = as({string: 123});
as({string: 123}) === "123"; // trueenums are simple, static, values that could be just named, or have a simple value.
enums are (currently?) defined in the global context, and it's not possible to define different enums with the same name.
define('Color', enums(
'RED', // by default enums are Symbol
'GREEN',
{BLUE: 123} // but these could be simple values too
));
console.log(Color);
// {RED: Symbol(RED), GREEN: Symbol(GREEN), BLUE: 123}
const {Color: red} = Color.RED;
const {[Color]: colors} = [Color.RED, Color.BLUE];Differently from other types, enums cannot really be casted.
The syntax to define a safe function must provide all information needed to make it safe.
const sum = fn({int: ({int: arg0}, {int: arg1 = 0}) => {
return arg0 + arg1;
}});
sum(1); // 1
sum(1, 2); // 3
sum('a', 'b'); // throws a TypeErrorPlease note:
- optional arguments must be at the end of the signature.
({int: a}, {int: b = 1})is OK, but({int: a = 1}, {int: b})is not. - the return type must always be present. If nothing is returned, a
voidtype is expected - rest arguments are probably supported but these should not be used
- for options/objects use the
{object: {props}}if destructuring fields is needed - for overloads define unions
Differently from regular JS functions, jdes functions can be serialized as JSON, and these will be parsed back once parsed.
const json = JSON.serialize(sum);
const fn = JSON.parse(json);
fn(2, 3); // 5In jdes classes are mostly discouraged for at least two reasons:
- these cannot be used as type
- these cannot target other programming languages, as they all have slightly different classes
Accordingly, whenever you think you need a class, you need to create a struct.
define('Point2D', struct(
// mandatory fields {type: name}
{int: 'x'},
{int: 'y'}
));
// literals are casted automatically
const {Point2D: p2d} = {x: 1, y: 2};
const {[Point2D]: p2ds} = [p2d, {x: 3, y: 2}];
// also OK through explicit new Point2D
const myPoint = new Point2D({x: 1, y: 2});If a mandatory field is not available as literal property, a TypeError will be thrown.
However, fields can also have optional entries that don't need to be present in the literal.
define('Point3D', struct(
// mandatory fields
// multiple type: [name, ...] allowed
{int: ['x', 'y']},
// optional fields
// {type: {name: defaultValue}}
{int: {z: 0}}
));
const {Point3D: p3d} = {x: 1, y: 2};
p3d.z; // 0A struct can also have methods, which are just guarded functions.
define('Point3D', struct(
{int: ['x', 'y']}, // mandatory fields
{int: {z: 0}}, // default fields
// methods {returnType: {methodName({argType: name}, ...) {}}}
{[int]: {coords() {
return [this.x, this.y, this.z];
}}}
));
const {Point3D: p3d} = {x: 1, y: 2};
p3d.coords(); // [1, 2, 0]The union utility makes overloads possible by defining multiple known types separated by an underscore.
define('int_float', union);
const {int_float: a} = 1;
const {int_float: b} = 1.2;
const {[int_float]: c} = [a, b];As the _ underscore is used to split/check types, it is a good idea to never define a type within an underscore, in case it needs to be used as union.
The map utility helps defining Map instances with a well known type for both keys or values.
define('StrInt', map(str, int));
// maps definition work via shortcut
const {StrInt: si} = [];
si.set('one', 1); // OK
si.set(1, 'one'); // failsPlease note, when targeting compilable targets it is mandatory to define typed maps, and jdes maps should not be iterated right away:
// this breaks
for (const [key, value] of si);
// this works
for (const [key, value] of si.entries());This is due to the fact for/of loops in jdes are always transformed as regular array loops.
The set utility helps defining Set instances with a well known type for values.
define('Str', set(str));
// maps definition work via shortcut
const {Str: s} = [];
s.add('one'); // OK
s.add(1); // failsPlease note, when targeting compilable targets it is mandatory to define typed sets, and jdes sets should not be iterated right away:
// this breaks
for (const value of s);
// this works
for (const value of s.values());This is due to the fact for/of loops in jdes are always transformed as regular array loops.
jdes runtime guards properties access, type checks, arguments and much more, but all these runtime checks come with a cost.
Even if performance are still very reasonable, a safe execution takes 10X up to 1000X what would be an unsafe execution time.
Accordingly, it is highly recommended to mark jdes unsafe after importing it, when the code is meant to run in production.
import {unsafe} from 'jdes';
if (global.PRODUCTION)
unsafe();The unsafe call is not reversible: once jdes is unsafe it's unsafe.
If the code is transpiled though, and JS is used as target, there's no need to flag anything unsafe, as the environment will be super clean and no guards whatsoever are used.
These are all the pre-defined generic types:
inta generic integer, casted viaparseInt(value, 10)floata generic float, casted viaparseFloat(value)boolean-booleithertrueorfalse, casted viaBoolean(value)number-numa generic number, casted viaNumber(value)string-stra generic string, casted viaString(value)object-obja generic object (literal/instance), casted viaObject(value)function-fna generic function, casted viaFunctionwhen parsed via JSONvoidusable to describe functions return type
There is no array type for the simple reason that any type, except for the void one, can be part of an array.
// not an array
const {int: i} = 0;
// as array of int
const {[int]: ii} = [0, 0];These are all specialized types:
f32a Float32Array compatible numberf64-doublea Float64Array compatible numberi8an Int8Array compatible numberi16an Int16Array compatible numberi32an Int32Array compatible numberu8a Uint8Array compatible numberu16a Uint16Array compatible numberu32a Uint32Array compatible numberuc8a Uint8ClampedArray compatible numberi64a BigInt64Array compatible numberu64a BigUint64Array compatible number
Each specialized type cast, as value, is performed by setting the value within the index 0 and retrieving it back, while as array, the cast is done via new SpecialConstructor(array) if the array is not already an instanceof such constructor.
Please note that all specialized types are static when retrieved as array.
// single value
const {i32: i} = 0;
// as array - implicit cast
const {[i32]: ii} = [1, 2, 3];If a predefined length is needed, it is always possible to create values explicitly.
const ii = new Int32Array(100);Please note that not all these types are necessarily available, as some engine might not have all of them.
Getting Started
import {
define, // used to define types
as, is, // cast and check utils
enums, fn, struct, union, // specialized types
set, map, // typed Set and Map
unsafe // performance boost for production
} from 'jdes';
// values and arrays declaration
const {int: i} = 0; // is({int: i});
const {[int]: ii} = [1, 2]; // is({[int]: ii});
// explicit cast
const {string: s} = as({string: 123}); // s === "123"
// unions (multi type overloads)
define('int_float', union);
// functions {returnType: ({argType: name}, ...) => {}}
const squared = fn({int: ({int_float: num = 0}) => num * num});
squared(3); // 9
// enums
define('RGB', enums('RED', 'GREEN', 'BLUE'));
const {RGB: color} = RGB.GREEN;
// struct
define('Point3D', struct(
{int: ['x', 'y']}, // mandatory properties
{int: {z: 0}}, // default properties
// methods {returnType: {methodName({argType: name}, ...) {}}}
{[int]: {coords() {
return [this.x, this.y, this.z];
}}}
));
const {Point3D: p3d} = {x: 1, y: 2};
p3d.coords(); // [1, 2, 0]
is({Point3D: p3d}); // true
// set
define('Str', set(str));
const {Str: typedSet} = ['entry'];
// map
define('StrInt', map(str, int));
const {StrInt: typedMap} = [['first', 1]];5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago