@mitranim/jol
v0.1.6
Published
JS Collection Classes. Tiny extensions on JS built-in classes such as `Object`, `Array`, `Set`, `Map`, with nice features such as: easy-to-use typed collections with automatic idempotent element instantiation; dictionary with support for structured keys w
Downloads
9
Readme
Overview
"JS Collection Classes". Tiny extensions on JS built-in classes such as Object
, Array
, Set
, Map
, with nice features such as:
- Easy-to-use typed collections such as
Arr<Cls>
orSet<Cls>
with automatic idempotent element instantiation. EqDict
: dictionary with support for structured keys, like["composite", "key"]
. Compares keys by value, not by reference.- Consistent constructor signatures: everything is
new Cls(val)
, like ES6Map
andSet
.
Tiny, dependency-free, single file, native JS module.
Browser compatibility: any ES6+ environment. For older browsers, polyfill Set
and Map
and allow your bundler to transpile this library.
TOC
Usage
Install with NPM, or import by URL:
npm i -E @mitranim/jol
import * as j from '@mitranim/jol'
import * as j from 'https://cdn.jsdelivr.net/npm/@mitranim/[email protected]/jol.mjs'
API
class Null
Inherits from null
rather than Object
. Extend Null
for a "squeaky clean" class.
class Arr extends Array
Like Array
, but instead of new Array(...vals)
, the constructor signature is new Arr(vals)
, mirroring the behavior of Set
and preventing gotchas.
Valid calls:
new j.Arr() // Like [].
new j.Arr(null) // Like [].
new j.Arr(0) // Like [].
new j.Arr(8) // Like Array(8).
new j.Arr([10, 20, 30]) // Like [10, 20, 30] or Array(10, 20, 30).
new j.Arr('one') // Like [...'one'] or Array(...'one').
Invalid calls (runtime exception):
new j.Arr({})
new j.Arr(10, 20)
new j.Arr(() => {})
class ClsArr extends Arr
Runtime approximation of Arr<Cls>
. Idempotently auto-instantiates values via this.cls
.
class Model {
constructor(val) {this.val = val * 2}
}
class Models extends j.ClsArr {
get cls() {return Model}
}
new Models([10, 20])
// [ Model { val: 20 }, Model { val: 40 } ]
class Dict extends Map
Variant of Map
whose behavior is closer to Object
:
- Can be constructed from plain objects:
new j.Dict({one: 10})
. - Compatible with JSON. Reversible encoding and decoding.
- Allows only strings as keys. Other keys cause exceptions.
Compatibility with JSON:
new j.Dict({one: 10, two: 20})
// Dict { "one" => 10, "two" => 20 }
new j.Dict(JSON.parse(`{"one": 10, "two": 20}`))
// Dict { "one" => 10, "two" => 20 }
JSON.stringify(new j.Dict({one: 10, two: 20}))
// {"one":10,"two":20}
class ClsDict extends Dict
Runtime approximation of Dict<Cls>
. Idempotently auto-instantiates values via this.cls
.
class Model {
constructor(val) {this.val = val * 2}
}
class Models extends j.ClsDict {
get cls() {return Model}
}
new Models({one: 10, two: 20})
// Models { "one" => Model { val: 20 }, "two" => Model { val: 40 } }
class ClsMap extends Map
Runtime approximation of Map<any, Cls>
. Idempotently auto-instantiates values via this.cls
.
class Model {
constructor(val) {this.val = val * 2}
}
class Models extends j.ClsMap {
get cls() {return Model}
}
new Models([[10, 20], [30, 40]])
// Models { 10 => Model { val: 40 }, 30 => Model { val: 80 } }
class EqDict
Like Object
or Map
, but:
- Keys can be structured data, such as
{one: 10}
or['two', 'three']
. - Keys are always compared by structure, not by reference.
- Internally, keys are encoded as deterministic JSON.
- Relies on engine quirks; has not been tested in all browsers.
- The conversion function
toKey
is exported separately. - Computational complexity for access-by-key should be similar to
{}
, with the added overhead of JSON encoding (should scale with key size but not overall dict size).
Methods are similar to Map
:
eqMap.has(key)
eqMap.get(key)
eqMap.set(key, val)
eqMap.delete(key)
eqMap.forEach()
eqMap.keys()
eqMap.values()
eqMap.entries()
- Supports
for .. of
.
These internal methods require plain string keys, and are provided for subclasses:
eqMap.hasRaw(key)
eqMap.getRaw(key)
eqMap.setRaw(key, val)
eqMap.deleteRaw(key)
Unlike the syntax object[key]
, methods of EqDict
operate only on own enumerable properties. Because keys are always JSON-encoded, there is no collision with Object
methods and properties.
const coll = new j.EqDict()
coll.set({one: 10, two: 20}, 'value')
coll.get({two: 20, one: 10}) === 'value'
Supports reversible JSON encoding and decoding:
const prev = new j.EqDict().set('one', 10)
const next = new j.EqDict(JSON.parse(JSON.stringify(prev)))
// prev ≈ next
However, avoid the following:
new j.EqDict({one: 10})
All keys must be created by calling .set()
, otherwise they don't get encoded, and you can't retrieve the values with .get()
!
class ClsSet extends Set
Runtime approximation of Set<Cls>
. Idempotently auto-instantiates values via this.cls
.
class Model {
constructor(val) {this.val = val * 2}
}
class Models extends j.ClsSet {
get cls() {return Model}
}
new Models([10, 20])
// Models { Model { val: 20 }, Model { val: 40 } }
class Que extends Set
Ordered FIFO queue of functions. Paused by default. Calling .flush()
immediately dequeues and calls functions one-by-one, and any future calls to .add()
will result in immediate calls. Call .pause()
to pause again.
const que = new Que()
que.add(function one() {console.log('one')})
que.add(function two() {console.log('two')})
// Prints 'one'.
// Prints 'two'.
que.flush()
// Prints 'three'.
que.add(function three() {console.log('three')})
que.pause()
function assign(target, source)
Similar to Object.assign
, but with differences:
target
must be a non-array object (internally termed "struct").source
must be either nil (undefined
ornull
), a plain dict ({}
orObject.create(null)
), or a subclass oftarget.constructor
.- Does not shadow inherited or non-enumerable properties.
Valid calls:
class Mock {
constructor() {this.key = 'val'}
}
j.assign({}, undefined)
j.assign({}, {key: 'val'})
j.assign({}, new Mock())
j.assign(new Mock(), {key: 'val'})
j.assign(new Mock(), new Mock())
j.assign(new j.Obj({}), {key: 'val'})
j.assign({}, new j.Obj({key: 'val'}))
Invalid calls (runtime exception):
class Mock {}
j.assign(10, {})
j.assign('one', {})
j.assign({}, [])
j.assign([], {})
j.assign([], [])
j.assign(new Mock(), new j.Obj({}))
j.assign(new j.Obj({}), new Mock())
Non-shadowing behavior:
j.assign({}, {constructor: 10, toString: 20, unknown: 30})
// {unknown: 30}
function inst(val, cls)
Idempotently converts val
to an instance of cls
:
- If
val
is an instance ofcls
, returnsval
as-is. - Otherwise returns
new cls(val)
.
class Mock {}
let val
val = j.inst(val, Mock) // new instance
val = j.inst(val, Mock) // preserves existing instance
val = j.inst(val, Mock) // preserves existing instance
val = j.inst(val, Mock) // preserves existing instance
function opt(val, cls)
Same as inst
, but if val
is null
or undefined
, it's returned as-is, without instantiation.
class Mock {constructor(val) {this.val = val}}
j.opt(undefined, Mock) // undefined
j.opt(10, Mock) // Mock{val: 10}
function toKey(key)
Used internally by ClsArr
, ClsSet
, ClsMap
. Like JSON.stringify
but:
- When input is
undefined
, returns''
instead ofundefined
. - Sorts object keys, producing deterministic output.
j.toKey() // ''
j.toKey(null) // 'null'
j.toKey(10) // '10'
j.toKey('one') // '"one"'
j.toKey({one: 10, two: 20}) // '{"one":10,"two":20}'
j.toKey({two: 20, one: 10}) // '{"one":10,"two":20}'
isPlain(val)
Returns true
if val is either:
- Primitive.
- Instance of
Array
. - Plain dict:
{}
orObject.create(null)
.
Used internally by toKey
, which rejects other inputs (runtime exception).
Changelog
0.1.6
Additions:
- Add
Dict
. - Add
ClsDict
.
Breaking:
- Removed
Obj
. assign
now avoids shadowing inherited or non-enumerable properties.assign
now allows nil source, doing nothing instead of throwing.
Misc:
- Overridden methods of
Arr
,ClsArr
,ClsMap
,ClsSet
now returnthis
instead ofundefined
. Que..pause
andQue..flush
now returnthis
.
0.1.5
Renamed toInst
→ inst
, toInstOpt
→ opt
for brevity.
0.1.4
toInst
now allows subclass instances, instead of requiring an exact class match. This affects the behavior of all class collections.
0.1.3
Avoid an infinite loop when using NaN
in toKey
and EqDict
.
0.1.2
new Null()
now also creates a null-based object, rather than {}
. The behavior of Null
subclasses is unchanged.
0.1.1
Added toInstOpt
. EqDict
now extends Object
rather than Null
.
License
https://unlicense.org
Misc
I'm receptive to suggestions. If this library almost satisfies you but needs changes, open an issue or chat me up. Contacts: https://mitranim.com/#contacts