# types.js A library of JavaScript type extensions, types and type utilities. - [types.js](#typesjs) - [Installation](#installation) - [Basic usage](#basic-usage) - [`Object`](#object) - [`Object.deepKeys(..)`](#objectdeepkeys) - [`Object.copy(..)` (EXPERIMENTAL)](#objectcopy-experimental) - [`Object.flatCopy(..)`](#objectflatcopy) - [`Object.match(..)`](#objectmatch) - [`Object.matchPartial(..)`](#objectmatchpartial) - [`.run(..)`](#objectrun) - [`Object.sort(..)`](#objectsort) - [`Array`](#array) - [`.first(..)` / `.last(..)`](#arrayfirst--arraylast) - [`.rol(..)`](#arrayrol) - [`.compact()`](#arraycompact) - [`.len`](#arraylen) - [`.unique(..)` / `.tailUnique(..)`](#arrayunique--arraytailunique) - [`.cmp(..)`](#arraycmp) - [`.setCmp(..)`](#arraysetcmp) - [`.sortAs(..)`](#arraysortas) - [`.inplaceSortAs(..)`](#arrayinplacesortas) - [`.toKeys(..)`](#arraytokeys) - [`.toMap(..)`](#arraytomap) - [`Array.zip(..)` / `.zip(..)`](#arrayzip--arrayzip) - [`.iter()`](#arrayiter) - [Abortable `Array` iteration](#abortable-array-iteration) - [`array.StopIteration`](#arraystopiteration) - [`.smap(..)` / `.sfilter(..)` / `.sreduce(..)` / `.sforEach(..)`](#arraysmap--arraysfilter--arraysreduce--arraysforeach) - [Large `Array` iteration (chunked)](#large-array-iteration-chunked) - [`array.StopIteration`](#arraystopiteration-1) - [`.CHUNK_SIZE`](#arraychunk_size) - [`.mapChunks(..)`](#arraymapchunks) - [`.filterChunks(..)`](#arrayfilterchunks) - [`.reduceChunks(..)`](#arrayreducechunks) - [`Array` (polyfill)](#array-polyfill) - [`.flat()`](#arrayflat) - [`.includes()`](#arrayincludes) - [`Map`](#map) - [`.sort(..)`](#mapsort) - [`Set`](#set) - [`.unite(..)`](#setunite) - [`.intersect(..)`](#setintersect) - [`.subtract(..)`](#setsubtract) - [`.sort(..)`](#setsort) - [`Date`](#date) - [`Date.timeStamp(..)`](#datetimestamp) - [`Date.fromTimeStamp(..)`](#datefromtimestamp) - [`Date.str2ms(..)`](#datestr2ms) - [`.toShortDate(..)`](#datetoshortdate) - [`.getTimeStamp(..)`](#dategettimestamp) - [`.setTimeStamp(..)`](#datesettimestamp) - [`String`](#string) - [`.capitalize()`](#stringcapitalize) - [`RegExp`](#regexp) - [`RegExp.quoteRegExp(..)`](#regexpquoteregexp) - [`Promise`](#promise) - [Cooperative promises](#cooperative-promises) - [`Promise.cooperative(..)`](#promisecooperative) - [`.set(..)`](#promise-coopset) - [`.isSet`](#promise-coopisset) - [Promise iteration](#promise-iteration) - [`Promise.iter(..)` / `promise.IterablePromise(..)`](#promiseiter--promiseiterablepromise) - [`.map(..)` / `.filter(..)` / `.reduce(..)`](#promise-itermap--promise-iterfilter--promise-iterreduce) - [`.flat(..)`](#promise-iterflat) - [`.then(..)` / `.catch(..)` / `.finally(..)`](#promise-iterthen--promise-itercatch--promise-iterfinally) - [Advanced handler](#advanced-handler) - [Generator extensions and utilities](#generator-extensions-and-utilities) - [The basics](#the-basics) - [`Generator`](#generator) - [`generator.iter(..)`](#generatoriter) - [Generator instance iteration](#generator-instance-iteration) - [`.map(..)` / `.filter(..)` / `.reduce(..)` / `.flat()`](#generatormap--generatorfilter--generatorreduce--generatorflat) - [`.promise()`](#generatorpromise) - [`.then(..)` / `.catch(..)` / `.finally(..)`](#generatorthen--generatorcatch--generatorfinally) - [`.toArray()`](#generatortoarray) - [Generator constructor iteration workflow](#generator-constructor-iteration-workflow) - [`Generator.at(..)`](#generatorat) - [`Generator.shift()` / `Generator.pop()`](#generatorshift--generatorpop) - [`Generator.slice(..)`](#generatorslice) - [`Generator.map(..)` / `Generator.filter(..)` / `Generator.reduce(..)` / `Generator.flat()`](#generatormap--generatorfilter--generatorreduce--generatorflat-1) - [`Generator.toArray()`](#generatortoarray-1) - [`Generator.then(..)` / `Generator.catch(..)` / `Generator.finally(..)`](#generatorthen--generatorcatch--generatorfinally-1) - [Containers](#containers) - [`containers.UniqueKeyMap()` (`Map`)](#containersuniquekeymap-map) - [`.set(..)`](#unique-key-mapset) - [`.reset(..)`](#unique-key-mapreset) - [`.rename(..)`](#unique-key-maprename) - [`.orderedRename(..)`](#unique-key-maporderedrename) - [`.unorderedRename(..)`](#unique-key-mapunorderedrename) - [`.keysOf(..)`](#unique-key-mapkeysof) - [`.originalKey(..)`](#unique-key-maporiginalkey) - [`.uniqueKey(..)`](#unique-key-mapuniquekey) - [`.__key_pattern__`](#unique-key-map__key_pattern__) - [`.__unordered_rename__`](#unique-key-map__unordered_rename__) - [Event](#event) - [`event.Eventfull(..)`](#eventeventfull) - [`event.Event(..)`](#eventevent) - [`event.TRIGGER`](#eventtrigger) - [`event.EventHandlerMixin`](#eventeventhandlermixin) - [`.on(..)`](#objon) - [`.one(..)`](#objone) - [`.off(..)`](#objoff) - [`.trigger(..)`](#objtrigger) - [`event.EventDocMixin`](#eventeventdocmixin) - [`.eventfull`](#objeventfull) - [`.events`](#objevents) - [`event.EventMixin`](#eventeventmixin) - [Runner](#runner) - [`runner.Queue(..)` / `runner.Queue.run(..)`](#runnerqueue--runnerqueuerun) - [`.state`](#queuestate) - [`.start(..)`](#queuestart) - [`.pause(..)`](#queuepause) - [`.abort(..)`](#queueabort) - [`.on(..)` / `.one(..)`](#queueon--queueone) - [`.off(..)`](#queueoff) - [`.trigger(..)`](#queuetrigger) - [`.taskCompleted(..)` (event)](#queuetaskcompleted-event) - [License](#license) ## Installation ```shell $ npm install -s 'ig-types' ``` ## Basic usage To extend everything: ```javascript require('ig-types') ``` To have access to additional library types and utilities: ```javascript var types = require('ig-types') ``` `types.js` is organized so as to be able to import/extend only specific sub-modules mostly independently so... In case there is a need to only extend a specific constructor just import the module dealing with that constructor (`Array` in this case): ```javascript // require `ig-types/`... require('ig-types/Array') ``` Note that type patching modules are mostly independent. And to import specific library modules only: ```javascript var containers = require('ig-types/containers') ``` ## `Object` ```javascript require('ig-types/Object') ``` ### `Object.deepKeys(..)` ``` Object.deepKeys() -> ``` Get list of keys from all objects in the prototype chain. This is different from `Object.keys(..)` which only gets _own_ keys from the current object. Example: ```javascript var a = { x: 123 } var b = Object.create(a) b.y = 321 // get own keys of b... Object.keys(b) // -> ['y'] // get all keys accessible from b... Object.deepKeys(b) // -> ['x', 'y'] ``` ### `Object.copy(..)` (EXPERIMENTAL) ``` Object.copy() -> ``` Create a copy of `` This will: - create a blank `` - link `` to the same prototype chain - copy all _own_ keys from `` to `` Note that this will make no attempt to clone object type. _XXX not yet sure how useful this is._ ### `Object.flatCopy(..)` ``` Object.flatCopy() -> ``` Copy all attributes from the prototype chain of `` into ``. ### `Object.match(..)` ### `Object.matchPartial(..)` ### `.run(..)` ``` .run() -> -> ``` Run a function in the context of `` returning either `` itself (if returning `undefined`) or the result. Note that this is accessible from all JavaScript non-primitive objects, i.e. everything that inherits from `Object`. Example: ```javascript var L = [1, 2, 3] .map(function(e){ return e * 2 }) // see if the first element is 1 and prepend 1 if it is not... .run(function(){ if(this[0] != 1){ this.unshift(1) } }) console.log(L) // -> [1, 2, 6, 8] ``` `.run(..)` is also available standalone via: ```shell $ npm install -s object-run ``` For more info see: https://github.com/flynx/object-run.js ### `Object.sort(..)` Sort `` attributes (same as `Array`'s `.sort(..)`) ``` Object.sort() -> ``` Sort `` attributes via `` function. ``` Object.sort(, ) -> ``` Sort `` attributes to the same order of ``. ``` Object.sort(, ) -> ``` Note that this rewrites all the keys of `` thus for very large sets of keys/attributes this may be quite expensive. Note that some keys of `Object` may misbehave in JavaScript, currently keys that are string values of numbers are sorted automatically by _number value_ and are not affected by `.sort(..)`, this affects both _Chrome_ and _Firefox_. Example: ```javascript var o = {x: 0, a: 1, '100':2, '0':3, ' 27 ':4, b:5} // notice that the order is already different to the order of attributes above... Object.keys(o) // -> ['0', '100', 'x', 'a', ' 27 ', 'b'] // '0' and '100' are not affected by .sort(..) while ' 27 ' is... Object.keys(Object.sort(o, ['x', 'a', '100'])) // -> [ '0', '100', 'x', 'a', ' 27 ', 'b' ] ``` ## `Array` ```javascript require('ig-types/Array') ``` or ```javascript var array = require('ig-types/Array') ``` ### `.first(..)` / `.last(..)` Get the first/last items of ``. ``` .first() -> .last() -> ``` Set the first/last items of ``. ``` .first() -> .last() -> ``` Note that these do not affect `` length unless setting items on an empty ``. ### `.rol(..)` Roll `` in-place left. ``` .rol() .rol(1) -> .rol(n) -> ``` To roll right pass a negative `n` to `.rol(..)`. ### `.compact()` ``` .compact() -> ``` Generate a compact `` from a sparse ``, i.e. removing all the empty slots. ### `.len` Number of non-empty slots/elements in ``. This is similar to: ```javascript var L = [,,, 1,, 2, 3,,] // this is the same as L.len... L.compact().length ``` Note that this is different from `.length` in that writing to `.len` has no effect. ### `.unique(..)` / `.tailUnique(..)` Generate an array with all duplicate elements removed. ### `.cmp(..)` ``` .cmp() -> ``` Compare `` to ``. This will return `true` if: - `` === `` or, - lengths are the same and, - values on the same positions are equal. ### `.setCmp(..)` ### `.sortAs(..)` ### `.inplaceSortAs(..)` ### `.toKeys(..)` ### `.toMap(..)` ### `Array.zip(..)` / `.zip(..)` ### `.iter()` Return an iterator/generator from the current array. This is mostly useful in combination with the [Generator extensions and utilities](#generator-extensions-and-utilities) ### Abortable `Array` iteration A an alternative to `Array`'s `.map(..)` / `.filter(..)` / .. methods with ability to stop the iteration process by `throw`ing `StopIteration`. ```javascript var {StopIteration} = require('ig-types/Array') ``` This can be used in two ways: 1) `throw` as-is to simply stop... ```javascript ;[1,2,3,4,5] .smap(function(e){ // simply abort here and now... throw StopIteration }) ``` Since we aborted the iteration without passing any arguments to `StopIteration`, `.smap(..)` will return `undefined`. 2) `throw` an instance and return the argument... ```javascript // this will print "4" -- the value passed to StopIteration... console.log([1,2,3,4,5] .smap(function(e){ if(e > 3){ // NOTE: new is optional here... // ...StopIteratiom is an object.js constructor. throw new StopIteration(e) } })) ``` Note that no partial result is returned unless passed through `StopIteration(..)`. #### `array.StopIteration` An exception that if raised while iterating via a supporting iterator method will abort further execution and correctly exit. #### `.smap(..)` / `.sfilter(..)` / `.sreduce(..)` / `.sforEach(..)` Like `Array`'s `.map(..)`, `.filter(..)`, `.reduce(..)` and `.forEach(..)` but with added support for aborting iteration by throwing `StopIteration`. ### Large `Array` iteration (chunked) Iterating over very large `Array` instances in JavaScript can block execution, to avoid this `types.js` implements `.map(..)`/`.filter(..)`/`.reduce(..)` equivalent methods that iterate the array in chunks and do it asynchronously giving the runtime a chance to run in between. In the simplest cases these are almost a drop-in replacements for the equivalent methods but return a promise. ```javascript var a = [1,2,3,4,5] .map(function(e){ return e*2 }) var b ;[1,2,3,4,5] .mapChunks(function(e){ return e*2 }) .then(function(res){ b = res }) // or with await... var c = await [1,2,3,4,5] .mapChunks(function(e){ return e*2 }) ``` These support setting the chunk size (default: `50`) as the first argument: ```javascript var c = await [1,2,3,4,5] .mapChunks(2, function(e){ return e*2 }) ``` #### `array.StopIteration` Like for [`.smap(..)` and friends](#abortable-array-iteration) iteration can be stopped by throwing a `array.StopIteration` and as before there are two ways to go: 1) `throw` as-is to simply stop ```javascript ;[1,2,3,4,5] .mapChunks(function(e){ // simply abort here and now... throw StopIteration }) .catch(function(){ console.log('done.') }) ``` 2) `Throw` an instance and pass a value to `.catch(..)` ```javascript ;[1,2,3,4,5] .mapChunks(function(e){ if(e > 3){ // NOTE: new is optional here... // ...StopIteratiom is an object.js constructor. throw new StopIteration(e) } }) .catch(function(e){ console.log('first value greater than 3:', e) }) ``` #### `.CHUNK_SIZE` The default iteration chunk size. Note that the smaller this is the more _responsive_ the code is, especially in UI applications but there is a small overhead added per chunk. Default value: `50` #### `.mapChunks(..)` ``` .mapChunks() .mapChunks(, ) -> (, , ) -> ``` ``` .mapChunks([, ]) .mapChunks(, [, ]) -> (, , ) -> (, , ) ``` #### `.filterChunks(..)` #### `.reduceChunks(..)` ### `Array` (polyfill) #### `.flat()` #### `.includes()` ## `Map` ```javascript require('ig-types/Map') ``` ### `.sort(..)` ## `Set` ```javascript require('ig-types/Set') ``` ### `.unite(..)` ### `.intersect(..)` ### `.subtract(..)` ### `.sort(..)` ## `Date` ```javascript require('ig-types/Date') ``` ### `Date.timeStamp(..)` ### `Date.fromTimeStamp(..)` ### `Date.str2ms(..)` ### `.toShortDate(..)` ### `.getTimeStamp(..)` ### `.setTimeStamp(..)` ## `String` ```javascript require('ig-types/String') ``` ### `.capitalize()` ## `RegExp` ```javascript require('ig-types/RegExp') ``` ### `RegExp.quoteRegExp(..)` ## `Promise` ```javascript require('ig-types/Promise') ``` or ```javascript var promise = require('ig-types/Promise') ``` ### Cooperative promises #### `Promise.cooperative(..)` #### `.set(..)` #### `.isSet` ### Promise iteration An _iterable promise_ is on one hand very similar to `Promise.all(..)` in that it generally takes a list of values each could be either an explicit value or a promise, and it is similar to a _generator_ in that allows iteration over the contained values and chaining of operations but unlike `Promise.all(..)` this iteration occurs depth first instead of breadth first. Here is a traditional example using `Promise.all(..)`: ```javascript var p = Promise.all([ .. ]) // this will not execute until ALL the inputs resolve... .then(function(lst){ return lst .filter(function(e){ }) // this will not run until ALL of lst is filtered... .map(function(e){ }) }) ``` ```javascript var p = Promise.iter([ .. ]) // each element is processed as soon as it is ready disregarding of its order // in the input array... .filter(function(e){ }) // items reach here as soon as they are returned by the filter stage... // NOTE: the filter handler may return promises, those will not be processed // until they are resolved... .map(function(e){ }) // .then(..) explicitly waits for the whole list of inputs to resolve... .then(function(lst){ }) ``` This approach has a number of advantages: - items are processed as soon as they are available without waiting for the slowest promise on each level to resolve - simpler and more intuitive code And some disadvantages: - item indexes are unknowable until all the promises resolve. #### `Promise.iter(..)` / `promise.IterablePromise(..)` Create an _iterable promise_ ``` Promise.iter() -> ``` #### `.map(..)` / `.filter(..)` / `.reduce(..)` #### `.flat(..)` #### `.then(..)` / `.catch(..)` / `.finally(..)` #### Advanced handler ``` Promise.iter(, ) -> () -> [ ] ``` ```bnf ::= [] | [ , .. ] ::= [] | [ , .. ] | | ``` Example: ```javascript var p = Promise.iter( // NOTE: if you want an element to explicitly be an array wrap it in // an array -- like the last element here... [[1, 2], 3, Promise.resolve(4), [[5, 6]]], function(elem){ return elem % 2 == 0 ? [elem, elem] : elem instanceof Array ? [elem] : [] }) .then(function(lst){ console.log(lst) // -> [2, 2, 4, 4, [5, 6]] }) ``` ## Generator extensions and utilities ```javascript var generator = require('ig-types/generator') ``` ### The basics #### `Generator` #### `generator.iter(..)` ### Generator instance iteration #### `.map(..)` / `.filter(..)` / `.reduce(..)` / `.flat()` #### `.promise()` #### `.then(..)` / `.catch(..)` / `.finally(..)` #### `.toArray()` ### Generator constructor iteration workflow ```javascript var sumOdds = generator.iter .filter(function(e){ return e % 2 == 1 }) .reduce(function(r, e){ return r + e }, 0) .pop() console.log(sumOdds([1, 2, 3])) // -> 4 console.log(sumOdds([1, 2, 3, 4, 5, 6, 7])) // -> 16 ``` The above code is the same in function to: ```javascript var sumOdds = function(lst){ return generator.iter(lst) .filter(function(e){ return e % 2 == 1 }) .reduce(function(r, e){ return r + e }, 0) .pop() } console.log(sumOdds([1, 2, 3])) // -> 4 console.log(sumOdds([1, 2, 3, 4, 5, 6, 7])) // -> 16 ``` #### `Generator.at(..)` #### `Generator.shift()` / `Generator.pop()` #### `Generator.slice(..)` This is like `Array`'s `.slice(..)` but does not support negative indexes. #### `Generator.map(..)` / `Generator.filter(..)` / `Generator.reduce(..)` / `Generator.flat()` #### `Generator.toArray()` #### `Generator.then(..)` / `Generator.catch(..)` / `Generator.finally(..)` ## Containers ```javascript var containers = require('ig-types').containers ``` or, to only import containers: ```javascript var containers = require('ig-types/containers') ``` Note that this will also import `ig-types/Map`. ### `containers.UniqueKeyMap()` (`Map`) `UniqueKeyMap` implements a key-value container (i.e. `Map`) that supports and maintains _duplicate_ keys by appending an index to them. The original keys are stored internally thus the renaming mechanics are stable. `UniqueKeyMap` extends the `Map` constructor, so all the usual `Map` methods and properties apply here. To construct an instance: ```javascript var x = new UniqueKeyMap() ``` or: ```javascript // new is optional... var y = UniqueKeyMap() ``` `UniqueKeyMap` supports the same initialization signature as `Map` but treats repeating keys differently. ```javascript var z = UniqueKeyMap([['a', 1], ['a', 2], ['b', 1]]) ``` The second `"a"` item will automatically get re-keyed as `"a (1)"`: ```javascript console.log([...z.keys()]) // -> ['a', 'a (1)', 'b'] ``` Note that `.set(..)` will never rewrite an element: ```javascript z.set('a', 3) console.log([...z.keys()]) // -> ['a', 'a (1)', 'b', 'a (2)'] z.get('a') // -> 1 z.get('a (1)') // -> 2 ``` To get the generated key: ```javascript var k = z.set('a', 4, true) console.log(k) // -> 'a (3)' ``` To explicitly rewrite an item: ```javascript z.reset('a (1)', 4) z.get('a (1)') // -> 4 ``` And we can _rename_ items, i.e. change their key: ```javascript z.rename('a (2)', 'c') console.log([...z.keys()]) // -> ['a', 'a (1)', 'b', 'a (3)', 'c'] ``` For more info on `Map` see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map #### `.set(..)` ``` .reset(, ) -> .reset(, , true) -> ``` Add an `` to ``. If `` already exists then add an index to it to make it unique. Key updating is done via [`.__key_pattern__`](#unique-key-map__key_pattern__). #### `.reset(..)` ``` .reset(, ) -> ``` Explicitly write an `` under `` as-is, this is like `Map`'s `.set(..)`. #### `.rename(..)` ``` .rename(, ) -> .rename(, , true) -> ``` Rename item key from `` to ``. Same mechanics apply as for [`.set(..)`](#unique-key-mapset) for key uniqueness. Note, if [`.__unordered_rename__`](#unique-key-map__unordered_rename__) is `false` (default) this calls [`.orderedRename(..)`](#unique-key-maporderedrename) otherwise [`.unorderedRename(..)`](#unique-key-mapunorderedrename) is called. #### `.orderedRename(..)` #### `.unorderedRename(..)` #### `.keysOf(..)` #### `.originalKey(..)` #### `.uniqueKey(..)` #### `.__key_pattern__` #### `.__unordered_rename__` ## Event ```javascript var event = require('ig-types/event') ``` ### `event.Eventfull(..)` ### `event.Event(..)` ### `event.TRIGGER` Special value when passed to an event method as first argument will force it to trigger event if the first argument was a function. ### `event.EventHandlerMixin` #### `.on(..)` #### `.one(..)` #### `.off(..)` #### `.trigger(..)` ### `event.EventDocMixin` #### `.eventfull` #### `.events` ### `event.EventMixin` ## Runner ```javascript var runner = require('ig-types/runner') ``` ### `runner.Queue(..)` / `runner.Queue.run(..)` #### `.state` #### `.start(..)` #### `.pause(..)` #### `.abort(..)` #### `.on(..)` / `.one(..)` #### `.off(..)` #### `.trigger(..)` Trigger an event. #### `.taskCompleted(..)` (event) Event, triggered when a task is completed passing in its result. ## License [BSD 3-Clause License](./LICENSE) Copyright (c) 2020, Alex A. Naanou, All rights reserved.