diff --git a/.travis.yml b/.travis.yml index 8d848155e5..60be2f977b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: node_js node_js: - - "node" + - "9" branches: only: diff --git a/README.md b/README.md index cde25c04e4..0ca29b0475 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ difficulties, many combining functionality from several packages) in the | [`@thi.ng/associative`](./packages/associative) | [![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/associative.svg)](https://www.npmjs.com/package/@thi.ng/associative) | [changelog](./packages/associative/CHANGELOG.md) | | [`@thi.ng/atom`](./packages/atom) | [![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/atom.svg)](https://www.npmjs.com/package/@thi.ng/atom) | [changelog](./packages/atom/CHANGELOG.md) | | [`@thi.ng/bitstream`](./packages/bitstream) | [![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/bitstream.svg)](https://www.npmjs.com/package/@thi.ng/bitstream) | [changelog](./packages/bitstream/CHANGELOG.md) | +| [`@thi.ng/cache`](./packages/cache) | [![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/cache.svg)](https://www.npmjs.com/package/@thi.ng/cache) | [changelog](./packages/cache/CHANGELOG.md) | | [`@thi.ng/checks`](./packages/checks) | [![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/checks.svg)](https://www.npmjs.com/package/@thi.ng/checks) | [changelog](./packages/checks/CHANGELOG.md) | | [`@thi.ng/csp`](./packages/csp) | [![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/csp.svg)](https://www.npmjs.com/package/@thi.ng/csp) | [changelog](./packages/csp/CHANGELOG.md) | | [`@thi.ng/dcons`](./packages/dcons) | [![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/dcons.svg)](https://www.npmjs.com/package/@thi.ng/dcons) | [changelog](./packages/dcons/CHANGELOG.md) | @@ -42,6 +43,7 @@ difficulties, many combining functionality from several packages) in the | [`@thi.ng/diff`](./packages/diff) | [![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/diff.svg)](https://www.npmjs.com/package/@thi.ng/diff) | [changelog](./packages/diff/CHANGELOG.md) | | [`@thi.ng/hdom`](./packages/hdom) | [![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/hdom.svg)](https://www.npmjs.com/package/@thi.ng/hdom) | [changelog](./packages/hdom/CHANGELOG.md) | | [`@thi.ng/hdom-components`](./packages/hdom-components) | [![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/hdom-components.svg)](https://www.npmjs.com/package/@thi.ng/hdom-components) | [changelog](./packages/hdom-components/CHANGELOG.md) | +| [`@thi.ng/heaps`](./packages/heaps) | [![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/heaps.svg)](https://www.npmjs.com/package/@thi.ng/heaps) | [changelog](./packages/heaps/CHANGELOG.md) | | [`@thi.ng/hiccup`](./packages/hiccup) | [![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/hiccup.svg)](https://www.npmjs.com/package/@thi.ng/hiccup) | [changelog](./packages/hiccup/CHANGELOG.md) | | [`@thi.ng/hiccup-css`](./packages/hiccup-css) | [![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/hiccup-css.svg)](https://www.npmjs.com/package/@thi.ng/hiccup-css) | [changelog](./packages/hiccup-css/CHANGELOG.md) | | [`@thi.ng/hiccup-svg`](./packages/hiccup-svg) | [![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/hiccup-svg.svg)](https://www.npmjs.com/package/@thi.ng/hiccup-svg) | [changelog](./packages/hiccup-svg/CHANGELOG.md) | @@ -55,9 +57,11 @@ difficulties, many combining functionality from several packages) in the | [`@thi.ng/router`](./packages/router) | [![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/router.svg)](https://www.npmjs.com/package/@thi.ng/router) | [changelog](./packages/router/CHANGELOG.md) | | [`@thi.ng/rstream`](./packages/rstream) | [![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/rstream.svg)](https://www.npmjs.com/package/@thi.ng/rstream) | [changelog](./packages/rstream/CHANGELOG.md) | | [`@thi.ng/rstream-csp`](./packages/rstream-csp) | [![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/rstream-csp.svg)](https://www.npmjs.com/package/@thi.ng/rstream-csp) | [changelog](./packages/rstream-csp/CHANGELOG.md) | +| [`@thi.ng/rstream-dot`](./packages/rstream-dot) | [![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/rstream-dot.svg)](https://www.npmjs.com/package/@thi.ng/rstream-dot) | [changelog](./packages/rstream-dot/CHANGELOG.md) | | [`@thi.ng/rstream-gestures`](./packages/rstream-gestures) | [![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/rstream-gestures.svg)](https://www.npmjs.com/package/@thi.ng/rstream-gestures) | [changelog](./packages/rstream-gestures/CHANGELOG.md) | | [`@thi.ng/rstream-graph`](./packages/rstream-graph) | [![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/rstream-graph.svg)](https://www.npmjs.com/package/@thi.ng/rstream-graph) | [changelog](./packages/rstream-graph/CHANGELOG.md) | | [`@thi.ng/rstream-log`](./packages/rstream-log) | [![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/rstream-log.svg)](https://www.npmjs.com/package/@thi.ng/rstream-log) | [changelog](./packages/rstream-log/CHANGELOG.md) | +| [`@thi.ng/rstream-query`](./packages/rstream-query) | [![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/rstream-query.svg)](https://www.npmjs.com/package/@thi.ng/rstream-query) | [changelog](./packages/rstream-query/CHANGELOG.md) | | [`@thi.ng/transducers`](./packages/transducers) | [![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/transducers.svg)](https://www.npmjs.com/package/@thi.ng/transducers) | [changelog](./packages/transducers/CHANGELOG.md) | | [`@thi.ng/unionstruct`](./packages/unionstruct) | [![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/unionstruct.svg)](https://www.npmjs.com/package/@thi.ng/unionstruct) | [changelog](./packages/unionstruct/CHANGELOG.md) | diff --git a/assets/deps.png b/assets/deps.png index f7426b020f..703f17909a 100644 Binary files a/assets/deps.png and b/assets/deps.png differ diff --git a/assets/rs-dflow.dot b/assets/rs-dflow.dot index 536426036d..2e099be178 100644 --- a/assets/rs-dflow.dot +++ b/assets/rs-dflow.dot @@ -1,36 +1,72 @@ digraph g { - - rankdir=LR; - node[fontname=Inconsolata,fontsize=9,fontcolor=white,style=filled,color=black]; - edge[fontname=Inconsolata,fontsize=9,arrowsize=0.66]; - - mousedown -> gestures; - mousemove -> gestures; - mouseup -> gestures; - touchstart -> gestures; - touchmove -> gestures; - touchend -> gestures; - - gestures -> mpos[label=extract]; - gestures -> clickpos[label=extract]; - gestures -> dist[label=extract]; - - clickpos -> color[label="pick random"]; - - clickpos -> circle; - radius -> circle; - color -> circle; - - RAF -> sine[label="frame counter"]; - - sine -> radius; - dist -> radius; - - mousedown[color="#666666"]; - mousemove[color="#666666"]; - mouseup[color="#666666"]; - touchstart[color="#666666"]; - touchmove[color="#666666"]; - touchend[color="#666666"]; - RAF[color="#666666"]; +rankdir=LR; +node[fontname=Inconsolata,fontsize=11,style=filled,fontcolor=white]; +edge[fontname=Inconsolata,fontsize=11]; +s0[label="gestures\n(StreamMerge)", color=red]; +s1[label="mpos", color=black]; +s2[label="out-mpos", color=black]; +s3[label="", color=gray]; +s4[label="clickpos", color=black]; +s5[label="out-clickpos", color=black]; +s6[label="", color=gray]; +s7[label="color", color=black]; +s8[label="out-color", color=black]; +s9[label="", color=gray]; +s10[label="in-color", color=black]; +s11[label="", color=gray]; +s12[label="circle\n(StreamSync)", color=red]; +s13[label="out-circle", color=black]; +s14[label="", color=gray]; +s15[label="in-click", color=black]; +s16[label="", color=gray]; +s17[label="dist", color=black]; +s18[label="out-dist", color=black]; +s19[label="", color=gray]; +s20[label="in-b", color=black]; +s21[label="", color=gray]; +s22[label="radius\n(StreamSync)", color=red]; +s23[label="out-radius", color=black]; +s24[label="", color=gray]; +s25[label="in-radius", color=black]; +s26[label="", color=gray]; +s27[label="raf-8\n(Stream)", color=blue]; +s28[label="sine", color=black]; +s29[label="out-sine", color=black]; +s30[label="", color=gray]; +s31[label="in-a", color=black]; +s32[label="", color=gray]; +s2 -> s3; +s1 -> s2; +s5 -> s6; +s8 -> s9; +s13 -> s14; +s12 -> s13; +s11 -> s12[label="xform"]; +s10 -> s11; +s7 -> s8; +s7 -> s10[label="xform"]; +s16 -> s12[label="xform"]; +s15 -> s16; +s4 -> s5; +s4 -> s7[label="xform"]; +s4 -> s15[label="xform"]; +s18 -> s19; +s23 -> s24; +s26 -> s12[label="xform"]; +s25 -> s26; +s22 -> s23; +s22 -> s25[label="xform"]; +s21 -> s22[label="xform"]; +s20 -> s21; +s17 -> s18; +s17 -> s20[label="xform"]; +s0 -> s1[label="xform"]; +s0 -> s4[label="xform"]; +s0 -> s17[label="xform"]; +s29 -> s30; +s32 -> s22[label="xform"]; +s31 -> s32; +s28 -> s29; +s28 -> s31[label="xform"]; +s27 -> s28[label="xform"]; } \ No newline at end of file diff --git a/assets/rs-dflow.png b/assets/rs-dflow.png index 7de494fb12..1f7d4cd238 100644 Binary files a/assets/rs-dflow.png and b/assets/rs-dflow.png differ diff --git a/assets/rs-dot-example.dot b/assets/rs-dot-example.dot new file mode 100644 index 0000000000..f44f5c6647 --- /dev/null +++ b/assets/rs-dot-example.dot @@ -0,0 +1,24 @@ +digraph g { +rankdir=LR; +node[fontname=Inconsolata,fontsize=11,style=filled,fontcolor=white]; +edge[fontname=Inconsolata,fontsize=11]; +s0[label="iterable-0\n(Stream)", color=blue]; +s1[label="x10", color=black]; +s2[label="in-iterable-0", color=black]; +s3[label="", color=gray]; +s4[label="streammerge-0\n(StreamMerge)", color=red]; +s5[label="sub-1", color=black]; +s6[label="", color=gray]; +s7[label="iterable-1\n(Stream)", color=blue]; +s8[label="in-iterable-1", color=black]; +s9[label="", color=gray]; +s5 -> s6; +s4 -> s5; +s3 -> s4; +s2 -> s3; +s0 -> s1[label="xform"]; +s0 -> s2; +s9 -> s4; +s8 -> s9; +s7 -> s8; +} diff --git a/assets/rs-dot-example.svg b/assets/rs-dot-example.svg new file mode 100644 index 0000000000..faaf129982 --- /dev/null +++ b/assets/rs-dot-example.svg @@ -0,0 +1,131 @@ + + + + + + +g + + + +s0 + +iterable-0 +(Stream) + + + +s1 + +x10 + + + +s0->s1 + + +xform + + + +s2 + +in-iterable-0 + + + +s0->s2 + + + + + +s3 + +<noid> + + + +s2->s3 + + + + + +s4 + +streammerge-0 +(StreamMerge) + + + +s3->s4 + + + + + +s5 + +sub-1 + + + +s4->s5 + + + + + +s6 + +<noid> + + + +s5->s6 + + + + + +s7 + +iterable-1 +(Stream) + + + +s8 + +in-iterable-1 + + + +s7->s8 + + + + + +s9 + +<noid> + + + +s8->s9 + + + + + +s9->s4 + + + + + diff --git a/assets/rs-query1.dot b/assets/rs-query1.dot new file mode 100644 index 0000000000..e14e373d2c --- /dev/null +++ b/assets/rs-query1.dot @@ -0,0 +1,69 @@ +digraph g { +rankdir=LR; +node[fontname=Inconsolata,fontsize=11,style=filled,fontcolor=white]; +edge[fontname=Inconsolata,fontsize=11]; +s0[label="S\n(Stream)", color=blue]; +s1[label="s", color=black]; +s2[label="in-s", color=black]; +s3[label="", color=gray]; +s4[label="london-raw\n(StreamSync)", color=red]; +s5[label="xform-2", color=black]; +s6[label="london", color=black]; +s7[label="sub-3", color=black]; +s8[label="", color=gray]; +s9[label="P\n(Stream)", color=blue]; +s10[label="p", color=black]; +s11[label="in-p", color=black]; +s12[label="", color=gray]; +s13[label="countries-raw\n(StreamSync)", color=red]; +s14[label="xform-0", color=black]; +s15[label="countries", color=black]; +s16[label="sub-1", color=black]; +s17[label="", color=gray]; +s18[label="O\n(Stream)", color=blue]; +s19[label="o", color=black]; +s20[label="in-o", color=black]; +s21[label="", color=gray]; +s22[label="ALL\n(Stream)", color=blue]; +s23[label="s", color=black]; +s24[label="in-s", color=black]; +s25[label="", color=gray]; +s26[label="p", color=black]; +s27[label="in-p", color=black]; +s28[label="", color=gray]; +s29[label="o", color=black]; +s30[label="in-o", color=black]; +s31[label="", color=gray]; +s7 -> s8; +s6 -> s7; +s5 -> s6[label="xform"]; +s4 -> s5[label="xform"]; +s3 -> s4[label="xform"]; +s2 -> s3; +s1 -> s2[label="xform"]; +s0 -> s1[label="xform"]; +s16 -> s17; +s15 -> s16; +s14 -> s15[label="xform"]; +s13 -> s14[label="xform"]; +s12 -> s13[label="xform"]; +s11 -> s12; +s10 -> s11[label="xform"]; +s9 -> s10[label="xform"]; +s21 -> s13[label="xform"]; +s20 -> s21; +s19 -> s20[label="xform"]; +s18 -> s19[label="xform"]; +s25 -> s13[label="xform"]; +s24 -> s25; +s23 -> s24[label="xform"]; +s28 -> s4[label="xform"]; +s27 -> s28; +s26 -> s27[label="xform"]; +s31 -> s4[label="xform"]; +s30 -> s31; +s29 -> s30[label="xform"]; +s22 -> s23; +s22 -> s26; +s22 -> s29; +} \ No newline at end of file diff --git a/assets/rs-query1.svg b/assets/rs-query1.svg new file mode 100644 index 0000000000..896d4c1b84 --- /dev/null +++ b/assets/rs-query1.svg @@ -0,0 +1,422 @@ + + + + + + +g + + + +s0 + +S +(Stream) + + + +s1 + +s + + + +s0->s1 + + +xform + + + +s2 + +in-s + + + +s1->s2 + + +xform + + + +s3 + +<noid> + + + +s2->s3 + + + + + +s4 + +london-raw +(StreamSync) + + + +s3->s4 + + +xform + + + +s5 + +xform-2 + + + +s4->s5 + + +xform + + + +s6 + +london + + + +s5->s6 + + +xform + + + +s7 + +sub-3 + + + +s6->s7 + + + + + +s8 + +<noid> + + + +s7->s8 + + + + + +s9 + +P +(Stream) + + + +s10 + +p + + + +s9->s10 + + +xform + + + +s11 + +in-p + + + +s10->s11 + + +xform + + + +s12 + +<noid> + + + +s11->s12 + + + + + +s13 + +countries-raw +(StreamSync) + + + +s12->s13 + + +xform + + + +s14 + +xform-0 + + + +s13->s14 + + +xform + + + +s15 + +countries + + + +s14->s15 + + +xform + + + +s16 + +sub-1 + + + +s15->s16 + + + + + +s17 + +<noid> + + + +s16->s17 + + + + + +s18 + +O +(Stream) + + + +s19 + +o + + + +s18->s19 + + +xform + + + +s20 + +in-o + + + +s19->s20 + + +xform + + + +s21 + +<noid> + + + +s20->s21 + + + + + +s21->s13 + + +xform + + + +s22 + +ALL +(Stream) + + + +s23 + +s + + + +s22->s23 + + + + + +s26 + +p + + + +s22->s26 + + + + + +s29 + +o + + + +s22->s29 + + + + + +s24 + +in-s + + + +s23->s24 + + +xform + + + +s25 + +<noid> + + + +s24->s25 + + + + + +s25->s13 + + +xform + + + +s27 + +in-p + + + +s26->s27 + + +xform + + + +s28 + +<noid> + + + +s27->s28 + + + + + +s28->s4 + + +xform + + + +s30 + +in-o + + + +s29->s30 + + +xform + + + +s31 + +<noid> + + + +s30->s31 + + + + + +s31->s4 + + +xform + + + diff --git a/examples/rstream-dataflow/README.md b/examples/rstream-dataflow/README.md index f7c8ed6fe0..e4aeb62f89 100644 --- a/examples/rstream-dataflow/README.md +++ b/examples/rstream-dataflow/README.md @@ -16,14 +16,18 @@ Installs all dependencies, runs `webpack-dev-server` and opens the app in your b ![dataflow graph](../../assets/rs-dflow.png) This example combines the following packages to create & execute the -above dataflow graph in a declarative manner: +above dataflow graph in a declarative manner. The diagram generation +itself is part of the example and handled via the @thi.ng/rstream-dot +package. - [@thi.ng/atom](https://github.com/thi-ng/umbrella/tree/master/packages/atom) - state container - [@thi.ng/hdom](https://github.com/thi-ng/umbrella/tree/master/packages/hdom) - UI component rendering - [@thi.ng/paths](https://github.com/thi-ng/umbrella/tree/master/packages/paths) - nested value accessors - [@thi.ng/resolve-map](https://github.com/thi-ng/umbrella/tree/master/packages/resolve-map) - DAG-based object resolution - [@thi.ng/rstream](https://github.com/thi-ng/umbrella/tree/master/packages/rstream) - reactive stream constructs +- [@thi.ng/rstream-dot](https://github.com/thi-ng/umbrella/tree/master/packages/rstream-dot) - GraphViz DOT output of graph topology - [@thi.ng/rstream-gestures](https://github.com/thi-ng/umbrella/tree/master/packages/rstream-gestures) - unified mouse & single-touch event stream +- [@thi.ng/rstream-graph](https://github.com/thi-ng/umbrella/tree/master/packages/rstream-graph) - declarative dataflow graph creation - [@thi.ng/transducers](https://github.com/thi-ng/umbrella/tree/master/packages/transducers) - data transformations (here used for stream transforms) Please see detailed comments in the source code for further explanations. diff --git a/examples/rstream-dataflow/package.json b/examples/rstream-dataflow/package.json index f632c5e034..db8d49a916 100644 --- a/examples/rstream-dataflow/package.json +++ b/examples/rstream-dataflow/package.json @@ -21,6 +21,7 @@ "@thi.ng/hdom": "latest", "@thi.ng/paths": "latest", "@thi.ng/rstream": "latest", + "@thi.ng/rstream-dot": "latest", "@thi.ng/rstream-gestures": "latest", "@thi.ng/rstream-graph": "latest", "@thi.ng/transducers": "latest" diff --git a/examples/rstream-dataflow/src/index.ts b/examples/rstream-dataflow/src/index.ts index d3456d893c..3a4b4cb097 100644 --- a/examples/rstream-dataflow/src/index.ts +++ b/examples/rstream-dataflow/src/index.ts @@ -3,6 +3,7 @@ import { Atom } from "@thi.ng/atom/atom"; import { start } from "@thi.ng/hdom"; import { getIn } from "@thi.ng/paths"; import { fromRAF } from "@thi.ng/rstream/from/raf"; +import { walk, toDot } from "@thi.ng/rstream-dot"; import { gestureStream } from "@thi.ng/rstream-gestures"; import { initGraph, node, node1 } from "@thi.ng/rstream-graph/graph"; import { extract } from "@thi.ng/rstream-graph/nodes/extract"; @@ -31,6 +32,11 @@ const db = new Atom({}); // Note: only single touches are supported, no multitouch! const gestures = gestureStream(document.getElementById("app")); +// requestAnimationFrame() based counter stream. this is consumed by the +// "sine" graph node below, but predefined here for visualization +// purposes (see end of file) +const raf = fromRAF(); + // dataflow graph definition. each key in this object represents a node // in the graph and its value is a `NodeSpec`. the `initGraph` function // transforms these specs into a DAG (directed acyclic graph) of @@ -52,7 +58,7 @@ const graph = initGraph(db, { // the `[1, 0]` is the lookup path, i.e. `gesture[1][0]` mpos: { fn: extract([1, "pos"]), - ins: [{ stream: () => gestures }], + ins: { src: { stream: () => gestures } }, out: "mpos" }, @@ -61,7 +67,7 @@ const graph = initGraph(db, { // (only defined during drag gestures) clickpos: { fn: extract([1, "click"]), - ins: [{ stream: () => gestures }], + ins: { src: { stream: () => gestures } }, out: "clickpos" }, @@ -70,11 +76,13 @@ const graph = initGraph(db, { // (`delta` is only defined during drag gestures) // `node1` is a helper function for nodes using only a single input dist: { - fn: node1(map((gesture) => { - const delta = getIn(gesture, [1, "delta"]); - return delta && Math.hypot.apply(null, delta) | 0; - })), - ins: [{ stream: () => gestures }], + fn: node1(map( + (gesture) => { + const delta = getIn(gesture, [1, "delta"]); + return delta && Math.hypot.apply(null, delta) | 0; + } + )), + ins: { src: { stream: () => gestures } }, out: "dist" }, @@ -86,15 +94,17 @@ const graph = initGraph(db, { // `node` is a helper function to create a `StreamSync` based node // with multiple inputs circle: { - fn: node(map(({ click, radius, color }) => - click && radius && color ? - circle(color, click[0], click[1], radius * 2) : - undefined)), - ins: [ - { stream: "clickpos", id: "click" }, - { stream: "radius", id: "radius" }, - { stream: "color", id: "color" }, - ], + fn: node(map( + ({ click, radius, color }) => + click && radius && color ? + circle(color, click[0], click[1], radius * 2) : + undefined + )), + ins: { + click: { stream: "clickpos" }, + radius: { stream: "radius" }, + color: { stream: "color" }, + }, out: "circle" }, @@ -105,8 +115,11 @@ const graph = initGraph(db, { // time clickpos is redefined (remember, clickpos is only defined // during drag gestures) color: { - fn: node1(comp(dedupe(equiv), map((x) => x && colors.next().value))), - ins: [{ stream: "clickpos" }], + fn: node1(comp( + dedupe(equiv), + map((x) => x && colors.next().value) + )), + ins: { src: { stream: "clickpos" } }, out: "color" }, @@ -114,7 +127,7 @@ const graph = initGraph(db, { // into a sine wave with 0.6 .. 1.0 interval sine: { fn: node1(map((x: number) => 0.8 + 0.2 * Math.sin(x * 0.05))), - ins: [{ stream: () => fromRAF() }], + ins: { src: { stream: () => raf } }, out: "sin" }, @@ -122,7 +135,10 @@ const graph = initGraph(db, { // radius value for `circle` radius: { fn: mul, - ins: [{ stream: "sine" }, { stream: "dist" }], + ins: { + a: { stream: "sine" }, + b: { stream: "dist" } + }, out: "radius" } }); @@ -138,3 +154,11 @@ start("app", () => // @thi.ng/atom's Atom, Cursor, View etc.) graph.circle ]); + +// create a GraphViz DOT file of the entire dataflow graph +// copy the output from the console into a new text file and then run: +// `dot -Tsvg -o graph.svg graph.dot` +// +// see for more info: +// https://github.com/thi-ng/umbrella/tree/master/packages/rstream-dot +console.log(toDot(walk([gestures, raf]))); diff --git a/packages/api/CHANGELOG.md b/packages/api/CHANGELOG.md index 0a24013c07..defd6bd200 100644 --- a/packages/api/CHANGELOG.md +++ b/packages/api/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [2.3.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/api@2.2.0...@thi.ng/api@2.3.0) (2018-04-26) + + +### Features + +* **api:** support more types in equiv(), add tests ([2ac8bff](https://github.com/thi-ng/umbrella/commit/2ac8bff)) + + + + # [2.2.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/api@2.1.3...@thi.ng/api@2.2.0) (2018-04-08) diff --git a/packages/api/package.json b/packages/api/package.json index 0a2f63c71b..2c84f0cf60 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/api", - "version": "2.2.0", + "version": "2.3.0", "description": "Common, generic types & interfaces for thi.ng projects", "main": "./index.js", "typings": "./index.d.ts", @@ -24,7 +24,7 @@ "typescript": "^2.8.1" }, "dependencies": { - "@thi.ng/checks": "^1.4.0" + "@thi.ng/checks": "^1.5.0" }, "keywords": [ "compare", diff --git a/packages/api/src/equiv.ts b/packages/api/src/equiv.ts index ca5b1b5c24..3f1f07981c 100644 --- a/packages/api/src/equiv.ts +++ b/packages/api/src/equiv.ts @@ -1,5 +1,9 @@ import { isArrayLike } from "@thi.ng/checks/is-arraylike"; +import { isDate } from "@thi.ng/checks/is-date"; +import { isMap } from "@thi.ng/checks/is-map"; import { isPlainObject } from "@thi.ng/checks/is-plain-object"; +import { isRegExp } from "@thi.ng/checks/is-regexp"; +import { isSet } from "@thi.ng/checks/is-set"; export function equiv(a, b): boolean { if (a === b) { @@ -28,7 +32,17 @@ export function equiv(a, b): boolean { if (isArrayLike(a) && isArrayLike(b)) { return equivArrayLike(a, b); } - return false; + if ((isSet(a) && isSet(b)) || (isMap(a) && isMap(b))) { + return equivSetLike(a, b); + } + if (isDate(a) && isDate(b)) { + return a.getTime() === b.getTime(); + } + if (isRegExp(a) && isRegExp(b)) { + return a.toString() === b.toString(); + } + // NaN + return (a !== a && b !== b); } function equivArrayLike(a: ArrayLike, b: ArrayLike) { @@ -39,6 +53,11 @@ function equivArrayLike(a: ArrayLike, b: ArrayLike) { return l < 0; } +function equivSetLike(a: Set, b: Set) { + if (a.size !== b.size) return false; + return equiv([...a].sort(), [...b].sort()); +} + function equivObject(a, b) { const keys = new Set(Object.keys(a).concat(Object.keys(b))); for (let k of keys) { diff --git a/packages/api/test/index.ts b/packages/api/test/index.ts index 44bcb98123..ee79e27465 100644 --- a/packages/api/test/index.ts +++ b/packages/api/test/index.ts @@ -89,4 +89,30 @@ describe("equiv", () => { assert(!equiv(new A(1), 2)); }); + it("set", () => { + const a = new Set([1, 2, 3]); + assert(equiv(a, a)); + assert(equiv(a, new Set([3, 2, 1]))); + assert(equiv(new Set([{ a: 1 }, new Set([{ b: 2 }, [3]])]), new Set([new Set([[3], { b: 2 }]), { a: 1 }]))); + assert(!equiv(a, new Set([3, 2, 0]))); + assert(!equiv(a, [3, 2, 0])); + assert(!equiv(a, new Map([[3, 3], [2, 2], [1, 1]]))); + assert(!equiv(a, null)); + assert(!equiv(null, a)); + }); + + it("date", () => { + const a = new Date(123456); + assert(equiv(a, a)); + assert(equiv(a, new Date(123456))); + assert(!equiv(a, new Date(123))); + }); + + it("regexp", () => { + const a = /(\w+)/g; + assert(equiv(a, a)); + assert(equiv(a, /(\w+)/g)); + assert(!equiv(a, /(\w+)/)); + assert(!equiv(a, /(\w*)/g)); + }); }); diff --git a/packages/associative/CHANGELOG.md b/packages/associative/CHANGELOG.md index 63cd37a8c5..a7c20e19a3 100644 --- a/packages/associative/CHANGELOG.md +++ b/packages/associative/CHANGELOG.md @@ -3,6 +3,33 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.4.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/associative@0.4.3...@thi.ng/associative@0.4.4) (2018-04-26) + + + + +**Note:** Version bump only for package @thi.ng/associative + + +## [0.4.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/associative@0.4.2...@thi.ng/associative@0.4.3) (2018-04-22) + + + + +**Note:** Version bump only for package @thi.ng/associative + + +## [0.4.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/associative@0.4.1...@thi.ng/associative@0.4.2) (2018-04-20) + + +### Bug Fixes + +* **associative:** allow partial options arg for EquivMap ctor ([bb11ddf](https://github.com/thi-ng/umbrella/commit/bb11ddf)) + + + + ## [0.4.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/associative@0.4.0...@thi.ng/associative@0.4.1) (2018-04-16) diff --git a/packages/associative/package.json b/packages/associative/package.json index 5940504dd4..b43c25c933 100644 --- a/packages/associative/package.json +++ b/packages/associative/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/associative", - "version": "0.4.1", + "version": "0.4.4", "description": "Alternative Set & Map data type implementations with customizable equality semantics & supporting operations", "main": "./index.js", "typings": "./index.d.ts", @@ -24,9 +24,9 @@ "typescript": "^2.8.1" }, "dependencies": { - "@thi.ng/api": "^2.1.0", - "@thi.ng/dcons": "^0.2.0", - "@thi.ng/iterators": "^4.1.5" + "@thi.ng/api": "^2.3.0", + "@thi.ng/dcons": "^0.3.1", + "@thi.ng/iterators": "^4.1.7" }, "keywords": [ "data structures", diff --git a/packages/associative/src/equiv-map.ts b/packages/associative/src/equiv-map.ts index 3fd897bec3..9b3885ffb5 100644 --- a/packages/associative/src/equiv-map.ts +++ b/packages/associative/src/equiv-map.ts @@ -43,13 +43,13 @@ export class EquivMap extends Map implements * @param pairs * @param opts */ - constructor(pairs?: Iterable>, opts?: EquivMapOpts) { + constructor(pairs?: Iterable>, opts?: Partial>) { super(); - opts = Object.assign(>{ equiv, keys: ArraySet }, opts); + const _opts: EquivMapOpts = Object.assign({ equiv, keys: ArraySet }, opts); __private.set(this, { - keys: new (opts.keys)(null, { equiv: opts.equiv }), + keys: new (_opts.keys)(null, { equiv: _opts.equiv }), map: new Map(), - opts + opts: _opts }); if (pairs) { this.into(pairs); diff --git a/packages/atom/CHANGELOG.md b/packages/atom/CHANGELOG.md index e631ad1805..078a84ac1f 100644 --- a/packages/atom/CHANGELOG.md +++ b/packages/atom/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [1.3.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/atom@1.3.3...@thi.ng/atom@1.3.4) (2018-04-26) + + + + +**Note:** Version bump only for package @thi.ng/atom + ## [1.3.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/atom@1.3.2...@thi.ng/atom@1.3.3) (2018-04-19) diff --git a/packages/atom/package.json b/packages/atom/package.json index 34178d09a9..e9f704e0b6 100644 --- a/packages/atom/package.json +++ b/packages/atom/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/atom", - "version": "1.3.3", + "version": "1.3.4", "description": "Mutable wrapper for immutable values", "main": "./index.js", "typings": "./index.d.ts", @@ -24,8 +24,8 @@ "typescript": "^2.8.1" }, "dependencies": { - "@thi.ng/api": "^2.2.0", - "@thi.ng/paths": "^1.3.1" + "@thi.ng/api": "^2.3.0", + "@thi.ng/paths": "^1.3.2" }, "keywords": [ "cursor", diff --git a/packages/bitstream/CHANGELOG.md b/packages/bitstream/CHANGELOG.md index 4a5baef478..9fdbf8b934 100644 --- a/packages/bitstream/CHANGELOG.md +++ b/packages/bitstream/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.4.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/bitstream@0.4.4...@thi.ng/bitstream@0.4.5) (2018-04-26) + + + + +**Note:** Version bump only for package @thi.ng/bitstream + ## [0.4.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/bitstream@0.4.3...@thi.ng/bitstream@0.4.4) (2018-04-08) diff --git a/packages/bitstream/package.json b/packages/bitstream/package.json index badc51f1ee..89d450ced7 100644 --- a/packages/bitstream/package.json +++ b/packages/bitstream/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/bitstream", - "version": "0.4.4", + "version": "0.4.5", "description": "ES6 iterator based read/write bit streams & support for variable word widths", "main": "./index.js", "typings": "./index.d.ts", @@ -16,7 +16,7 @@ "test": "rm -rf build && tsc -p test && nyc mocha build/test/*.js" }, "dependencies": { - "@thi.ng/api": "^2.2.0" + "@thi.ng/api": "^2.3.0" }, "devDependencies": { "@types/mocha": "^5.0.0", diff --git a/packages/cache/.npmignore b/packages/cache/.npmignore new file mode 100644 index 0000000000..d703bda97a --- /dev/null +++ b/packages/cache/.npmignore @@ -0,0 +1,10 @@ +build +coverage +dev +doc +src* +test +.nyc_output +tsconfig.json +*.tgz +*.html diff --git a/packages/cache/CHANGELOG.md b/packages/cache/CHANGELOG.md new file mode 100644 index 0000000000..1e1dfbfef3 --- /dev/null +++ b/packages/cache/CHANGELOG.md @@ -0,0 +1,52 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + + +## [0.2.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/cache@0.2.1...@thi.ng/cache@0.2.2) (2018-04-26) + + + + +**Note:** Version bump only for package @thi.ng/cache + + +## [0.2.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/cache@0.2.0...@thi.ng/cache@0.2.1) (2018-04-24) + + + + +**Note:** Version bump only for package @thi.ng/cache + + +# [0.2.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/cache@0.1.0...@thi.ng/cache@0.2.0) (2018-04-22) + + +### Bug Fixes + +* **cache:** TLRUCache.get(), add tests, update package ([aa78d77](https://github.com/thi-ng/umbrella/commit/aa78d77)) + + +### Features + +* **cache:** add TLRUCache.prune(), fix ensureSize() ([9d53ae3](https://github.com/thi-ng/umbrella/commit/9d53ae3)) + + + + + +# 0.1.0 (2018-04-22) + + +### Bug Fixes + +* **cache:** don't insert new val if > maxsize ([3947419](https://github.com/thi-ng/umbrella/commit/3947419)) +* **cache:** recompute size in LRUCache.delete(), extract removeEntry() ([c4a9c07](https://github.com/thi-ng/umbrella/commit/c4a9c07)) + + +### Features + +* **cache:** add MRUCache, update package & readme ([26c4cfd](https://github.com/thi-ng/umbrella/commit/26c4cfd)) +* **cache:** add TLRUCache ([574b5d9](https://github.com/thi-ng/umbrella/commit/574b5d9)) +* **cache:** initial import [@thi](https://github.com/thi).ng/cache package ([7bbbfa8](https://github.com/thi-ng/umbrella/commit/7bbbfa8)) diff --git a/packages/cache/LICENSE b/packages/cache/LICENSE new file mode 100644 index 0000000000..8dada3edaf --- /dev/null +++ b/packages/cache/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/cache/README.md b/packages/cache/README.md new file mode 100644 index 0000000000..8fd5247ea1 --- /dev/null +++ b/packages/cache/README.md @@ -0,0 +1,170 @@ +# @thi.ng/cache + +[![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/cache.svg)](https://www.npmjs.com/package/@thi.ng/cache) + +This project is part of the +[@thi.ng/umbrella](https://github.com/thi-ng/umbrella/) monorepo. + +## About + +In-memory cache implementations with different [eviction +strategies](https://en.wikipedia.org/wiki/Cache_replacement_policies). +This package is still in early development and currently the only +strategies available are: + +- **LRU**: Least Recently Used +- **TLRU**: Time-aware Least Recently Used +- **MRU**: Most Recently Used + +### Features + +- ES6 Map-like API (with minor differences) +- Supports any types for both keys & values +- Customizable cache limits (no. of items / actual size) +- Customizable key equality checks (@thi.ng/api/equiv by default) +- Optional item release callbacks (to clean up resources when value is expunged) + +## Installation + +``` +yarn add @thi.ng/cache +``` + +## Usage examples + +All caches support at least the following options (all optional): + +```ts +interface CacheOpts { + /** + * Key size calculation + */ + ksize: (k: K) => number; + /** + * Value size calculation + */ + vsize: (v: V) => number; + /** + * Eviction callback to clean up resources + */ + release: (k: K, v: V) => void; + /** + * Factory for ES6 Map compatible instance + * to index cache entries + */ + map: () => Map; + /** + * Max number of items in cache (default: ∞) + */ + maxlen: number; + /** + * Max cache size (computed via `ksize` & `vsize`) (default: ∞) + */ + maxsize: number; +} +``` + +### LRU + +Removes least recently used items if a new item is added, but would not satisfy cache limit. Every time a cached item is accessed, it's recency is updated. + +```typescript +import * as cache from "@thi.ng/cache"; + +// caches can be configured with maxLen, maxSize and sizing functions (see below) +const lru = new cache.LRUCache(null, { maxlen: 3 }); +lru.set("foo", 23); +lru.set("bar", 42); +lru.set("baz", 66); + +lru.has("foo"); +// true +// retrieving a value from the cache updates its timestamp +lru.get("foo"); +// 23 + +// caches are fully iterable +// largely intended for inspection, does not update recency +// btw. "foo" appears last since most recently accessed +[...lru] +// [ { k: 'bar', v: 42, s: 0 }, +// { k: 'baz', v: 66, s: 0 }, +// { k: 'foo', v: 23, s: 0 } ] +[...lru.keys()] +// [ 'bar', 'baz', 'foo' ] +[...lru.values()] +// [ 42, 66, 23 ] + +// remove from cache +lru.delete("foo"); +// true + +// caches have a getSet() method to obtain & store a new value +// if its key is not known. this process is asynchronous +lru.getSet("boo", () => Promise.resolve(999)).then(console.log); +// 999 + +// the given retrieval fn is only called if there's a cache miss +// (not the case here). `getSet()` always returns a promise +lru.getSet("boo", () => Promise.resolve(123)).then(console.log); +// 999 + +// caches can be limited by size instead of (or in addition to) +// number of items. the meaning of `size` is user-defined. +// sizing fns can be provided for both keys & values (both default to 0) +// here we multiply value size by 8 since JS numbers are doubles by default +// we also provide a release hook for demo purposes + +// the first arg is an iterable of KV pairs to store (just as for Map) +lru = new cache.LRUCache( + [ ["a", [1.0, 2.0]], ["b", [3.0, 4.0, 5.0]] ], + { + maxsize: 32, + ksize: (k) => k.length, + vsize: (v) => v.length * 8, + release: (k, v) => console.log("release", k, v) + } +); +// release a [1, 2] ("a" is evicted due to maxsize constraint) +lru.size +// 25 + +[...lru.keys()] +// [ 'b' ] +``` + +### TLRU + +Time-aware LRU cache. Extends LRU strategy with TTL (time-to-live) +values associated with each entry. `has()` will only return `true` and +`get()` only returns a cached value if its TTL hasn't yet expired. When +adding a new value to the cache, first removes expired entries and if +there's still not sufficient space removes entries in LRU order. `set()` +takes an optional entry specific `ttl` arg. If not given, uses the cache +instance's default (provided via ctor option arg). If no instance TTL is +given, TTL defaults to 1 hour. + +```ts +// same opts as LRUCache, but here with custom TTL period (in ms) +tlru = new cache.TLRUCache(null, { ttl: 10000 }); + +// with item specific TTL (500ms) +tlru.set("foo", 42, 500) +``` + +### MRU + +Similar to LRU, but removes most recently accessed items first. [Wikipedia](https://en.wikipedia.org/wiki/Cache_replacement_policies#Most_recently_used_(MRU)) + +```ts +// same opts as LRUCache +mru = new cache.MRUCache(); +``` + +## Authors + +- Karsten Schmidt + +## License + +© 2018 Karsten Schmidt // Apache Software License 2.0 diff --git a/packages/cache/package.json b/packages/cache/package.json new file mode 100644 index 0000000000..f971dbaaa2 --- /dev/null +++ b/packages/cache/package.json @@ -0,0 +1,45 @@ +{ + "name": "@thi.ng/cache", + "version": "0.2.2", + "description": "In-memory cache implementations with ES6 Map-like API and different eviction strategies", + "main": "./index.js", + "typings": "./index.d.ts", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn run clean && tsc --declaration", + "clean": "rm -rf *.js *.d.ts .nyc_output build coverage doc", + "cover": "yarn test && nyc report --reporter=lcov", + "doc": "node_modules/.bin/typedoc --mode modules --out doc src", + "pub": "yarn run build && yarn publish --access public", + "test": "rm -rf build && tsc -p test && nyc mocha build/test/*.js" + }, + "devDependencies": { + "@types/mocha": "^5.0.0", + "@types/node": "^9.6.1", + "mocha": "^5.0.5", + "nyc": "^11.6.0", + "typedoc": "^0.11.1", + "typescript": "^2.8.1" + }, + "dependencies": { + "@thi.ng/api": "^2.3.0", + "@thi.ng/dcons": "^0.3.1", + "@thi.ng/iterators": "^4.1.7" + }, + "keywords": [ + "cache", + "data structure", + "ES6", + "LRU", + "Map", + "MRU", + "TLRU", + "TTL", + "typescript" + ], + "publishConfig": { + "access": "public" + } +} diff --git a/packages/cache/src/api.ts b/packages/cache/src/api.ts new file mode 100644 index 0000000000..6b8aa999f6 --- /dev/null +++ b/packages/cache/src/api.ts @@ -0,0 +1,36 @@ +import { ICopy, IEmpty, ILength, IRelease } from "@thi.ng/api/api"; + +export interface ICache extends + Iterable]>>, + ICopy>, + IEmpty>, + ILength, + IRelease { + + readonly size: number; + + has(key: K): boolean; + get(key: K, notFound?: V): V; + set(key: K, val: V): V; + getSet(key: K, fn: () => Promise): Promise; + delete(key: K): boolean; + + entries(): IterableIterator]>>; + keys(): IterableIterator>; + values(): IterableIterator>; +} + +export interface CacheOpts { + ksize: (k: K) => number; + vsize: (v: V) => number; + release: (k: K, v: V) => void; + map: () => Map; + maxlen: number; + maxsize: number; +} + +export interface CacheEntry { + k: K; + v: V; + s: number; +} \ No newline at end of file diff --git a/packages/cache/src/index.ts b/packages/cache/src/index.ts new file mode 100644 index 0000000000..1d9bfce597 --- /dev/null +++ b/packages/cache/src/index.ts @@ -0,0 +1,4 @@ +export * from "./api"; +export * from "./lru"; +export * from "./mru"; +export * from "./tlru"; diff --git a/packages/cache/src/lru.ts b/packages/cache/src/lru.ts new file mode 100644 index 0000000000..a2621e3fcf --- /dev/null +++ b/packages/cache/src/lru.ts @@ -0,0 +1,171 @@ +import { DCons, ConsCell } from "@thi.ng/dcons"; +import { map } from "@thi.ng/iterators/map"; + +import { ICache, CacheEntry, CacheOpts } from "./api"; + +export class LRUCache implements ICache { + + protected map: Map>>; + protected items: DCons>; + protected opts: CacheOpts; + protected _size: number; + + constructor(pairs?: Iterable<[K, V]>, opts?: Partial>) { + const _opts = >Object.assign({ + maxlen: Number.POSITIVE_INFINITY, + maxsize: Number.POSITIVE_INFINITY, + map: () => new Map(), + ksize: () => 0, + vsize: () => 0, + }, opts); + this.map = _opts.map(); + this.items = new DCons>(); + this._size = 0; + this.opts = _opts; + if (pairs) { + this.into(pairs); + } + } + + get length() { + return this.items.length; + } + + get size() { + return this._size; + } + + [Symbol.iterator]() { + return this.entries(); + } + + *entries(): IterableIterator]>> { + yield* map((e) => [e.k, e], this.items); + } + + *keys(): IterableIterator> { + yield* map((e) => e.k, this.items); + } + + *values(): IterableIterator> { + yield* map((e) => e.v, this.items); + } + + copy(): ICache { + const c = this.empty(); + c.items = this.items.copy(); + let cell = c.items.head; + while (cell) { + c.map.set(cell.value.k, cell); + cell = cell.next; + } + return c; + } + + empty(): LRUCache { + return new LRUCache(null, this.opts); + } + + release() { + this._size = 0; + this.map.clear(); + const release = this.opts.release; + if (release) { + let e; + while (e = this.items.drop()) { + release(e.k, e.v); + } + return true; + } + return this.items.release(); + } + + has(key: K): boolean { + return this.map.has(key); + } + + get(key: K, notFound?: any) { + const e = this.map.get(key); + if (e) { + return this.resetEntry(e); + } + return notFound; + } + + set(key: K, value: V) { + const size = this.opts.ksize(key) + this.opts.vsize(value); + const e = this.map.get(key); + if (e) { + this._size -= e.value.s; + } + this._size += size; + if (this.ensureSize()) { + if (e) { + e.value.v = value; + e.value.s = size; + this.items.asTail(e); + } else { + this.items.push({ + k: key, + v: value, + s: size, + }); + this.map.set(key, this.items.tail); + } + } + return value; + } + + into(pairs: Iterable<[K, V]>) { + for (let p of pairs) { + this.set(p[0], p[1]); + } + return this; + } + + getSet(key: K, retrieve: () => Promise): Promise { + const e = this.map.get(key); + if (e) { + return Promise.resolve(this.resetEntry(e)); + } + return retrieve().then((v) => this.set(key, v)); + } + + delete(key: K): boolean { + const e = this.map.get(key); + if (e) { + this.removeEntry(e); + return true; + } + return false; + } + + protected resetEntry(e: ConsCell>) { + this.items.asTail(e); + return e.value.v; + } + + protected ensureSize() { + const release = this.opts.release; + const maxs = this.opts.maxsize; + const maxl = this.opts.maxlen; + while (this._size > maxs || this.length >= maxl) { + const e = this.items.drop(); + if (!e) { + return false; + } + this.map.delete(e.k); + release && release(e.k, e.v); + this._size -= e.s; + } + return true; + } + + protected removeEntry(e: ConsCell>) { + const ee = e.value; + this.map.delete(ee.k); + this.items.remove(e); + this.opts.release && this.opts.release(ee.k, ee.v); + this._size -= ee.s; + } +} diff --git a/packages/cache/src/mru.ts b/packages/cache/src/mru.ts new file mode 100644 index 0000000000..ae8e3632b0 --- /dev/null +++ b/packages/cache/src/mru.ts @@ -0,0 +1,44 @@ +import { ConsCell } from "@thi.ng/dcons"; + +import { CacheEntry, CacheOpts } from "./api"; +import { LRUCache } from "./lru"; + +export class MRUCache extends LRUCache { + + constructor(pairs?: Iterable<[K, V]>, opts?: Partial>) { + super(pairs, opts); + } + + empty(): MRUCache { + return new MRUCache(null, this.opts); + } + + set(key: K, value: V) { + const size = this.opts.ksize(key) + this.opts.vsize(value); + const e = this.map.get(key); + if (e) { + this._size -= e.value.s; + } + this._size += size; + if (this.ensureSize()) { + if (e) { + e.value.v = value; + e.value.s = size; + this.items.asHead(e); + } else { + this.items.cons({ + k: key, + v: value, + s: size, + }); + this.map.set(key, this.items.head); + } + } + return value; + } + + protected resetEntry(e: ConsCell>) { + this.items.asHead(e); + return e.value.v; + } +} diff --git a/packages/cache/src/tlru.ts b/packages/cache/src/tlru.ts new file mode 100644 index 0000000000..4d0e2f71b9 --- /dev/null +++ b/packages/cache/src/tlru.ts @@ -0,0 +1,105 @@ +import { ConsCell, DCons } from "@thi.ng/dcons"; + +import { CacheEntry, CacheOpts } from "./api"; +import { LRUCache } from "./lru"; + +export interface TLRUCacheOpts extends CacheOpts { + ttl: number; +} + +export interface TLRUCacheEntry extends CacheEntry { + t: number; +} + +/** + * Time-aware LRU cache. Extends LRU strategy with TTL (time-to-live) + * values associated to each entry. `has()` will only return true and + * `get()` only returns a cached value if its TTL hasn't yet expired. + * When adding a new value to the cache, first removes expired entries + * and if still not sufficient space then removes entries in LRU order. + * `set()` takes an optional entry specific `ttl` arg. If not given, + * uses the cache instance's default (provided via ctor option arg). + * If no instance TTL is given, TTL defaults to 1 hour. + */ +export class TLRUCache extends LRUCache { + + protected opts: TLRUCacheOpts; + protected map: Map>>; + protected items: DCons>; + + constructor(pairs?: Iterable<[K, V]>, opts?: Partial>) { + opts = Object.assign({ ttl: 60 * 60 * 1000 }, opts) + super(pairs, opts); + } + + empty(): TLRUCache { + return new TLRUCache(null, this.opts); + } + + has(key: K) { + return this.get(key) !== undefined; + } + + get(key: K, notFound?: any) { + const e = this.map.get(key); + if (e) { + if (e.value.t >= Date.now()) { + return this.resetEntry(e); + } + this.removeEntry(e); + } + return notFound; + } + + set(key: K, value: V, ttl = this.opts.ttl) { + const size = this.opts.ksize(key) + this.opts.vsize(value); + const e = this.map.get(key); + if (e) { + this._size -= e.value.s; + } + this._size += size; + if (this.ensureSize()) { + const t = Date.now() + ttl; + if (e) { + e.value.v = value; + e.value.s = size; + e.value.t = t; + this.items.asTail(e); + } else { + this.items.push({ + k: key, + v: value, + s: size, + t, + }); + this.map.set(key, this.items.tail); + } + } + return value; + } + + prune() { + const now = Date.now(); + let cell = this.items.head; + while (cell) { + if (cell.value.t < now) { + this.removeEntry(cell); + } + cell = cell.next; + } + } + + protected ensureSize() { + const maxs = this.opts.maxsize; + const maxl = this.opts.maxlen; + const now = Date.now(); + let cell = this.items.head; + while (cell && (this._size > maxs || this.length >= maxl)) { + if (cell.value.t < now) { + this.removeEntry(cell); + } + cell = cell.next; + } + return super.ensureSize(); + } +} diff --git a/packages/cache/test/lru.ts b/packages/cache/test/lru.ts new file mode 100644 index 0000000000..48a54124f9 --- /dev/null +++ b/packages/cache/test/lru.ts @@ -0,0 +1,41 @@ +import * as assert from "assert"; +import { LRUCache } from "../src/index"; + +describe("LRU", () => { + + let c: LRUCache; + let evicts: any[]; + + beforeEach(() => { + evicts = []; + c = new LRUCache( + [["a", 1], ["b", 2], ["c", 3]], + { + maxlen: 4, + release: (k, v) => evicts.push([k, v]) + } + ); + }); + + it("max length", () => { + assert.equal(c.length, 3); + c.set("d", 4); + assert.equal(c.length, 4); + c.set("e", 5); + assert.equal(c.length, 4); + assert.deepEqual(evicts, [["a", 1]]); + }); + + it("get", () => { + assert.equal(c.get("a"), 1); + assert.equal(c.get("b"), 2); + assert.deepEqual([...c.keys()], ["c", "a", "b"]); + c.set("d", 4); + assert.deepEqual([...c.keys()], ["c", "a", "b", "d"]); + c.set("e", 5); + assert.deepEqual([...c.keys()], ["a", "b", "d", "e"]); + assert.deepEqual([...c.values()], [1, 2, 4, 5]); + assert.deepEqual(evicts, [["c", 3]]); + }); + +}); diff --git a/packages/cache/test/mru.ts b/packages/cache/test/mru.ts new file mode 100644 index 0000000000..3a6105caa5 --- /dev/null +++ b/packages/cache/test/mru.ts @@ -0,0 +1,41 @@ +import * as assert from "assert"; +import { MRUCache } from "../src/index"; + +describe("MRU", () => { + + let c: MRUCache; + let evicts: any[]; + + beforeEach(() => { + evicts = []; + c = new MRUCache( + [["a", 1], ["b", 2], ["c", 3]], + { + maxlen: 4, + release: (k, v) => evicts.push([k, v]) + } + ); + }); + + it("max length", () => { + assert.equal(c.length, 3); + c.set("d", 4); + assert.equal(c.length, 4); + c.set("e", 5); + assert.equal(c.length, 4); + assert.deepEqual(evicts, [["d", 4]]); + }); + + it("get", () => { + assert.equal(c.get("a"), 1); + assert.equal(c.get("b"), 2); + assert.deepEqual([...c.keys()], ["b", "a", "c"]); + c.set("d", 4); + assert.deepEqual([...c.keys()], ["d", "b", "a", "c"]); + c.set("e", 5); + assert.deepEqual([...c.keys()], ["e", "b", "a", "c"]); + assert.deepEqual([...c.values()], [5, 2, 1, 3]); + assert.deepEqual(evicts, [["d", 4]]); + }); + +}); diff --git a/packages/cache/test/tlru.ts b/packages/cache/test/tlru.ts new file mode 100644 index 0000000000..f3734f8f49 --- /dev/null +++ b/packages/cache/test/tlru.ts @@ -0,0 +1,53 @@ +import * as assert from "assert"; +import { TLRUCache } from "../src/index"; + +describe("TLRU", () => { + + let c: TLRUCache; + let evicts: any[]; + + beforeEach(() => { + evicts = []; + c = new TLRUCache( + [["a", 1], ["b", 2], ["c", 3]], + { + maxlen: 4, + ttl: 10, + release: (k, v) => evicts.push([k, v]) + } + ); + }); + + it("max length", () => { + assert.equal(c.length, 3); + c.set("d", 4); + assert.equal(c.length, 4); + c.set("e", 5); + assert.equal(c.length, 4); + assert.deepEqual(evicts, [["a", 1]]); + }); + + it("get lru", () => { + assert.equal(c.get("a"), 1); + assert.equal(c.get("b"), 2); + assert.deepEqual([...c.keys()], ["c", "a", "b"]); + c.set("d", 4); + assert.deepEqual([...c.keys()], ["c", "a", "b", "d"]); + c.set("e", 5); + assert.deepEqual([...c.keys()], ["a", "b", "d", "e"]); + assert.deepEqual([...c.values()], [1, 2, 4, 5]); + assert.deepEqual(evicts, [["c", 3]]); + }); + + it("get ttl", (done) => { + assert.equal(c.set("a", 10, 100), 10); + setTimeout(() => { + assert(!c.has("b")); + assert(!c.has("c")); + assert.deepEqual(evicts, [["b", 2], ["c", 3]]); + assert.deepEqual([...c.keys()], ["a"]); + done(); + }, 20); + }); + +}); diff --git a/packages/cache/test/tsconfig.json b/packages/cache/test/tsconfig.json new file mode 100644 index 0000000000..bcf29ace54 --- /dev/null +++ b/packages/cache/test/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "../build" + }, + "include": [ + "./**/*.ts", + "../src/**/*.ts" + ] +} diff --git a/packages/cache/tsconfig.json b/packages/cache/tsconfig.json new file mode 100644 index 0000000000..bd6481a5a6 --- /dev/null +++ b/packages/cache/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": [ + "./src/**/*.ts" + ] +} diff --git a/packages/checks/CHANGELOG.md b/packages/checks/CHANGELOG.md index b5f0518917..1264f81fb3 100644 --- a/packages/checks/CHANGELOG.md +++ b/packages/checks/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [1.5.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/checks@1.4.0...@thi.ng/checks@1.5.0) (2018-04-26) + + +### Features + +* **checks:** add date, map, nan, set checks ([a865f62](https://github.com/thi-ng/umbrella/commit/a865f62)) + + + + # [1.4.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/checks@1.3.2...@thi.ng/checks@1.4.0) (2018-04-08) diff --git a/packages/checks/package.json b/packages/checks/package.json index 9efedf69e0..5a3d892923 100644 --- a/packages/checks/package.json +++ b/packages/checks/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/checks", - "version": "1.4.0", + "version": "1.5.0", "description": "Single-function sub-modules for type, feature & value checks", "main": "./index.js", "typings": "./index.d.ts", diff --git a/packages/checks/src/index.ts b/packages/checks/src/index.ts index caa8656a6d..b52b952ba7 100644 --- a/packages/checks/src/index.ts +++ b/packages/checks/src/index.ts @@ -13,6 +13,7 @@ export * from "./is-arraylike"; export * from "./is-blob"; export * from "./is-boolean"; export * from "./is-chrome"; +export * from "./is-date"; export * from "./is-even"; export * from "./is-false"; export * from "./is-file"; @@ -22,7 +23,9 @@ export * from "./is-ie"; export * from "./is-in-range"; export * from "./is-int32"; export * from "./is-iterable"; +export * from "./is-map"; export * from "./is-mobile"; +export * from "./is-nan"; export * from "./is-negative"; export * from "./is-node"; export * from "./is-null"; @@ -35,6 +38,7 @@ export * from "./is-promise"; export * from "./is-promiselike"; export * from "./is-regexp"; export * from "./is-safari"; +export * from "./is-set"; export * from "./is-string"; export * from "./is-symbol"; export * from "./is-transferable"; diff --git a/packages/checks/src/is-date.ts b/packages/checks/src/is-date.ts new file mode 100644 index 0000000000..36e8a3bfee --- /dev/null +++ b/packages/checks/src/is-date.ts @@ -0,0 +1,3 @@ +export function isDate(x: any): x is Date { + return x instanceof Date; +} diff --git a/packages/checks/src/is-map.ts b/packages/checks/src/is-map.ts new file mode 100644 index 0000000000..cf8bb3ed2f --- /dev/null +++ b/packages/checks/src/is-map.ts @@ -0,0 +1,3 @@ +export function isMap(x: any): x is Set { + return x instanceof Map; +} diff --git a/packages/checks/src/is-nan.ts b/packages/checks/src/is-nan.ts new file mode 100644 index 0000000000..bf8ee8bb89 --- /dev/null +++ b/packages/checks/src/is-nan.ts @@ -0,0 +1,3 @@ +export function isNaN(x: any) { + return x !== x; +} diff --git a/packages/checks/src/is-plain-object.ts b/packages/checks/src/is-plain-object.ts index 54d021677b..9a4df35bb7 100644 --- a/packages/checks/src/is-plain-object.ts +++ b/packages/checks/src/is-plain-object.ts @@ -4,7 +4,7 @@ * * @param x */ -export function isPlainObject(x: any): x is Object { +export function isPlainObject(x: any): x is object { let proto; return Object.prototype.toString.call(x) === "[object Object]" && (proto = Object.getPrototypeOf(x), proto === null || proto === Object.getPrototypeOf({})); diff --git a/packages/checks/src/is-set.ts b/packages/checks/src/is-set.ts new file mode 100644 index 0000000000..a7e5d3e65b --- /dev/null +++ b/packages/checks/src/is-set.ts @@ -0,0 +1,3 @@ +export function isSet(x: any): x is Set { + return x instanceof Set; +} diff --git a/packages/csp/CHANGELOG.md b/packages/csp/CHANGELOG.md index 7ee6e9a93d..a06c431b80 100644 --- a/packages/csp/CHANGELOG.md +++ b/packages/csp/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.3.30](https://github.com/thi-ng/umbrella/compare/@thi.ng/csp@0.3.29...@thi.ng/csp@0.3.30) (2018-04-26) + + + + +**Note:** Version bump only for package @thi.ng/csp + + +## [0.3.29](https://github.com/thi-ng/umbrella/compare/@thi.ng/csp@0.3.28...@thi.ng/csp@0.3.29) (2018-04-22) + + + + +**Note:** Version bump only for package @thi.ng/csp + ## [0.3.28](https://github.com/thi-ng/umbrella/compare/@thi.ng/csp@0.3.27...@thi.ng/csp@0.3.28) (2018-04-18) diff --git a/packages/csp/package.json b/packages/csp/package.json index 66ea9fc86a..433de5d7e5 100644 --- a/packages/csp/package.json +++ b/packages/csp/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/csp", - "version": "0.3.28", + "version": "0.3.30", "description": "ES6 promise based CSP implementation", "main": "./index.js", "typings": "./index.d.ts", @@ -28,8 +28,8 @@ "typescript": "^2.8.1" }, "dependencies": { - "@thi.ng/dcons": "^0.2.0", - "@thi.ng/transducers": "^1.8.1" + "@thi.ng/dcons": "^0.3.1", + "@thi.ng/transducers": "^1.8.2" }, "keywords": [ "async", diff --git a/packages/dcons/CHANGELOG.md b/packages/dcons/CHANGELOG.md index b0e7dfa13a..906afc813f 100644 --- a/packages/dcons/CHANGELOG.md +++ b/packages/dcons/CHANGELOG.md @@ -3,6 +3,25 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.3.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/dcons@0.3.0...@thi.ng/dcons@0.3.1) (2018-04-26) + + + + +**Note:** Version bump only for package @thi.ng/dcons + + +# [0.3.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/dcons@0.2.0...@thi.ng/dcons@0.3.0) (2018-04-22) + + +### Features + +* **dcons:** add asHead()/asTail() ([19f7e76](https://github.com/thi-ng/umbrella/commit/19f7e76)) + + + + # [0.2.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/dcons@0.1.19...@thi.ng/dcons@0.2.0) (2018-04-10) diff --git a/packages/dcons/package.json b/packages/dcons/package.json index 7ef18cb484..e2a0dd9868 100644 --- a/packages/dcons/package.json +++ b/packages/dcons/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/dcons", - "version": "0.2.0", + "version": "0.3.1", "description": "Comprehensive doubly linked list structure w/ iterator support", "main": "./index.js", "typings": "./index.d.ts", @@ -24,7 +24,7 @@ "typescript": "^2.8.1" }, "dependencies": { - "@thi.ng/api": "^2.2.0" + "@thi.ng/api": "^2.3.0" }, "keywords": [ "datastructure", diff --git a/packages/dcons/src/index.ts b/packages/dcons/src/index.ts index 24361be445..0914cc14b2 100644 --- a/packages/dcons/src/index.ts +++ b/packages/dcons/src/index.ts @@ -474,6 +474,32 @@ export class DCons implements return this; } + asHead(cell: ConsCell) { + if (cell === this.head) { + return this; + } + this.remove(cell); + this.head.prev = cell; + cell.next = this.head; + cell.prev = undefined; + this.head = cell; + this._length++; + return this; + } + + asTail(cell: ConsCell) { + if (cell === this.tail) { + return this; + } + this.remove(cell); + this.tail.next = cell; + cell.prev = this.tail; + cell.next = undefined; + this.tail = cell; + this._length++; + return this; + } + toString() { let res: any = []; let cell = this.head; diff --git a/packages/dgraph/CHANGELOG.md b/packages/dgraph/CHANGELOG.md index 27da0ec43f..2870ccbaa1 100644 --- a/packages/dgraph/CHANGELOG.md +++ b/packages/dgraph/CHANGELOG.md @@ -3,6 +3,30 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.1.7](https://github.com/thi-ng/umbrella/compare/@thi.ng/dgraph@0.1.6...@thi.ng/dgraph@0.1.7) (2018-04-26) + + + + +**Note:** Version bump only for package @thi.ng/dgraph + + +## [0.1.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/dgraph@0.1.5...@thi.ng/dgraph@0.1.6) (2018-04-22) + + + + +**Note:** Version bump only for package @thi.ng/dgraph + + +## [0.1.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/dgraph@0.1.4...@thi.ng/dgraph@0.1.5) (2018-04-20) + + + + +**Note:** Version bump only for package @thi.ng/dgraph + ## [0.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/dgraph@0.1.3...@thi.ng/dgraph@0.1.4) (2018-04-16) diff --git a/packages/dgraph/package.json b/packages/dgraph/package.json index ac7ea28dba..f97a21ef81 100644 --- a/packages/dgraph/package.json +++ b/packages/dgraph/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/dgraph", - "version": "0.1.4", + "version": "0.1.7", "description": "Type-agnostic directed acyclic graph (DAG) & graph operations", "main": "./index.js", "typings": "./index.d.ts", @@ -24,9 +24,9 @@ "typescript": "^2.8.1" }, "dependencies": { - "@thi.ng/api": "^2.2.0", - "@thi.ng/associative": "^0.4.1", - "@thi.ng/iterators": "^4.1.5" + "@thi.ng/api": "^2.3.0", + "@thi.ng/associative": "^0.4.4", + "@thi.ng/iterators": "^4.1.7" }, "keywords": [ "data structure", diff --git a/packages/diff/CHANGELOG.md b/packages/diff/CHANGELOG.md index b3711268e1..bc9789af11 100644 --- a/packages/diff/CHANGELOG.md +++ b/packages/diff/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [1.0.8](https://github.com/thi-ng/umbrella/compare/@thi.ng/diff@1.0.7...@thi.ng/diff@1.0.8) (2018-04-26) + + + + +**Note:** Version bump only for package @thi.ng/diff + ## [1.0.7](https://github.com/thi-ng/umbrella/compare/@thi.ng/diff@1.0.6...@thi.ng/diff@1.0.7) (2018-04-13) diff --git a/packages/diff/package.json b/packages/diff/package.json index dfd9e29a91..ea1118d149 100644 --- a/packages/diff/package.json +++ b/packages/diff/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/diff", - "version": "1.0.7", + "version": "1.0.8", "description": "Array & object Diff", "main": "./index.js", "typings": "./index.d.ts", @@ -22,7 +22,7 @@ "typescript": "^2.8.1" }, "dependencies": { - "@thi.ng/api": "^2.2.0" + "@thi.ng/api": "^2.3.0" }, "keywords": [ "array", diff --git a/packages/hdom-components/CHANGELOG.md b/packages/hdom-components/CHANGELOG.md index 79c203a04a..24debd9671 100644 --- a/packages/hdom-components/CHANGELOG.md +++ b/packages/hdom-components/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [2.0.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom-components@2.0.1...@thi.ng/hdom-components@2.0.2) (2018-04-26) + + + + +**Note:** Version bump only for package @thi.ng/hdom-components + ## [2.0.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom-components@2.0.0...@thi.ng/hdom-components@2.0.1) (2018-04-13) diff --git a/packages/hdom-components/package.json b/packages/hdom-components/package.json index bd57569d76..86b2e36e53 100644 --- a/packages/hdom-components/package.json +++ b/packages/hdom-components/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/hdom-components", - "version": "2.0.1", + "version": "2.0.2", "description": "Raw, skinnable UI & SVG components for @thi.ng/hdom", "main": "./index.js", "typings": "./index.d.ts", @@ -24,7 +24,7 @@ "typescript": "^2.8.1" }, "dependencies": { - "@thi.ng/checks": "^1.4.0", + "@thi.ng/checks": "^1.5.0", "@types/webgl2": "^0.0.3" }, "keywords": [ diff --git a/packages/hdom/CHANGELOG.md b/packages/hdom/CHANGELOG.md index bed80b1c4b..0fb8842fe2 100644 --- a/packages/hdom/CHANGELOG.md +++ b/packages/hdom/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [3.0.10](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom@3.0.9...@thi.ng/hdom@3.0.10) (2018-04-26) + + + + +**Note:** Version bump only for package @thi.ng/hdom + + +## [3.0.9](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom@3.0.8...@thi.ng/hdom@3.0.9) (2018-04-22) + + + + +**Note:** Version bump only for package @thi.ng/hdom + ## [3.0.8](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom@3.0.7...@thi.ng/hdom@3.0.8) (2018-04-19) diff --git a/packages/hdom/package.json b/packages/hdom/package.json index 8836e98deb..ebbbe56856 100644 --- a/packages/hdom/package.json +++ b/packages/hdom/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/hdom", - "version": "3.0.8", + "version": "3.0.10", "description": "Lightweight vanilla ES6 UI component & virtual DOM system", "main": "./index.js", "typings": "./index.d.ts", @@ -16,7 +16,7 @@ "test": "rm -rf build && tsc -p test && nyc mocha build/test/*.js" }, "devDependencies": { - "@thi.ng/atom": "^1.3.3", + "@thi.ng/atom": "^1.3.4", "@types/mocha": "^5.0.0", "@types/node": "^9.6.1", "mocha": "^5.0.5", @@ -25,10 +25,10 @@ "typescript": "^2.8.1" }, "dependencies": { - "@thi.ng/api": "^2.2.0", - "@thi.ng/diff": "^1.0.7", - "@thi.ng/hiccup": "^1.3.9", - "@thi.ng/iterators": "^4.1.5" + "@thi.ng/api": "^2.3.0", + "@thi.ng/diff": "^1.0.8", + "@thi.ng/hiccup": "^1.3.10", + "@thi.ng/iterators": "^4.1.7" }, "keywords": [ "browser", diff --git a/packages/heaps/.npmignore b/packages/heaps/.npmignore new file mode 100644 index 0000000000..d703bda97a --- /dev/null +++ b/packages/heaps/.npmignore @@ -0,0 +1,10 @@ +build +coverage +dev +doc +src* +test +.nyc_output +tsconfig.json +*.tgz +*.html diff --git a/packages/heaps/CHANGELOG.md b/packages/heaps/CHANGELOG.md new file mode 100644 index 0000000000..c0439606c9 --- /dev/null +++ b/packages/heaps/CHANGELOG.md @@ -0,0 +1,53 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + + +## [0.2.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/heaps@0.2.2...@thi.ng/heaps@0.2.3) (2018-04-26) + + + + +**Note:** Version bump only for package @thi.ng/heaps + + +## [0.2.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/heaps@0.2.1...@thi.ng/heaps@0.2.2) (2018-04-24) + + + + +**Note:** Version bump only for package @thi.ng/heaps + + +## [0.2.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/heaps@0.2.0...@thi.ng/heaps@0.2.1) (2018-04-22) + + + + +**Note:** Version bump only for package @thi.ng/heaps + + +# [0.2.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/heaps@0.1.0...@thi.ng/heaps@0.2.0) (2018-04-22) + + +### Bug Fixes + +* **heaps:** add DHeap ICopy/IEmpty impls, fix return types ([5894572](https://github.com/thi-ng/umbrella/commit/5894572)) + + +### Features + +* **heaps:** add min/max(), update heapify() and percolate methods ([c4bbee0](https://github.com/thi-ng/umbrella/commit/c4bbee0)) +* **heaps:** iterator now returns min() seq ([fccb3af](https://github.com/thi-ng/umbrella/commit/fccb3af)) + + + + + +# 0.1.0 (2018-04-22) + + +### Features + +* **heaps:** import [@thi](https://github.com/thi).ng/heaps package ([0ea0847](https://github.com/thi-ng/umbrella/commit/0ea0847)) diff --git a/packages/heaps/LICENSE b/packages/heaps/LICENSE new file mode 100644 index 0000000000..8dada3edaf --- /dev/null +++ b/packages/heaps/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/heaps/README.md b/packages/heaps/README.md new file mode 100644 index 0000000000..d78abc0e52 --- /dev/null +++ b/packages/heaps/README.md @@ -0,0 +1,53 @@ +# @thi.ng/heaps + +[![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/heaps.svg)](https://www.npmjs.com/package/@thi.ng/heaps) + +This project is part of the +[@thi.ng/umbrella](https://github.com/thi-ng/umbrella/) monorepo. + +## About + +Type agnostic binary heap & d-ary heap implementations with customizable +ordering and fanout / tree arity (in case of `DHeap`). Both `Heap` and +`DHeap` have identical API. + +## Installation + +``` +yarn add @thi.ng/heaps +``` + +## Usage examples + +```typescript +import { Heap, DHeap } from "@thi.ng/heaps"; + +// with initial values, custom comparator and heap arity +const h = new DHeap( + [5, 2, 10, 15, 18, 23, 22, -1], + { + compare: (a,b) => b - a, + d: 4 + } +); + +h.pop(); +// 23 +h.pop(); +// 22 + +// insert new value unless it's a new root +// else pop and return current root +h.pushPop(16) +// 18 + +h.push(24); +``` + +## Authors + +- Karsten Schmidt + +## License + +© 2017 - 2018 Karsten Schmidt // Apache Software License 2.0 diff --git a/packages/heaps/package.json b/packages/heaps/package.json new file mode 100644 index 0000000000..80300f3f83 --- /dev/null +++ b/packages/heaps/package.json @@ -0,0 +1,41 @@ +{ + "name": "@thi.ng/heaps", + "version": "0.2.3", + "description": "Generic binary heap & d-ary heap implementations with customizable ordering", + "main": "./index.js", + "typings": "./index.d.ts", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn run clean && tsc --declaration", + "clean": "rm -rf *.js *.d.ts .nyc_output build coverage doc", + "cover": "yarn test && nyc report --reporter=lcov", + "doc": "node_modules/.bin/typedoc --mode modules --out doc src", + "pub": "yarn run build && yarn publish --access public", + "test": "rm -rf build && tsc -p test && nyc mocha build/test/*.js" + }, + "devDependencies": { + "@types/mocha": "^5.0.0", + "@types/node": "^9.6.1", + "mocha": "^5.0.5", + "nyc": "^11.6.0", + "typedoc": "^0.11.1", + "typescript": "^2.8.1" + }, + "dependencies": { + "@thi.ng/api": "^2.3.0" + }, + "keywords": [ + "data structure", + "d-heap", + "ES6", + "heap", + "priority queue", + "sort", + "typescript" + ], + "publishConfig": { + "access": "public" + } +} diff --git a/packages/heaps/src/api.ts b/packages/heaps/src/api.ts new file mode 100644 index 0000000000..18b1b4466b --- /dev/null +++ b/packages/heaps/src/api.ts @@ -0,0 +1,9 @@ +import { Comparator } from "@thi.ng/api/api"; + +export interface HeapOpts { + compare: Comparator; +} + +export interface DHeapOpts extends HeapOpts { + d: number; +} diff --git a/packages/heaps/src/dheap.ts b/packages/heaps/src/dheap.ts new file mode 100644 index 0000000000..fb6f5deeb5 --- /dev/null +++ b/packages/heaps/src/dheap.ts @@ -0,0 +1,124 @@ +import { compare } from "@thi.ng/api/compare"; + +import { DHeapOpts } from "./api"; +import { Heap } from "./heap"; +import { ICopy, IEmpty } from "@thi.ng/api/api"; + +/** + * Generic d-ary heap / priority queue with configurable arity (default + * = 4) and ordering via user-supplied comparator. By default, + * implements min-heap ordering and uses @thi.ng/api/compare. The arity + * `d` must be >= 2. If `d=2`, the default binary `Heap` implementation + * will be faster. + * + * https://en.wikipedia.org/wiki/D-ary_heap + */ +export class DHeap extends Heap implements + ICopy>, + IEmpty> { + + /** + * Returns index of parent node or -1 if `idx < 1`. + * + * @param idx + * @param d + */ + static parentIndex(idx: number, d = 4) { + return idx > 0 ? ((idx - 1) / d) | 0 : -1; + } + + /** + * Returns index of 1st child or -1 if `idx < 0`. + * + * @param idx + * @param d + */ + static childIndex(idx: number, d = 4) { + return idx >= 0 ? (idx * d) + 1 : -1; + } + + protected d: number; + + constructor(values?: Iterable, opts?: Partial>) { + super(null, Object.assign({ compare }, opts)); + this.d = (opts && opts.d) || 4; + this.values = []; + if (values) { + this.into(values); + } + } + + copy() { + return >super.copy(); + } + + empty() { + return new DHeap(null, { compare: this.compare, d: this.d }); + } + + parent(n: number) { + n = DHeap.parentIndex(n, this.d); + return n >= 0 ? this.values[n] : undefined; + } + + children(n: number) { + n = DHeap.childIndex(n, this.d); + const vals = this.values; + if (n >= vals.length) return; + return vals.slice(n, n + this.d); + } + + leaves() { + const vals = this.values; + if (!vals.length) { + return [] + } + return vals.slice(DHeap.parentIndex(vals.length - 1, this.d) + 1); + } + + heapify(vals = this.values) { + for (var i = ((vals.length - 1) / this.d) | 0; i >= 0; i--) { + this.percolateDown(i, vals); + } + } + + protected percolateUp(i: number, vals = this.values) { + const node = vals[i]; + const d = this.d; + const cmp = this.compare; + while (i > 0) { + const pi = ((i - 1) / d) | 0; + const parent = vals[pi]; + if (cmp(node, parent) >= 0) { + break; + } + vals[pi] = node; + vals[i] = parent; + i = pi; + } + } + + protected percolateDown(i: number, vals = this.values) { + const n = vals.length; + const d = this.d; + const node = vals[i]; + const cmp = this.compare; + let child = (i * d) + 1, minChild; + while (child < n) { + minChild = child; + for (let j = child + 1, k = child + d; j < k; j++) { + if (j < n && cmp(vals[j], vals[minChild]) < 0) { + minChild = j; + } + } + if (cmp(vals[minChild], node) < 0) { + vals[i] = vals[minChild]; + } else { + break; + } + i = minChild; + child = (i * d) + 1; + } + vals[i] = node; + } +} \ No newline at end of file diff --git a/packages/heaps/src/heap.ts b/packages/heaps/src/heap.ts new file mode 100644 index 0000000000..9df6ffd2d9 --- /dev/null +++ b/packages/heaps/src/heap.ts @@ -0,0 +1,238 @@ +import { ICopy, ILength, IEmpty, Comparator } from "@thi.ng/api/api"; +import { compare } from "@thi.ng/api/compare"; + +import { HeapOpts } from "./api"; + +/** + * Generic binary heap / priority queue with customizable ordering via + * user-supplied comparator. By default, implements min-heap ordering + * and uses @thi.ng/api/compare. + * + * ``` + * h = new Heap([20, 5, 10]); + * h.push(15); + * + * h.pop(); // 5 + * h.pop(); // 10 + * h.pop(); // 15 + * h.pop(); // 20 + * h.pop(); // undefined + * ``` + */ +export class Heap implements + ICopy>, + IEmpty>, + ILength { + + static parentIndex(idx: number) { + return idx > 0 ? (idx - 1) >> 1 : -1; + } + + static childIndex(idx: number) { + return idx >= 0 ? (idx << 1) + 1 : -1; + } + + protected values: T[]; + protected compare: Comparator; + + constructor(values?: Iterable, opts?: HeapOpts) { + opts = Object.assign({ compare: compare }, opts); + this.compare = opts.compare; + this.values = []; + if (values) { + this.into(values); + } + } + + *[Symbol.iterator]() { + yield* this.min(); + } + + get length() { + return this.values.length; + } + + copy() { + const h = this.empty(); + h.values = this.values.slice(); + return h; + } + + clear() { + this.values.length = 0; + } + + empty() { + return new Heap(null, { compare: this.compare }); + } + + peek() { + return this.values[0]; + } + + push(val: T) { + this.values.push(val); + this.percolateUp(this.values.length - 1); + return this; + } + + pop() { + const vals = this.values; + const tail = vals.pop(); + let res: T; + if (vals.length > 0) { + res = vals[0]; + vals[0] = tail; + this.percolateDown(0); + } else { + res = tail; + } + return res; + } + + pushPop(val: T, vals = this.values) { + const head = vals[0]; + if (vals.length > 0 && this.compare(head, val) < 0) { + vals[0] = val; + val = head; + this.percolateDown(0, vals); + } + return val; + } + + into(vals: Iterable) { + for (let v of vals) { + this.push(v); + } + return this; + } + + replaceHead(val: T) { + const res = this.values[0]; + this.values[0] = val; + this.percolateDown(0); + return res; + } + + heapify(vals = this.values) { + for (var i = (vals.length - 1) >> 1; i >= 0; i--) { + this.percolateDown(i, vals); + } + } + + /** + * Returns the largest `n` values (or less) in heap, based on + * comparator ordering. + * + * @param n + */ + max(n = this.values.length) { + const vals = this.values; + const cmp = this.compare; + const res = vals.slice(0, n); + if (!n) { + return res; + } + this.heapify(res); + for (let m = vals.length; n < m; n++) { + this.pushPop(vals[n], res); + } + return res.sort((a, b) => cmp(b, a)); + } + + /** + * Returns the smallest `n` values (or less) in heap, based on + * comparator ordering. + * + * @param n + */ + min(n = this.values.length) { + const vals = this.values; + const cmp = this.compare; + const res = vals.slice(0, n).sort(cmp); + if (!n) { + return res; + } + let x = res[n - 1], y; + for (let i = n, m = vals.length; i < m; i++) { + y = vals[i]; + if (cmp(y, x) < 0) { + res.splice(binarySearch(res, y, 0, n, cmp), 0, y); + res.pop(); + x = res[n - 1]; + } + } + return res; + } + + parent(n: number) { + n = Heap.parentIndex(n); + return n >= 0 ? this.values[n] : undefined; + } + + children(n: number) { + n = Heap.childIndex(n); + const vals = this.values; + const m = vals.length; + if (n >= m) return; + if (n === m - 1) return [vals[n]]; + return [vals[n], vals[n + 1]]; + } + + leaves() { + const vals = this.values; + if (!vals.length) { + return [] + } + return vals.slice(Heap.parentIndex(vals.length - 1) + 1); + } + + protected percolateUp(i: number, vals = this.values) { + const node = vals[i]; + const cmp = this.compare; + while (i > 0) { + const pi = (i - 1) >> 1; + const parent = vals[pi]; + if (cmp(node, parent) >= 0) { + break; + } + vals[pi] = node; + vals[i] = parent; + i = pi; + } + } + + protected percolateDown(i: number, vals = this.values) { + const n = vals.length; + const node = vals[i]; + const cmp = this.compare; + let child = (i << 1) + 1; + while (child < n) { + const next = child + 1; + if (next < n && cmp(vals[child], vals[next]) >= 0) { + child = next; + } + if (cmp(vals[child], node) < 0) { + vals[i] = vals[child]; + } else { + break; + } + i = child; + child = (i << 1) + 1; + } + vals[i] = node; + } +} + +function binarySearch(vals: T[], x: T, lo: number, hi: number, cmp: Comparator) { + let m; + while (lo < hi) { + m = (lo + hi) >>> 1; + if (cmp(x, vals[m]) < 0) { + hi = m; + } else { + lo = m + 1; + } + } + return lo; +} diff --git a/packages/heaps/src/index.ts b/packages/heaps/src/index.ts new file mode 100644 index 0000000000..abf9a520ae --- /dev/null +++ b/packages/heaps/src/index.ts @@ -0,0 +1,3 @@ +export * from "./api"; +export * from "./dheap"; +export * from "./heap"; diff --git a/packages/heaps/test/dheap.ts b/packages/heaps/test/dheap.ts new file mode 100644 index 0000000000..97381b8c38 --- /dev/null +++ b/packages/heaps/test/dheap.ts @@ -0,0 +1,98 @@ +import * as assert from "assert"; +import { DHeap } from "../src/index"; +import { compare } from "@thi.ng/api/compare"; + +describe("DHeap", () => { + + const rcmp: (a: number, b: number) => number = (a, b) => b - a; + + const src = [5, 2, 10, 20, 15, 18, 23, 22, -1]; + let h: DHeap; + + beforeEach(() => { + h = new DHeap(src); + }); + + it("length", () => { + assert.equal(h.length, src.length); + }); + + it("copy", () => { + assert.deepEqual(drain(h.copy()), drain(h)); + h = new DHeap(src, { compare: rcmp }); + assert.deepEqual(drain(h.copy()), drain(h)); + }); + + it("peek", () => { + assert.equal(h.peek(), -1); + h.push(-2); + assert.equal(h.peek(), -2); + }); + + it("pop", () => { + assert.deepEqual(drain(h), src.slice().sort(compare)); + h = new DHeap(src, { compare: rcmp }); + assert.deepEqual(drain(h), src.slice().sort(compare).reverse()); + }); + + it("into", () => { + assert.deepEqual(drain(h.into(src)), src.concat(src).sort(compare)); + }); + + it("pushPop", () => { + assert.equal(h.pushPop(-2), -2); + assert.equal(h.length, src.length); + assert.equal(h.pushPop(-1), -1); + assert.equal(h.length, src.length); + assert.equal(h.pushPop(11), -1); + assert.equal(h.length, src.length); + assert.equal(h.pushPop(24), 2); + assert.equal(h.length, src.length); + }); + + it("min", () => { + assert.deepEqual(h.min(0), []); + assert.deepEqual(h.min(1), [-1]); + assert.deepEqual(h.min(2), [-1, 2]); + assert.deepEqual(h.min(3), [-1, 2, 5]); + assert.deepEqual(h.min(4), [-1, 2, 5, 10]); + assert.deepEqual(h.min(), src.slice().sort(compare)); + }); + + it("max", () => { + assert.deepEqual(h.max(0), []); + assert.deepEqual(h.max(1), [23]); + assert.deepEqual(h.max(2), [23, 22]); + assert.deepEqual(h.max(3), [23, 22, 20]); + assert.deepEqual(h.max(4), [23, 22, 20, 18]); + assert.deepEqual(h.max(), src.slice().sort(compare).reverse()); + }); + + it("parent", () => { + assert.equal(h.parent(0), undefined); + assert.equal(h.parent(1), -1); + assert.equal(h.parent(2), -1); + assert.equal(h.parent(3), -1); + assert.equal(h.parent(4), -1); + assert.equal(h.parent(5), 2); + assert.equal(h.parent(6), 2); + assert.equal(h.parent(7), 2); + assert.equal(h.parent(8), 2); + }); + + it("children", () => { + assert.deepEqual(h.children(0), [2, 10, 20, 15]); + assert.deepEqual(h.children(1), [18, 23, 22, 5]); + assert.deepEqual(h.children(2), undefined); + }); +}); + +function drain(h: DHeap) { + const res = []; + let x; + while (x = h.pop()) { + res.push(x); + } + return res; +} + diff --git a/packages/heaps/test/heap.ts b/packages/heaps/test/heap.ts new file mode 100644 index 0000000000..eeaebe72cd --- /dev/null +++ b/packages/heaps/test/heap.ts @@ -0,0 +1,98 @@ +import * as assert from "assert"; +import { Heap } from "../src/index"; +import { compare } from "@thi.ng/api/compare"; + +describe("Heap", () => { + + const rcmp: (a: number, b: number) => number = (a, b) => b - a; + + const src = [5, 2, 10, 20, 15, 18, 23, 22, -1]; + let h: Heap; + + beforeEach(() => { + h = new Heap(src); + }); + + it("length", () => { + assert.equal(h.length, src.length); + }); + + it("copy", () => { + assert.deepEqual(drain(h.copy()), drain(h)); + h = new Heap(src, { compare: rcmp }); + assert.deepEqual(drain(h.copy()), drain(h)); + }); + + it("peek", () => { + assert.equal(h.peek(), -1); + h.push(-2); + assert.equal(h.peek(), -2); + }); + + it("pop", () => { + assert.deepEqual(drain(h), src.slice().sort(compare)); + h = new Heap(src, { compare: rcmp }); + assert.deepEqual(drain(h), src.slice().sort(compare).reverse()); + }); + + it("into", () => { + assert.deepEqual(drain(h.into(src)), src.concat(src).sort(compare)); + }); + + it("pushPop", () => { + assert.equal(h.pushPop(-2), -2); + assert.equal(h.length, src.length); + assert.equal(h.pushPop(-1), -1); + assert.equal(h.length, src.length); + assert.equal(h.pushPop(11), -1); + assert.equal(h.length, src.length); + assert.equal(h.pushPop(24), 2); + assert.equal(h.length, src.length); + }); + + it("min", () => { + assert.deepEqual(h.min(0), []); + assert.deepEqual(h.min(1), [-1]); + assert.deepEqual(h.min(2), [-1, 2]); + assert.deepEqual(h.min(3), [-1, 2, 5]); + assert.deepEqual(h.min(4), [-1, 2, 5, 10]); + assert.deepEqual(h.min(), src.slice().sort(compare)); + }); + + it("max", () => { + assert.deepEqual(h.max(0), []); + assert.deepEqual(h.max(1), [23]); + assert.deepEqual(h.max(2), [23, 22]); + assert.deepEqual(h.max(3), [23, 22, 20]); + assert.deepEqual(h.max(4), [23, 22, 20, 18]); + assert.deepEqual(h.max(), src.slice().sort(compare).reverse()); + }); + + it("parent", () => { + assert.equal(h.parent(0), undefined); + assert.equal(h.parent(1), -1); + assert.equal(h.parent(2), -1); + assert.equal(h.parent(3), 2); + assert.equal(h.parent(4), 2); + assert.equal(h.parent(5), 10); + assert.equal(h.parent(6), 10); + }); + + it("children", () => { + assert.deepEqual(h.children(0), [2, 10]); + assert.deepEqual(h.children(1), [5, 15]); + assert.deepEqual(h.children(2), [18, 23]); + assert.deepEqual(h.children(3), [22, 20]); + assert.deepEqual(h.children(4), undefined); + }); +}); + +function drain(h: Heap) { + const res = []; + let x; + while (x = h.pop()) { + res.push(x); + } + return res; +} + diff --git a/packages/heaps/test/tsconfig.json b/packages/heaps/test/tsconfig.json new file mode 100644 index 0000000000..bcf29ace54 --- /dev/null +++ b/packages/heaps/test/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "../build" + }, + "include": [ + "./**/*.ts", + "../src/**/*.ts" + ] +} diff --git a/packages/heaps/tsconfig.json b/packages/heaps/tsconfig.json new file mode 100644 index 0000000000..bd6481a5a6 --- /dev/null +++ b/packages/heaps/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": [ + "./src/**/*.ts" + ] +} diff --git a/packages/hiccup-css/CHANGELOG.md b/packages/hiccup-css/CHANGELOG.md index 5800aa8398..e9df35c551 100644 --- a/packages/hiccup-css/CHANGELOG.md +++ b/packages/hiccup-css/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.1.12](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup-css@0.1.11...@thi.ng/hiccup-css@0.1.12) (2018-04-26) + + + + +**Note:** Version bump only for package @thi.ng/hiccup-css + ## [0.1.11](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup-css@0.1.10...@thi.ng/hiccup-css@0.1.11) (2018-04-18) diff --git a/packages/hiccup-css/package.json b/packages/hiccup-css/package.json index 44478f5f2c..f26794a735 100644 --- a/packages/hiccup-css/package.json +++ b/packages/hiccup-css/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/hiccup-css", - "version": "0.1.11", + "version": "0.1.12", "description": "CSS from nested JS data structures", "main": "./index.js", "typings": "./index.d.ts", @@ -24,8 +24,8 @@ "typescript": "^2.8.1" }, "dependencies": { - "@thi.ng/api": "^2.2.0", - "@thi.ng/transducers": "^1.8.1" + "@thi.ng/api": "^2.3.0", + "@thi.ng/transducers": "^1.8.2" }, "keywords": [ "clojure", diff --git a/packages/hiccup-svg/CHANGELOG.md b/packages/hiccup-svg/CHANGELOG.md index 20ec18b3b2..de7a1fd704 100644 --- a/packages/hiccup-svg/CHANGELOG.md +++ b/packages/hiccup-svg/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.2.7](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup-svg@0.2.6...@thi.ng/hiccup-svg@0.2.7) (2018-04-26) + + + + +**Note:** Version bump only for package @thi.ng/hiccup-svg + ## [0.2.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup-svg@0.2.5...@thi.ng/hiccup-svg@0.2.6) (2018-04-19) diff --git a/packages/hiccup-svg/package.json b/packages/hiccup-svg/package.json index 5b1e5cab49..3ebab6a38d 100644 --- a/packages/hiccup-svg/package.json +++ b/packages/hiccup-svg/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/hiccup-svg", - "version": "0.2.6", + "version": "0.2.7", "description": "SVG element functions for @thi.ng/hiccup & @thi.ng/hdom", "main": "./index.js", "typings": "./index.d.ts", @@ -24,8 +24,8 @@ "typescript": "^2.8.1" }, "dependencies": { - "@thi.ng/api": "^2.2.0", - "@thi.ng/hiccup": "^1.3.9" + "@thi.ng/api": "^2.3.0", + "@thi.ng/hiccup": "^1.3.10" }, "keywords": [ "components", diff --git a/packages/hiccup/CHANGELOG.md b/packages/hiccup/CHANGELOG.md index f81ebb3f9a..1dbd17559e 100644 --- a/packages/hiccup/CHANGELOG.md +++ b/packages/hiccup/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [1.3.10](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup@1.3.9...@thi.ng/hiccup@1.3.10) (2018-04-26) + + + + +**Note:** Version bump only for package @thi.ng/hiccup + ## [1.3.9](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup@1.3.8...@thi.ng/hiccup@1.3.9) (2018-04-19) diff --git a/packages/hiccup/package.json b/packages/hiccup/package.json index b414a64c4e..b73f45ca8f 100644 --- a/packages/hiccup/package.json +++ b/packages/hiccup/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/hiccup", - "version": "1.3.9", + "version": "1.3.10", "description": "HTML/SVG/XML serialization of nested data structures, iterables & closures", "main": "./index.js", "typings": "./index.d.ts", @@ -16,7 +16,7 @@ "test": "rm -rf build && tsc -p test && nyc mocha build/test/*.js" }, "devDependencies": { - "@thi.ng/atom": "^1.3.3", + "@thi.ng/atom": "^1.3.4", "@types/mocha": "^5.0.0", "@types/node": "^9.6.1", "mocha": "^5.0.5", @@ -25,8 +25,8 @@ "typescript": "^2.8.1" }, "dependencies": { - "@thi.ng/api": "^2.2.0", - "@thi.ng/checks": "^1.4.0" + "@thi.ng/api": "^2.3.0", + "@thi.ng/checks": "^1.5.0" }, "keywords": [ "clojure", diff --git a/packages/interceptors/CHANGELOG.md b/packages/interceptors/CHANGELOG.md index 88cf65a928..4ed17a32a0 100644 --- a/packages/interceptors/CHANGELOG.md +++ b/packages/interceptors/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [1.5.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/interceptors@1.5.1...@thi.ng/interceptors@1.5.2) (2018-04-26) + + + + +**Note:** Version bump only for package @thi.ng/interceptors + + +## [1.5.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/interceptors@1.5.0...@thi.ng/interceptors@1.5.1) (2018-04-19) + + + + +**Note:** Version bump only for package @thi.ng/interceptors + # [1.5.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/interceptors@1.4.1...@thi.ng/interceptors@1.5.0) (2018-04-19) diff --git a/packages/interceptors/package.json b/packages/interceptors/package.json index d4d633517a..8d1b8dfed0 100644 --- a/packages/interceptors/package.json +++ b/packages/interceptors/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/interceptors", - "version": "1.5.0", + "version": "1.5.2", "description": "Interceptor based event bus, side effect & immutable state handling", "main": "./index.js", "typings": "./index.d.ts", @@ -24,9 +24,9 @@ "typescript": "^2.8.1" }, "dependencies": { - "@thi.ng/api": "^2.2.0", - "@thi.ng/atom": "^1.3.3", - "@thi.ng/paths": "^1.3.1" + "@thi.ng/api": "^2.3.0", + "@thi.ng/atom": "^1.3.4", + "@thi.ng/paths": "^1.3.2" }, "keywords": [ "ES6", diff --git a/packages/interceptors/src/event-bus.ts b/packages/interceptors/src/event-bus.ts index 61592ee7c6..08cd2e895e 100644 --- a/packages/interceptors/src/event-bus.ts +++ b/packages/interceptors/src/event-bus.ts @@ -622,7 +622,7 @@ export class EventBus extends StatelessEventBus implements ({ [FX_STATE]: setIn(state, path, val) }), [api.EV_UPDATE_VALUE]: (state, [_, [path, fn, ...args]]) => ({ [FX_STATE]: updateIn(state, path, fn, ...args) }), - [api.EV_TOGGLE_VALUE]: (state, [_, [path]]) => + [api.EV_TOGGLE_VALUE]: (state, [_, path]) => ({ [FX_STATE]: updateIn(state, path, (x) => !x) }), [api.EV_UNDO]: undoHandler("undo"), [api.EV_REDO]: undoHandler("redo"), diff --git a/packages/iterators/CHANGELOG.md b/packages/iterators/CHANGELOG.md index 8eab01ccd8..0ec21e28fa 100644 --- a/packages/iterators/CHANGELOG.md +++ b/packages/iterators/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [4.1.7](https://github.com/thi-ng/umbrella/compare/@thi.ng/iterators@4.1.6...@thi.ng/iterators@4.1.7) (2018-04-26) + + + + +**Note:** Version bump only for package @thi.ng/iterators + + +## [4.1.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/iterators@4.1.5...@thi.ng/iterators@4.1.6) (2018-04-22) + + + + +**Note:** Version bump only for package @thi.ng/iterators + ## [4.1.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/iterators@4.1.4...@thi.ng/iterators@4.1.5) (2018-04-10) diff --git a/packages/iterators/package.json b/packages/iterators/package.json index b0ab86e365..36778887f3 100644 --- a/packages/iterators/package.json +++ b/packages/iterators/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/iterators", - "version": "4.1.5", + "version": "4.1.7", "description": "clojure.core inspired, composable ES6 iterators & generators", "main": "./index.js", "typings": "./index.d.ts", @@ -24,8 +24,8 @@ "typescript": "^2.8.1" }, "dependencies": { - "@thi.ng/api": "^2.2.0", - "@thi.ng/dcons": "^0.2.0" + "@thi.ng/api": "^2.3.0", + "@thi.ng/dcons": "^0.3.1" }, "keywords": [ "clojure", diff --git a/packages/paths/CHANGELOG.md b/packages/paths/CHANGELOG.md index c208fc8938..4977d9d2a5 100644 --- a/packages/paths/CHANGELOG.md +++ b/packages/paths/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [1.3.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/paths@1.3.1...@thi.ng/paths@1.3.2) (2018-04-26) + + + + +**Note:** Version bump only for package @thi.ng/paths + ## [1.3.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/paths@1.3.0...@thi.ng/paths@1.3.1) (2018-04-19) diff --git a/packages/paths/package.json b/packages/paths/package.json index 823e2afb3e..be5e2002fe 100644 --- a/packages/paths/package.json +++ b/packages/paths/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/paths", - "version": "1.3.1", + "version": "1.3.2", "description": "immutable, optimized path-based object property / array accessors", "main": "./index.js", "typings": "./index.d.ts", @@ -24,8 +24,8 @@ "typescript": "^2.8.1" }, "dependencies": { - "@thi.ng/api": "^2.2.0", - "@thi.ng/checks": "^1.4.0" + "@thi.ng/api": "^2.3.0", + "@thi.ng/checks": "^1.5.0" }, "keywords": [ "accessors", diff --git a/packages/pointfree-lang/CHANGELOG.md b/packages/pointfree-lang/CHANGELOG.md index 8ea1927fac..a6d1321099 100644 --- a/packages/pointfree-lang/CHANGELOG.md +++ b/packages/pointfree-lang/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.2.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/pointfree-lang@0.2.3...@thi.ng/pointfree-lang@0.2.4) (2018-04-26) + + + + +**Note:** Version bump only for package @thi.ng/pointfree-lang + ## [0.2.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/pointfree-lang@0.2.2...@thi.ng/pointfree-lang@0.2.3) (2018-04-13) diff --git a/packages/pointfree-lang/package.json b/packages/pointfree-lang/package.json index 24b9c51527..8d5650898a 100644 --- a/packages/pointfree-lang/package.json +++ b/packages/pointfree-lang/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/pointfree-lang", - "version": "0.2.3", + "version": "0.2.4", "description": "Forth style syntax layer/compiler for the @thi.ng/pointfree DSL", "main": "./index.js", "typings": "./index.d.ts", @@ -26,8 +26,8 @@ "typescript": "^2.8.1" }, "dependencies": { - "@thi.ng/api": "^2.2.0", - "@thi.ng/pointfree": "^0.7.3" + "@thi.ng/api": "^2.3.0", + "@thi.ng/pointfree": "^0.7.4" }, "keywords": [ "concatenative", diff --git a/packages/pointfree/CHANGELOG.md b/packages/pointfree/CHANGELOG.md index d874ca6087..8de3e74bc2 100644 --- a/packages/pointfree/CHANGELOG.md +++ b/packages/pointfree/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.7.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/pointfree@0.7.3...@thi.ng/pointfree@0.7.4) (2018-04-26) + + + + +**Note:** Version bump only for package @thi.ng/pointfree + ## [0.7.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/pointfree@0.7.2...@thi.ng/pointfree@0.7.3) (2018-04-13) diff --git a/packages/pointfree/package.json b/packages/pointfree/package.json index cdaffe4691..a14856ae6f 100644 --- a/packages/pointfree/package.json +++ b/packages/pointfree/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/pointfree", - "version": "0.7.3", + "version": "0.7.4", "description": "Pointfree functional composition / Forth style stack execution engine", "main": "./index.js", "typings": "./index.d.ts", @@ -24,7 +24,7 @@ "typescript": "^2.8.1" }, "dependencies": { - "@thi.ng/api": "^2.2.0" + "@thi.ng/api": "^2.3.0" }, "keywords": [ "composition", diff --git a/packages/resolve-map/CHANGELOG.md b/packages/resolve-map/CHANGELOG.md index 4a8eeba8d8..217e7c1c75 100644 --- a/packages/resolve-map/CHANGELOG.md +++ b/packages/resolve-map/CHANGELOG.md @@ -3,7 +3,15 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. - + +## [1.0.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/resolve-map@1.0.2...@thi.ng/resolve-map@1.0.3) (2018-04-26) + + + + +**Note:** Version bump only for package @thi.ng/resolve-map + + ## [1.0.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/resolve-map@1.0.1...@thi.ng/resolve-map@1.0.2) (2018-04-19) @@ -11,7 +19,7 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline **Note:** Version bump only for package @thi.ng/resolve-map - + ## [1.0.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/resolve-map@1.0.0...@thi.ng/resolve-map@1.0.1) (2018-04-17) diff --git a/packages/resolve-map/package.json b/packages/resolve-map/package.json index d6b4363443..06bddff40a 100644 --- a/packages/resolve-map/package.json +++ b/packages/resolve-map/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/resolve-map", - "version": "1.0.2", + "version": "1.0.3", "description": "DAG resolution of vanilla objects & arrays with internally linked values", "main": "./index.js", "typings": "./index.d.ts", @@ -22,9 +22,9 @@ "typescript": "^2.8.1" }, "dependencies": { - "@thi.ng/api": "^2.2.0", - "@thi.ng/checks": "^1.4.0", - "@thi.ng/paths": "^1.3.1" + "@thi.ng/api": "^2.3.0", + "@thi.ng/checks": "^1.5.0", + "@thi.ng/paths": "^1.3.2" }, "keywords": [ "configuration", diff --git a/packages/rle-pack/CHANGELOG.md b/packages/rle-pack/CHANGELOG.md index 03a73bda86..62b34bd6ec 100644 --- a/packages/rle-pack/CHANGELOG.md +++ b/packages/rle-pack/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.2.14](https://github.com/thi-ng/umbrella/compare/@thi.ng/rle-pack@0.2.13...@thi.ng/rle-pack@0.2.14) (2018-04-26) + + + + +**Note:** Version bump only for package @thi.ng/rle-pack + ## [0.2.13](https://github.com/thi-ng/umbrella/compare/@thi.ng/rle-pack@0.2.12...@thi.ng/rle-pack@0.2.13) (2018-04-08) diff --git a/packages/rle-pack/package.json b/packages/rle-pack/package.json index 306faeb64f..6238ea1329 100644 --- a/packages/rle-pack/package.json +++ b/packages/rle-pack/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rle-pack", - "version": "0.2.13", + "version": "0.2.14", "description": "Binary run-length encoding packer w/ flexible repeat bit widths", "main": "./index.js", "typings": "./index.d.ts", @@ -25,7 +25,7 @@ "typescript": "^2.8.1" }, "dependencies": { - "@thi.ng/bitstream": "^0.4.4" + "@thi.ng/bitstream": "^0.4.5" }, "keywords": [ "binary", diff --git a/packages/router/CHANGELOG.md b/packages/router/CHANGELOG.md index 6139088299..1232ccd9d9 100644 --- a/packages/router/CHANGELOG.md +++ b/packages/router/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.1.7](https://github.com/thi-ng/umbrella/compare/@thi.ng/router@0.1.6...@thi.ng/router@0.1.7) (2018-04-26) + + + + +**Note:** Version bump only for package @thi.ng/router + ## [0.1.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/router@0.1.5...@thi.ng/router@0.1.6) (2018-04-13) diff --git a/packages/router/package.json b/packages/router/package.json index 795f35c9ad..64ab41488a 100644 --- a/packages/router/package.json +++ b/packages/router/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/router", - "version": "0.1.6", + "version": "0.1.7", "description": "Generic router for browser & non-browser based applications", "main": "./index.js", "typings": "./index.d.ts", @@ -23,7 +23,7 @@ "typescript": "^2.8.1" }, "dependencies": { - "@thi.ng/api": "^2.2.0" + "@thi.ng/api": "^2.3.0" }, "keywords": [ "declarative", diff --git a/packages/rstream-csp/CHANGELOG.md b/packages/rstream-csp/CHANGELOG.md index fe0d3714dd..85b19818d0 100644 --- a/packages/rstream-csp/CHANGELOG.md +++ b/packages/rstream-csp/CHANGELOG.md @@ -3,6 +3,46 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.1.61](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-csp@0.1.60...@thi.ng/rstream-csp@0.1.61) (2018-04-26) + + + + +**Note:** Version bump only for package @thi.ng/rstream-csp + + +## [0.1.60](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-csp@0.1.59...@thi.ng/rstream-csp@0.1.60) (2018-04-25) + + + + +**Note:** Version bump only for package @thi.ng/rstream-csp + + +## [0.1.59](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-csp@0.1.58...@thi.ng/rstream-csp@0.1.59) (2018-04-24) + + + + +**Note:** Version bump only for package @thi.ng/rstream-csp + + +## [0.1.58](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-csp@0.1.57...@thi.ng/rstream-csp@0.1.58) (2018-04-22) + + + + +**Note:** Version bump only for package @thi.ng/rstream-csp + + +## [0.1.57](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-csp@0.1.56...@thi.ng/rstream-csp@0.1.57) (2018-04-20) + + + + +**Note:** Version bump only for package @thi.ng/rstream-csp + ## [0.1.56](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-csp@0.1.55...@thi.ng/rstream-csp@0.1.56) (2018-04-19) diff --git a/packages/rstream-csp/package.json b/packages/rstream-csp/package.json index a996c8dc2d..2c20a55187 100644 --- a/packages/rstream-csp/package.json +++ b/packages/rstream-csp/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rstream-csp", - "version": "0.1.56", + "version": "0.1.61", "description": "@thi.ng/csp bridge module for @thi.ng/rstream", "main": "./index.js", "typings": "./index.d.ts", @@ -24,8 +24,8 @@ "typescript": "^2.8.1" }, "dependencies": { - "@thi.ng/csp": "^0.3.28", - "@thi.ng/rstream": "^1.4.3" + "@thi.ng/csp": "^0.3.30", + "@thi.ng/rstream": "^1.6.2" }, "keywords": [ "bridge", diff --git a/packages/rstream-dot/.npmignore b/packages/rstream-dot/.npmignore new file mode 100644 index 0000000000..d703bda97a --- /dev/null +++ b/packages/rstream-dot/.npmignore @@ -0,0 +1,10 @@ +build +coverage +dev +doc +src* +test +.nyc_output +tsconfig.json +*.tgz +*.html diff --git a/packages/rstream-dot/CHANGELOG.md b/packages/rstream-dot/CHANGELOG.md new file mode 100644 index 0000000000..f79c9baaf2 --- /dev/null +++ b/packages/rstream-dot/CHANGELOG.md @@ -0,0 +1,41 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + + +# [0.2.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-dot@0.1.2...@thi.ng/rstream-dot@0.2.0) (2018-04-26) + + +### Features + +* **rstream-dot:** add option to include stream values in diag ([d057d95](https://github.com/thi-ng/umbrella/commit/d057d95)) + + + + + +## [0.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-dot@0.1.1...@thi.ng/rstream-dot@0.1.2) (2018-04-25) + + + + +**Note:** Version bump only for package @thi.ng/rstream-dot + + +## [0.1.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-dot@0.1.0...@thi.ng/rstream-dot@0.1.1) (2018-04-25) + + + + +**Note:** Version bump only for package @thi.ng/rstream-dot + + +# 0.1.0 (2018-04-24) + + +### Features + +* **rstream-dot:** add xform edge labels, extract types to api.ts ([7ffaa61](https://github.com/thi-ng/umbrella/commit/7ffaa61)) +* **rstream-dot:** initial import [@thi](https://github.com/thi).ng/rstream-dot package ([e72478a](https://github.com/thi-ng/umbrella/commit/e72478a)) +* **rstream-dot:** support multiple roots in walk() ([704025a](https://github.com/thi-ng/umbrella/commit/704025a)) diff --git a/packages/rstream-dot/LICENSE b/packages/rstream-dot/LICENSE new file mode 100644 index 0000000000..8dada3edaf --- /dev/null +++ b/packages/rstream-dot/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/rstream-dot/README.md b/packages/rstream-dot/README.md new file mode 100644 index 0000000000..19c2434890 --- /dev/null +++ b/packages/rstream-dot/README.md @@ -0,0 +1,80 @@ +# @thi.ng/rstream-dot + +[![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/rstream-dot.svg)](https://www.npmjs.com/package/@thi.ng/rstream-dot) + +This project is part of the +[@thi.ng/umbrella](https://github.com/thi-ng/umbrella/) monorepo. + +## About + +Graphviz DOT conversion of +[@thi.ng/rstream](https://github.com/thi-ng/umbrella/tree/master/packages/rstream) +dataflow graph topologies. + +## Installation + +``` +yarn add @thi.ng/rstream-dot +``` + +## Usage examples + +```typescript +import * as rsd from "@thi.ng/rstream-dot"; + +import * as rs from "@thi.ng/rstream"; +import * as tx from "@thi.ng/transducers"; + +// create dummy dataflow +a = rs.fromIterable([1,2,3]); +b = rs.fromIterable([10, 20, 30]); +a.transform(tx.map((x) => x * 10), "x10"); +rs.merge({src: [a, b]}).subscribe(rs.trace()); + +// now capture the topology by walking the graph from its root(s) +// and convert the result to GraphViz DOT format +console.log(rsd.toDot(rsd.walk([a, b]))); + +// digraph g { +// rankdir=LR; +// node[fontname=Inconsolata,fontsize=11,style=filled,fontcolor=white]; +// edge[fontname=Inconsolata,fontsize=11]; +// s0[label="iterable-0\n(Stream)", color=blue]; +// s1[label="x10", color=black]; +// s2[label="in-iterable-0", color=black]; +// s3[label="", color=gray]; +// s4[label="streammerge-0\n(StreamMerge)", color=red]; +// s5[label="sub-1", color=black]; +// s6[label="", color=gray]; +// s7[label="iterable-1\n(Stream)", color=blue]; +// s8[label="in-iterable-1", color=black]; +// s9[label="", color=gray]; +// s5 -> s6; +// s4 -> s5; +// s3 -> s4; +// s2 -> s3; +// s0 -> s1[label="xform"]; +// s0 -> s2; +// s9 -> s4; +// s8 -> s9; +// s7 -> s8; +// } +``` + +Copy output to file `graph.dot` and then run: + +``` +dot -Tsvg -o graph.svg graph.dot +``` + +This will generate this diagram: + +![graphviz output](../../assets/rs-dot-example.svg) + +## Authors + +- Karsten Schmidt + +## License + +© 2018 Karsten Schmidt // Apache Software License 2.0 diff --git a/packages/rstream-dot/package.json b/packages/rstream-dot/package.json new file mode 100644 index 0000000000..6a8a1c5b5c --- /dev/null +++ b/packages/rstream-dot/package.json @@ -0,0 +1,46 @@ +{ + "name": "@thi.ng/rstream-dot", + "version": "0.2.0", + "description": "Graphviz DOT conversion of @thi.ng/rstream dataflow graph topologies", + "main": "./index.js", + "typings": "./index.d.ts", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn run clean && tsc --declaration", + "clean": "rm -rf *.js *.d.ts .nyc_output build coverage doc", + "cover": "yarn test && nyc report --reporter=lcov", + "doc": "node_modules/.bin/typedoc --mode modules --out doc src", + "pub": "yarn run build && yarn publish --access public", + "test": "rm -rf build && tsc -p test && nyc mocha build/test/*.js" + }, + "devDependencies": { + "@types/mocha": "^5.0.0", + "@types/node": "^9.6.1", + "mocha": "^5.0.5", + "nyc": "^11.6.0", + "typedoc": "^0.11.1", + "typescript": "^2.8.1" + }, + "dependencies": { + "@thi.ng/api": "^2.3.0", + "@thi.ng/rstream": "^1.6.2", + "@thi.ng/transducers": "^1.8.2" + }, + "keywords": [ + "conversion", + "dataflow", + "DAG", + "ES6", + "graph", + "graphviz", + "stream", + "topology", + "typescript", + "visualization" + ], + "publishConfig": { + "access": "public" + } +} diff --git a/packages/rstream-dot/src/api.ts b/packages/rstream-dot/src/api.ts new file mode 100644 index 0000000000..faf19869dd --- /dev/null +++ b/packages/rstream-dot/src/api.ts @@ -0,0 +1,35 @@ +import { ISubscribable } from "@thi.ng/rstream"; + +export interface IToDot { + toDot(opts?: Partial): string; +} + +export type NodeType = + "default" | + "noid" | + "stream" | + "streammerge" | + "streamsync"; + +export interface Node { + id: number; + label: string; + type: string; + xform: boolean; + body: string; +} + +export interface WalkState { + subs: Map, Node>; + rels: Node[][]; + id: number; +} + +export interface DotOpts { + values: boolean; + dir: string; + font: string; + fontsize: string; + text: string; + color: Record; +} diff --git a/packages/rstream-dot/src/index.ts b/packages/rstream-dot/src/index.ts new file mode 100644 index 0000000000..b4436cff65 --- /dev/null +++ b/packages/rstream-dot/src/index.ts @@ -0,0 +1,97 @@ +import { ISubscribable, StreamSync, StreamMerge, Stream } from "@thi.ng/rstream"; + +import { DotOpts, Node, WalkState } from "./api"; + +export * from "./api"; + +const getNodeType = (sub: ISubscribable) => { + if (sub instanceof Stream) { + return "Stream"; + } + if (sub instanceof StreamSync) { + return "StreamSync"; + } + if (sub instanceof StreamMerge) { + return "StreamMerge"; + } +} + +const dotNode = (s: Node, opts: DotOpts) => { + let res = `s${s.id}[label="`; + if (s.type) { + res += `${s.label}\\n(${s.type})`; + } else { + res += `${s.label}`; + } + if (s.body !== undefined) { + res += `\\n${s.body.replace(/"/g, `'`).replace(/\n/g, "\\n")}`; + } + res += `", color="`; + res += (s.type && opts.color[s.type.toLowerCase()]) || + (s.label === "" ? + opts.color.noid : + opts.color.default); + return res + `"];` +}; + +const dotEdge = (a: Node, b: Node, _: DotOpts) => { + let res = `s${a.id} -> s${b.id}`; + if (b.xform) { + res += `[label="xform"]`; + } + return res + ";" +}; + +export const walk = (subs: ISubscribable[], opts?: Partial, state?: WalkState) => { + opts || (opts = {}); + state || (state = { id: 0, subs: new Map(), rels: [] }); + for (let sub of subs) { + if (state.subs.get(sub)) return state; + const id = state.id; + const desc: Node = { + id, + label: sub.id || "", + type: getNodeType(sub), + xform: !!(sub).xform, + body: opts.values && sub.deref ? JSON.stringify(sub.deref()) : undefined + }; + state.subs.set(sub, desc); + state.id++; + const children = (sub).subs || + ((sub).__owner ? + [(sub).__owner] : + undefined); + if (children) { + walk(children, opts, state); + for (let c of children) { + state.rels.push([desc, state.subs.get(c)]); + } + } + } + return state; +} + +export const toDot = (state: WalkState, opts?: Partial) => { + opts = Object.assign({ + dir: "LR", + font: "Inconsolata", + fontsize: 11, + text: "white", + color: { + default: "black", + noid: "gray", + stream: "blue", + streammerge: "red", + streamsync: "red", + } + }, opts); + return [ + "digraph g {", + `rankdir=${opts.dir};`, + `node[fontname=${opts.font},fontsize=${opts.fontsize},style=filled,fontcolor=${opts.text}];`, + `edge[fontname=${opts.font},fontsize=${opts.fontsize}];`, + ...[...state.subs.values()].map((n) => dotNode(n, opts)), + ...state.rels.map((r) => dotEdge(r[0], r[1], opts)), + "}" + ].join("\n"); +}; \ No newline at end of file diff --git a/packages/rstream-dot/test/index.ts b/packages/rstream-dot/test/index.ts new file mode 100644 index 0000000000..f84b88f178 --- /dev/null +++ b/packages/rstream-dot/test/index.ts @@ -0,0 +1,6 @@ +// import * as assert from "assert"; +// import * as rstream-dot from "../src/index"; + +describe("rstream-dot", () => { + it("tests pending"); +}); diff --git a/packages/rstream-dot/test/tsconfig.json b/packages/rstream-dot/test/tsconfig.json new file mode 100644 index 0000000000..bcf29ace54 --- /dev/null +++ b/packages/rstream-dot/test/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "../build" + }, + "include": [ + "./**/*.ts", + "../src/**/*.ts" + ] +} diff --git a/packages/rstream-dot/tsconfig.json b/packages/rstream-dot/tsconfig.json new file mode 100644 index 0000000000..bd6481a5a6 --- /dev/null +++ b/packages/rstream-dot/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": [ + "./src/**/*.ts" + ] +} diff --git a/packages/rstream-gestures/CHANGELOG.md b/packages/rstream-gestures/CHANGELOG.md index ce95e986f7..b124cc9d3f 100644 --- a/packages/rstream-gestures/CHANGELOG.md +++ b/packages/rstream-gestures/CHANGELOG.md @@ -3,6 +3,57 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.2.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-gestures@0.2.2...@thi.ng/rstream-gestures@0.2.3) (2018-04-26) + + + + +**Note:** Version bump only for package @thi.ng/rstream-gestures + + +## [0.2.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-gestures@0.2.1...@thi.ng/rstream-gestures@0.2.2) (2018-04-25) + + + + +**Note:** Version bump only for package @thi.ng/rstream-gestures + + +## [0.2.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-gestures@0.2.0...@thi.ng/rstream-gestures@0.2.1) (2018-04-25) + + + + +**Note:** Version bump only for package @thi.ng/rstream-gestures + + +# [0.2.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-gestures@0.1.9...@thi.ng/rstream-gestures@0.2.0) (2018-04-24) + + +### Features + +* **rstream-gestures:** allows partial opts, add ID option ([3408c13](https://github.com/thi-ng/umbrella/commit/3408c13)) + + + + + +## [0.1.9](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-gestures@0.1.8...@thi.ng/rstream-gestures@0.1.9) (2018-04-22) + + + + +**Note:** Version bump only for package @thi.ng/rstream-gestures + + +## [0.1.8](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-gestures@0.1.7...@thi.ng/rstream-gestures@0.1.8) (2018-04-20) + + + + +**Note:** Version bump only for package @thi.ng/rstream-gestures + ## [0.1.7](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-gestures@0.1.6...@thi.ng/rstream-gestures@0.1.7) (2018-04-19) diff --git a/packages/rstream-gestures/README.md b/packages/rstream-gestures/README.md index 357ce2e46e..c0e88a85ca 100644 --- a/packages/rstream-gestures/README.md +++ b/packages/rstream-gestures/README.md @@ -2,6 +2,9 @@ [![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/rstream-gestures.svg)](https://www.npmjs.com/package/@thi.ng/rstream-gestures) +This project is part of the +[@thi.ng/umbrella](https://github.com/thi-ng/umbrella/) monorepo. + ## About Unified mouse, mouse wheel & single-touch event stream abstraction. @@ -27,8 +30,13 @@ yarn add @thi.ng/rstream-gestures ## Usage examples +A small, fully commented project can be found in the `/examples` folder: + +[Source](https://github.com/thi-ng/umbrella/tree/master/examples/rstream-dataflow) | +[Live version](http://demo.thi.ng/umbrella/rstream-dataflow) + ```typescript -import * as rsg from "@thi.ng/rstream-gestures"; +import { gestureStream } from "@thi.ng/rstream-gestures"; ``` ## Authors diff --git a/packages/rstream-gestures/package.json b/packages/rstream-gestures/package.json index ea3f64f743..98fc8779e1 100644 --- a/packages/rstream-gestures/package.json +++ b/packages/rstream-gestures/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rstream-gestures", - "version": "0.1.7", + "version": "0.2.3", "description": "Unified mouse, mouse wheel & single-touch event stream abstraction", "main": "./index.js", "typings": "./index.d.ts", @@ -24,11 +24,12 @@ "typescript": "^2.8.1" }, "dependencies": { - "@thi.ng/api": "^2.2.0", - "@thi.ng/rstream": "^1.4.3", - "@thi.ng/transducers": "^1.8.1" + "@thi.ng/api": "^2.3.0", + "@thi.ng/rstream": "^1.6.2", + "@thi.ng/transducers": "^1.8.2" }, "keywords": [ + "dataflow", "ES6", "events", "interaction", diff --git a/packages/rstream-gestures/src/index.ts b/packages/rstream-gestures/src/index.ts index fcf67f7c4d..35e0290503 100644 --- a/packages/rstream-gestures/src/index.ts +++ b/packages/rstream-gestures/src/index.ts @@ -1,3 +1,4 @@ +import { IID } from "@thi.ng/api/api"; import { fromEvent } from "@thi.ng/rstream/from/event"; import { merge, StreamMerge } from "@thi.ng/rstream/stream-merge"; import { map } from "@thi.ng/transducers/xform/map"; @@ -22,7 +23,7 @@ export interface GestureEvent { [1]: GestureInfo; } -export interface GestureStreamOpts { +export interface GestureStreamOpts extends IID { zoom: number; minZoom: number; maxZoom: number; @@ -53,16 +54,18 @@ export interface GestureStreamOpts { * @param el * @param opts */ -export function gestureStream(el: Element, opts?: GestureStreamOpts): StreamMerge { +export function gestureStream(el: Element, opts?: Partial): StreamMerge { let isDown = false, clickPos: number[]; opts = Object.assign({ + id: "gestures", zoom: 1, minZoom: 0.25, maxZoom: 4, }, opts); let zoom = Math.min(Math.max(opts.zoom, opts.minZoom), opts.maxZoom); return merge({ + id: opts.id, src: [ "mousedown", "mousemove", "mouseup", "touchstart", "touchmove", "touchend", "touchcancel", diff --git a/packages/rstream-graph/CHANGELOG.md b/packages/rstream-graph/CHANGELOG.md index 7ae0ab5991..94dcb9344b 100644 --- a/packages/rstream-graph/CHANGELOG.md +++ b/packages/rstream-graph/CHANGELOG.md @@ -3,6 +3,73 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [1.0.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-graph@1.0.2...@thi.ng/rstream-graph@1.0.3) (2018-04-26) + + + + +**Note:** Version bump only for package @thi.ng/rstream-graph + + +## [1.0.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-graph@1.0.1...@thi.ng/rstream-graph@1.0.2) (2018-04-25) + + + + +**Note:** Version bump only for package @thi.ng/rstream-graph + + +## [1.0.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-graph@1.0.0...@thi.ng/rstream-graph@1.0.1) (2018-04-25) + + + + +**Note:** Version bump only for package @thi.ng/rstream-graph + + +# [1.0.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-graph@0.2.6...@thi.ng/rstream-graph@1.0.0) (2018-04-24) + + +### Code Refactoring + +* **rstream-graph:** update node input specs & node factories ([d564e10](https://github.com/thi-ng/umbrella/commit/d564e10)) + + +### Features + +* **rstream-graph:** add IDs for all generated nodes, rename factory type ([0153903](https://github.com/thi-ng/umbrella/commit/0153903)) + + +### BREAKING CHANGES + +* **rstream-graph:** node inputs now specified as object, node factory function +signature change + +- input spec keys now used as input IDs +- NodeFactory now accepts object of input stream (not array) +- update node() & node1(), add support for required input IDs +- update all existing node impls + + + + + +## [0.2.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-graph@0.2.5...@thi.ng/rstream-graph@0.2.6) (2018-04-22) + + + + +**Note:** Version bump only for package @thi.ng/rstream-graph + + +## [0.2.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-graph@0.2.4...@thi.ng/rstream-graph@0.2.5) (2018-04-20) + + + + +**Note:** Version bump only for package @thi.ng/rstream-graph + ## [0.2.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-graph@0.2.3...@thi.ng/rstream-graph@0.2.4) (2018-04-19) diff --git a/packages/rstream-graph/README.md b/packages/rstream-graph/README.md index a3cc825dd3..d03a0b4c5a 100644 --- a/packages/rstream-graph/README.md +++ b/packages/rstream-graph/README.md @@ -21,6 +21,13 @@ yarn add @thi.ng/rstream-graph ## Usage examples +A small, fully commented project can be found in the `/examples` folder: + +[Source](https://github.com/thi-ng/umbrella/tree/master/examples/rstream-dataflow) | +[Live version](http://demo.thi.ng/umbrella/rstream-dataflow) + +More basic: + ```typescript import { Atom } from "@thi.ng/atom"; import * as rs from "@thi.ng/rstream"; @@ -35,19 +42,19 @@ const graph = rsg.initGraph(state, { // from values in the state atom add: { fn: rsg.add, - ins: [ - { path: "a" }, - { path: "b" } - ], + ins: { + a: { path: "a" }, + b: { path: "b" } + }, }, // this node receives values from the `add` node // and the given iterable mul: { fn: rsg.mul, - ins: [ - { stream: "add" }, - { stream: () => rs.fromIterable([10, 20, 30]) } - ], + ins: { + a: { stream: "add" }, + b: { stream: () => rs.fromIterable([10, 20, 30]) } + }, } }); @@ -65,6 +72,8 @@ state.resetIn("a", 10); // result: 360 ``` +Please documentation in the source code for further details. + ## Authors - Karsten Schmidt diff --git a/packages/rstream-graph/package.json b/packages/rstream-graph/package.json index 5c31db7910..a711f53a46 100644 --- a/packages/rstream-graph/package.json +++ b/packages/rstream-graph/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rstream-graph", - "version": "0.2.4", + "version": "1.0.3", "description": "Declarative dataflow graph construction for @thi.ng/rstream", "main": "./index.js", "typings": "./index.d.ts", @@ -24,11 +24,11 @@ "typescript": "^2.8.1" }, "dependencies": { - "@thi.ng/api": "^2.2.0", - "@thi.ng/paths": "^1.3.1", - "@thi.ng/resolve-map": "^1.0.2", - "@thi.ng/rstream": "^1.4.3", - "@thi.ng/transducers": "^1.8.1" + "@thi.ng/api": "^2.3.0", + "@thi.ng/paths": "^1.3.2", + "@thi.ng/resolve-map": "^1.0.3", + "@thi.ng/rstream": "^1.6.2", + "@thi.ng/transducers": "^1.8.2" }, "keywords": [ "compute", diff --git a/packages/rstream-graph/src/api.ts b/packages/rstream-graph/src/api.ts index a0ac852bfe..bbc10c7496 100644 --- a/packages/rstream-graph/src/api.ts +++ b/packages/rstream-graph/src/api.ts @@ -2,7 +2,7 @@ import { ISubscribable } from "@thi.ng/rstream/api"; import { Transducer } from "@thi.ng/transducers/api"; import { IObjectOf } from "@thi.ng/api/api"; -export type MultiInputNodeFn = (src: ISubscribable[]) => ISubscribable; +export type NodeFactory = (src: IObjectOf>, id: string) => ISubscribable; /** * A dataflow graph spec is simply an object where keys are node names @@ -18,12 +18,17 @@ export type GraphSpec = IObjectOf; * combine the inputs and produce values for the node's result stream. * * The `fn` function is responsible to produce such a stream construct. + * The keys used to specify inputs in the `ins` object are dictated by + * the actual node `fn` used. Most node functions with multiple inputs + * are implemented as `StreamSync` instances and the input IDs are used + * to locally rename input streams within the `StreamSync` container. * - * See `initGraph` and `nodeFromSpec` for more details (in /src/nodes.ts) + * See `initGraph` and `nodeFromSpec` for more details (in + * /src/nodes.ts) */ export interface NodeSpec { - fn: (src: ISubscribable[]) => ISubscribable; - ins: NodeInput[]; + fn: NodeFactory; + ins: IObjectOf; out?: NodeOutput; } @@ -67,11 +72,6 @@ export interface NodeSpec { * * If the optional `xform` is given, a subscription with the transducer * is added to the input and then used as input instead. - * - * If the optional `id` is specified, a dummy subscription with the ID - * is added to the input and used as input instead. This allows for - * local renaming of inputs and is needed for some ops (e.g. - * `StreamSync` based nodes). */ export interface NodeInput { id?: string; diff --git a/packages/rstream-graph/src/graph.ts b/packages/rstream-graph/src/graph.ts index 4c3b732dc2..f1ffb8b9d6 100644 --- a/packages/rstream-graph/src/graph.ts +++ b/packages/rstream-graph/src/graph.ts @@ -10,7 +10,7 @@ import { sync, StreamSync } from "@thi.ng/rstream/stream-sync"; import { Subscription } from "@thi.ng/rstream/subscription"; import { Transducer } from "@thi.ng/transducers/api"; -import { NodeSpec } from "./api"; +import { NodeSpec, NodeFactory } from "./api"; /** * Dataflow graph initialization function. Takes an object of @@ -23,8 +23,8 @@ import { NodeSpec } from "./api"; * @param nodes */ export const initGraph = (state: IAtom, nodes: IObjectOf): IObjectOf> => { - for (let k in nodes) { - (nodes)[k] = nodeFromSpec(state, nodes[k]); + for (let id in nodes) { + (nodes)[id] = nodeFromSpec(state, nodes[id], id); } return resolveMap(nodes); }; @@ -43,30 +43,31 @@ export const initGraph = (state: IAtom, nodes: IObjectOf): IObjec * * @param spec */ -const nodeFromSpec = (state: IAtom, spec: NodeSpec) => (resolve) => { - const src: ISubscribable[] = []; - for (let i of spec.ins) { +const nodeFromSpec = (state: IAtom, spec: NodeSpec, id: string) => (resolve) => { + const src: IObjectOf> = {}; + for (let id in spec.ins) { let s; + const i = spec.ins[id]; if (i.path) { s = fromView(state, i.path); } else if (i.stream) { - s = isString(i.stream) ? resolve(i.stream) : i.stream(resolve); + s = isString(i.stream) ? + resolve(i.stream) : + (i).stream(resolve); } else if (i.const) { s = fromIterableSync([i.const]); } else { illegalArgs(`invalid node spec`); } if (i.xform) { - s = s.subscribe(i.xform, i.id); - } else if (i.id) { - s = s.subscribe(null, i.id); + s = s.subscribe(i.xform, id); } - src.push(s); + src[id] = s; } - const node = spec.fn(src); + const node = spec.fn(src, id); if (spec.out) { if (isString(spec.out)) { - ((path) => node.subscribe({ next: (x) => state.resetIn(path, x) }))(spec.out); + ((path) => node.subscribe({ next: (x) => state.resetIn(path, x) }, `out-${id}`))(spec.out); } else { spec.out(node); } @@ -75,7 +76,7 @@ const nodeFromSpec = (state: IAtom, spec: NodeSpec) => (resolve) => { }; export const addNode = (graph: IObjectOf>, state: IAtom, id: string, spec: NodeSpec) => - graph[id] = nodeFromSpec(state, spec)((nodeID) => graph[nodeID]); + graph[id] = nodeFromSpec(state, spec, id)((nodeID) => graph[nodeID]); export const removeNode = (graph: IObjectOf>, id: string) => { if (graph[id]) { @@ -86,26 +87,50 @@ export const removeNode = (graph: IObjectOf>, id: string) => }; /** - * Higher order node / stream creator. Takes a transducer and optional - * arity (number of required input streams). The returned function takes - * an array of input streams and returns a new - * @thi.ng/rstream/StreamSync instance. + * Higher order node / stream creator. Takes a transducer and (optional) + * required input stream IDs. The returned function takes an object of + * input streams and returns a new `StreamSync` instance. The returned + * function will throw an error if `inputIDs` is given and the object of + * inputs does not contain all of them. * * @param xform - * @param arity + * @param inputIDs */ -export const node = (xform: Transducer, any>, arity?: number) => - (src: ISubscribable[]): StreamSync => { - if (arity !== undefined && src.length !== arity) { - illegalArgs(`wrong number of inputs: got ${src.length}, but needed ${arity}`); - } - return sync({ src, xform, reset: false }); +export const node = (xform: Transducer, any>, inputIDs?: string[]): NodeFactory => + (src: IObjectOf>, id: string): StreamSync => { + ensureInputs(src, inputIDs, id); + return sync({ src, xform, reset: false, id }); }; /** - * Syntax sugar / helper fn for nodes using only single input. + * Similar to `node()`, but optimized for nodes using only a single + * input. Uses "src" as default input ID. * * @param xform + * @param inputID + */ +export const node1 = (xform: Transducer, inputID = "src"): NodeFactory => + (src: IObjectOf>, id: string): Subscription => { + ensureInputs(src, [inputID], id); + return src[inputID].subscribe(xform, id); + }; + +/** + * Helper function to verify given object of inputs has required input IDs. + * Throws error if validation fails. + * + * @param src + * @param inputIDs + * @param nodeID */ -export const node1 = (xform: Transducer) => - ([src]: ISubscribable[]): Subscription => src.subscribe(xform); +export const ensureInputs = (src: IObjectOf>, inputIDs: string[], nodeID: string) => { + if (inputIDs !== undefined) { + const missing: string[] = []; + for (let i of inputIDs) { + !src[i] && missing.push(i); + } + if (missing.length) { + illegalArgs(`node "${nodeID}": missing input(s): ${missing.join(", ")}`); + } + } +}; diff --git a/packages/rstream-graph/src/nodes/extract.ts b/packages/rstream-graph/src/nodes/extract.ts index 7a488f74e3..09851aa46f 100644 --- a/packages/rstream-graph/src/nodes/extract.ts +++ b/packages/rstream-graph/src/nodes/extract.ts @@ -1,7 +1,7 @@ import { Path, getIn } from "@thi.ng/paths"; import { map } from "@thi.ng/transducers/xform/map"; -import { MultiInputNodeFn } from "../api"; +import { NodeFactory } from "../api"; import { node1 } from "../graph"; /** @@ -9,5 +9,5 @@ import { node1 } from "../graph"; * * Inputs: 1 */ -export const extract = (path: Path): MultiInputNodeFn => - node1(map((x) => getIn(x, path))); +export const extract = (path: Path, inputID?: string): NodeFactory => + node1(map((x) => getIn(x, path)), inputID); diff --git a/packages/rstream-graph/src/nodes/math.ts b/packages/rstream-graph/src/nodes/math.ts index 5fbd00c2cc..9409c8ca62 100644 --- a/packages/rstream-graph/src/nodes/math.ts +++ b/packages/rstream-graph/src/nodes/math.ts @@ -1,7 +1,7 @@ import { IObjectOf } from "@thi.ng/api/api"; import { map } from "@thi.ng/transducers/xform/map"; -import { MultiInputNodeFn } from "../api"; +import { NodeFactory } from "../api"; import { node } from "../graph"; /** @@ -9,7 +9,7 @@ import { node } from "../graph"; * * Inputs: any */ -export const add: MultiInputNodeFn = node( +export const add: NodeFactory = node( map((ports: IObjectOf) => { let acc = 0; let v; @@ -25,7 +25,7 @@ export const add: MultiInputNodeFn = node( * * Inputs: any */ -export const mul: MultiInputNodeFn = node( +export const mul: NodeFactory = node( map((ports: IObjectOf) => { let acc = 1; let v; @@ -41,13 +41,13 @@ export const mul: MultiInputNodeFn = node( * * Inputs: 2 */ -export const sub: MultiInputNodeFn = - node(map((ports: IObjectOf) => ports.a - ports.b), 2); +export const sub: NodeFactory = + node(map((ports: IObjectOf) => ports.a - ports.b), ["a", "b"]); /** * Division node. * * Inputs: 2 */ -export const div: MultiInputNodeFn = - node(map((ports: IObjectOf) => ports.a / ports.b), 2); +export const div: NodeFactory = + node(map((ports: IObjectOf) => ports.a / ports.b), ["a", "b"]); diff --git a/packages/rstream-log/CHANGELOG.md b/packages/rstream-log/CHANGELOG.md index a2e7fc3657..255b18e38e 100644 --- a/packages/rstream-log/CHANGELOG.md +++ b/packages/rstream-log/CHANGELOG.md @@ -3,6 +3,54 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [1.0.12](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-log@1.0.11...@thi.ng/rstream-log@1.0.12) (2018-04-26) + + + + +**Note:** Version bump only for package @thi.ng/rstream-log + + +## [1.0.11](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-log@1.0.10...@thi.ng/rstream-log@1.0.11) (2018-04-25) + + + + +**Note:** Version bump only for package @thi.ng/rstream-log + + +## [1.0.10](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-log@1.0.9...@thi.ng/rstream-log@1.0.10) (2018-04-25) + + + + +**Note:** Version bump only for package @thi.ng/rstream-log + + +## [1.0.9](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-log@1.0.8...@thi.ng/rstream-log@1.0.9) (2018-04-24) + + + + +**Note:** Version bump only for package @thi.ng/rstream-log + + +## [1.0.8](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-log@1.0.7...@thi.ng/rstream-log@1.0.8) (2018-04-22) + + + + +**Note:** Version bump only for package @thi.ng/rstream-log + + +## [1.0.7](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-log@1.0.6...@thi.ng/rstream-log@1.0.7) (2018-04-20) + + + + +**Note:** Version bump only for package @thi.ng/rstream-log + ## [1.0.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-log@1.0.5...@thi.ng/rstream-log@1.0.6) (2018-04-19) diff --git a/packages/rstream-log/package.json b/packages/rstream-log/package.json index 2a290d6efa..33fce69b75 100644 --- a/packages/rstream-log/package.json +++ b/packages/rstream-log/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rstream-log", - "version": "1.0.6", + "version": "1.0.12", "description": "Structured, multilevel & hierarchical loggers based on @thi.ng/rstream", "main": "./index.js", "typings": "./index.d.ts", @@ -24,8 +24,8 @@ "typescript": "^2.8.1" }, "dependencies": { - "@thi.ng/api": "^2.2.0", - "@thi.ng/rstream": "^1.4.3" + "@thi.ng/api": "^2.3.0", + "@thi.ng/rstream": "^1.6.2" }, "keywords": [ "ES6", @@ -34,6 +34,7 @@ "multilevel", "multiplex", "pipeline", + "stream", "transducers", "typescript" ], diff --git a/packages/rstream-query/.npmignore b/packages/rstream-query/.npmignore new file mode 100644 index 0000000000..d703bda97a --- /dev/null +++ b/packages/rstream-query/.npmignore @@ -0,0 +1,10 @@ +build +coverage +dev +doc +src* +test +.nyc_output +tsconfig.json +*.tgz +*.html diff --git a/packages/rstream-query/CHANGELOG.md b/packages/rstream-query/CHANGELOG.md new file mode 100644 index 0000000000..83b4871a47 --- /dev/null +++ b/packages/rstream-query/CHANGELOG.md @@ -0,0 +1,46 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + + +# [0.2.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-query@0.1.2...@thi.ng/rstream-query@0.2.0) (2018-04-26) + + +### Features + +* **rstream-query:** add addQueryJoin(), add type aliases, update tests ([c5f36a2](https://github.com/thi-ng/umbrella/commit/c5f36a2)) +* **rstream-query:** add path query, multi-joins, pattern query reuse ([679c4e0](https://github.com/thi-ng/umbrella/commit/679c4e0)) +* **rstream-query:** add query spec types, addQueryFromSpec(), dedupe xforms ([d093a5c](https://github.com/thi-ng/umbrella/commit/d093a5c)) +* **rstream-query:** add removeTriple(), simplify wildcard subqueries ([443ff8f](https://github.com/thi-ng/umbrella/commit/443ff8f)) +* **rstream-query:** rename TripleStore methods, use Set-like API ([9b5c58a](https://github.com/thi-ng/umbrella/commit/9b5c58a)) + + + + + +## [0.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-query@0.1.1...@thi.ng/rstream-query@0.1.2) (2018-04-25) + + + + +**Note:** Version bump only for package @thi.ng/rstream-query + + +## [0.1.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-query@0.1.0...@thi.ng/rstream-query@0.1.1) (2018-04-25) + + + + +**Note:** Version bump only for package @thi.ng/rstream-query + + +# 0.1.0 (2018-04-24) + + +### Features + +* **rstream-query:** add IToDot impl for graphviz conversion/viz ([a68eca0](https://github.com/thi-ng/umbrella/commit/a68eca0)) +* **rstream-query:** add param queries w/ variables, update addPatternQuery ([d9b845e](https://github.com/thi-ng/umbrella/commit/d9b845e)) +* **rstream-query:** initial import ([ef3903e](https://github.com/thi-ng/umbrella/commit/ef3903e)) +* **rstream-query:** update index & sub-query caching/reuse ([66ec92f](https://github.com/thi-ng/umbrella/commit/66ec92f)) diff --git a/packages/rstream-query/LICENSE b/packages/rstream-query/LICENSE new file mode 100644 index 0000000000..8dada3edaf --- /dev/null +++ b/packages/rstream-query/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/rstream-query/README.md b/packages/rstream-query/README.md new file mode 100644 index 0000000000..ee601f9df8 --- /dev/null +++ b/packages/rstream-query/README.md @@ -0,0 +1,98 @@ +# @thi.ng/rstream-query + +[![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/rstream-query.svg)](https://www.npmjs.com/package/@thi.ng/rstream-query) + +This project is part of the +[@thi.ng/umbrella](https://github.com/thi-ng/umbrella/) monorepo. + +## About + +@thi.ng/rstream based [triple +store](https://en.wikipedia.org/wiki/Triplestore) & reactive query +engine with high re-use ratio of sub-query results. Inserted triples / +facts are broadcast to multiple indexing streams and any query +subscriptions attached to them. This enables push-based, auto-updating +query results, which are changing each time upstream transformations & +filters have been triggered (due to updates to the set of triples / +facts). + +Triples are 3-tuples of `[subject, predicate, object]`. Unlike with +traditional +[RDF](https://en.wikipedia.org/wiki/Resource_Description_Framework) +triple stores, any JS data types can be used as subject, predicate or +object (though support for such must be explicitly enabled & this +feature is currently WIP). + +### Current features + +- Entirely based on stream abstractions provided by @thi.ng/rstream +- All data transformations done using @thi.ng/tranducers +- Dynamic dataflow graph construction via high-level methods +- Extensive re-use of existing sub-query results (via subscriptions) +- Auto-updating query results + +### Status + +This project is currently still in early development and intended as a +continuation of the Clojure based [thi.ng/fabric](http://thi.ng/fabric), +this time built on the streaming primitives provided by +[@thi.ng/rstream](https://github.com/thi-ng/umbrella/tree/master/packages/rstream). + +## Installation + +``` +yarn add @thi.ng/rstream-query +``` + +## Usage examples + +```typescript +import { TripleStore } from "@thi.ng/rstream-query"; + +store = new TripleStore(); +store.addTriples([ + ["london", "type", "city"], + ["london", "part-of", "uk"], + ["portland", "type", "city"], + ["portland", "part-of", "oregon"], + ["portland", "part-of", "usa"], + ["oregon", "type", "state"], + ["usa", "type", "country"], + ["uk", "type", "country"], +]); + +// find all subjects with type = country +store.addParamQuery("countries", ["?country", "type", "country"]).subscribe(trace("country results:")); + +// find all relations for subject "london" +store.addParamQuery("london", ["london", "?p", "?o"]).subscribe(trace("london results:")); + +// country results: [ { country: 'usa' }, { country: 'uk' } ] +// london results: [ { p: 'type', o: 'city' }, { p: 'part-of', o: 'uk' } ] +``` + +After setting up the above 2 queries, the dataflow topology then looks as follows: + +![graphviz output](../../assets/rs-query1.svg) + +The blue nodes are `TripleStore`-internal index stream sources, which +emit changes when new triples are added. The red nodes are basic pattern +queries, responsible for joining the individual (S)ubject, (P)redicate +and (O)bject sub-queries. The results of these are then further +transformed to bind result values to query variables, as well as the +final subscriptions to output them to the console (using `trace`, see +above). + +Btw. The diagram has been generated using +[@thi.ng/rstream-dot](https://github.com/thi-ng/umbrella/tree/master/packages/rstream-dot) +and can be recreated by calling `store.toDot()` (for the above example) + +(Many) more features forthcoming... + +## Authors + +- Karsten Schmidt + +## License + +© 2018 Karsten Schmidt // Apache Software License 2.0 diff --git a/packages/rstream-query/package.json b/packages/rstream-query/package.json new file mode 100644 index 0000000000..963b9c6c4f --- /dev/null +++ b/packages/rstream-query/package.json @@ -0,0 +1,47 @@ +{ + "name": "@thi.ng/rstream-query", + "version": "0.2.0", + "description": "@thi.ng/rstream based triple store & reactive query engine", + "main": "./index.js", + "typings": "./index.d.ts", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn run clean && tsc --declaration", + "clean": "rm -rf *.js *.d.ts .nyc_output build coverage doc", + "cover": "yarn test && nyc report --reporter=lcov", + "doc": "node_modules/.bin/typedoc --mode modules --out doc src", + "pub": "yarn run build && yarn publish --access public", + "test": "rm -rf build && tsc -p test && nyc mocha build/test/*.js" + }, + "devDependencies": { + "@types/mocha": "^5.0.0", + "@types/node": "^9.6.1", + "mocha": "^5.0.5", + "nyc": "^11.6.0", + "typedoc": "^0.11.1", + "typescript": "^2.8.1" + }, + "dependencies": { + "@thi.ng/api": "^2.3.0", + "@thi.ng/associative": "^0.4.4", + "@thi.ng/rstream": "^1.6.2", + "@thi.ng/rstream-dot": "^0.2.0", + "@thi.ng/transducers": "^1.8.2" + }, + "keywords": [ + "dataflow", + "ES6", + "query engine", + "reactive", + "stream", + "subscription", + "triples", + "triplestore", + "typescript" + ], + "publishConfig": { + "access": "public" + } +} diff --git a/packages/rstream-query/src/api.ts b/packages/rstream-query/src/api.ts new file mode 100644 index 0000000000..25c264b154 --- /dev/null +++ b/packages/rstream-query/src/api.ts @@ -0,0 +1,50 @@ +import { IObjectOf } from "@thi.ng/api/api"; +import { ISubscribable } from "@thi.ng/rstream/api"; + +export let DEBUG = false; + +export type Pattern = [any, any, any]; + +export type PathPattern = [any, any[], any]; + +export type Triple = Pattern; + +export type Triples = Set; + +export type TripleIds = Set; + +export type Solution = IObjectOf; + +export type Solutions = Set; + +export type QuerySolution = ISubscribable; + +export type BindFn = (s: Solution) => any; + +export interface Edit { + index: Set; + key: any; +} + +export interface QuerySpec { + q: SubQuerySpec[]; + select?: string[]; + order?: string; + bind?: IObjectOf; + limit?: number; +} + +export type SubQuerySpec = WhereQuerySpec | PathQuerySpec; + +export interface WhereQuerySpec { + where: Pattern[]; +} + +export interface PathQuerySpec { + path: PathPattern; +} + +export interface JoinOpts { + limit: number; + select: string[]; +} \ No newline at end of file diff --git a/packages/rstream-query/src/index.ts b/packages/rstream-query/src/index.ts new file mode 100644 index 0000000000..cde4f0f325 --- /dev/null +++ b/packages/rstream-query/src/index.ts @@ -0,0 +1,4 @@ +export * from "./api"; +export * from "./pattern"; +export * from "./qvar"; +export * from "./store"; diff --git a/packages/rstream-query/src/pattern.ts b/packages/rstream-query/src/pattern.ts new file mode 100644 index 0000000000..a9ee73d396 --- /dev/null +++ b/packages/rstream-query/src/pattern.ts @@ -0,0 +1,53 @@ +import { repeatedly } from "@thi.ng/transducers/iter/repeatedly"; + +import { Pattern, PathPattern } from "./api"; +import { isQVar, autoQVar, qvarName } from "./qvar"; + +export const patternVarCount = (p: Pattern) => { + let n = 0; + if (isQVar(p[0])) n++; + if (isQVar(p[1])) n++; + if (isQVar(p[2])) n++; + return n; +}; + +export const patternVars = ([s, p, o]: Pattern) => { + const vars = []; + isQVar(s) && vars.push(qvarName(s)); + isQVar(p) && vars.push(qvarName(p)); + isQVar(o) && vars.push(qvarName(o)); + return vars; +}; + +/** + * Takes a path triple pattern and max depth. The pattern's predicate + * must be a seq of preds. Returns a 2-elem vector [patterns vars], + * where `patterns` is a list of generated sub-query patterns with + * injected temp qvars for in between patterns and `vars` are the temp + * qvars themselves. + * + * Example: + * + * ``` + * ["?s", [p1, p2, p3], "?o"] => + * [ + * [["?s", p1, "?__q0"], ["?__q0", p2, "?__q1"], ["?__q1", p3, "?o"] ], + * ["?__q0", "?__q1"] + * ] + * ``` + * + * @param pattern + * @param maxLen + */ +export const resolvePathPattern = ([s, p, o]: PathPattern, maxLen = p.length): [Pattern[], string[]] => { + const res = []; + const avars = [...repeatedly(autoQVar, maxLen - 1)]; + for (let i = 0; i < maxLen; i++) { + res.push([s, p[i % p.length], s = avars[i]]); + } + res[res.length - 1][2] = o; + return [res, avars]; +}; + +export const sortPatterns = (patterns: Pattern[]) => + patterns.sort((a, b) => patternVarCount(a) - patternVarCount(b)); diff --git a/packages/rstream-query/src/qvar.ts b/packages/rstream-query/src/qvar.ts new file mode 100644 index 0000000000..611ff2749b --- /dev/null +++ b/packages/rstream-query/src/qvar.ts @@ -0,0 +1,58 @@ +import { isString } from "@thi.ng/checks/is-string"; + +import { Triple } from "./api"; + +const AUTO_QVAR_PREFIX = "?__q"; +let AUTO_QVAR_ID = 0; + +export const isQVar = (x: any) => + isString(x) && x.charAt(0) === "?"; + +export const isAutoQVar = (x: any) => + isString(x) && x.indexOf(AUTO_QVAR_PREFIX) == 0; + +export const autoQVar = () => + AUTO_QVAR_PREFIX + (AUTO_QVAR_ID++).toString(36); + +export const qvarName = (x: string) => + x.substr(1); + +/** +* Returns an optimized query variable solution extractor function based +* on given pattern type. `vs`, `vp`, `vo` are flags to indicate if `s`, +* `p` and/or `o` pattern items are query variables. The returned fn +* will be optimized to 1 of the 8 possible case and accepts a single +* fact to extract the respective variables from. +* +* @param vs +* @param vp +* @param vo +* @param s +* @param p +* @param o +*/ +export const qvarResolver = (vs: boolean, vp: boolean, vo: boolean, s, p, o) => { + const type = ((vs) << 2) | ((vp) << 1) | (vo); + let ss: any = vs && qvarName(s); + let pp: any = vp && qvarName(p); + let oo: any = vo && qvarName(o); + switch (type) { + case 0: + default: + return; + case 1: + return (f: Triple) => ({ [oo]: f[2] }); + case 2: + return (f: Triple) => ({ [pp]: f[1] }); + case 3: + return (f: Triple) => ({ [pp]: f[1], [oo]: f[2] }); + case 4: + return (f: Triple) => ({ [ss]: f[0] }); + case 5: + return (f: Triple) => ({ [ss]: f[0], [oo]: f[2] }); + case 6: + return (f: Triple) => ({ [ss]: f[0], [pp]: f[1] }); + case 7: + return (f: Triple) => ({ [ss]: f[0], [pp]: f[1], [oo]: f[2] }); + } +}; diff --git a/packages/rstream-query/src/store.ts b/packages/rstream-query/src/store.ts new file mode 100644 index 0000000000..96f64368b2 --- /dev/null +++ b/packages/rstream-query/src/store.ts @@ -0,0 +1,454 @@ +import { IObjectOf } from "@thi.ng/api/api"; +import { equiv } from "@thi.ng/api/equiv"; +import { illegalArgs } from "@thi.ng/api/error"; +import { intersection } from "@thi.ng/associative/intersection"; +import { join } from "@thi.ng/associative"; +import { ISubscribable } from "@thi.ng/rstream/api"; +import { Stream } from "@thi.ng/rstream/stream"; +import { sync } from "@thi.ng/rstream/stream-sync"; +import { Subscription } from "@thi.ng/rstream/subscription"; +import { toDot, walk, DotOpts, IToDot } from "@thi.ng/rstream-dot"; +import { Transducer, Reducer } from "@thi.ng/transducers/api"; +import { iterator } from "@thi.ng/transducers/iterator"; +import { transduce } from "@thi.ng/transducers/transduce"; +import { keySelector } from "@thi.ng/transducers/func/key-selector"; +import { comp } from "@thi.ng/transducers/func/comp"; +import { compR } from "@thi.ng/transducers/func/compr"; +import { assocObj } from "@thi.ng/transducers/rfn/assoc-obj"; +import { dedupe } from "@thi.ng/transducers/xform/dedupe"; +import { map } from "@thi.ng/transducers/xform/map"; +import { mapIndexed } from "@thi.ng/transducers/xform/map-indexed"; + +import { DEBUG, Edit, Triple, TripleIds, Pattern, Solutions, Triples, PathPattern, QuerySolution, QuerySpec, SubQuerySpec, WhereQuerySpec, PathQuerySpec, BindFn } from "./api"; +import { resolvePathPattern, patternVars } from "./pattern"; +import { isQVar, qvarResolver } from "./qvar"; + +export class TripleStore implements + Iterable, + IToDot { + + NEXT_ID: number; + freeIDs: number[]; + + triples: Triple[]; + indexS: Map; + indexP: Map; + indexO: Map; + indexSelections: IObjectOf>>; + queries: Map>; + allIDs: TripleIds; + + streamAll: Stream; + streamS: Stream; + streamP: Stream; + streamO: Stream; + + constructor(triples?: Iterable) { + this.triples = []; + this.freeIDs = []; + this.queries = new Map(); + this.indexS = new Map(); + this.indexP = new Map(); + this.indexO = new Map(); + this.indexSelections = { + "s": new Map(), + "p": new Map(), + "o": new Map() + }; + this.streamS = new Stream("S"); + this.streamP = new Stream("P"); + this.streamO = new Stream("O"); + this.streamAll = new Stream("ALL"); + this.allIDs = new Set(); + this.NEXT_ID = 0; + if (triples) { + this.into(triples); + } + } + + *[Symbol.iterator](): IterableIterator { + for (let t of this.triples) { + if (t) { + yield t; + } + } + } + + has(t: Triple) { + return this.get(t) !== undefined; + } + + get(t: Triple, notFound?: any) { + const id = this.findTriple( + this.indexS.get(t[0]), + this.indexP.get(t[1]), + this.indexO.get(t[2]), + t + ); + return id !== -1 ? this.triples[id] : notFound; + } + + add(t: Triple) { + let s = this.indexS.get(t[0]); + let p = this.indexP.get(t[1]); + let o = this.indexO.get(t[2]); + if (this.findTriple(s, p, o, t) !== -1) return false; + const id = this.nextID(); + const is = s || new Set(); + const ip = p || new Set(); + const io = o || new Set(); + this.triples[id] = t; + is.add(id); + ip.add(id); + io.add(id); + this.allIDs.add(id); + !s && this.indexS.set(t[0], is); + !p && this.indexP.set(t[1], ip); + !o && this.indexO.set(t[2], io); + this.broadcastTriple(is, ip, io, t); + return true; + } + + into(triples: Iterable) { + let ok = true; + for (let f of triples) { + ok = this.add(f) && ok; + } + return ok; + } + + delete(t: Triple) { + let s = this.indexS.get(t[0]); + let p = this.indexP.get(t[1]); + let o = this.indexO.get(t[2]); + const id = this.findTriple(s, p, o, t); + if (id === -1) return false; + s.delete(id); + !s.size && this.indexS.delete(t[0]); + p.delete(id); + !p.size && this.indexP.delete(t[1]); + o.delete(id); + !o.size && this.indexO.delete(t[2]); + this.allIDs.delete(id); + delete this.triples[id]; + this.freeIDs.push(id); + this.broadcastTriple(s, p, o, t); + return true; + } + + /** + * Replaces triple `a` with `b`, *iff* `a` is actually in the store. + * Else does nothing. + * + * @param a + * @param b + */ + replace(a: Triple, b: Triple) { + if (this.delete(a)) { + return this.add(b); + } + return false; + } + + /** + * Creates a new query subscription from given SPO pattern. Any + * `null` values in the pattern act as wildcard selectors and any + * other value as filter for the given triple component. E.g. the + * pattern `[null, "type", "person"]` matches all triples which have + * `"type"` as predicate and `"person"` as object. Likewise the + * pattern `[null, null, null]` matches ALL triples in the graph. + * + * By default, the returned rstream subscription emits sets of + * matched triples. If only the raw triple IDs are wanted, set + * `emitTriples` arg to `false`. + * + * @param id + * @param param1 + */ + addPatternQuery(pattern: Pattern, id?: string): ISubscribable; + addPatternQuery(pattern: Pattern, id?: string, emitTriples?: boolean): ISubscribable; + addPatternQuery(pattern: Pattern, id?: string, emitTriples = true) { + let results: ISubscribable; + const [s, p, o] = pattern; + if (s == null && p == null && o == null) { + results = this.streamAll; + } else { + const key = JSON.stringify(pattern); + if (!(results = this.queries.get(key))) { + const qs = this.getIndexSelection(this.streamS, s, "s"); + const qp = this.getIndexSelection(this.streamP, p, "p"); + const qo = this.getIndexSelection(this.streamO, o, "o"); + results = sync({ + id, + src: { s: qs, p: qp, o: qo }, + xform: comp(map(({ s, p, o }) => intersection(intersection(s, p), o)), dedupe(equiv)), + reset: true, + }); + this.queries.set(key, >results); + submit(this.indexS, qs, s); + submit(this.indexP, qp, p); + submit(this.indexO, qo, o); + } + } + return emitTriples ? + results.subscribe(asTriples(this)) : + results; + } + + /** + * Creates a new parametric query using given pattern with at least + * 1 query variable. Query vars are strings with `?` prefix. The + * rest of the string is considered the variable name. + * + * ``` + * g.addParamQuery(["?a", "friend", "?b"]); + * ``` + * + * Internally, the query pattern is translated into a basic param + * query with an additional result transformation to resolve the + * stated query variable solutions. Returns a rstream subscription + * emitting arrays of solution objects like: + * + * ``` + * [{a: "asterix", b: "obelix"}, {a: "romeo", b: "julia"}] + * ``` + * + * @param id + * @param param1 + */ + addParamQuery([s, p, o]: Pattern, id?: string): QuerySolution { + const vs = isQVar(s); + const vp = isQVar(p); + const vo = isQVar(o); + const resolve = qvarResolver(vs, vp, vo, s, p, o); + if (!resolve) { + illegalArgs("at least 1 query variable is required in pattern"); + } + id || (id = `query-${Subscription.NEXT_ID++}`); + const query = >this.addPatternQuery( + [vs ? null : s, vp ? null : p, vo ? null : o], + id + "-raw" + ); + return query.transform( + map((triples) => { + const res = new Set(); + for (let f of triples) { + res.add(resolve(f)); + } + return res; + }), + dedupe(equiv), + id + ); + } + + addParamQueries(patterns: Iterable) { + return iterator( + map((q) => this.addParamQuery(q)), + patterns + ); + } + + /** + * Converts the given path pattern into a number of sub-queries and + * return a rstream subscription of re-joined result solutions. If + * `maxLen` is given and greater than the number of actual path + * predicates, the predicates are repeated. + * + * @param path + * @param maxDepth + * @param id + */ + addPathQuery(path: PathPattern, len = path[1].length, id?: string): QuerySolution { + return this.addMultiJoin( + this.addParamQueries(resolvePathPattern(path, len)[0]), + patternVars(path), + id + ); + } + + /** + * Like `addMultiJoin()`, but optimized for only two input queries. + * Returns a rstream subscription computing the natural join of the + * given input query results. + * + * @param id + * @param a + * @param b + */ + addJoin(a: QuerySolution, b: QuerySolution, id?: string): QuerySolution { + return sync({ + id, + src: { a, b }, + xform: comp(map(({ a, b }) => join(a, b)), dedupe(equiv)), + reset: false, + }); + } + + addMultiJoin(queries: Iterable, keepVars?: Iterable, id?: string): QuerySolution { + const src = transduce( + mapIndexed((i, q) => [String(i), q]), + assocObj(), + queries + ); + let xforms: Transducer[] = [joinSolutions(Object.keys(src).length), dedupe(equiv)]; + keepVars && (xforms.push(filterSolutions(keepVars))); + return sync({ id, src, xform: >comp.apply(null, xforms), reset: false }); + } + + /** + * Compiles given query spec into a number of sub-queries and result + * transformations. Returns rstream subscription of final result + * sets. See `QuerySpec` docs for further details. + * + * @param spec + */ + addQueryFromSpec(spec: QuerySpec): QuerySolution { + let query: QuerySolution; + let curr: QuerySolution; + for (let q of spec.q) { + if (isWhereQuery(q)) { + curr = this.addMultiJoin(this.addParamQueries(q.where)); + query && (curr = this.addJoin(query, curr)); + } else if (isPathQuery(q)) { + curr = this.addPathQuery(q.path); + query && (curr = this.addJoin(query, curr)); + } + query = curr; + } + let xforms: Transducer[] = []; + if (spec.limit) { + xforms.push(limitSolutions(spec.limit)); + } + if (spec.bind) { + xforms.push(bindVars(spec.bind)); + } + if (spec.select) { + xforms.push(filterSolutions(spec.select)); + } + if (xforms.length) { + query = >query.subscribe(comp.apply(null, xforms)); + } + return query; + } + + toDot(opts?: Partial) { + return toDot(walk([this.streamS, this.streamP, this.streamO, this.streamAll], opts), opts); + } + + protected nextID() { + if (this.freeIDs.length) { + return this.freeIDs.pop(); + } + return this.NEXT_ID++; + } + + private broadcastTriple(s: TripleIds, p: TripleIds, o: TripleIds, t: Triple) { + this.streamAll.next(this.allIDs); + this.streamS.next({ index: s, key: t[0] }); + this.streamP.next({ index: p, key: t[1] }); + this.streamO.next({ index: o, key: t[2] }); + } + + protected findTriple(s: TripleIds, p: TripleIds, o: TripleIds, f: Triple) { + if (s && p && o) { + const triples = this.triples; + const index = s.size < p.size ? + s.size < o.size ? s : p.size < o.size ? p : o : + p.size < o.size ? p : s.size < o.size ? s : o; + for (let id of index) { + if (equiv(triples[id], f)) { + return id; + } + } + } + return -1; + } + + protected getIndexSelection(stream: Stream, key: any, id: string): Subscription { + if (key == null) { + return this.streamAll; + } + let sel = this.indexSelections[id].get(key); + if (!sel) { + this.indexSelections[id].set(key, sel = stream.transform(indexSel(key), id)); + } + return sel; + } +} + +const submit = (index: Map, stream: Subscription, key: any) => { + if (key != null) { + const ids = index.get(key); + ids && stream.next({ index: ids, key }); + } +}; + +const indexSel = (key: any): Transducer => + (rfn: Reducer) => { + const r = rfn[2]; + return compR(rfn, + (acc, e) => { + DEBUG && console.log("index sel", e.key, key); + if (equiv(e.key, key)) { + return r(acc, e.index); + } + return acc; + } + ); + }; + +const asTriples = (graph: TripleStore) => + map>( + (ids) => { + const res = new Set(); + for (let id of ids) res.add(graph.triples[id]); + return res; + }); + +const joinSolutions = (n: number) => + map, Solutions>((src) => { + let res: Solutions = src[0]; + for (let i = 1; i < n && res.size; i++) { + res = join(res, src[i]); + } + return res; + }); + +const filterSolutions = (qvars: Iterable) => { + const filterVars = keySelector([...qvars]); + return map((sol: Solutions) => { + const res: Solutions = new Set(); + for (let s of sol) { + res.add(filterVars(s)); + } + return res; + }); +}; + +const limitSolutions = (n: number) => + map((sol: Solutions) => { + if (sol.size <= n) { + return sol; + } + const res: Solutions = new Set(); + let m = n; + for (let s of sol) { + res.add(s); + if (--m <= 0) break; + } + return res; + }); + +const bindVars = (bindings: IObjectOf) => + map((sol: Solutions) => { + for (let s of sol) { + for (let b in bindings) { + s[b] = bindings[b](s); + } + } + return sol; + }); + +const isWhereQuery = (q: SubQuerySpec): q is WhereQuerySpec => !!(q).where; +const isPathQuery = (q: SubQuerySpec): q is PathQuerySpec => !!(q).path; diff --git a/packages/rstream-query/test/index.ts b/packages/rstream-query/test/index.ts new file mode 100644 index 0000000000..27062ebc4a --- /dev/null +++ b/packages/rstream-query/test/index.ts @@ -0,0 +1,121 @@ +import * as assert from "assert"; +import { TripleStore, Triple } from "../src/index"; + +describe("rstream-query", () => { + + const triples: Triple[] = [ + ["a", "type", "foo"], // 0 + ["b", "type", "bar"], // 1 + ["c", "type", "baz"], // 2 + ["a", "value", 0], // 3 + ["b", "value", 1], // 4 + ["c", "friend", "a"], // 5 + ]; + + let store: TripleStore; + + beforeEach(() => { + store = new TripleStore(triples); + }); + + it("pattern query (S)", () => { + const res = []; + store.addPatternQuery(["a", null, null], "q", false).subscribe({ next: (r) => res.push(r) }); + assert.deepEqual(res, [new Set([0, 3])]); + }); + + it("pattern query (P)", () => { + const res = []; + store.addPatternQuery([null, "type", null], "q", false).subscribe({ next: (r) => res.push(r) }); + assert.deepEqual(res, [new Set([0, 1, 2])]); + }); + + it("pattern query (O)", () => { + const res = []; + store.addPatternQuery([null, null, "a"], "q", false).subscribe({ next: (r) => res.push(r) }); + assert.deepEqual(res, [new Set([5])]); + }); + + it("pattern query (SP)", () => { + const res = []; + store.addPatternQuery(["a", "value", null], "q", false).subscribe({ next: (r) => res.push(r) }); + assert.deepEqual(res, [new Set([3])]); + }); + + it("pattern query (PO)", () => { + const res = []; + store.addPatternQuery([null, "value", 0], "q", false).subscribe({ next: (r) => res.push(r) }); + assert.deepEqual(res, [new Set([3])]); + }); + + it("pattern query (SO)", () => { + const res = []; + store.addPatternQuery(["b", null, "bar"], "q", false).subscribe({ next: (r) => res.push(r) }); + assert.deepEqual(res, [new Set([1])]); + }); + + it("pattern query (SPO)", () => { + const res = []; + store.addPatternQuery(["c", "type", "baz"], "q", false).subscribe({ next: (r) => res.push(r) }); + assert.deepEqual(res, [new Set([2])]); + }); + + it("pattern query (all)", () => { + const res = []; + store.addPatternQuery([null, null, null], "q", false).subscribe({ next: (r) => res.push(r) }); + assert.deepEqual(res, [new Set([0, 1, 2, 3, 4, 5])]); + }); + + it("param query (S)", () => { + const res = []; + store.addParamQuery(["a", "?p", "?o"]).subscribe({ next: (r) => res.push(r) }); + assert.deepEqual(res, [new Set([{ p: "type", o: "foo" }, { p: "value", o: 0 }])]); + }); + + it("param query (P)", () => { + const res = []; + store.addParamQuery(["?s", "type", "?o"]).subscribe({ next: (r) => res.push(r) }); + assert.deepEqual(res, [new Set([{ s: "a", o: "foo" }, { s: "b", o: "bar" }, { s: "c", o: "baz" }])]); + }); + + it("param query (O)", () => { + const res = []; + store.addParamQuery(["?s", "?p", "a"]).subscribe({ next: (r) => res.push(r) }); + assert.deepEqual(res, [new Set([{ s: "c", p: "friend" }])]); + }); + + it("param query (SP)", () => { + const res = []; + store.addParamQuery(["a", "value", "?o"]).subscribe({ next: (r) => res.push(r) }); + assert.deepEqual(res, [new Set([{ o: 0 }])]); + }); + + it("param query (PO)", () => { + const res = []; + store.addParamQuery(["?s", "value", 0]).subscribe({ next: (r) => res.push(r) }); + assert.deepEqual(res, [new Set([{ s: "a" }])]); + }); + + it("param query (SO)", () => { + const res = []; + store.addParamQuery(["b", "?p", "bar"]).subscribe({ next: (r) => res.push(r) }); + assert.deepEqual(res, [new Set([{ p: "type" }])]); + }); + + it("param query (SPO)", () => { + assert.throws(() => store.addParamQuery(["c", "type", "baz"])); + }); + + it("param query (all)", () => { + const res = []; + store.addParamQuery(["?s", "?p", "?o"]).subscribe({ next: (r) => res.push(r) }); + assert.deepEqual(res, [new Set([ + { s: "a", p: "type", o: "foo" }, + { s: "b", p: "type", o: "bar" }, + { s: "c", p: "type", o: "baz" }, + { s: "a", p: "value", o: 0 }, + { s: "b", p: "value", o: 1 }, + { s: "c", p: "friend", o: "a" }, + ])]); + }); +}); diff --git a/packages/rstream-query/test/tsconfig.json b/packages/rstream-query/test/tsconfig.json new file mode 100644 index 0000000000..bcf29ace54 --- /dev/null +++ b/packages/rstream-query/test/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "../build" + }, + "include": [ + "./**/*.ts", + "../src/**/*.ts" + ] +} diff --git a/packages/rstream-query/tsconfig.json b/packages/rstream-query/tsconfig.json new file mode 100644 index 0000000000..bd6481a5a6 --- /dev/null +++ b/packages/rstream-query/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": [ + "./src/**/*.ts" + ] +} diff --git a/packages/rstream/CHANGELOG.md b/packages/rstream/CHANGELOG.md index 2df23ad207..34f80bf6d1 100644 --- a/packages/rstream/CHANGELOG.md +++ b/packages/rstream/CHANGELOG.md @@ -3,6 +3,61 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [1.6.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream@1.6.1...@thi.ng/rstream@1.6.2) (2018-04-26) + + + + +**Note:** Version bump only for package @thi.ng/rstream + + +## [1.6.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream@1.6.0...@thi.ng/rstream@1.6.1) (2018-04-25) + + +### Bug Fixes + +* **rstream:** minor fix StreamSync.addAll() ([cc286e1](https://github.com/thi-ng/umbrella/commit/cc286e1)) + + + + + +# [1.6.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream@1.5.1...@thi.ng/rstream@1.6.0) (2018-04-24) + + +### Features + +* **rstream:** add owner meta data & IDs for merge/sync inputs ([33f55b3](https://github.com/thi-ng/umbrella/commit/33f55b3)) + + +### Performance Improvements + +* **rstream:** support (re)named StreamSync inputs ([b392817](https://github.com/thi-ng/umbrella/commit/b392817)) + + + + + +## [1.5.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream@1.5.0...@thi.ng/rstream@1.5.1) (2018-04-22) + + + + +**Note:** Version bump only for package @thi.ng/rstream + + +# [1.5.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream@1.4.3...@thi.ng/rstream@1.5.0) (2018-04-20) + + +### Features + +* **rstream:** add PubSub, add ISubscribableSubscriber, remove cache() ([27a098d](https://github.com/thi-ng/umbrella/commit/27a098d)) +* **rstream:** allow arbitrary PubSub topic vals, add [@thi](https://github.com/thi).ng/associative dep ([ba10907](https://github.com/thi-ng/umbrella/commit/ba10907)) + + + + ## [1.4.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream@1.4.2...@thi.ng/rstream@1.4.3) (2018-04-19) diff --git a/packages/rstream/README.md b/packages/rstream/README.md index c23f0dff67..684bd63164 100644 --- a/packages/rstream/README.md +++ b/packages/rstream/README.md @@ -48,9 +48,13 @@ are provided too: - [merge](https://github.com/thi-ng/umbrella/tree/master/packages/rstream/src/stream-merge.ts) - unsorted merge from multiple inputs (dynamic add/remove) - [sync](https://github.com/thi-ng/umbrella/tree/master/packages/rstream/src/stream-sync.ts) - synchronized merge and labeled tuple objects -### Useful subscription ops +### Stream splitting - [bisect](https://github.com/thi-ng/umbrella/tree/master/packages/rstream/src/subs/bisect.ts) - split via predicate +- [pubsub](https://github.com/thi-ng/umbrella/tree/master/packages/rstream/src/pubsub.ts) - topic based splitting + +### Useful subscription ops + - [postWorker](https://github.com/thi-ng/umbrella/tree/master/packages/rstream/src/subs/post-worker.ts) - send values to workers (incl. optional worker instantiation) - [resolve](https://github.com/thi-ng/umbrella/tree/master/packages/rstream/src/subs/resolve.ts) - resolve on-stream promises - [sidechainPartition](https://github.com/thi-ng/umbrella/tree/master/packages/rstream/src/subs/sidechain-partition.ts) - emits chunks from source, controlled by sidechain stream @@ -67,9 +71,11 @@ are provided too: ## Supporting packages - [@thi.ng/rstream-csp](https://github.com/thi-ng/umbrella/tree/master/packages/rstream-csp) - CSP channel-to-stream adapter +- [@thi.ng/rstream-dot](https://github.com/thi-ng/umbrella/tree/master/packages/rstream-dot) - GraphViz DOT conversion of rstream dataflow graph topologies - [@thi.ng/rstream-gestures](https://github.com/thi-ng/umbrella/tree/master/packages/rstream-gestures) - unified mouse, single-touch & wheel event stream - [@thi.ng/rstream-graph](https://github.com/thi-ng/umbrella/tree/master/packages/rstream-graph) - declarative dataflow graph construction - [@thi.ng/rstream-log](https://github.com/thi-ng/umbrella/tree/master/packages/rstream-log) - extensible multi-level, multi-target structured logging +- [@thi.ng/rstream-query](https://github.com/thi-ng/umbrella/tree/master/packages/rstream-query) - triple store & reactive query engine ## Conceptual differences to RxJS @@ -115,6 +121,8 @@ directory of this repo: ### Declarative dataflow graph +This demo is utilizing the [@thi.ng/rstream-graph](https://github.com/thi-ng/umbrella/tree/master/packages/rstream-graph) support package. + [Source](https://github.com/thi-ng/umbrella/tree/master/examples/rstream-dataflow) | [Live version](http://demo.thi.ng/umbrella/rstream-dataflow) @@ -164,8 +172,12 @@ setTimeout(()=> raf.done(), 10000); ### Stream merging +See +[@thi.ng/rstream-gestures](https://github.com/thi-ng/umbrella/tree/master/packages/rstream-graph) +for a related, but more highlevel approach. + ```typescript -new rs.StreamMerge({ +rs.merge({ src: [ rs.fromEvent(document, "mousemove"), rs.fromEvent(document, "mousedown"), @@ -183,6 +195,23 @@ new rs.StreamMerge({ // ... ``` +### PubSub + +```ts +import { mapIndexed } from "@thi.ng/transducers"; + +pub = rs.pubsub({ topic: (x) => x[0], xform: mapIndexed((i,x) => [x, i]) }); +pub.subscribeTopic("e", rs.trace("topic E:")); +pub.subscribeTopic("o", rs.trace("topic O:")); + +rs.fromIterable("hello world").subscribe(pub); +// topic E: [ 'e', 1 ] +// topic O: [ 'o', 4 ] +// topic O: [ 'o', 7 ] +// topic E: done +// topic O: done +``` + ### Dataflow graph example This example uses [synchronized stream diff --git a/packages/rstream/package.json b/packages/rstream/package.json index 0b0692f56f..fab7837910 100644 --- a/packages/rstream/package.json +++ b/packages/rstream/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rstream", - "version": "1.4.3", + "version": "1.6.2", "description": "Reactive multi-tap streams, dataflow & transformation pipeline constructs", "main": "./index.js", "typings": "./index.d.ts", @@ -24,9 +24,10 @@ "typescript": "^2.8.1" }, "dependencies": { - "@thi.ng/api": "^2.2.0", - "@thi.ng/atom": "^1.3.3", - "@thi.ng/transducers": "^1.8.1" + "@thi.ng/api": "^2.3.0", + "@thi.ng/associative": "^0.4.4", + "@thi.ng/atom": "^1.3.4", + "@thi.ng/transducers": "^1.8.2" }, "keywords": [ "datastructure", diff --git a/packages/rstream/src/api.ts b/packages/rstream/src/api.ts index 6f4c7469e8..f5093a9f49 100644 --- a/packages/rstream/src/api.ts +++ b/packages/rstream/src/api.ts @@ -1,4 +1,4 @@ -import { IID } from "@thi.ng/api/api"; +import { IID, IDeref } from "@thi.ng/api/api"; import { Transducer } from "@thi.ng/transducers/api"; import { Subscription } from "./subscription"; @@ -21,7 +21,10 @@ export interface ISubscriber { [id: string]: any; } -export interface ISubscribable extends IID { +export interface ISubscribable extends + IDeref, + IID { + subscribe(xform: Transducer, id?: string): Subscription; subscribe(sub: Partial>, xform: Transducer, id?: string): Subscription; subscribe(sub: Partial>, id?: string): Subscription; @@ -29,6 +32,11 @@ export interface ISubscribable extends IID { getState(): State; } +export interface ISubscribableSubscriber extends + ISubscriber, + ISubscribable { +} + export interface IStream extends ISubscriber { cancel: StreamCancel; } diff --git a/packages/rstream/src/index.ts b/packages/rstream/src/index.ts index c93e5a0f17..a044d1a02d 100644 --- a/packages/rstream/src/index.ts +++ b/packages/rstream/src/index.ts @@ -1,4 +1,5 @@ export * from "./api"; +export * from "./pubsub"; export * from "./stream"; export * from "./stream-merge"; export * from "./stream-sync"; @@ -15,7 +16,6 @@ export * from "./from/view"; export * from "./from/worker"; export * from "./subs/bisect"; -export * from "./subs/cache"; export * from "./subs/post-worker"; export * from "./subs/resolve"; export * from "./subs/sidechain-partition"; diff --git a/packages/rstream/src/pubsub.ts b/packages/rstream/src/pubsub.ts new file mode 100644 index 0000000000..e3334397c7 --- /dev/null +++ b/packages/rstream/src/pubsub.ts @@ -0,0 +1,139 @@ +import { Predicate2 } from "@thi.ng/api/api"; +import { unsupported } from "@thi.ng/api/error"; +import { EquivMap } from "@thi.ng/associative/equiv-map"; +import { Transducer } from "@thi.ng/transducers/api"; + +import { DEBUG, ISubscriber } from "./api"; +import { Subscription } from "./subscription"; + +export interface PubSubOpts { + /** + * Topic function. Incoming values will be routed to topic + * subscriptions using this function's return value. + */ + topic: (x: B) => any; + /** + * Optional transformer for incoming values. If given, `xform` will + * be applied first and the transformed value passed to the + * `topic` fn. + */ + xform?: Transducer; + /** + * Equivalence check for topic values. Should return truthy result + * if given topics are considered equal. + */ + equiv?: Predicate2; + /** + * Optional subscription ID for the PubSub instance. + */ + id?: string; +} + +/** + * Topic based stream splitter. Applies `topic` function to each + * received value and only forwards it to child subscriptions for + * returned topic. The actual topic (return value from `topic` fn) can + * be of any type, apart from `undefined`. Complex topics (e.g objects / + * arrays) are allowed and they're matched with registered topics using + * @thi.ng/api/equiv by default (but customizable via `equiv` option). + * Each topic can have any number of subscribers. + * + * If a transducer is specified for the `PubSub`, it is always applied + * prior to passing the input to the topic function. I.e. in this case + * the topic function will receive the transformed inputs. + * + * PubSub supports dynamic topic subscriptions and unsubscriptions via + * `subscribeTopic()` and `unsubscribeTopic()`. However, the standard + * `subscribe()` / `unsubscribe()` methods are NOT supported (since + * meaningless) and will throw an error! `unsubscribe()` can only be + * called WITHOUT argument to unsubscribe the entire `PubSub` instance + * (incl. all topic subscriptions) from the parent stream. + */ +export class PubSub extends Subscription { + + topicfn: (x: B) => PropertyKey; + topics: EquivMap>; + + constructor(opts?: PubSubOpts) { + opts = opts || >{}; + super(null, opts.xform, null, opts.id || `pubsub-${Subscription.NEXT_ID++}`); + this.topicfn = opts.topic; + this.topics = new EquivMap>(null, { equiv: opts.equiv }); + } + + /** + * Unsupported. Use `subscribeTopic()` instead. + * + * @param _ + */ + subscribe(..._: any[]): Subscription { + unsupported(`use subscribeTopic() instead`); + return null; + } + + /** + * Unsupported. Use `subscribeTopic()` instead. + * + * @param _ + */ + transform(..._: any[]): Subscription { + unsupported(`use subscribeTopic() instead`); + return null; + } + + subscribeTopic(topicID: any, sub: Partial>, id?: string): Subscription; + subscribeTopic(topicID: any, tx: Transducer, id?: string): Subscription; + subscribeTopic(topicID: any, sub: any, id?: string): Subscription { + let t = this.topics.get(topicID); + if (!t) { + this.topics.set(topicID, t = new Subscription()); + } + return t.subscribe(sub, id); + } + + unsubscribeTopic(topicID: any, sub: Subscription) { + let t = this.topics.get(topicID); + if (t) { + return t.unsubscribe(sub); + } + return false; + } + + unsubscribe(sub?: Subscription) { + if (!sub) { + for (let t of this.topics.values()) { + t.unsubscribe(); + } + this.topics.clear(); + return super.unsubscribe(); + } + unsupported(); + return false; + } + + done() { + for (let t of this.topics.values()) { + t.done(); + } + super.done(); + } + + protected dispatch(x: B) { + DEBUG && console.log(this.id, "dispatch", x); + const t = this.topicfn(x); + if (t !== undefined) { + const sub = this.topics.get(t); + if (sub) { + try { + sub.next && sub.next(x); + } catch (e) { + sub.error ? sub.error(e) : this.error(e); + } + } + } + } +} + +export function pubsub(opts: PubSubOpts) { + return new PubSub(opts); +} \ No newline at end of file diff --git a/packages/rstream/src/stream-merge.ts b/packages/rstream/src/stream-merge.ts index be3e18b048..e9b99b8af4 100644 --- a/packages/rstream/src/stream-merge.ts +++ b/packages/rstream/src/stream-merge.ts @@ -36,16 +36,21 @@ export class StreamMerge extends Subscription { this.ensureState(); this.sources.set( src, - src.subscribe({ - next: (x) => { - if (x instanceof Subscription) { - this.add(x); - } else { - this.next(x); - } + src.subscribe( + { + next: (x) => { + if (x instanceof Subscription) { + this.add(x); + } else { + this.next(x); + } + }, + done: () => this.markDone(src), + __owner: this }, - done: () => this.markDone(src) - })); + `in-${src.id}` + ) + ); } addAll(src: ISubscribable[]) { diff --git a/packages/rstream/src/stream-sync.ts b/packages/rstream/src/stream-sync.ts index 9df4640e66..4a91df2855 100644 --- a/packages/rstream/src/stream-sync.ts +++ b/packages/rstream/src/stream-sync.ts @@ -1,4 +1,5 @@ import { IObjectOf, IID } from "@thi.ng/api/api"; +import { isPlainObject } from "@thi.ng/checks/is-plain-object"; import { Transducer } from "@thi.ng/transducers/api"; import { comp } from "@thi.ng/transducers/func/comp"; import { labeled } from "@thi.ng/transducers/xform/labeled"; @@ -9,7 +10,7 @@ import { ISubscribable, State } from "./api"; import { Subscription } from "./subscription"; export interface StreamSyncOpts extends IID { - src: ISubscribable[]; + src: ISubscribable[] | IObjectOf>; xform: Transducer, B>; reset: boolean; all: boolean; @@ -58,7 +59,26 @@ export interface StreamSyncOpts extends IID { */ export class StreamSync extends Subscription { + /** + * maps actual inputs to their virtual input subs + */ sources: Map, Subscription>; + /** + * maps real source IDs to their actual input + */ + idSources: Map>; + /** + * maps (potentially aliased) input IDs to their actual src.id + */ + realSourceIDs: Map; + /** + * maps real src.id to (potentially aliased) input IDs + */ + invRealSourceIDs: Map; + /** + * set of (potentially aliased) input IDs + * these IDs are used to label inputs in result tuple + */ sourceIDs: Set; autoClose: boolean; @@ -73,6 +93,9 @@ export class StreamSync extends Subscription { } super(null, xform, null, opts.id || `streamsync-${Subscription.NEXT_ID++}`); this.sources = new Map(); + this.realSourceIDs = new Map(); + this.invRealSourceIDs = new Map(); + this.idSources = new Map(); this.sourceIDs = srcIDs; this.autoClose = opts.close !== false; if (opts.src) { @@ -80,41 +103,60 @@ export class StreamSync extends Subscription { } } - add(src: ISubscribable) { + add(src: ISubscribable, id?: string) { + id || (id = src.id); this.ensureState(); - this.sourceIDs.add(src.id); + this.sourceIDs.add(id); + this.realSourceIDs.set(id, src.id); + this.invRealSourceIDs.set(src.id, id); + this.idSources.set(src.id, src); this.sources.set( src, src.subscribe( { next: (x) => { - if (x instanceof Subscription) { - this.add(x); + if (x[1] instanceof Subscription) { + this.add(x[1]); } else { this.next(x); } }, - done: () => this.markDone(src) + done: () => this.markDone(src), + __owner: this }, - labeled(src.id) + labeled(id), + `in-${id}` ) ); } - addAll(src: ISubscribable[]) { - // pre-add all source ids for partitionSync - for (let s of src) { - this.sourceIDs.add(s.id); - } - for (let s of src) { - this.add(s); + addAll(src: ISubscribable[] | IObjectOf>) { + if (isPlainObject(src)) { + // pre-add all source ids for partitionSync + for (let id in src) { + this.sourceIDs.add(id); + } + for (let id in src) { + this.add(src[id], id); + } + } else { + // pre-add all source ids for partitionSync + for (let s of []>src) { + this.sourceIDs.add(s.id); + } + for (let s of []>src) { + this.add(s); + } } } remove(src: ISubscribable) { const sub = this.sources.get(src); if (sub) { - this.sourceIDs.delete(src.id); + const id = this.invRealSourceIDs.get(src.id); + this.sourceIDs.delete(id); + this.realSourceIDs.delete(id); + this.idSources.delete(src.id); this.sources.delete(src); sub.unsubscribe(); return true; @@ -123,10 +165,9 @@ export class StreamSync extends Subscription { } removeID(id: string) { - for (let s of this.sources) { - if (s[0].id === id) { - return this.remove(s[0]); - } + const src = this.idSources.get(this.realSourceIDs.get(id)); + if (src) { + return this.remove(src); } return false; } @@ -134,7 +175,7 @@ export class StreamSync extends Subscription { removeAll(src: ISubscribable[]) { // pre-remove all source ids for partitionSync for (let s of src) { - this.sourceIDs.delete(s.id); + this.sourceIDs.delete(this.invRealSourceIDs.get(s.id)); } let ok = true; for (let s of src) { @@ -159,6 +200,9 @@ export class StreamSync extends Subscription { this.state = State.DONE; this.sources.clear(); this.sourceIDs.clear(); + this.realSourceIDs.clear(); + this.invRealSourceIDs.clear(); + this.idSources.clear(); } return super.unsubscribe(sub); } diff --git a/packages/rstream/src/subs/cache.ts b/packages/rstream/src/subs/cache.ts deleted file mode 100644 index c771871f89..0000000000 --- a/packages/rstream/src/subs/cache.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { IDeref } from "@thi.ng/api/api"; -import { Transducer } from "@thi.ng/transducers/api"; - -import { Subscription } from "../subscription"; - -/** - * Deprecated since v1.1.0. Subscriptions now store last received value - * and new subs will receive the last value stored in parent as their - * first value (if there is one). - */ -export class Cache extends Subscription implements - IDeref { - - value: T; - - constructor(xf?: Transducer, id?: string) { - super(null, xf, null, id || `cache-${Subscription.NEXT_ID++}`); - } - - deref(): T { - return this.value; - } - - dispatch(x: T) { - this.value = x; - super.dispatch(x); - } -} - -export function cache(xf?: Transducer, id?: string) { - return new Cache(xf, id); -} diff --git a/packages/rstream/test/bisect.ts b/packages/rstream/test/bisect.ts index b5c99866c5..1dcb03ecb0 100644 --- a/packages/rstream/test/bisect.ts +++ b/packages/rstream/test/bisect.ts @@ -2,7 +2,6 @@ import * as tx from "@thi.ng/transducers"; import * as assert from "assert"; import * as rs from "../src/index"; -import { Subscription } from "../src/index"; describe("bisect", () => { let src: rs.Stream; @@ -29,11 +28,11 @@ describe("bisect", () => { it("subs", (done) => { const odds = [], evens = []; - const subo = new Subscription( + const subo = new rs.Subscription( { next(x) { odds.push(x) }, done() { doneCount++; } }, tx.map(x => x * 10) ); - const sube = new Subscription( + const sube = new rs.Subscription( { next(x) { evens.push(x) }, done() { doneCount++; } }, tx.map(x => x * 100) ); diff --git a/packages/rstream/test/pubsub.ts b/packages/rstream/test/pubsub.ts new file mode 100644 index 0000000000..dc540d98a3 --- /dev/null +++ b/packages/rstream/test/pubsub.ts @@ -0,0 +1,68 @@ +import { EquivMap } from "@thi.ng/associative"; +import * as tx from "@thi.ng/transducers"; +import * as assert from "assert"; + +import * as rs from "../src/index"; + +describe("PubSub", () => { + let pub: rs.PubSub; + + it("simple", () => { + const acc = { a: [], b: [] }; + const collect = { next: (x) => acc[x].push(x) }; + pub = rs.pubsub({ topic: (x) => x }); + const a = pub.subscribeTopic("a", collect); + const b = pub.subscribeTopic("b", collect); + rs.fromIterableSync("abcbd").subscribe(pub); + assert.deepEqual(acc, { a: ["a"], b: ["b", "b"] }); + assert.equal(pub.getState(), rs.State.DONE); + assert.equal(a.getState(), rs.State.DONE); + assert.equal(b.getState(), rs.State.DONE); + }); + + it("complex keys", () => { + const acc = new EquivMap(); + const collect = { next: (x) => { let v = acc.get(x); v ? v.push(x) : acc.set(x, [x]) } }; + pub = rs.pubsub({ topic: (x) => x }); + pub.subscribeTopic(["a", 0], collect); + pub.subscribeTopic(["a", 1], collect); + pub.subscribeTopic(["b", 2], collect); + rs.fromIterableSync([["a", 0], ["a", 1], ["b", 2], ["a", 0], ["c", 3]]).subscribe(pub); + assert.deepEqual([...acc], [ + [["a", 0], [["a", 0], ["a", 0]]], + [["a", 1], [["a", 1]]], + [["b", 2], [["b", 2]]], + ]); + assert.equal(pub.getState(), rs.State.DONE); + }); + + it("transducer", () => { + const acc = { a: [], b: [], c: [], d: [] }; + const collect = { next: (x) => acc[x[0]].push(x) }; + pub = rs.pubsub({ + topic: (x) => x[0], + xform: tx.mapIndexed((i, x) => [x, i]) + }); + pub.subscribeTopic("a", collect); + pub.subscribeTopic("b", collect); + rs.fromIterableSync("abcbd").subscribe(pub); + assert.deepEqual(acc, { a: [["a", 0]], b: [["b", 1], ["b", 3]], c: [], d: [] }); + assert.equal(pub.getState(), rs.State.DONE); + }); + + it("unsubTopic", (done) => { + const acc = { a: [], b: [] }; + const collect = { next: (x) => acc[x].push(x) }; + pub = rs.pubsub({ topic: (x) => x }); + pub.subscribeTopic("a", collect); + const b = pub.subscribeTopic("b", collect); + rs.fromIterable("abcbd", 5).subscribe(pub); + setTimeout(() => pub.unsubscribeTopic("b", b), 15); + setTimeout(() => { + assert.deepEqual(acc, { a: ["a"], b: ["b"] }); + assert.equal(pub.getState(), rs.State.DONE); + done(); + }, 40); + }); + +}); \ No newline at end of file diff --git a/packages/transducers/CHANGELOG.md b/packages/transducers/CHANGELOG.md index bff0b4d1e4..fba0cd2e4e 100644 --- a/packages/transducers/CHANGELOG.md +++ b/packages/transducers/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [1.8.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/transducers@1.8.1...@thi.ng/transducers@1.8.2) (2018-04-26) + + + + +**Note:** Version bump only for package @thi.ng/transducers + ## [1.8.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/transducers@1.8.0...@thi.ng/transducers@1.8.1) (2018-04-18) diff --git a/packages/transducers/package.json b/packages/transducers/package.json index 8f272f45a1..210b89c9c6 100644 --- a/packages/transducers/package.json +++ b/packages/transducers/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/transducers", - "version": "1.8.1", + "version": "1.8.2", "description": "Lightweight transducer implementations for ES6 / TypeScript", "main": "./index.js", "typings": "./index.d.ts", @@ -24,7 +24,7 @@ "typescript": "^2.8.1" }, "dependencies": { - "@thi.ng/api": "^2.2.0" + "@thi.ng/api": "^2.3.0" }, "keywords": [ "ES6", diff --git a/scripts/make-module b/scripts/make-module index 82b7cc1751..4da33d4e44 100755 --- a/scripts/make-module +++ b/scripts/make-module @@ -51,7 +51,7 @@ cat << EOF > $MODULE/package.json "typescript": "^2.8.1" }, "dependencies": { - "@thi.ng/api": "^2.1.0" + "@thi.ng/api": "^2.2.0" }, "keywords": [ "ES6", @@ -110,6 +110,9 @@ cat << EOF > $MODULE/README.md [![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/$1.svg)](https://www.npmjs.com/package/@thi.ng/$1) +This project is part of the +[@thi.ng/umbrella](https://github.com/thi-ng/umbrella/) monorepo. + ## About TODO...