diff --git a/README.md b/README.md index 398737ad49..857e540d7d 100644 --- a/README.md +++ b/README.md @@ -2,25 +2,25 @@ [![Travis status](https://api.travis-ci.org/thi-ng/umbrella.svg?branch=master)](https://travis-ci.org/thi-ng/umbrella) [![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg)](https://conventionalcommits.org/) +[![Discord chat](https://img.shields.io/discord/445761008837984256.svg)](https://discord.gg/JhYcmBw) Mono-repository for thi.ng TypeScript/ES6 projects, a collection of largely -data / transformation oriented packages and building blocks reactive -applications, components (not just UI related), dataflow. +data / transformation oriented packages and building blocks for reactive +applications, dataflow graphs, components (not just UI related). -All/most packages: +Most packages: - have detailed, individual README files w/ small usage examples - versioned independently -- distributed as ES6 (CommonJS modules) with bundled TypeScript typings - & changelogs +- distributed as ES6 (CommonJS modules) with doc comments (incl. example + code snippets), bundled TypeScript typings & changelogs - highly modular with largely only a few closely related functions / single class per file to help w/ tree shaking - provide re-exports of all their publics for full library imports -- have either none or only @thi.ng internal runtime dependencies (see - graph below) +- have either none or only @thi.ng internal runtime dependencies - declare public interfaces, enums & types in an `src/api.ts` file (larger packages only) -- autogenerated online documentation at [docs.thi.ng](http://docs.thi.ng) +- auto-generated online documentation at [docs.thi.ng](http://docs.thi.ng) - licensed under Apache Software License 2.0 There's a steadily growing number of standalone examples (different @@ -29,47 +29,49 @@ difficulties, many combining functionality from several packages) in the ## Projects -| Projects | Version | Changelog | Description | -|----|----|----|----| -| [`@thi.ng/api`](./packages/api) | [![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/api.svg)](https://www.npmjs.com/package/@thi.ng/api) | [changelog](./packages/api/CHANGELOG.md) | Common types, decorators, mixins | -| [`@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) | Alt Set & Map implementations | -| [`@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) | Immutable value wrappers, views, history | -| [`@thi.ng/bench`](./packages/bench) | [![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/bench.svg)](https://www.npmjs.com/package/@thi.ng/bench) | [changelog](./packages/bench/CHANGELOG.md) | Basic benchmarking helpers | -| [`@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) | Bitwise input / output streams | -| [`@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) | In-memory caches / strategies | -| [`@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) | Type & value checks | -| [`@thi.ng/compare`](./packages/compare) | [![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/compare.svg)](https://www.npmjs.com/package/@thi.ng/compare) | [changelog](./packages/compare/CHANGELOG.md) | Comparator | -| [`@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) | Channel based async ops | -| [`@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) | Doubly-linked list | -| [`@thi.ng/defmulti`](./packages/defmulti) | [![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/defmulti.svg)](https://www.npmjs.com/package/@thi.ng/defmulti) | [changelog](./packages/defmulti/CHANGELOG.md) | Dynamic multiple dispatch | -| [`@thi.ng/dgraph`](./packages/dgraph) | [![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/dgraph.svg)](https://www.npmjs.com/package/@thi.ng/dgraph) | [changelog](./packages/dgraph/CHANGELOG.md) | Dependency graph | -| [`@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) | Array & object diffing | -| [`@thi.ng/dot`](./packages/dot) | [![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/dot.svg)](https://www.npmjs.com/package/@thi.ng/dot) | [changelog](./packages/dot/CHANGELOG.md) | Graphviz DOM & export | -| [`@thi.ng/equiv`](./packages/equiv) | [![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/equiv.svg)](https://www.npmjs.com/package/@thi.ng/equiv) | [changelog](./packages/equiv/CHANGELOG.md) | Deep value equivalence checking | -| [`@thi.ng/errors`](./packages/errors) | [![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/errors.svg)](https://www.npmjs.com/package/@thi.ng/errors) | [changelog](./packages/errors/CHANGELOG.md) | Custom error types | -| [`@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) | Hiccup based VDOM & diffing | -| [`@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) | hdom based UI components | -| [`@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) | Binary & d-ary heap impls | -| [`@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) | S-expression based HTML/XML serialization | -| [`@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) | CSS from nested JS data structures | -| [`@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) | hiccup based SVG vocab | -| [`@thi.ng/interceptors`](./packages/interceptors) | [![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/interceptors.svg)](https://www.npmjs.com/package/@thi.ng/interceptors) | [changelog](./packages/interceptors/CHANGELOG.md) | Composable event handlers & processor | -| [`@thi.ng/iterators`](./packages/iterators) | [![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/iterators.svg)](https://www.npmjs.com/package/@thi.ng/iterators) | [changelog](./packages/iterators/CHANGELOG.md) | ES6 generators / iterators | -| [`@thi.ng/paths`](./packages/paths) | [![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/paths.svg)](https://www.npmjs.com/package/@thi.ng/paths) | [changelog](./packages/paths/CHANGELOG.md) | Immutable nested object accessors | -| [`@thi.ng/pointfree`](./packages/pointfree) | [![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/pointfree.svg)](https://www.npmjs.com/package/@thi.ng/pointfree) | [changelog](./packages/pointfree/CHANGELOG.md) | stack-based DSL & functional composition | -| [`@thi.ng/pointfree-lang`](./packages/pointfree-lang) | [![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/pointfree-lang.svg)](https://www.npmjs.com/package/@thi.ng/pointfree-lang) | [changelog](./packages/pointfree-lang/CHANGELOG.md) | Forth-like syntax layer for @thi.ng/pointfree | -| [`@thi.ng/rle-pack`](./packages/rle-pack) | [![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/rle-pack.svg)](https://www.npmjs.com/package/@thi.ng/rle-pack) | [changelog](./packages/rle-pack/CHANGELOG.md) | Run-length encoding data compression | -| [`@thi.ng/resolve-map`](./packages/resolve-map) | [![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/resolve-map.svg)](https://www.npmjs.com/package/@thi.ng/resolve-map) | [changelog](./packages/resolve-map/CHANGELOG.md) | DAG computations & value resolution | -| [`@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) | Customizable browser & non-browser router | -| [`@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) | Push-based, reactive event stream primitves | -| [`@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) | Adapter bridge CSP -> rstream | -| [`@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) | Graphviz visualization of rstream topologies | -| [`@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) | Mouse & touch event stream abstraction | -| [`@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) | Declarative dataflow graph construction | -| [`@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) | Hierarchical structured data logging | -| [`@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) | Triple store & query engine | -| [`@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) | Composable data transformations | -| [`@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) | Wrapper for C-like structs / unions | +| Projects | Version | Changelog | Description | +|-----------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------|-----------------------------------------------| +| [`@thi.ng/api`](./packages/api) | [![version](https://img.shields.io/npm/v/@thi.ng/api.svg)](https://www.npmjs.com/package/@thi.ng/api) | [changelog](./packages/api/CHANGELOG.md) | Common types, decorators, mixins | +| [`@thi.ng/associative`](./packages/associative) | [![version](https://img.shields.io/npm/v/@thi.ng/associative.svg)](https://www.npmjs.com/package/@thi.ng/associative) | [changelog](./packages/associative/CHANGELOG.md) | Alt Set & Map implementations | +| [`@thi.ng/atom`](./packages/atom) | [![version](https://img.shields.io/npm/v/@thi.ng/atom.svg)](https://www.npmjs.com/package/@thi.ng/atom) | [changelog](./packages/atom/CHANGELOG.md) | Immutable value wrappers, views, history | +| [`@thi.ng/bench`](./packages/bench) | [![version](https://img.shields.io/npm/v/@thi.ng/bench.svg)](https://www.npmjs.com/package/@thi.ng/bench) | [changelog](./packages/bench/CHANGELOG.md) | Basic benchmarking helpers | +| [`@thi.ng/bitstream`](./packages/bitstream) | [![version](https://img.shields.io/npm/v/@thi.ng/bitstream.svg)](https://www.npmjs.com/package/@thi.ng/bitstream) | [changelog](./packages/bitstream/CHANGELOG.md) | Bitwise input / output streams | +| [`@thi.ng/cache`](./packages/cache) | [![version](https://img.shields.io/npm/v/@thi.ng/cache.svg)](https://www.npmjs.com/package/@thi.ng/cache) | [changelog](./packages/cache/CHANGELOG.md) | In-memory caches / strategies | +| [`@thi.ng/checks`](./packages/checks) | [![version](https://img.shields.io/npm/v/@thi.ng/checks.svg)](https://www.npmjs.com/package/@thi.ng/checks) | [changelog](./packages/checks/CHANGELOG.md) | Type & value checks | +| [`@thi.ng/compare`](./packages/compare) | [![version](https://img.shields.io/npm/v/@thi.ng/compare.svg)](https://www.npmjs.com/package/@thi.ng/compare) | [changelog](./packages/compare/CHANGELOG.md) | Comparator | +| [`@thi.ng/csp`](./packages/csp) | [![version](https://img.shields.io/npm/v/@thi.ng/csp.svg)](https://www.npmjs.com/package/@thi.ng/csp) | [changelog](./packages/csp/CHANGELOG.md) | Channel based async ops | +| [`@thi.ng/dcons`](./packages/dcons) | [![version](https://img.shields.io/npm/v/@thi.ng/dcons.svg)](https://www.npmjs.com/package/@thi.ng/dcons) | [changelog](./packages/dcons/CHANGELOG.md) | Doubly-linked list | +| [`@thi.ng/defmulti`](./packages/defmulti) | [![version](https://img.shields.io/npm/v/@thi.ng/defmulti.svg)](https://www.npmjs.com/package/@thi.ng/defmulti) | [changelog](./packages/defmulti/CHANGELOG.md) | Dynamic multiple dispatch | +| [`@thi.ng/dgraph`](./packages/dgraph) | [![version](https://img.shields.io/npm/v/@thi.ng/dgraph.svg)](https://www.npmjs.com/package/@thi.ng/dgraph) | [changelog](./packages/dgraph/CHANGELOG.md) | Dependency graph | +| [`@thi.ng/diff`](./packages/diff) | [![version](https://img.shields.io/npm/v/@thi.ng/diff.svg)](https://www.npmjs.com/package/@thi.ng/diff) | [changelog](./packages/diff/CHANGELOG.md) | Array & object diffing | +| [`@thi.ng/dot`](./packages/dot) | [![version](https://img.shields.io/npm/v/@thi.ng/dot.svg)](https://www.npmjs.com/package/@thi.ng/dot) | [changelog](./packages/dot/CHANGELOG.md) | Graphviz DOM & export | +| [`@thi.ng/equiv`](./packages/equiv) | [![version](https://img.shields.io/npm/v/@thi.ng/equiv.svg)](https://www.npmjs.com/package/@thi.ng/equiv) | [changelog](./packages/equiv/CHANGELOG.md) | Deep value equivalence checking | +| [`@thi.ng/errors`](./packages/errors) | [![version](https://img.shields.io/npm/v/@thi.ng/errors.svg)](https://www.npmjs.com/package/@thi.ng/errors) | [changelog](./packages/errors/CHANGELOG.md) | Custom error types | +| [`@thi.ng/hdom`](./packages/hdom) | [![version](https://img.shields.io/npm/v/@thi.ng/hdom.svg)](https://www.npmjs.com/package/@thi.ng/hdom) | [changelog](./packages/hdom/CHANGELOG.md) | Hiccup based VDOM & diffing | +| [`@thi.ng/hdom-components`](./packages/hdom-components) | [![version](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) | hdom based UI components | +| [`@thi.ng/heaps`](./packages/heaps) | [![version](https://img.shields.io/npm/v/@thi.ng/heaps.svg)](https://www.npmjs.com/package/@thi.ng/heaps) | [changelog](./packages/heaps/CHANGELOG.md) | Binary & d-ary heap impls | +| [`@thi.ng/hiccup`](./packages/hiccup) | [![version](https://img.shields.io/npm/v/@thi.ng/hiccup.svg)](https://www.npmjs.com/package/@thi.ng/hiccup) | [changelog](./packages/hiccup/CHANGELOG.md) | S-expression based HTML/XML serialization | +| [`@thi.ng/hiccup-css`](./packages/hiccup-css) | [![version](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) | CSS from nested JS data structures | +| [`@thi.ng/hiccup-svg`](./packages/hiccup-svg) | [![version](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) | hiccup based SVG vocab | +| [`@thi.ng/interceptors`](./packages/interceptors) | [![version](https://img.shields.io/npm/v/@thi.ng/interceptors.svg)](https://www.npmjs.com/package/@thi.ng/interceptors) | [changelog](./packages/interceptors/CHANGELOG.md) | Composable event handlers & processor | +| [`@thi.ng/iterators`](./packages/iterators) | [![version](https://img.shields.io/npm/v/@thi.ng/iterators.svg)](https://www.npmjs.com/package/@thi.ng/iterators) | [changelog](./packages/iterators/CHANGELOG.md) | ES6 generators / iterators | +| [`@thi.ng/paths`](./packages/paths) | [![version](https://img.shields.io/npm/v/@thi.ng/paths.svg)](https://www.npmjs.com/package/@thi.ng/paths) | [changelog](./packages/paths/CHANGELOG.md) | Immutable nested object accessors | +| [`@thi.ng/pointfree`](./packages/pointfree) | [![version](https://img.shields.io/npm/v/@thi.ng/pointfree.svg)](https://www.npmjs.com/package/@thi.ng/pointfree) | [changelog](./packages/pointfree/CHANGELOG.md) | stack-based DSL & functional composition | +| [`@thi.ng/pointfree-lang`](./packages/pointfree-lang) | [![version](https://img.shields.io/npm/v/@thi.ng/pointfree-lang.svg)](https://www.npmjs.com/package/@thi.ng/pointfree-lang) | [changelog](./packages/pointfree-lang/CHANGELOG.md) | Forth-like syntax layer for @thi.ng/pointfree | +| [`@thi.ng/rle-pack`](./packages/rle-pack) | [![version](https://img.shields.io/npm/v/@thi.ng/rle-pack.svg)](https://www.npmjs.com/package/@thi.ng/rle-pack) | [changelog](./packages/rle-pack/CHANGELOG.md) | Run-length encoding data compression | +| [`@thi.ng/resolve-map`](./packages/resolve-map) | [![version](https://img.shields.io/npm/v/@thi.ng/resolve-map.svg)](https://www.npmjs.com/package/@thi.ng/resolve-map) | [changelog](./packages/resolve-map/CHANGELOG.md) | DAG computations & value resolution | +| [`@thi.ng/router`](./packages/router) | [![version](https://img.shields.io/npm/v/@thi.ng/router.svg)](https://www.npmjs.com/package/@thi.ng/router) | [changelog](./packages/router/CHANGELOG.md) | Customizable browser & non-browser router | +| [`@thi.ng/rstream`](./packages/rstream) | [![version](https://img.shields.io/npm/v/@thi.ng/rstream.svg)](https://www.npmjs.com/package/@thi.ng/rstream) | [changelog](./packages/rstream/CHANGELOG.md) | Push-based, reactive event stream primitves | +| [`@thi.ng/rstream-csp`](./packages/rstream-csp) | [![version](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) | Adapter bridge CSP -> rstream | +| [`@thi.ng/rstream-dot`](./packages/rstream-dot) | [![version](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) | Graphviz visualization of rstream topologies | +| [`@thi.ng/rstream-gestures`](./packages/rstream-gestures) | [![version](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) | Mouse & touch event stream abstraction | +| [`@thi.ng/rstream-graph`](./packages/rstream-graph) | [![version](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) | Declarative dataflow graph construction | +| [`@thi.ng/rstream-log`](./packages/rstream-log) | [![version](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) | Hierarchical structured data logging | +| [`@thi.ng/rstream-query`](./packages/rstream-query) | [![version](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) | Triple store & query engine | +| [`@thi.ng/sax`](./packages/sax) | [![version](https://img.shields.io/npm/v/@thi.ng/sax.svg)](https://www.npmjs.com/package/@thi.ng/sax) | [changelog](./packages/sax/CHANGELOG.md) | SAX-like XML parser / transducer | +| [`@thi.ng/transducers`](./packages/transducers) | [![version](https://img.shields.io/npm/v/@thi.ng/transducers.svg)](https://www.npmjs.com/package/@thi.ng/transducers) | [changelog](./packages/transducers/CHANGELOG.md) | Composable data transformations | +| [`@thi.ng/transducers-fsm`](./packages/transducers-fsm) | [![version](https://img.shields.io/npm/v/@thi.ng/transducers-fsm.svg)](https://www.npmjs.com/package/@thi.ng/transducers-fsm) | [changelog](./packages/transducers-fsm/CHANGELOG.md) | Finite State Machine | +| [`@thi.ng/unionstruct`](./packages/unionstruct) | [![version](https://img.shields.io/npm/v/@thi.ng/unionstruct.svg)](https://www.npmjs.com/package/@thi.ng/unionstruct) | [changelog](./packages/unionstruct/CHANGELOG.md) | Wrapper for C-like structs / unions | ## Dependency graph diff --git a/examples/hdom-dropdown-fuzzy/.gitignore b/examples/hdom-dropdown-fuzzy/.gitignore new file mode 100644 index 0000000000..9c418ce79f --- /dev/null +++ b/examples/hdom-dropdown-fuzzy/.gitignore @@ -0,0 +1,3 @@ +node_modules +yarn.lock +*.js diff --git a/examples/hdom-dropdown-fuzzy/README.md b/examples/hdom-dropdown-fuzzy/README.md new file mode 100644 index 0000000000..861253b871 --- /dev/null +++ b/examples/hdom-dropdown-fuzzy/README.md @@ -0,0 +1,21 @@ +# hdom-dropdown-fuzzy + +[Live demo](http://demo.thi.ng/umbrella/hdom-dropdown-fuzzy/) + +**This example is a work-in-progress trying out different ideas. Do not +(yet) take as reference for your own projects.** + +``` +git clone https://github.com/thi-ng/umbrella.git +cd umbrella/examples/hdom-dropdown-fuzzy +yarn install +yarn start +``` + +## Authors + +- Karsten Schmidt + +## License + +© 2018 Karsten Schmidt // Apache Software License 2.0 diff --git a/examples/hdom-dropdown-fuzzy/index.html b/examples/hdom-dropdown-fuzzy/index.html new file mode 100644 index 0000000000..d58b6fa6d9 --- /dev/null +++ b/examples/hdom-dropdown-fuzzy/index.html @@ -0,0 +1,25 @@ + + + + + + + + hdom-dropdown-fuzzy + + + + + + + +
WIP customizable dropdown component w/ fuzzy filter. + Source +
+
+ + + + \ No newline at end of file diff --git a/examples/hdom-dropdown-fuzzy/package.json b/examples/hdom-dropdown-fuzzy/package.json new file mode 100644 index 0000000000..513d7f148b --- /dev/null +++ b/examples/hdom-dropdown-fuzzy/package.json @@ -0,0 +1,26 @@ +{ + "name": "hdom-dropdown-fuzzy", + "version": "0.0.1", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "webpack --mode production --display-reasons --display-modules", + "start": "webpack-dev-server --open --mode development --devtool inline-source-map" + }, + "devDependencies": { + "ts-loader": "^4.3.0", + "typescript": "^2.8.3", + "webpack": "^4.8.1", + "webpack-cli": "^2.1.3", + "webpack-dev-server": "^3.1.4" + }, + "dependencies": { + "@thi.ng/api": "latest", + "@thi.ng/hdom": "latest", + "@thi.ng/hdom-components": "latest", + "@thi.ng/interceptors": "latest", + "@thi.ng/paths": "latest", + "@thi.ng/transducers": "latest" + } +} \ No newline at end of file diff --git a/examples/hdom-dropdown-fuzzy/src/config.ts b/examples/hdom-dropdown-fuzzy/src/config.ts new file mode 100644 index 0000000000..815dfc3342 --- /dev/null +++ b/examples/hdom-dropdown-fuzzy/src/config.ts @@ -0,0 +1,241 @@ +export const state = { + countries: { + open: false, + filter: "", + items: [ + "Afghanistan", + "Albania", + "Algeria", + "Andorra", + "Angola", + "Antigua and Barbuda", + "Argentina", + "Armenia", + "Australia", + "Austria", + "Azerbaijan", + "Bahamas", + "Bahrain", + "Bangladesh", + "Barbados", + "Belarus", + "Belgium", + "Belize", + "Benin", + "Bhutan", + "Bolivia", + "Bosnia and Herzegovina", + "Botswana", + "Brazil", + "Brunei", + "Bulgaria", + "Burkina Faso", + "Burundi", + "Cabo Verde", + "Cambodia", + "Cameroon", + "Canada", + "Central African Republic (CAR)", + "Chad", + "Chile", + "China", + "Colombia", + "Comoros", + "Democratic Republic of the Congo", + "Republic of the Congo", + "Costa Rica", + "Cote d'Ivoire", + "Croatia", + "Cuba", + "Cyprus", + "Czech Republic", + "Denmark", + "Djibouti", + "Dominica", + "Dominican Republic", + "Ecuador", + "Egypt", + "El Salvador", + "Equatorial Guinea", + "Eritrea", + "Estonia", + "Eswatini (formerly Swaziland)", + "Ethiopia", + "Fiji", + "Finland", + "France", + "Gabon", + "Gambia", + "Georgia", + "Germany", + "Ghana", + "Greece", + "Grenada", + "Guatemala", + "Guinea", + "Guinea-Bissau", + "Guyana", + "Haiti", + "Honduras", + "Hungary", + "Iceland", + "India", + "Indonesia", + "Iran", + "Iraq", + "Ireland", + "Israel", + "Italy", + "Jamaica", + "Japan", + "Jordan", + "Kazakhstan", + "Kenya", + "Kiribati", + "Kosovo", + "Kuwait", + "Kyrgyzstan", + "Laos", + "Latvia", + "Lebanon", + "Lesotho", + "Liberia", + "Libya", + "Liechtenstein", + "Lithuania", + "Luxembourg", + "Macedonia (FYROM)", + "Madagascar", + "Malawi", + "Malaysia", + "Maldives", + "Mali", + "Malta", + "Marshall Islands", + "Mauritania", + "Mauritius", + "Mexico", + "Micronesia", + "Moldova", + "Monaco", + "Mongolia", + "Montenegro", + "Morocco", + "Mozambique", + "Myanmar (formerly Burma)", + "Namibia", + "Nauru", + "Nepal", + "Netherlands", + "New Zealand", + "Nicaragua", + "Niger", + "Nigeria", + "North Korea", + "Norway", + "Oman", + "Pakistan", + "Palau", + "Palestine", + "Panama", + "Papua New Guinea", + "Paraguay", + "Peru", + "Philippines", + "Poland", + "Portugal", + "Qatar", + "Romania", + "Russia", + "Rwanda", + "Saint Kitts and Nevis", + "Saint Lucia", + "Saint Vincent and the Grenadines", + "Samoa", + "San Marino", + "Sao Tome and Principe", + "Saudi Arabia", + "Senegal", + "Serbia", + "Seychelles", + "Sierra Leone", + "Singapore", + "Slovakia", + "Slovenia", + "Solomon Islands", + "Somalia", + "South Africa", + "South Korea", + "South Sudan", + "Spain", + "Sri Lanka", + "Sudan", + "Suriname", + "Sweden", + "Switzerland", + "Syria", + "Taiwan", + "Tajikistan", + "Tanzania", + "Thailand", + "Timor-Leste", + "Togo", + "Tonga", + "Trinidad and Tobago", + "Tunisia", + "Turkey", + "Turkmenistan", + "Tuvalu", + "Uganda", + "Ukraine", + "United Arab Emirates (UAE)", + "United Kingdom (UK)", + "United States of America (USA)", + "Uruguay", + "Uzbekistan", + "Vanuatu", + "Vatican City (Holy See)", + "Venezuela", + "Vietnam", + "Yemen", + "Zambia", + "Zimbabwe" + ].map((x, i) => [i, x]) + } +}; + +export const theme = { + root: { + class: "sans-serif" + }, + column: { + class: "fl w-100 w-50-ns w-33-l pa2" + }, + input: { + class: "bg-transparent w-100 bn pa2" + }, + dd: { + root: { class: "" }, + bodyClosed: { + style: { + "max-height": 0, + "overflow-y": "hidden", + opacity: 0 + } + }, + bodyOpen: { + style: { + "max-height": "calc(11 * 1.8rem)", + "overflow-y": "scroll", + opacity: 1, + transition: "all 100ms ease-in" + } + }, + item: { class: "pointer link db w-100 ph3 pv2 black hover-bg-washed-green bg-animate bb b--moon-gray" }, + itemSelected: { class: "pointer link db w-100 ph3 pv2 black hover-bg-light-gray bg-animate bb b--moon-gray b" }, + itemDisabled: { class: "db w-100 ph3 pv2 gray bb b--moon-gray" }, + }, + fuzzy: { + class: "b underline" + } +}; diff --git a/examples/hdom-dropdown-fuzzy/src/dropdown.ts b/examples/hdom-dropdown-fuzzy/src/dropdown.ts new file mode 100644 index 0000000000..44640e3708 --- /dev/null +++ b/examples/hdom-dropdown-fuzzy/src/dropdown.ts @@ -0,0 +1,89 @@ +import { IObjectOf } from "@thi.ng/api/api"; +import { ReadonlyAtom } from "@thi.ng/atom/api"; +import { isString } from "@thi.ng/checks"; +import { appLink } from "@thi.ng/hdom-components/link"; +import { EV_SET_VALUE, EV_TOGGLE_VALUE } from "@thi.ng/interceptors/api"; +import { EventBus } from "@thi.ng/interceptors/event-bus"; +import { getIn, Path } from "@thi.ng/paths"; + +export interface BaseContext { + bus: EventBus; + state: ReadonlyAtom; +} + +export interface DropdownArgs { + state: DropdownState; + statePath: Path; + ontoggle: EventListener; + onchange: (id: any) => EventListener; + attribs: IObjectOf; + hoverLabel: any; + openLabel: any; + noItems: any; + onmouseover: EventListener; + onmouseleave: EventListener; +} + +export interface DropdownState { + open: boolean; + hover: boolean; + selected: any; + items: DropdownItem[]; +} + +export type DropdownItem = [any, any]; + +export interface DropdownTheme { + root: IObjectOf; + bodyOpen: IObjectOf; + bodyClosed: IObjectOf; + item: IObjectOf; + itemSelected: IObjectOf; + itemDisabled: IObjectOf; +} + +export function dropdown(themeCtxPath: Path) { + return (ctx: any, opts: Partial) => { + const ui: DropdownTheme = getIn(ctx, themeCtxPath); + const state = opts.statePath ? getIn(ctx, opts.statePath) : opts.state; + const hattribs = { + onmouseover: opts.onmouseover, + onmouseleave: opts.onmouseleave, + }; + return state.open ? + ["div", { ...ui.root, onkeydown: (e) => console.log(e) }, + [appLink, { ...hattribs, ...ui.itemSelected }, opts.ontoggle, opts.openLabel || opts.hoverLabel], + ["div", ui.bodyOpen, + state.items.length ? + state.items.map( + (x) => + ["a", + { + ...x[0] === state.selected ? ui.itemSelected : ui.item, + href: "#", + onclick: opts.onchange(x[0]), + }, + ...(isString(x[1]) ? [x[1]] : x[1])] + ) : + ["span", ui.itemDisabled, opts.noItems]]] : + ["div", ui.root, + [appLink, { ...hattribs, ...ui.item }, opts.ontoggle, + state.hover ? + opts.hoverLabel : + (state.items.find((x) => x[0] === state.selected) || + [, opts.hoverLabel])[1]], + ["div", ui.bodyClosed]]; + }; +} + +export const dropdownListeners = (ctx: BaseContext, basePath: PropertyKey[]) => ({ + onmouseover: () => ctx.bus.dispatch([EV_SET_VALUE, [[...basePath, "hover"], true]]), + onmouseleave: () => ctx.bus.dispatch([EV_SET_VALUE, [[...basePath, "hover"], false]]), + ontoggle: () => ctx.bus.dispatch([EV_TOGGLE_VALUE, [...basePath, "open"]]), + onchange: (x) => () => { + ctx.bus.dispatch( + [EV_SET_VALUE, [[...basePath, "selected"], x]], + [EV_SET_VALUE, [[...basePath, "open"], false]] + ); + } +}); diff --git a/examples/hdom-dropdown-fuzzy/src/fuzzy.ts b/examples/hdom-dropdown-fuzzy/src/fuzzy.ts new file mode 100644 index 0000000000..dc36c3e4b8 --- /dev/null +++ b/examples/hdom-dropdown-fuzzy/src/fuzzy.ts @@ -0,0 +1,70 @@ +import { IView } from "@thi.ng/atom/api"; +import { EV_SET_VALUE } from "@thi.ng/interceptors/api"; +import { comp } from "@thi.ng/transducers/func/comp"; +import { iterator } from "@thi.ng/transducers/iterator"; +import { filterFuzzy } from "@thi.ng/transducers/xform/filter-fuzzy"; +import { map } from "@thi.ng/transducers/xform/map"; + +import { dropdownListeners, DropdownState, DropdownItem } from "./dropdown"; + +export interface FuzzyArgs { + state: IView; + filter: IView; + dropdown: any; + input: any; + hoverLabel: any; + placeholder: string; +} + +export const fuzzyDropdown = (ctx, opts: FuzzyArgs) => { + const close = () => ctx.bus.dispatch([EV_SET_VALUE, [opts.state.path + ".open", false]]); + const filterInput = [opts.input, { + state: opts.filter.deref(), + placeholder: opts.placeholder, + oninput: (e) => ctx.bus.dispatch([EV_SET_VALUE, [opts.filter.path, e.target.value]]), + onclear: () => ctx.bus.dispatch([EV_SET_VALUE, [opts.filter.path, ""]]), + oncancel: close, + onconfirm: close, + }]; + return () => { + const state = { ...opts.state.deref() }; + const filter = opts.filter.deref().toLowerCase(); + if (filter && state.open) { + state.items = [ + ...iterator( + comp( + filterFuzzy(filter, (x: DropdownItem) => x[1].toLowerCase()), + map(([id, x]) => + [ + id, + highlightMatches((y) => ["span", ctx.theme.fuzzy, y], x, filter) + ]) + ), + state.items) + ]; + } + return [opts.dropdown, { + ...dropdownListeners(ctx, opts.state.path), + openLabel: filterInput, + hoverLabel: opts.hoverLabel, + noItems: "no matches", + state + }]; + }; +}; + +const highlightMatches = (fn: (x: string) => any, x: string, filter: string) => { + const res: any[] = []; + let prev = -1, n = x.length - 1, m = filter.length; + for (let i = 0, j = 0; i <= n && j < m; i++) { + const c = x.charAt(i); + if (c.toLowerCase() === filter.charAt(j)) { + i - prev > 1 && res.push(x.substring(prev + 1, i)); + res.push(fn(c)); + prev = i; + j++; + } + } + prev < n && res.push(x.substr(prev + 1)); + return res; +}; diff --git a/examples/hdom-dropdown-fuzzy/src/index.ts b/examples/hdom-dropdown-fuzzy/src/index.ts new file mode 100644 index 0000000000..9298463047 --- /dev/null +++ b/examples/hdom-dropdown-fuzzy/src/index.ts @@ -0,0 +1,44 @@ +import { Atom } from "@thi.ng/atom/atom"; +import { start } from "@thi.ng/hdom/start"; +import { EventBus } from "@thi.ng/interceptors/event-bus"; +import { trace } from "@thi.ng/interceptors/interceptors"; + +import { state, theme } from "./config"; +import { dropdown } from "./dropdown"; +import { fuzzyDropdown } from "./fuzzy"; +import { cancelableInput } from "./input"; + +const bus = new EventBus(new Atom(state)); +bus.instrumentWith([trace]); + +const ctx = { + bus, + theme, + views: { + countries: bus.state.addView("countries"), + filter: bus.state.addView("countries.filter"), + } +}; + +const dd = dropdown("theme.dd"); +const input = cancelableInput("theme.input"); + +start("app", + (ctx) => { + ctx.bus.processQueue(); + return ["div", ctx.theme.root, + ["div", ctx.theme.column, + [fuzzyDropdown, { + state: ctx.views.countries, + filter: ctx.views.filter, + placeholder: "Start typing to fuzzy match", + hoverLabel: [["span", "Choose a country..."], ["i.fr.fas.fa-angle-down"]], + dropdown: dd, + input, + }] + ], + ]; + }, + ctx); + +// window["ctx"] = ctx; diff --git a/examples/hdom-dropdown-fuzzy/src/input.ts b/examples/hdom-dropdown-fuzzy/src/input.ts new file mode 100644 index 0000000000..847ca34f1f --- /dev/null +++ b/examples/hdom-dropdown-fuzzy/src/input.ts @@ -0,0 +1,63 @@ +import { Path } from "@thi.ng/api/api"; +import { IView } from "@thi.ng/atom/api"; +import { getIn } from "@thi.ng/paths"; + +export interface InputArgs { + state: IView; + orig: IView; + attribs: any; + placeholder: string; + oninput: EventListener; + oncancel: EventListener; + onconfirm: EventListener; + onclear: EventListener; + onblur: EventListener; +} + +export function cancelableInput(themeCtxPath: Path) { + let input; + return { + init: (el: HTMLElement) => (input = el.firstChild).focus(), + render: (ctx, args: InputArgs) => + ["span.relative", + ["input", + { + ...getIn(ctx, themeCtxPath), + ...args.attribs, + type: "text", + oninput: args.oninput, + onblur: args.onblur, + onkeydown: (e: KeyboardEvent) => { + switch (e.key) { + case "Escape": + args.oncancel && args.oncancel(e); + (e.target).blur(); + break; + case "Enter": + // case "Tab": + args.onconfirm && args.onconfirm(e); + (e.target).blur(); + break; + default: + } + }, + placeholder: args.placeholder, + value: args.state + }, + ], + args.onclear ? + ["a", + { + href: "#", + onclick: (e) => { + e.stopPropagation(); + input.focus(); + args.onclear(e); + } + }, + ["i.absolute.fas.fa-times-circle.gray.f7", + { style: { right: "0.5rem", top: "0.25rem" } }]] : + undefined + ] + }; +} diff --git a/examples/hdom-dropdown-fuzzy/tsconfig.json b/examples/hdom-dropdown-fuzzy/tsconfig.json new file mode 100644 index 0000000000..bd6481a5a6 --- /dev/null +++ b/examples/hdom-dropdown-fuzzy/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": [ + "./src/**/*.ts" + ] +} diff --git a/examples/hdom-dropdown-fuzzy/webpack.config.js b/examples/hdom-dropdown-fuzzy/webpack.config.js new file mode 100644 index 0000000000..e2bf1e8d3a --- /dev/null +++ b/examples/hdom-dropdown-fuzzy/webpack.config.js @@ -0,0 +1,18 @@ +module.exports = { + entry: "./src/index.ts", + output: { + path: __dirname, + filename: "bundle.js" + }, + resolve: { + extensions: [".ts", ".js"] + }, + module: { + rules: [ + { test: /\.ts$/, use: "ts-loader" } + ] + }, + devServer: { + contentBase: "." + } +}; diff --git a/examples/rstream-dataflow/src/index.ts b/examples/rstream-dataflow/src/index.ts index 42f7a0b216..07cce78ace 100644 --- a/examples/rstream-dataflow/src/index.ts +++ b/examples/rstream-dataflow/src/index.ts @@ -59,7 +59,7 @@ const graph = initGraph(db, { mpos: { fn: extract([1, "pos"]), ins: { src: { stream: () => gestures } }, - out: "mpos" + outs: { "*": "mpos" } }, // extracts last click position from gesture tuple @@ -68,7 +68,7 @@ const graph = initGraph(db, { clickpos: { fn: extract([1, "click"]), ins: { src: { stream: () => gestures } }, - out: "clickpos" + outs: { "*": "clickpos" } }, // extracts & computes length of `delta` vector in gesture tuple @@ -83,7 +83,7 @@ const graph = initGraph(db, { } )), ins: { src: { stream: () => gestures } }, - out: "dist" + outs: { "*": "dist" } }, // combines `clickpos`, `dist` and `color` streams to produce a @@ -101,11 +101,11 @@ const graph = initGraph(db, { undefined )), ins: { - click: { stream: "clickpos" }, - radius: { stream: "radius" }, - color: { stream: "color" }, + click: { stream: "/clickpos/node" }, + radius: { stream: "/radius/node" }, + color: { stream: "/color/node" }, }, - out: "circle" + outs: { "*": "circle" } }, // produces a new random color for each new drag gesture (and @@ -119,8 +119,8 @@ const graph = initGraph(db, { dedupe(equiv), map((x) => x && colors.next().value) )), - ins: { src: { stream: "clickpos" } }, - out: "color" + ins: { src: { stream: "/clickpos/node" } }, + outs: { "*": "color" } }, // transforms a `requestAnimationFrame` event stream (frame counter @ 60fps) @@ -128,7 +128,7 @@ const graph = initGraph(db, { sine: { fn: node1(map((x: number) => 0.8 + 0.2 * Math.sin(x * 0.05))), ins: { src: { stream: () => raf } }, - out: "sin" + outs: { "*": "sin" } }, // multiplies `dist` and `sine` streams to produce an animated @@ -136,10 +136,10 @@ const graph = initGraph(db, { radius: { fn: mul, ins: { - a: { stream: "sine" }, - b: { stream: "dist" } + a: { stream: "/sine/node" }, + b: { stream: "/dist/node" } }, - out: "radius" + outs: { "*": "radius" } } }); @@ -152,7 +152,7 @@ start("app", () => // since all @thi.ng/rstream subscriptions implement the // @thi.ng/api/IDeref interface (like several other types, e.g. // @thi.ng/atom's Atom, Cursor, View etc.) - graph.circle + graph.circle.node ]); // create a GraphViz DOT file of the entire dataflow graph diff --git a/examples/rstream-grid/src/dataflow.ts b/examples/rstream-grid/src/dataflow.ts index c48f792a23..67a395377f 100644 --- a/examples/rstream-grid/src/dataflow.ts +++ b/examples/rstream-grid/src/dataflow.ts @@ -32,22 +32,24 @@ export function initDataflow(bus: EventBus) { rotation: { fn: rotate, ins: { - shapes: { stream: "grid" }, + shapes: { stream: "/grid/node" }, theta: { path: paths.THETA }, }, }, svg: { fn: createSVG, ins: { - shapes: { stream: "rotation" }, + shapes: { stream: "/rotation/node" }, cols: { path: paths.COLS }, rows: { path: paths.ROWS }, stroke: { path: paths.STROKE }, }, // dispatch SVG result doc as event - out: (node) => node.subscribe({ - next: (svg) => bus.dispatch([ev.UPDATE_SVG, svg]) - }) + outs: { + "*": (node) => node.subscribe({ + next: (svg) => bus.dispatch([ev.UPDATE_SVG, svg]) + }) + } } }); return graph; diff --git a/package.json b/package.json index 1731618bbd..531a2faa09 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "webpack-dev-server": "^3.1.4" }, "scripts": { + "bootstrap": "lerna bootstrap", "build": "yarn install && lerna -v && lerna bootstrap && lerna run build --sort", "cover": "lerna run cover", "depgraph": "scripts/depgraph && git add assets/deps.png && git commit -m 'docs: update dep graph' && git push", diff --git a/packages/api/CHANGELOG.md b/packages/api/CHANGELOG.md index d27c806af6..0b588ebc99 100644 --- a/packages/api/CHANGELOG.md +++ b/packages/api/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.0.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/api@4.0.3...@thi.ng/api@4.0.4) (2018-06-21) + + + + +**Note:** Version bump only for package @thi.ng/api + + +## [4.0.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/api@4.0.2...@thi.ng/api@4.0.3) (2018-05-14) + + + + +**Note:** Version bump only for package @thi.ng/api + ## [4.0.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/api@4.0.1...@thi.ng/api@4.0.2) (2018-05-14) diff --git a/packages/api/package.json b/packages/api/package.json index ee4f6a8515..618face072 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,10 +1,14 @@ { "name": "@thi.ng/api", - "version": "4.0.2", + "version": "4.0.4", "description": "Common, generic types & interfaces for thi.ng projects", "main": "./index.js", "typings": "./index.d.ts", - "repository": "https://github.com/thi-ng/umbrella", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/api", "author": "Karsten Schmidt ", "license": "Apache-2.0", "scripts": { @@ -24,7 +28,7 @@ "typescript": "^2.8.3" }, "dependencies": { - "@thi.ng/errors": "^0.1.3" + "@thi.ng/errors": "^0.1.4" }, "keywords": [ "compare", diff --git a/packages/api/src/api.ts b/packages/api/src/api.ts index 867ac23ddc..bd88b13385 100644 --- a/packages/api/src/api.ts +++ b/packages/api/src/api.ts @@ -95,8 +95,11 @@ export interface IBuffered { */ export interface ICompare { /** - * Compares this value with given value `x`. - * Must follow same contract as `Comparator`. + * Compares this value with given value `x`. MUST follow same + * contract as `Comparator`. MUST return 0 if the type also + * implements `IEquiv` and `equiv` returns true for same `x`. + * + * Also see `IHash`. * * @param x */ @@ -159,6 +162,8 @@ export interface IEmpty { /** * Interface to provide enabled/disabled functionality. Also see * `@IEnable` decorator mixin + * + * @param T type for enable/disable option arg */ export interface IEnable { isEnabled(): boolean; @@ -185,6 +190,9 @@ export interface IEquiv { equiv(o: any): boolean; } +/** + * @param T value type + */ export interface IEqualsDelta { /** * Returns `true` if this value equals `o` with optional allowance diff --git a/packages/associative/CHANGELOG.md b/packages/associative/CHANGELOG.md index d6dc487c6e..27879e058d 100644 --- a/packages/associative/CHANGELOG.md +++ b/packages/associative/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.5.8](https://github.com/thi-ng/umbrella/compare/@thi.ng/associative@0.5.7...@thi.ng/associative@0.5.8) (2018-06-21) + + + + +**Note:** Version bump only for package @thi.ng/associative + + +## [0.5.7](https://github.com/thi-ng/umbrella/compare/@thi.ng/associative@0.5.6...@thi.ng/associative@0.5.7) (2018-06-18) + + + + +**Note:** Version bump only for package @thi.ng/associative + + +## [0.5.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/associative@0.5.5...@thi.ng/associative@0.5.6) (2018-05-14) + + + + +**Note:** Version bump only for package @thi.ng/associative + ## [0.5.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/associative@0.5.4...@thi.ng/associative@0.5.5) (2018-05-14) diff --git a/packages/associative/README.md b/packages/associative/README.md index a228908913..3c9a6e9e9b 100644 --- a/packages/associative/README.md +++ b/packages/associative/README.md @@ -142,7 +142,7 @@ interface, an extension of the native ES6 Set API. ### ArraySet Simple array based `Set` implementation which by default uses -[@thi.ng/api/equiv](https://github.com/thi-ng/umbrella/tree/master/packages/api/src/equiv.ts) +[@thi.ng/equiv](https://github.com/thi-ng/umbrella/tree/master/packages/equiv/src/index.ts) for value equivalence checking. ### LLSet @@ -161,8 +161,8 @@ canonical keys. By default uses `ArraySet` for this purpose. Alternative implementation of the ES6 Map API using a Skip list as backing store and support for configurable key equality and sorting -semantics. Like with sets, uses @thi.ng/api/equiv & @thi.ng/api/compare -by default. +semantics. Like with sets, uses @thi.ng/equiv & @thi.ng/compare by +default. William Pugh's (creator of this data structure) description: diff --git a/packages/associative/package.json b/packages/associative/package.json index 9d9cdd31b8..aeb5d6ecdb 100644 --- a/packages/associative/package.json +++ b/packages/associative/package.json @@ -1,10 +1,14 @@ { "name": "@thi.ng/associative", - "version": "0.5.5", + "version": "0.5.8", "description": "Alternative Set & Map data type implementations with customizable equality semantics & supporting operations", "main": "./index.js", "typings": "./index.d.ts", - "repository": "https://github.com/thi-ng/umbrella", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/associative", "author": "Karsten Schmidt ", "license": "Apache-2.0", "scripts": { @@ -24,13 +28,13 @@ "typescript": "^2.8.3" }, "dependencies": { - "@thi.ng/api": "^4.0.2", - "@thi.ng/checks": "^1.5.3", - "@thi.ng/compare": "^0.1.2", - "@thi.ng/dcons": "^1.0.2", - "@thi.ng/equiv": "^0.1.2", - "@thi.ng/errors": "^0.1.3", - "@thi.ng/iterators": "^4.1.15" + "@thi.ng/api": "^4.0.4", + "@thi.ng/checks": "^1.5.5", + "@thi.ng/compare": "^0.1.4", + "@thi.ng/dcons": "^1.0.5", + "@thi.ng/equiv": "^0.1.5", + "@thi.ng/errors": "^0.1.4", + "@thi.ng/iterators": "^4.1.18" }, "keywords": [ "data structures", diff --git a/packages/atom/CHANGELOG.md b/packages/atom/CHANGELOG.md index f8c37fcc5b..823f6e2fc3 100644 --- a/packages/atom/CHANGELOG.md +++ b/packages/atom/CHANGELOG.md @@ -3,6 +3,42 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [1.4.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/atom@1.4.1...@thi.ng/atom@1.4.2) (2018-06-21) + + + + +**Note:** Version bump only for package @thi.ng/atom + + +## [1.4.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/atom@1.4.0...@thi.ng/atom@1.4.1) (2018-06-18) + + + + +**Note:** Version bump only for package @thi.ng/atom + + +# [1.4.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/atom@1.3.13...@thi.ng/atom@1.4.0) (2018-05-30) + + +### Features + +* **atom:** add INotify impl for History ([9422598](https://github.com/thi-ng/umbrella/commit/9422598)) +* **atom:** provide prev/curr states to history event listeners ([7ac6227](https://github.com/thi-ng/umbrella/commit/7ac6227)) + + + + + +## [1.3.13](https://github.com/thi-ng/umbrella/compare/@thi.ng/atom@1.3.12...@thi.ng/atom@1.3.13) (2018-05-14) + + + + +**Note:** Version bump only for package @thi.ng/atom + ## [1.3.12](https://github.com/thi-ng/umbrella/compare/@thi.ng/atom@1.3.11...@thi.ng/atom@1.3.12) (2018-05-14) diff --git a/packages/atom/README.md b/packages/atom/README.md index 30eae4a003..552b25d978 100644 --- a/packages/atom/README.md +++ b/packages/atom/README.md @@ -227,7 +227,7 @@ value change (in contrast to normal watches, which execute with each update, regardless of value change). Related, the actual value change predicate can be customized. If not -given, the default `@thi.ng/api/equiv` will be used. +given, the default `@thi.ng/equiv` will be used. ```typescript let x; diff --git a/packages/atom/package.json b/packages/atom/package.json index 459fb4ded1..82475b9ed7 100644 --- a/packages/atom/package.json +++ b/packages/atom/package.json @@ -1,10 +1,14 @@ { "name": "@thi.ng/atom", - "version": "1.3.12", + "version": "1.4.2", "description": "Mutable wrapper for immutable values", "main": "./index.js", "typings": "./index.d.ts", - "repository": "https://github.com/thi-ng/umbrella", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/atom", "author": "Karsten Schmidt ", "license": "Apache-2.0", "scripts": { @@ -24,11 +28,11 @@ "typescript": "^2.8.3" }, "dependencies": { - "@thi.ng/api": "^4.0.2", - "@thi.ng/checks": "^1.5.3", - "@thi.ng/equiv": "^0.1.2", - "@thi.ng/errors": "^0.1.3", - "@thi.ng/paths": "^1.3.8" + "@thi.ng/api": "^4.0.4", + "@thi.ng/checks": "^1.5.5", + "@thi.ng/equiv": "^0.1.5", + "@thi.ng/errors": "^0.1.4", + "@thi.ng/paths": "^1.3.10" }, "keywords": [ "cursor", diff --git a/packages/atom/src/api.ts b/packages/atom/src/api.ts index 8e52b14d82..377d5c1e63 100644 --- a/packages/atom/src/api.ts +++ b/packages/atom/src/api.ts @@ -50,7 +50,10 @@ export interface CursorOpts { id?: string; } -export interface IHistory extends IAtom { +export interface IHistory extends + IAtom, + api.INotify { + canUndo(): boolean; canRedo(): boolean; diff --git a/packages/atom/src/history.ts b/packages/atom/src/history.ts index ad27234799..97a1b9a52e 100644 --- a/packages/atom/src/history.ts +++ b/packages/atom/src/history.ts @@ -1,4 +1,5 @@ -import { Predicate2, Watch } from "@thi.ng/api/api"; +import { Event, Predicate2, Watch } from "@thi.ng/api/api"; +import * as mixin from "@thi.ng/api/mixins/inotify"; import { equiv } from "@thi.ng/equiv"; import { getIn, @@ -21,11 +22,18 @@ import { View } from "./view"; * `IAtom` interface and so can be used directly in place and delegates * to wrapped atom/cursor. Value changes are only recorded in history if * `changed` predicate returns truthy value, or else by calling - * `record()` directly. + * `record()` directly. This class too implements the @thi.ng/api + * `INotify` interface to support event listeners for `undo()`, `redo()` + * and `record()`. */ +@mixin.INotify export class History implements IHistory { + static readonly EVENT_UNDO = "undo"; + static readonly EVENT_REDO = "redo"; + static readonly EVENT_RECORD = "record"; + state: IAtom; maxLen: number; changed: Predicate2; @@ -67,11 +75,21 @@ export class History implements * switch, first records the atom's current value into the future * stack (to enable `redo()` feature). Returns `undefined` if * there's no history. + * + * If undo was possible, the `History.EVENT_UNDO` event is emitted + * after the restoration with both the `prev` and `curr` (restored) + * states provided as event value (and object with these two keys). + * This allows for additional state handling to be executed, e.g. + * application of the "Command pattern". See `addListener()` for + * registering event listeners. */ undo() { if (this.history.length) { - this.future.push(this.state.deref()); - return this.state.reset(this.history.pop()); + const prev = this.state.deref(); + this.future.push(prev); + const curr = this.state.reset(this.history.pop()); + this.notify({ id: History.EVENT_UNDO, value: { prev, curr } }); + return curr; } } @@ -81,11 +99,21 @@ export class History implements * switch, first records the atom's current value into the history * stack (to enable `undo()` feature). Returns `undefined` if * there's no future (so sad!). + * + * If redo was possible, the `History.EVENT_REDO` event is emitted + * after the restoration with both the `prev` and `curr` (restored) + * states provided as event value (and object with these two keys). + * This allows for additional state handling to be executed, e.g. + * application of the "Command pattern". See `addListener()` for + * registering event listeners. */ redo() { if (this.future.length) { - this.history.push(this.state.deref()); - return this.state.reset(this.future.pop()); + const prev = this.state.deref(); + this.history.push(prev); + const curr = this.state.reset(this.future.pop()); + this.notify({ id: History.EVENT_REDO, value: { prev, curr } }); + return curr; } } @@ -140,30 +168,34 @@ export class History implements * manually managing snapshots, i.e. when applying multiple swaps on * the wrapped atom directly, but not wanting to create an history * entry for each change. **DO NOT call this explicitly if using - * `History.reset()` / `History.swap()`**. + * `History.reset()` / `History.swap()` etc.** * * If no `state` is given, uses the wrapped atom's current state - * value. + * value (user code SHOULD always call without arg). + * + * If recording succeeded, the `History.EVENT_RECORD` event is + * emitted with the recorded state provided as event value. * * @param state */ record(state?: T) { const history = this.history; const n = history.length; - if (n >= this.maxLen) { - history.shift(); - } + let ok = true; // check for arg given and not if `state == null` we want to // allow null/undefined as possible values if (!arguments.length) { state = this.state.deref(); - if (!n || this.changed(history[n - 1], state)) { - history.push(state); + ok = (!n || this.changed(history[n - 1], state)); + } + if (ok) { + if (n >= this.maxLen) { + history.shift(); } - } else { history.push(state); + this.notify({ id: History.EVENT_RECORD, value: state }); + this.future.length = 0; } - this.future.length = 0; } /** @@ -214,4 +246,15 @@ export class History implements delete this.state; return true; } + + addListener(id: string, fn: (e: Event) => void, scope?: any): boolean { + return false; + } + + removeListener(id: string, fn: (e: Event) => void, scope?: any): boolean { + return false; + } + + notify(event: Event): void { + } } diff --git a/packages/bench/CHANGELOG.md b/packages/bench/CHANGELOG.md index 92f1536d9f..beceb0fa00 100644 --- a/packages/bench/CHANGELOG.md +++ b/packages/bench/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.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/bench@0.1.2...@thi.ng/bench@0.1.3) (2018-06-21) + + + + +**Note:** Version bump only for package @thi.ng/bench + + +## [0.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/bench@0.1.1...@thi.ng/bench@0.1.2) (2018-06-18) + + + + +**Note:** Version bump only for package @thi.ng/bench + ## [0.1.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/bench@0.1.0...@thi.ng/bench@0.1.1) (2018-05-13) diff --git a/packages/bench/package.json b/packages/bench/package.json index 5bb16a2a19..6de3c242ee 100644 --- a/packages/bench/package.json +++ b/packages/bench/package.json @@ -1,10 +1,14 @@ { "name": "@thi.ng/bench", - "version": "0.1.1", - "description": "TODO", + "version": "0.1.3", + "description": "Basic benchmarking helpers", "main": "./index.js", "typings": "./index.d.ts", - "repository": "https://github.com/thi-ng/umbrella", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/bench", "author": "Karsten Schmidt ", "license": "Apache-2.0", "scripts": { diff --git a/packages/bitstream/CHANGELOG.md b/packages/bitstream/CHANGELOG.md index 4cf46f5115..dd7e09712e 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.13](https://github.com/thi-ng/umbrella/compare/@thi.ng/bitstream@0.4.12...@thi.ng/bitstream@0.4.13) (2018-06-21) + + + + +**Note:** Version bump only for package @thi.ng/bitstream + ## [0.4.12](https://github.com/thi-ng/umbrella/compare/@thi.ng/bitstream@0.4.11...@thi.ng/bitstream@0.4.12) (2018-05-14) diff --git a/packages/bitstream/package.json b/packages/bitstream/package.json index 9903acc15d..3a5af6218d 100644 --- a/packages/bitstream/package.json +++ b/packages/bitstream/package.json @@ -1,10 +1,14 @@ { "name": "@thi.ng/bitstream", - "version": "0.4.12", + "version": "0.4.13", "description": "ES6 iterator based read/write bit streams & support for variable word widths", "main": "./index.js", "typings": "./index.d.ts", - "repository": "https://github.com/thi-ng/umbrella", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/bitstream", "author": "Karsten Schmidt ", "license": "Apache-2.0", "scripts": { @@ -16,7 +20,7 @@ "test": "rm -rf build && tsc -p test && nyc mocha build/test/*.js" }, "dependencies": { - "@thi.ng/errors": "^0.1.3" + "@thi.ng/errors": "^0.1.4" }, "devDependencies": { "@types/mocha": "^5.2.0", diff --git a/packages/cache/CHANGELOG.md b/packages/cache/CHANGELOG.md index f3efb231b0..5dfcb11ab1 100644 --- a/packages/cache/CHANGELOG.md +++ b/packages/cache/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.2.13](https://github.com/thi-ng/umbrella/compare/@thi.ng/cache@0.2.12...@thi.ng/cache@0.2.13) (2018-06-21) + + + + +**Note:** Version bump only for package @thi.ng/cache + + +## [0.2.12](https://github.com/thi-ng/umbrella/compare/@thi.ng/cache@0.2.11...@thi.ng/cache@0.2.12) (2018-06-18) + + + + +**Note:** Version bump only for package @thi.ng/cache + + +## [0.2.11](https://github.com/thi-ng/umbrella/compare/@thi.ng/cache@0.2.10...@thi.ng/cache@0.2.11) (2018-05-14) + + + + +**Note:** Version bump only for package @thi.ng/cache + ## [0.2.10](https://github.com/thi-ng/umbrella/compare/@thi.ng/cache@0.2.9...@thi.ng/cache@0.2.10) (2018-05-14) diff --git a/packages/cache/README.md b/packages/cache/README.md index 9adc4c5464..8d71d93c9b 100644 --- a/packages/cache/README.md +++ b/packages/cache/README.md @@ -21,7 +21,7 @@ strategies available are: - 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) +- Customizable key equality checks (@thi.ng/equiv by default) - Optional item release callbacks (to clean up resources when value is expunged) ## Installation @@ -34,6 +34,7 @@ yarn add @thi.ng/cache - [@thi.ng/api](https://github.com/thi-ng/umbrella/tree/master/packages/api) - [@thi.ng/dcons](https://github.com/thi-ng/umbrella/tree/master/packages/dcons) +- [@thi.ng/equiv](https://github.com/thi-ng/umbrella/tree/master/packages/equiv) - [@thi.ng/iterators](https://github.com/thi-ng/umbrella/tree/master/packages/iterators) ## Usage examples diff --git a/packages/cache/package.json b/packages/cache/package.json index ce9d5f9637..c39f4f6a89 100644 --- a/packages/cache/package.json +++ b/packages/cache/package.json @@ -1,10 +1,14 @@ { "name": "@thi.ng/cache", - "version": "0.2.10", + "version": "0.2.13", "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", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/cache", "author": "Karsten Schmidt ", "license": "Apache-2.0", "scripts": { @@ -24,9 +28,9 @@ "typescript": "^2.8.3" }, "dependencies": { - "@thi.ng/api": "^4.0.2", - "@thi.ng/dcons": "^1.0.2", - "@thi.ng/iterators": "^4.1.15" + "@thi.ng/api": "^4.0.4", + "@thi.ng/dcons": "^1.0.5", + "@thi.ng/iterators": "^4.1.18" }, "keywords": [ "cache", diff --git a/packages/checks/CHANGELOG.md b/packages/checks/CHANGELOG.md index 00afe9f620..54d23f58f4 100644 --- a/packages/checks/CHANGELOG.md +++ b/packages/checks/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. + +## [1.5.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/checks@1.5.4...@thi.ng/checks@1.5.5) (2018-06-21) + + + + +**Note:** Version bump only for package @thi.ng/checks + + +## [1.5.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/checks@1.5.3...@thi.ng/checks@1.5.4) (2018-06-18) + + +### Bug Fixes + +* **checks:** isOdd() for negative values ([3589e15](https://github.com/thi-ng/umbrella/commit/3589e15)) + + + + ## [1.5.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/checks@1.5.2...@thi.ng/checks@1.5.3) (2018-05-13) diff --git a/packages/checks/package.json b/packages/checks/package.json index 64e68ac6e1..62c40bffd1 100644 --- a/packages/checks/package.json +++ b/packages/checks/package.json @@ -1,10 +1,14 @@ { "name": "@thi.ng/checks", - "version": "1.5.3", + "version": "1.5.5", "description": "Single-function sub-modules for type, feature & value checks", "main": "./index.js", "typings": "./index.d.ts", - "repository": "https://github.com/thi-ng/umbrella", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/checks", "author": "Karsten Schmidt ", "license": "Apache-2.0", "scripts": { diff --git a/packages/checks/src/is-odd.ts b/packages/checks/src/is-odd.ts index a97a0e004c..46eb851ada 100644 --- a/packages/checks/src/is-odd.ts +++ b/packages/checks/src/is-odd.ts @@ -1,3 +1,3 @@ export function isOdd(x: number) { - return (x % 2) === 1; + return (x % 2) !== 0; } diff --git a/packages/compare/CHANGELOG.md b/packages/compare/CHANGELOG.md index 87c420bd7d..664386f43f 100644 --- a/packages/compare/CHANGELOG.md +++ b/packages/compare/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.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/compare@0.1.3...@thi.ng/compare@0.1.4) (2018-06-21) + + + + +**Note:** Version bump only for package @thi.ng/compare + + +## [0.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/compare@0.1.2...@thi.ng/compare@0.1.3) (2018-05-14) + + + + +**Note:** Version bump only for package @thi.ng/compare + ## [0.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/compare@0.1.1...@thi.ng/compare@0.1.2) (2018-05-14) diff --git a/packages/compare/README.md b/packages/compare/README.md index 7e9cb85b20..65d92270bd 100644 --- a/packages/compare/README.md +++ b/packages/compare/README.md @@ -27,9 +27,10 @@ None ## Usage examples ```typescript +import { ICompare } from "@thi.ng/api"; import { compare } from "@thi.ng/compare"; -class Foo { +class Foo implements ICompare { x: number; diff --git a/packages/compare/package.json b/packages/compare/package.json index 263039a556..2775241220 100644 --- a/packages/compare/package.json +++ b/packages/compare/package.json @@ -1,10 +1,14 @@ { "name": "@thi.ng/compare", - "version": "0.1.2", + "version": "0.1.4", "description": "Comparator with optional delegation for types implementing @thi.ng/api/ICompare interface", "main": "./index.js", "typings": "./index.d.ts", - "repository": "https://github.com/thi-ng/umbrella", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/compare", "author": "Karsten Schmidt ", "license": "Apache-2.0", "scripts": { diff --git a/packages/csp/CHANGELOG.md b/packages/csp/CHANGELOG.md index 94994f36b8..460d8e1287 100644 --- a/packages/csp/CHANGELOG.md +++ b/packages/csp/CHANGELOG.md @@ -3,6 +3,38 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.3.45](https://github.com/thi-ng/umbrella/compare/@thi.ng/csp@0.3.44...@thi.ng/csp@0.3.45) (2018-06-21) + + + + +**Note:** Version bump only for package @thi.ng/csp + + +## [0.3.44](https://github.com/thi-ng/umbrella/compare/@thi.ng/csp@0.3.43...@thi.ng/csp@0.3.44) (2018-06-19) + + + + +**Note:** Version bump only for package @thi.ng/csp + + +## [0.3.43](https://github.com/thi-ng/umbrella/compare/@thi.ng/csp@0.3.42...@thi.ng/csp@0.3.43) (2018-06-18) + + + + +**Note:** Version bump only for package @thi.ng/csp + + +## [0.3.42](https://github.com/thi-ng/umbrella/compare/@thi.ng/csp@0.3.41...@thi.ng/csp@0.3.42) (2018-05-14) + + + + +**Note:** Version bump only for package @thi.ng/csp + ## [0.3.41](https://github.com/thi-ng/umbrella/compare/@thi.ng/csp@0.3.40...@thi.ng/csp@0.3.41) (2018-05-14) diff --git a/packages/csp/package.json b/packages/csp/package.json index 9feb830e1b..36e0c11d82 100644 --- a/packages/csp/package.json +++ b/packages/csp/package.json @@ -1,10 +1,14 @@ { "name": "@thi.ng/csp", - "version": "0.3.41", + "version": "0.3.45", "description": "ES6 promise based CSP implementation", "main": "./index.js", "typings": "./index.d.ts", - "repository": "https://github.com/thi-ng/umbrella", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/csp", "author": "Karsten Schmidt ", "license": "Apache-2.0", "scripts": { @@ -28,11 +32,11 @@ "typescript": "^2.8.3" }, "dependencies": { - "@thi.ng/api": "^4.0.2", - "@thi.ng/checks": "^1.5.3", - "@thi.ng/dcons": "^1.0.2", - "@thi.ng/errors": "^0.1.3", - "@thi.ng/transducers": "^1.10.1" + "@thi.ng/api": "^4.0.4", + "@thi.ng/checks": "^1.5.5", + "@thi.ng/dcons": "^1.0.5", + "@thi.ng/errors": "^0.1.4", + "@thi.ng/transducers": "^1.11.1" }, "keywords": [ "async", diff --git a/packages/dcons/CHANGELOG.md b/packages/dcons/CHANGELOG.md index e76bd0a6d0..e4997a4552 100644 --- a/packages/dcons/CHANGELOG.md +++ b/packages/dcons/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. + +## [1.0.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/dcons@1.0.4...@thi.ng/dcons@1.0.5) (2018-06-21) + + + + +**Note:** Version bump only for package @thi.ng/dcons + + +## [1.0.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/dcons@1.0.3...@thi.ng/dcons@1.0.4) (2018-06-18) + + + + +**Note:** Version bump only for package @thi.ng/dcons + + +## [1.0.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/dcons@1.0.2...@thi.ng/dcons@1.0.3) (2018-05-14) + + + + +**Note:** Version bump only for package @thi.ng/dcons + ## [1.0.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/dcons@1.0.1...@thi.ng/dcons@1.0.2) (2018-05-14) diff --git a/packages/dcons/package.json b/packages/dcons/package.json index 1e06757fc2..c11ddcf3be 100644 --- a/packages/dcons/package.json +++ b/packages/dcons/package.json @@ -1,10 +1,14 @@ { "name": "@thi.ng/dcons", - "version": "1.0.2", + "version": "1.0.5", "description": "Comprehensive doubly linked list structure w/ iterator support", "main": "./index.js", "typings": "./index.d.ts", - "repository": "https://github.com/thi-ng/umbrella", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/dcons", "author": "Karsten Schmidt ", "license": "Apache-2.0", "scripts": { @@ -24,11 +28,11 @@ "typescript": "^2.8.3" }, "dependencies": { - "@thi.ng/api": "^4.0.2", - "@thi.ng/checks": "^1.5.3", - "@thi.ng/compare": "^0.1.2", - "@thi.ng/equiv": "^0.1.2", - "@thi.ng/errors": "^0.1.3" + "@thi.ng/api": "^4.0.4", + "@thi.ng/checks": "^1.5.5", + "@thi.ng/compare": "^0.1.4", + "@thi.ng/equiv": "^0.1.5", + "@thi.ng/errors": "^0.1.4" }, "keywords": [ "datastructure", @@ -36,6 +40,7 @@ "ES6", "iterators", "linkedlist", + "list", "queue", "stack", "typescript" diff --git a/packages/defmulti/CHANGELOG.md b/packages/defmulti/CHANGELOG.md index 1637ff6cb4..58dd8076dd 100644 --- a/packages/defmulti/CHANGELOG.md +++ b/packages/defmulti/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.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/defmulti@0.3.4...@thi.ng/defmulti@0.3.5) (2018-06-21) + + + + +**Note:** Version bump only for package @thi.ng/defmulti + + +## [0.3.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/defmulti@0.3.3...@thi.ng/defmulti@0.3.4) (2018-05-14) + + + + +**Note:** Version bump only for package @thi.ng/defmulti + ## [0.3.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/defmulti@0.3.2...@thi.ng/defmulti@0.3.3) (2018-05-14) diff --git a/packages/defmulti/package.json b/packages/defmulti/package.json index 89b10132d5..fa1ffe5c95 100644 --- a/packages/defmulti/package.json +++ b/packages/defmulti/package.json @@ -1,10 +1,14 @@ { "name": "@thi.ng/defmulti", - "version": "0.3.3", + "version": "0.3.5", "description": "Dynamically extensible multiple dispatch via user supplied dispatch function.", "main": "./index.js", "typings": "./index.d.ts", - "repository": "https://github.com/thi-ng/umbrella", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/defmulti", "author": "Karsten Schmidt ", "license": "Apache-2.0", "scripts": { @@ -24,8 +28,8 @@ "typescript": "^2.8.3" }, "dependencies": { - "@thi.ng/api": "^4.0.2", - "@thi.ng/errors": "^0.1.3" + "@thi.ng/api": "^4.0.4", + "@thi.ng/errors": "^0.1.4" }, "keywords": [ "ES6", diff --git a/packages/dgraph/CHANGELOG.md b/packages/dgraph/CHANGELOG.md index 8dc1487548..b189365a50 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.2.8](https://github.com/thi-ng/umbrella/compare/@thi.ng/dgraph@0.2.7...@thi.ng/dgraph@0.2.8) (2018-06-21) + + + + +**Note:** Version bump only for package @thi.ng/dgraph + + +## [0.2.7](https://github.com/thi-ng/umbrella/compare/@thi.ng/dgraph@0.2.6...@thi.ng/dgraph@0.2.7) (2018-06-18) + + + + +**Note:** Version bump only for package @thi.ng/dgraph + + +## [0.2.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/dgraph@0.2.5...@thi.ng/dgraph@0.2.6) (2018-05-14) + + + + +**Note:** Version bump only for package @thi.ng/dgraph + ## [0.2.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/dgraph@0.2.4...@thi.ng/dgraph@0.2.5) (2018-05-14) diff --git a/packages/dgraph/package.json b/packages/dgraph/package.json index 1804649da9..2512297c86 100644 --- a/packages/dgraph/package.json +++ b/packages/dgraph/package.json @@ -1,10 +1,14 @@ { "name": "@thi.ng/dgraph", - "version": "0.2.5", + "version": "0.2.8", "description": "Type-agnostic directed acyclic graph (DAG) & graph operations", "main": "./index.js", "typings": "./index.d.ts", - "repository": "https://github.com/thi-ng/umbrella", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/dgraph", "author": "Karsten Schmidt ", "license": "Apache-2.0", "scripts": { @@ -24,11 +28,11 @@ "typescript": "^2.8.3" }, "dependencies": { - "@thi.ng/api": "^4.0.2", - "@thi.ng/associative": "^0.5.5", - "@thi.ng/equiv": "^0.1.2", - "@thi.ng/errors": "^0.1.3", - "@thi.ng/iterators": "^4.1.15" + "@thi.ng/api": "^4.0.4", + "@thi.ng/associative": "^0.5.8", + "@thi.ng/equiv": "^0.1.5", + "@thi.ng/errors": "^0.1.4", + "@thi.ng/iterators": "^4.1.18" }, "keywords": [ "data structure", diff --git a/packages/diff/CHANGELOG.md b/packages/diff/CHANGELOG.md index 8b9f85cca2..fad4861c8a 100644 --- a/packages/diff/CHANGELOG.md +++ b/packages/diff/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. + +## [1.0.19](https://github.com/thi-ng/umbrella/compare/@thi.ng/diff@1.0.18...@thi.ng/diff@1.0.19) (2018-06-21) + + + + +**Note:** Version bump only for package @thi.ng/diff + + +## [1.0.18](https://github.com/thi-ng/umbrella/compare/@thi.ng/diff@1.0.17...@thi.ng/diff@1.0.18) (2018-06-18) + + + + +**Note:** Version bump only for package @thi.ng/diff + + +## [1.0.17](https://github.com/thi-ng/umbrella/compare/@thi.ng/diff@1.0.16...@thi.ng/diff@1.0.17) (2018-05-14) + + + + +**Note:** Version bump only for package @thi.ng/diff + ## [1.0.16](https://github.com/thi-ng/umbrella/compare/@thi.ng/diff@1.0.15...@thi.ng/diff@1.0.16) (2018-05-14) diff --git a/packages/diff/package.json b/packages/diff/package.json index 9d8f8ae5bf..b562bb7e9a 100644 --- a/packages/diff/package.json +++ b/packages/diff/package.json @@ -1,10 +1,14 @@ { "name": "@thi.ng/diff", - "version": "1.0.16", + "version": "1.0.19", "description": "Array & object Diff", "main": "./index.js", "typings": "./index.d.ts", - "repository": "https://github.com/thi-ng/umbrella", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/diff", "author": "Karsten Schmidt ", "license": "Apache-2.0", "scripts": { @@ -22,8 +26,8 @@ "typescript": "^2.8.3" }, "dependencies": { - "@thi.ng/api": "^4.0.2", - "@thi.ng/equiv": "^0.1.2" + "@thi.ng/api": "^4.0.4", + "@thi.ng/equiv": "^0.1.5" }, "keywords": [ "array", diff --git a/packages/dot/CHANGELOG.md b/packages/dot/CHANGELOG.md index b644f2561e..b7f5194b75 100644 --- a/packages/dot/CHANGELOG.md +++ b/packages/dot/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.8](https://github.com/thi-ng/umbrella/compare/@thi.ng/dot@0.1.7...@thi.ng/dot@0.1.8) (2018-06-21) + + + + +**Note:** Version bump only for package @thi.ng/dot + + +## [0.1.7](https://github.com/thi-ng/umbrella/compare/@thi.ng/dot@0.1.6...@thi.ng/dot@0.1.7) (2018-06-18) + + + + +**Note:** Version bump only for package @thi.ng/dot + + +## [0.1.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/dot@0.1.5...@thi.ng/dot@0.1.6) (2018-05-14) + + + + +**Note:** Version bump only for package @thi.ng/dot + ## [0.1.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/dot@0.1.4...@thi.ng/dot@0.1.5) (2018-05-14) diff --git a/packages/dot/package.json b/packages/dot/package.json index e610c60e9d..2ffd61f490 100644 --- a/packages/dot/package.json +++ b/packages/dot/package.json @@ -1,10 +1,14 @@ { "name": "@thi.ng/dot", - "version": "0.1.5", - "description": "TODO", + "version": "0.1.8", + "description": "Graphviz DOM abstraction as vanilla JS objects & serialization to DOT format", "main": "./index.js", "typings": "./index.d.ts", - "repository": "https://github.com/thi-ng/umbrella", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/dot", "author": "Karsten Schmidt ", "license": "Apache-2.0", "scripts": { @@ -24,8 +28,8 @@ "typescript": "^2.8.3" }, "dependencies": { - "@thi.ng/api": "^4.0.2", - "@thi.ng/checks": "^1.5.3" + "@thi.ng/api": "^4.0.4", + "@thi.ng/checks": "^1.5.5" }, "keywords": [ "ES6", diff --git a/packages/equiv/CHANGELOG.md b/packages/equiv/CHANGELOG.md index 3e9ee8a75b..eece6af9dd 100644 --- a/packages/equiv/CHANGELOG.md +++ b/packages/equiv/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.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/equiv@0.1.4...@thi.ng/equiv@0.1.5) (2018-06-21) + + + + +**Note:** Version bump only for package @thi.ng/equiv + + +## [0.1.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/equiv@0.1.3...@thi.ng/equiv@0.1.4) (2018-06-18) + + + + +**Note:** Version bump only for package @thi.ng/equiv + + +## [0.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/equiv@0.1.2...@thi.ng/equiv@0.1.3) (2018-05-14) + + + + +**Note:** Version bump only for package @thi.ng/equiv + ## [0.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/equiv@0.1.1...@thi.ng/equiv@0.1.2) (2018-05-14) diff --git a/packages/equiv/README.md b/packages/equiv/README.md index f6fb73c171..bca552c97f 100644 --- a/packages/equiv/README.md +++ b/packages/equiv/README.md @@ -40,6 +40,39 @@ equiv({a: {b: [1, 2]}}, {a: {b: [1, 2]}}); // true ``` +### Implement IEquiv interface + +This is useful & required for custom types to take part in `equiv` +checks, by default only plain objects & array are traversed deeply. + +Furthemore by implementing this interface we can better control which +internal values / criteria are required to establish equivalence. In +this example we exclude the `meta` property and only check for same type +& `children` equality. + +```ts +import { IEquiv } from "@thi.ng/api"; +import { equiv } from "@thi.ng/equiv"; + +class Node implements IEquiv { + + meta: any; + children: any[]; + + constructor(children: any[], meta?) { + this.children = children; + this.meta = meta; + } + + equiv(o: any) { + return o instanceof Node && equiv(this.children, o.children); + } +} + +equiv(new Node([1,2,3], "foo"), new Node([1,2,3], "bar")); +// true +``` + ## Authors - Karsten Schmidt diff --git a/packages/equiv/package.json b/packages/equiv/package.json index 740650491b..58e1148a41 100644 --- a/packages/equiv/package.json +++ b/packages/equiv/package.json @@ -1,10 +1,14 @@ { "name": "@thi.ng/equiv", - "version": "0.1.2", + "version": "0.1.5", "description": "Extensible deep equivalence checking for any data types", "main": "./index.js", "typings": "./index.d.ts", - "repository": "https://github.com/thi-ng/umbrella", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/equiv", "author": "Karsten Schmidt ", "license": "Apache-2.0", "scripts": { @@ -24,7 +28,7 @@ "typescript": "^2.8.3" }, "dependencies": { - "@thi.ng/checks": "^1.5.3" + "@thi.ng/checks": "^1.5.5" }, "keywords": [ "deep", diff --git a/packages/errors/CHANGELOG.md b/packages/errors/CHANGELOG.md index 4aecca8f2d..cddd89c9ea 100644 --- a/packages/errors/CHANGELOG.md +++ b/packages/errors/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.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/errors@0.1.3...@thi.ng/errors@0.1.4) (2018-06-21) + + + + +**Note:** Version bump only for package @thi.ng/errors + ## [0.1.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/errors@0.1.2...@thi.ng/errors@0.1.3) (2018-05-14) diff --git a/packages/errors/package.json b/packages/errors/package.json index c58f22fc85..cd858f1431 100644 --- a/packages/errors/package.json +++ b/packages/errors/package.json @@ -1,10 +1,14 @@ { "name": "@thi.ng/errors", - "version": "0.1.3", + "version": "0.1.4", "description": "Custom error types and helper fns.", "main": "./index.js", "typings": "./index.d.ts", - "repository": "https://github.com/thi-ng/umbrella", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/errors", "author": "Karsten Schmidt ", "license": "Apache-2.0", "scripts": { diff --git a/packages/hdom-components/CHANGELOG.md b/packages/hdom-components/CHANGELOG.md index fbf49ece49..6db3b12c04 100644 --- a/packages/hdom-components/CHANGELOG.md +++ b/packages/hdom-components/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. + +## [2.1.8](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom-components@2.1.7...@thi.ng/hdom-components@2.1.8) (2018-06-21) + + + + +**Note:** Version bump only for package @thi.ng/hdom-components + + +## [2.1.7](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom-components@2.1.6...@thi.ng/hdom-components@2.1.7) (2018-06-18) + + + + +**Note:** Version bump only for package @thi.ng/hdom-components + + +## [2.1.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom-components@2.1.5...@thi.ng/hdom-components@2.1.6) (2018-05-14) + + + + +**Note:** Version bump only for package @thi.ng/hdom-components + ## [2.1.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom-components@2.1.4...@thi.ng/hdom-components@2.1.5) (2018-05-14) diff --git a/packages/hdom-components/package.json b/packages/hdom-components/package.json index 53061833c4..ff485ef6a2 100644 --- a/packages/hdom-components/package.json +++ b/packages/hdom-components/package.json @@ -1,10 +1,14 @@ { "name": "@thi.ng/hdom-components", - "version": "2.1.5", + "version": "2.1.8", "description": "Raw, skinnable UI & SVG components for @thi.ng/hdom", "main": "./index.js", "typings": "./index.d.ts", - "repository": "https://github.com/thi-ng/umbrella", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/hdom-components", "author": "Karsten Schmidt ", "license": "Apache-2.0", "scripts": { @@ -24,9 +28,9 @@ "typescript": "^2.8.3" }, "dependencies": { - "@thi.ng/api": "^4.0.2", - "@thi.ng/checks": "^1.5.3", - "@thi.ng/iterators": "^4.1.15", + "@thi.ng/api": "^4.0.4", + "@thi.ng/checks": "^1.5.5", + "@thi.ng/iterators": "^4.1.18", "@types/webgl2": "^0.0.3" }, "keywords": [ diff --git a/packages/hdom/CHANGELOG.md b/packages/hdom/CHANGELOG.md index 1d478a5b76..d64a23749d 100644 --- a/packages/hdom/CHANGELOG.md +++ b/packages/hdom/CHANGELOG.md @@ -3,6 +3,49 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [3.0.26](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom@3.0.25...@thi.ng/hdom@3.0.26) (2018-06-21) + + + + +**Note:** Version bump only for package @thi.ng/hdom + + +## [3.0.25](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom@3.0.24...@thi.ng/hdom@3.0.25) (2018-06-18) + + + + +**Note:** Version bump only for package @thi.ng/hdom + + +## [3.0.24](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom@3.0.23...@thi.ng/hdom@3.0.24) (2018-05-30) + + + + +**Note:** Version bump only for package @thi.ng/hdom + + +## [3.0.23](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom@3.0.22...@thi.ng/hdom@3.0.23) (2018-05-15) + + +### Bug Fixes + +* **hdom:** delay init() lifecycle call to ensure children are available ([2482b16](https://github.com/thi-ng/umbrella/commit/2482b16)) + + + + + +## [3.0.22](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom@3.0.21...@thi.ng/hdom@3.0.22) (2018-05-14) + + + + +**Note:** Version bump only for package @thi.ng/hdom + ## [3.0.21](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom@3.0.20...@thi.ng/hdom@3.0.21) (2018-05-14) diff --git a/packages/hdom/package.json b/packages/hdom/package.json index be900090ae..898ff4eef2 100644 --- a/packages/hdom/package.json +++ b/packages/hdom/package.json @@ -1,10 +1,14 @@ { "name": "@thi.ng/hdom", - "version": "3.0.21", + "version": "3.0.26", "description": "Lightweight vanilla ES6 UI component & virtual DOM system", "main": "./index.js", "typings": "./index.d.ts", - "repository": "https://github.com/thi-ng/umbrella", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/hdom", "author": "Karsten Schmidt ", "license": "Apache-2.0", "scripts": { @@ -16,7 +20,7 @@ "test": "rm -rf build && tsc -p test && nyc mocha build/test/*.js" }, "devDependencies": { - "@thi.ng/atom": "^1.3.12", + "@thi.ng/atom": "^1.4.2", "@types/mocha": "^5.2.0", "@types/node": "^10.0.6", "mocha": "^5.1.1", @@ -25,12 +29,12 @@ "typescript": "^2.8.3" }, "dependencies": { - "@thi.ng/api": "^4.0.2", - "@thi.ng/checks": "^1.5.3", - "@thi.ng/diff": "^1.0.16", - "@thi.ng/equiv": "^0.1.2", - "@thi.ng/hiccup": "^2.0.1", - "@thi.ng/iterators": "^4.1.15" + "@thi.ng/api": "^4.0.4", + "@thi.ng/checks": "^1.5.5", + "@thi.ng/diff": "^1.0.19", + "@thi.ng/equiv": "^0.1.5", + "@thi.ng/hiccup": "^2.0.5", + "@thi.ng/iterators": "^4.1.18" }, "keywords": [ "browser", diff --git a/packages/hdom/src/diff.ts b/packages/hdom/src/diff.ts index 496768f4fc..3e46a533d6 100644 --- a/packages/hdom/src/diff.ts +++ b/packages/hdom/src/diff.ts @@ -56,10 +56,6 @@ function _diffElement(parent: Element, prev: any, curr: any, child: number) { if ((i = prev.__release) && i !== curr.__release) { releaseDeep(prev); } - if ((i = curr.__init) && i != prev.__init) { - // DEBUG && console.log("call __init", curr); - i.apply(curr, [el, ...(curr.__args)]); - } if (edits[1][0] !== 0) { diffAttributes(el, prev[1], curr[1]); } @@ -109,6 +105,10 @@ function _diffElement(parent: Element, prev: any, curr: any, child: number) { } } } + if ((i = curr.__init) && i != prev.__init) { + // DEBUG && console.log("call __init", curr); + i.apply(curr, [el, ...(curr.__args)]); + } } function releaseDeep(tag: any) { diff --git a/packages/hdom/test/index.ts b/packages/hdom/test/index.ts index 1a440c5d67..55e630f16e 100644 --- a/packages/hdom/test/index.ts +++ b/packages/hdom/test/index.ts @@ -111,18 +111,21 @@ describe("hdom", () => { ); it("life cycle", () => { + let src: any = { render: () => ["div"] }; let res: any = ["div", {}]; + res.__this = src; res.__init = res.__release = undefined; res.__args = [null]; assert.deepEqual( - normalizeTree([{ render: () => ["div"] }], null, [], false, false), + normalizeTree([src], null, [], false, false), res ); res = ["div", { key: "0" }]; + res.__this = src; res.__init = res.__release = undefined; res.__args = [null]; assert.deepEqual( - normalizeTree([{ render: () => ["div"] }], null, [0], true, false), + normalizeTree([src], null, [0], true, false), res ); }); diff --git a/packages/heaps/CHANGELOG.md b/packages/heaps/CHANGELOG.md index 314f0a074b..8f0b924c04 100644 --- a/packages/heaps/CHANGELOG.md +++ b/packages/heaps/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.2.13](https://github.com/thi-ng/umbrella/compare/@thi.ng/heaps@0.2.12...@thi.ng/heaps@0.2.13) (2018-06-21) + + + + +**Note:** Version bump only for package @thi.ng/heaps + + +## [0.2.12](https://github.com/thi-ng/umbrella/compare/@thi.ng/heaps@0.2.11...@thi.ng/heaps@0.2.12) (2018-05-14) + + + + +**Note:** Version bump only for package @thi.ng/heaps + ## [0.2.11](https://github.com/thi-ng/umbrella/compare/@thi.ng/heaps@0.2.10...@thi.ng/heaps@0.2.11) (2018-05-14) diff --git a/packages/heaps/package.json b/packages/heaps/package.json index 23e5927cf7..65d0c50058 100644 --- a/packages/heaps/package.json +++ b/packages/heaps/package.json @@ -1,10 +1,14 @@ { "name": "@thi.ng/heaps", - "version": "0.2.11", + "version": "0.2.13", "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", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/heaps", "author": "Karsten Schmidt ", "license": "Apache-2.0", "scripts": { @@ -24,8 +28,8 @@ "typescript": "^2.8.3" }, "dependencies": { - "@thi.ng/api": "^4.0.2", - "@thi.ng/compare": "^0.1.2" + "@thi.ng/api": "^4.0.4", + "@thi.ng/compare": "^0.1.4" }, "keywords": [ "data structure", diff --git a/packages/hiccup-css/CHANGELOG.md b/packages/hiccup-css/CHANGELOG.md index 0d43a8e53f..db432a47f9 100644 --- a/packages/hiccup-css/CHANGELOG.md +++ b/packages/hiccup-css/CHANGELOG.md @@ -3,6 +3,50 @@ 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/hiccup-css@0.2.2...@thi.ng/hiccup-css@0.2.3) (2018-06-21) + + + + +**Note:** Version bump only for package @thi.ng/hiccup-css + + +## [0.2.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup-css@0.2.1...@thi.ng/hiccup-css@0.2.2) (2018-06-19) + + + + +**Note:** Version bump only for package @thi.ng/hiccup-css + + +## [0.2.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup-css@0.2.0...@thi.ng/hiccup-css@0.2.1) (2018-06-18) + + + + +**Note:** Version bump only for package @thi.ng/hiccup-css + + +# [0.2.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup-css@0.1.24...@thi.ng/hiccup-css@0.2.0) (2018-06-08) + + +### Features + +* **hiccup-css:** add class scoping support ([244bf21](https://github.com/thi-ng/umbrella/commit/244bf21)) +* **hiccup-css:** add injectStyleSheet() ([8d6e6c8](https://github.com/thi-ng/umbrella/commit/8d6e6c8)) + + + + + +## [0.1.24](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup-css@0.1.23...@thi.ng/hiccup-css@0.1.24) (2018-05-14) + + + + +**Note:** Version bump only for package @thi.ng/hiccup-css + ## [0.1.23](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup-css@0.1.22...@thi.ng/hiccup-css@0.1.23) (2018-05-14) diff --git a/packages/hiccup-css/README.md b/packages/hiccup-css/README.md index 563bbd1d35..66a4a6fd67 100644 --- a/packages/hiccup-css/README.md +++ b/packages/hiccup-css/README.md @@ -18,10 +18,12 @@ structures, functions, iterators. - Uses JS object to define selector properties - Multiple objects per scope are combined automatically - Supports nested selectors and computes their cartesian products +- Optional CSS class scoping +- DOM stylesheet injection - Configurable auto-prefixed properties & vendor prefixes (disabled by default) - Automatically consumes embedded iterators -- Supports embeded functions, either: +- Supports embedded functions, either: - to define entire selector branches/scopes - to produce single selector items - to produce property values @@ -158,7 +160,7 @@ css.css( } ``` -### Iterator support +### Iterators & CSS class scoping ```typescript import * as tx from "@thi.ng/transducers"; @@ -176,40 +178,41 @@ css.css( tx.mapcat(tx.juxt(prop(".w", "width"), prop(".h", "height"))), tx.range(25, 101, 25) ), - { format: css.PRETTY } + // supply a scope ID (suffix) for all class names + { format: css.PRETTY, scope: "_xyz" } ); ``` ```css -.w25 { +.w25_xyz { width: 25%; } -.h25 { +.h25_xyz { height: 25%; } -.w50 { +.w50_xyz { width: 50%; } -.h50 { +.h50_xyz { height: 50%; } -.w75 { +.w75_xyz { width: 75%; } -.h75 { +.h75_xyz { height: 75%; } -.w100 { +.w100_xyz { width: 100%; } -.h100 { +.h100_xyz { height: 100%; } ``` @@ -390,6 +393,18 @@ css.css( } ``` +### DOM stylesheet injection + +CSS strings can be installed into the DOM `` element via `injectStyleSheet()`: + +```ts +css.injectStyleSheet( + css.css([ + "body", { background: "#000", color: "#fff" } + ]) +); +``` + ### General function handling **Functions are handled differently based on their position in the rule diff --git a/packages/hiccup-css/package.json b/packages/hiccup-css/package.json index e68dda6c63..f2d4aa488e 100644 --- a/packages/hiccup-css/package.json +++ b/packages/hiccup-css/package.json @@ -1,10 +1,14 @@ { "name": "@thi.ng/hiccup-css", - "version": "0.1.23", + "version": "0.2.3", "description": "CSS from nested JS data structures", "main": "./index.js", "typings": "./index.d.ts", - "repository": "https://github.com/thi-ng/umbrella", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/hiccup-css", "author": "Karsten Schmidt ", "license": "Apache-2.0", "scripts": { @@ -24,10 +28,10 @@ "typescript": "^2.8.3" }, "dependencies": { - "@thi.ng/api": "^4.0.2", - "@thi.ng/checks": "^1.5.3", - "@thi.ng/errors": "^0.1.3", - "@thi.ng/transducers": "^1.10.1" + "@thi.ng/api": "^4.0.4", + "@thi.ng/checks": "^1.5.5", + "@thi.ng/errors": "^0.1.4", + "@thi.ng/transducers": "^1.11.1" }, "keywords": [ "clojure", diff --git a/packages/hiccup-css/src/api.ts b/packages/hiccup-css/src/api.ts index 44c3f92a87..c604a121a3 100644 --- a/packages/hiccup-css/src/api.ts +++ b/packages/hiccup-css/src/api.ts @@ -62,6 +62,10 @@ export interface CSSOpts { * Current tree depth. Internal use only. Ignore. */ depth: number; + /** + * Optional scoping suffix for CSS classes + */ + scope: string; } export const DEFAULT_VENDORS = [ diff --git a/packages/hiccup-css/src/impl.ts b/packages/hiccup-css/src/impl.ts index 591ca499fe..02766d4398 100644 --- a/packages/hiccup-css/src/impl.ts +++ b/packages/hiccup-css/src/impl.ts @@ -4,6 +4,8 @@ import { isIterable } from "@thi.ng/checks/is-iterable"; import { isPlainObject } from "@thi.ng/checks/is-plain-object"; import { isString } from "@thi.ng/checks/is-string"; import { illegalArgs } from "@thi.ng/errors/illegal-arguments"; +import { Transducer } from "@thi.ng/transducers/api"; +import { comp } from "@thi.ng/transducers/func/comp"; import { permutations } from "@thi.ng/transducers/iter/permutations"; import { repeat } from "@thi.ng/transducers/iter/repeat"; import { str } from "@thi.ng/transducers/rfn/str"; @@ -17,12 +19,14 @@ const EMPTY = new Set(); const NO_SPACES = ":["; -// like tx.comp(), but avoiding import to save space -const xfSel = ((a, b) => (x) => a(b(x)))( +const xfSel = comp( flatten(), map((x: string) => NO_SPACES.indexOf(x.charAt(0)) >= 0 ? x : " " + x) ); +const withScope = (xf: Transducer, scope: string) => + comp(xf, map((x) => isString(x) && x.indexOf(" .") == 0 ? x + scope : x)); + export function expand(acc: string[], parent: any[], rules: any[], opts: CSSOpts) { const n = rules.length; const sel: string[] = []; @@ -34,7 +38,7 @@ export function expand(acc: string[], parent: any[], rules: any[], opts: CSSOpts } else if (isIterable(r) && !isString(r)) { expand(acc, makeSelector(parent, sel), [...r], opts); } else if ((isFn = isFunction(r)) || opts.fns[r]) { - if (i === 0) { + if (!parent.length) { if (opts.fns[r]) { opts.fns[r].apply(null, rules.slice(i + 1))(acc, opts); return true; @@ -64,16 +68,19 @@ export function expand(acc: string[], parent: any[], rules: any[], opts: CSSOpts } function makeSelector(parent: any[], curr: any[]) { - return parent.length ? [...permutations(parent, curr)] : curr; + return parent.length ? + [...permutations(parent, curr)] : + curr; } function formatRule(parent: any[], sel: any[], curr: any, opts: CSSOpts) { const f = opts.format; const space = indent(opts); + const xf = opts.scope ? withScope(xfSel, opts.scope) : xfSel; return [ space, transduce( - map((sel: any[]) => transduce(xfSel, str(), isArray(sel) ? sel : [sel]).trim()), + map((sel: any[]) => transduce(xf, str(), isArray(sel) ? sel : [sel]).trim()), str(f.ruleSep), makeSelector(parent, sel)), f.declStart, diff --git a/packages/hiccup-css/src/index.ts b/packages/hiccup-css/src/index.ts index 67ffae1c6e..3468f7b9a1 100644 --- a/packages/hiccup-css/src/index.ts +++ b/packages/hiccup-css/src/index.ts @@ -4,6 +4,7 @@ export * from "./comment"; export * from "./conditional"; export * from "./css"; export * from "./import"; +export * from "./inject"; export * from "./keyframes"; export * from "./media"; export * from "./namespace"; diff --git a/packages/hiccup-css/src/inject.ts b/packages/hiccup-css/src/inject.ts new file mode 100644 index 0000000000..b2cbdc4940 --- /dev/null +++ b/packages/hiccup-css/src/inject.ts @@ -0,0 +1,27 @@ +// https://davidwalsh.name/add-rules-stylesheets + +/** + * Injects given CSS string as global stylesheet in DOM head. If `first` + * is true, inserts it as first stylesheet, else (default) appends it. + * + * Returns created style DOM element. + * + * @param css + * @param first + */ +export const injectStyleSheet = (css: string, first = false) => { + const head = document.getElementsByTagName("head")[0]; + const sheet = document.createElement("style"); + sheet.setAttribute("type", "text/css"); + if ((sheet).styleSheet !== undefined) { + (sheet).styleSheet.cssText = css; + } else { + sheet.textContent = css; + } + if (first) { + head.insertBefore(sheet, head.firstChild); + } else { + head.appendChild(sheet); + } + return sheet; +}; diff --git a/packages/hiccup-svg/CHANGELOG.md b/packages/hiccup-svg/CHANGELOG.md index 2d2181ace8..f3a5f6e1a9 100644 --- a/packages/hiccup-svg/CHANGELOG.md +++ b/packages/hiccup-svg/CHANGELOG.md @@ -3,6 +3,38 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [1.0.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup-svg@1.0.4...@thi.ng/hiccup-svg@1.0.5) (2018-06-21) + + + + +**Note:** Version bump only for package @thi.ng/hiccup-svg + + +## [1.0.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup-svg@1.0.3...@thi.ng/hiccup-svg@1.0.4) (2018-06-18) + + + + +**Note:** Version bump only for package @thi.ng/hiccup-svg + + +## [1.0.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup-svg@1.0.2...@thi.ng/hiccup-svg@1.0.3) (2018-05-30) + + + + +**Note:** Version bump only for package @thi.ng/hiccup-svg + + +## [1.0.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup-svg@1.0.1...@thi.ng/hiccup-svg@1.0.2) (2018-05-14) + + + + +**Note:** Version bump only for package @thi.ng/hiccup-svg + ## [1.0.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup-svg@1.0.0...@thi.ng/hiccup-svg@1.0.1) (2018-05-14) diff --git a/packages/hiccup-svg/package.json b/packages/hiccup-svg/package.json index 0d63f020fd..36e355af77 100644 --- a/packages/hiccup-svg/package.json +++ b/packages/hiccup-svg/package.json @@ -1,10 +1,14 @@ { "name": "@thi.ng/hiccup-svg", - "version": "1.0.1", + "version": "1.0.5", "description": "SVG element functions for @thi.ng/hiccup & @thi.ng/hdom", "main": "./index.js", "typings": "./index.d.ts", - "repository": "https://github.com/thi-ng/umbrella", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/hiccup-svg", "author": "Karsten Schmidt ", "license": "Apache-2.0", "scripts": { @@ -24,7 +28,7 @@ "typescript": "^2.8.3" }, "dependencies": { - "@thi.ng/hiccup": "^2.0.1" + "@thi.ng/hiccup": "^2.0.5" }, "keywords": [ "components", diff --git a/packages/hiccup/CHANGELOG.md b/packages/hiccup/CHANGELOG.md index 369414746c..bb61d95af7 100644 --- a/packages/hiccup/CHANGELOG.md +++ b/packages/hiccup/CHANGELOG.md @@ -3,6 +3,38 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [2.0.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup@2.0.4...@thi.ng/hiccup@2.0.5) (2018-06-21) + + + + +**Note:** Version bump only for package @thi.ng/hiccup + + +## [2.0.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup@2.0.3...@thi.ng/hiccup@2.0.4) (2018-06-18) + + + + +**Note:** Version bump only for package @thi.ng/hiccup + + +## [2.0.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup@2.0.2...@thi.ng/hiccup@2.0.3) (2018-05-30) + + + + +**Note:** Version bump only for package @thi.ng/hiccup + + +## [2.0.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup@2.0.1...@thi.ng/hiccup@2.0.2) (2018-05-14) + + + + +**Note:** Version bump only for package @thi.ng/hiccup + ## [2.0.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/hiccup@2.0.0...@thi.ng/hiccup@2.0.1) (2018-05-14) diff --git a/packages/hiccup/package.json b/packages/hiccup/package.json index 85fc6e3ff0..4ab7e21972 100644 --- a/packages/hiccup/package.json +++ b/packages/hiccup/package.json @@ -1,10 +1,14 @@ { "name": "@thi.ng/hiccup", - "version": "2.0.1", + "version": "2.0.5", "description": "HTML/SVG/XML serialization of nested data structures, iterables & closures", "main": "./index.js", "typings": "./index.d.ts", - "repository": "https://github.com/thi-ng/umbrella", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/hiccup", "author": "Karsten Schmidt ", "license": "Apache-2.0", "scripts": { @@ -16,7 +20,7 @@ "test": "rm -rf build && tsc -p test && nyc mocha build/test/*.js" }, "devDependencies": { - "@thi.ng/atom": "^1.3.12", + "@thi.ng/atom": "^1.4.2", "@types/mocha": "^5.2.0", "@types/node": "^10.0.6", "mocha": "^5.1.1", @@ -25,8 +29,8 @@ "typescript": "^2.8.3" }, "dependencies": { - "@thi.ng/checks": "^1.5.3", - "@thi.ng/errors": "^0.1.3" + "@thi.ng/checks": "^1.5.5", + "@thi.ng/errors": "^0.1.4" }, "keywords": [ "clojure", diff --git a/packages/interceptors/CHANGELOG.md b/packages/interceptors/CHANGELOG.md index 8779f00b28..99bf1a63ee 100644 --- a/packages/interceptors/CHANGELOG.md +++ b/packages/interceptors/CHANGELOG.md @@ -3,6 +3,38 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [1.8.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/interceptors@1.8.4...@thi.ng/interceptors@1.8.5) (2018-06-21) + + + + +**Note:** Version bump only for package @thi.ng/interceptors + + +## [1.8.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/interceptors@1.8.3...@thi.ng/interceptors@1.8.4) (2018-06-18) + + + + +**Note:** Version bump only for package @thi.ng/interceptors + + +## [1.8.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/interceptors@1.8.2...@thi.ng/interceptors@1.8.3) (2018-05-30) + + + + +**Note:** Version bump only for package @thi.ng/interceptors + + +## [1.8.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/interceptors@1.8.1...@thi.ng/interceptors@1.8.2) (2018-05-14) + + + + +**Note:** Version bump only for package @thi.ng/interceptors + ## [1.8.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/interceptors@1.8.0...@thi.ng/interceptors@1.8.1) (2018-05-14) diff --git a/packages/interceptors/package.json b/packages/interceptors/package.json index 8a6a4bc54c..b275e0adbc 100644 --- a/packages/interceptors/package.json +++ b/packages/interceptors/package.json @@ -1,10 +1,14 @@ { "name": "@thi.ng/interceptors", - "version": "1.8.1", + "version": "1.8.5", "description": "Interceptor based event bus, side effect & immutable state handling", "main": "./index.js", "typings": "./index.d.ts", - "repository": "https://github.com/thi-ng/umbrella", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/interceptors", "author": "Karsten Schmidt ", "license": "Apache-2.0", "scripts": { @@ -24,11 +28,11 @@ "typescript": "^2.8.3" }, "dependencies": { - "@thi.ng/api": "^4.0.2", - "@thi.ng/atom": "^1.3.12", - "@thi.ng/checks": "^1.5.3", - "@thi.ng/errors": "^0.1.3", - "@thi.ng/paths": "^1.3.8" + "@thi.ng/api": "^4.0.4", + "@thi.ng/atom": "^1.4.2", + "@thi.ng/checks": "^1.5.5", + "@thi.ng/errors": "^0.1.4", + "@thi.ng/paths": "^1.3.10" }, "keywords": [ "ES6", diff --git a/packages/iterators/CHANGELOG.md b/packages/iterators/CHANGELOG.md index 55f883e730..8b4c629542 100644 --- a/packages/iterators/CHANGELOG.md +++ b/packages/iterators/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. + +## [4.1.18](https://github.com/thi-ng/umbrella/compare/@thi.ng/iterators@4.1.17...@thi.ng/iterators@4.1.18) (2018-06-21) + + + + +**Note:** Version bump only for package @thi.ng/iterators + + +## [4.1.17](https://github.com/thi-ng/umbrella/compare/@thi.ng/iterators@4.1.16...@thi.ng/iterators@4.1.17) (2018-06-18) + + + + +**Note:** Version bump only for package @thi.ng/iterators + + +## [4.1.16](https://github.com/thi-ng/umbrella/compare/@thi.ng/iterators@4.1.15...@thi.ng/iterators@4.1.16) (2018-05-14) + + + + +**Note:** Version bump only for package @thi.ng/iterators + ## [4.1.15](https://github.com/thi-ng/umbrella/compare/@thi.ng/iterators@4.1.14...@thi.ng/iterators@4.1.15) (2018-05-14) diff --git a/packages/iterators/package.json b/packages/iterators/package.json index ff82da5366..4389886040 100644 --- a/packages/iterators/package.json +++ b/packages/iterators/package.json @@ -1,10 +1,14 @@ { "name": "@thi.ng/iterators", - "version": "4.1.15", + "version": "4.1.18", "description": "clojure.core inspired, composable ES6 iterators & generators", "main": "./index.js", "typings": "./index.d.ts", - "repository": "https://github.com/thi-ng/umbrella", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/iterators", "author": "Karsten Schmidt ", "license": "Apache-2.0", "scripts": { @@ -24,9 +28,9 @@ "typescript": "^2.8.3" }, "dependencies": { - "@thi.ng/api": "^4.0.2", - "@thi.ng/dcons": "^1.0.2", - "@thi.ng/errors": "^0.1.3" + "@thi.ng/api": "^4.0.4", + "@thi.ng/dcons": "^1.0.5", + "@thi.ng/errors": "^0.1.4" }, "keywords": [ "clojure", diff --git a/packages/paths/CHANGELOG.md b/packages/paths/CHANGELOG.md index 944a3cdad0..ef99e390be 100644 --- a/packages/paths/CHANGELOG.md +++ b/packages/paths/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.3.10](https://github.com/thi-ng/umbrella/compare/@thi.ng/paths@1.3.9...@thi.ng/paths@1.3.10) (2018-06-21) + + + + +**Note:** Version bump only for package @thi.ng/paths + + +## [1.3.9](https://github.com/thi-ng/umbrella/compare/@thi.ng/paths@1.3.8...@thi.ng/paths@1.3.9) (2018-06-18) + + + + +**Note:** Version bump only for package @thi.ng/paths + ## [1.3.8](https://github.com/thi-ng/umbrella/compare/@thi.ng/paths@1.3.7...@thi.ng/paths@1.3.8) (2018-05-14) diff --git a/packages/paths/package.json b/packages/paths/package.json index 2abc40430e..1974edc056 100644 --- a/packages/paths/package.json +++ b/packages/paths/package.json @@ -1,10 +1,14 @@ { "name": "@thi.ng/paths", - "version": "1.3.8", + "version": "1.3.10", "description": "immutable, optimized path-based object property / array accessors", "main": "./index.js", "typings": "./index.d.ts", - "repository": "https://github.com/thi-ng/umbrella", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/paths", "author": "Karsten Schmidt ", "license": "Apache-2.0", "scripts": { @@ -24,8 +28,8 @@ "typescript": "^2.8.3" }, "dependencies": { - "@thi.ng/checks": "^1.5.3", - "@thi.ng/errors": "^0.1.3" + "@thi.ng/checks": "^1.5.5", + "@thi.ng/errors": "^0.1.4" }, "keywords": [ "accessors", diff --git a/packages/pointfree-lang/CHANGELOG.md b/packages/pointfree-lang/CHANGELOG.md index 84a4259479..68a37d1fac 100644 --- a/packages/pointfree-lang/CHANGELOG.md +++ b/packages/pointfree-lang/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.2.15](https://github.com/thi-ng/umbrella/compare/@thi.ng/pointfree-lang@0.2.14...@thi.ng/pointfree-lang@0.2.15) (2018-06-21) + + + + +**Note:** Version bump only for package @thi.ng/pointfree-lang + + +## [0.2.14](https://github.com/thi-ng/umbrella/compare/@thi.ng/pointfree-lang@0.2.13...@thi.ng/pointfree-lang@0.2.14) (2018-06-18) + + + + +**Note:** Version bump only for package @thi.ng/pointfree-lang + + +## [0.2.13](https://github.com/thi-ng/umbrella/compare/@thi.ng/pointfree-lang@0.2.12...@thi.ng/pointfree-lang@0.2.13) (2018-05-14) + + + + +**Note:** Version bump only for package @thi.ng/pointfree-lang + ## [0.2.12](https://github.com/thi-ng/umbrella/compare/@thi.ng/pointfree-lang@0.2.11...@thi.ng/pointfree-lang@0.2.12) (2018-05-14) diff --git a/packages/pointfree-lang/package.json b/packages/pointfree-lang/package.json index e406e72db0..71482737d6 100644 --- a/packages/pointfree-lang/package.json +++ b/packages/pointfree-lang/package.json @@ -1,10 +1,14 @@ { "name": "@thi.ng/pointfree-lang", - "version": "0.2.12", + "version": "0.2.15", "description": "Forth style syntax layer/compiler for the @thi.ng/pointfree DSL", "main": "./index.js", "typings": "./index.d.ts", - "repository": "https://github.com/thi-ng/umbrella", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/pointfree-lang", "author": "Karsten Schmidt ", "license": "Apache-2.0", "scripts": { @@ -26,9 +30,9 @@ "typescript": "^2.8.3" }, "dependencies": { - "@thi.ng/api": "^4.0.2", - "@thi.ng/errors": "^0.1.3", - "@thi.ng/pointfree": "^0.8.1" + "@thi.ng/api": "^4.0.4", + "@thi.ng/errors": "^0.1.4", + "@thi.ng/pointfree": "^0.8.4" }, "keywords": [ "concatenative", diff --git a/packages/pointfree/CHANGELOG.md b/packages/pointfree/CHANGELOG.md index d4132a0f3a..424ca22d1d 100644 --- a/packages/pointfree/CHANGELOG.md +++ b/packages/pointfree/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.8.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/pointfree@0.8.3...@thi.ng/pointfree@0.8.4) (2018-06-21) + + + + +**Note:** Version bump only for package @thi.ng/pointfree + + +## [0.8.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/pointfree@0.8.2...@thi.ng/pointfree@0.8.3) (2018-06-18) + + + + +**Note:** Version bump only for package @thi.ng/pointfree + + +## [0.8.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/pointfree@0.8.1...@thi.ng/pointfree@0.8.2) (2018-05-14) + + + + +**Note:** Version bump only for package @thi.ng/pointfree + ## [0.8.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/pointfree@0.8.0...@thi.ng/pointfree@0.8.1) (2018-05-14) diff --git a/packages/pointfree/package.json b/packages/pointfree/package.json index a89e1af012..0dd7a77c04 100644 --- a/packages/pointfree/package.json +++ b/packages/pointfree/package.json @@ -1,10 +1,14 @@ { "name": "@thi.ng/pointfree", - "version": "0.8.1", + "version": "0.8.4", "description": "Pointfree functional composition / Forth style stack execution engine", "main": "./index.js", "typings": "./index.d.ts", - "repository": "https://github.com/thi-ng/umbrella", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/pointfree", "author": "Karsten Schmidt ", "license": "Apache-2.0", "scripts": { @@ -24,10 +28,10 @@ "typescript": "^2.8.3" }, "dependencies": { - "@thi.ng/api": "^4.0.2", - "@thi.ng/checks": "^1.5.3", - "@thi.ng/equiv": "^0.1.2", - "@thi.ng/errors": "^0.1.3" + "@thi.ng/api": "^4.0.4", + "@thi.ng/checks": "^1.5.5", + "@thi.ng/equiv": "^0.1.5", + "@thi.ng/errors": "^0.1.4" }, "keywords": [ "composition", diff --git a/packages/resolve-map/CHANGELOG.md b/packages/resolve-map/CHANGELOG.md index a5176bc1a6..160d8fde2b 100644 --- a/packages/resolve-map/CHANGELOG.md +++ b/packages/resolve-map/CHANGELOG.md @@ -3,7 +3,52 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. - + +## [3.0.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/resolve-map@3.0.1...@thi.ng/resolve-map@3.0.2) (2018-06-21) + + + + +**Note:** Version bump only for package @thi.ng/resolve-map + + +## [3.0.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/resolve-map@3.0.0...@thi.ng/resolve-map@3.0.1) (2018-06-18) + + + + +**Note:** Version bump only for package @thi.ng/resolve-map + + +# [3.0.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/resolve-map@2.0.6...@thi.ng/resolve-map@3.0.0) (2018-06-07) + + +### Features + +* **resolve-map:** add cycle detection, fix edge cases ([e61c3b5](https://github.com/thi-ng/umbrella/commit/e61c3b5)) +* **resolve-map:** add ES6 destructuring shorthands for function vals ([57f1ed5](https://github.com/thi-ng/umbrella/commit/57f1ed5)) + + +### BREAKING CHANGES + +* **resolve-map:** `resolveMap()` renamed to `resolve()`, update docs + + + + + +## [2.0.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/resolve-map@2.0.5...@thi.ng/resolve-map@2.0.6) (2018-06-06) + + +### Bug Fixes + +* **resolve-map:** add private _resolveDeep ([558f4f8](https://github.com/thi-ng/umbrella/commit/558f4f8)) +* **resolve-map:** also use _resolvePath for plain lookups, optimize ([48c796f](https://github.com/thi-ng/umbrella/commit/48c796f)) + + + + + ## [2.0.5](https://github.com/thi-ng/umbrella/compare/@thi.ng/resolve-map@2.0.4...@thi.ng/resolve-map@2.0.5) (2018-05-14) @@ -11,7 +56,7 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline **Note:** Version bump only for package @thi.ng/resolve-map - + ## [2.0.4](https://github.com/thi-ng/umbrella/compare/@thi.ng/resolve-map@2.0.3...@thi.ng/resolve-map@2.0.4) (2018-05-14) diff --git a/packages/resolve-map/README.md b/packages/resolve-map/README.md index 27381be083..fae5816cd9 100644 --- a/packages/resolve-map/README.md +++ b/packages/resolve-map/README.md @@ -12,6 +12,8 @@ values. This is useful for expressing complex configurations with derived values or computing interrelated values without having to specify the order of computations. +**TL;DR go check out [the examples](#usage-examples)** + It's common practice to use nested JS objects for configuration purposes. Frequently some values in the object are copies or derivatives of other values, which can lead to mistakes during refactoring and / or @@ -25,47 +27,74 @@ supported. ## API -### `resolveMap(obj)` +### `resolve(obj)` + +Visits all key-value pairs or array items in depth-first order, +expands any reference values, mutates the original object and returns +it. Cyclic references are not allowed and will throw an error. +However, refs pointing to other refs are recursively resolved (again, +provided there are no cycles). +Reference values are special strings representing lookup paths of +other values in the object and are prefixed with `@` for relative +refs or `@/` for absolute refs and both using `/` as path separator +(Note: trailing slashes are NOT allowed!). Relative refs are resolved +from the currently visited object and support "../" prefixes to +access any parent levels. Absolute refs are always resolved from the +root level (the original object passed to this function). + +```ts +// `c` references sibling `d` +// `d` references parent `a` +resolve({a: 1, b: {c: "@d", d: "@/a"} }) +// { a: 1, b: { c: 1, d: 1 } } +``` + +Any function values are called using two possible conventions: + +1) If the user function uses ES6 object destructuring for its first + argument, the given object keys are resolved prior to calling the + function and the resolved values provided as first argument (object) + and a general `resolve` function as second argument. +2) If no de-structure form is found in the function's arguments, the + function is only called with `resolve` as argument. -Visits all key-value pairs in depth-first order for given object or -array, expands any reference values, mutates the original object and -returns it. Cyclic references are not allowed or checked for and if -present will cause a stack overflow error. However, refs pointing to -other refs are recursively resolved (again, provided there are no -cycles). +**Important:** Since ES6 var names can't contain special characters, +destructured keys can ALWAYS only be looked up as siblings of the +currently processed key. -Reference values are special strings representing lookup paths of other -values in the object and are prefixed with `@` for relative refs or -`@/` for absolute refs and both using `/` as path separator (Note: -trailing slashes are NOT allowed!). Relative refs are resolved from -currently visited object and support "../" prefixes to access any parent -levels. Absolute refs are always resolved from the root level (the -original object passed to this function). +The `resolve` function provided as arg to the user function accepts a +path (**without `@` prefix**) to look up any other values in the root +object. ```ts -resolveMap({a: 1, b: {c: "@d", d: "@/a"} }) -// { a: 1, b: { c: 1, d: 1 } } +// `c` uses ES6 destructuring form to look up `a` & `b` values +// `d` uses provided resolve fn arg `$` to look up `c` +resolve({a: 1, b: 2, c: ({a,b}) => a + b, d: ($) => $("c") }) +// { a: 1, b: 2, c: 3, d: 3 } + +// last item references item @ index = 2 +resolve([1,2, ($) => $("0") + $("1"), "@2"]) +// [1, 2, 3, 3] ``` -If a value is a function, it is called with a single arg `resolve`, a -function which accepts a path (**without `@` prefix**) to look up other -values. The return value of the user provided function is used as final -value for that key. This mechanism can be used to compute derived values -of other values stored anywhere in the root object. **Function values -will always be called only once.** Therefore, in order to associate a -function as value to a key, it needs to be wrapped with an additional -function, as shown for the `e` key in the example below. Similarly, if -an actual string value should happen to start with `@`, it needs to be -wrapped in a function (see `f` key below). +The return value of the user provided function is used as final value +for that key in the object. This mechanism can be used to compute +derived values of other values stored anywhere in the root object. +**Function values will always be called only once.** Therefore, in order +to associate a function as final value to a key, it MUST be wrapped with +an additional function, as shown for the `e` key in the example below. +Similarly, if an actual string value should happen to start with `@`, it +needs to be wrapped in a function (see `f` key below). ```ts // `a` is derived from 1st array element in `b.d` // `b.c` is looked up from `b.d[0]` // `b.d[1]` is derived from calling `e(2)` // `e` is a wrapped function -res = resolveMap({ - a: (resolve) => resolve("b/c") * 100, - b: { c: "@d/0", d: [2, (resolve) => resolve("../../e")(2) ] }, +// `f` is wrapped to ignore `@` prefix +res = resolve({ + a: ($) => $("b/c") * 100, + b: { c: "@d/0", d: [2, ($) => $("../../e")(2) ] }, e: () => (x) => x * 10, f: () => "@foo", }) @@ -94,40 +123,34 @@ yarn add @thi.ng/resolve-map In this example we construct a graph to compute a number of statistical properties for some numeric input array. The graph is a plain object of possibly dependent functions, which can be specified in any order. Each -function accepts a "resolver" function as argument (`$`) to look up and -execute other computations. Each computation is only executed once. +function uses ES6 object destructuring to look up and execute other +computations in the graph. Each computation is only executed once. ```ts -import { resolveMap } from "@thi.ng/resolve-map"; +import { resolve } from "@thi.ng/resolve-map"; import * as tx from "@thi.ng/transducers"; -// define object of interrelated computations -// the `$` arg passed to each fn is the resolver -// the `src` key is still missing here and will be -// provided later +// define object of interrelated computations to be executed later +// the `src` key used by most functions is still missing here and +// will be injected later as well const stats = { // sequence average - mean: ($) => tx.reduce(tx.mean(), $("src")), + mean: ({src}) => tx.reduce(tx.mean(), src), // sequence range - range: ($) => $("max") - $("min"), + range: ({min,max}) => max - min, // computes sequence min val - min: ($) => tx.reduce(tx.min(), $("src")), + min: ({src}) => tx.reduce(tx.min(), src), // computes sequence max val - max: ($) => tx.reduce(tx.max(), $("src")), + max: ({src}) => tx.reduce(tx.max(), src), // sorted copy - sorted: ($) => [...$("src")].sort((a, b) => a - b), + sorted: ({src}) => [...src].sort((a, b) => a - b), // standard deviation - sd: ($)=> { - const src = $("src"); - const mean = $("mean"); - return Math.sqrt( + sd: ({src, mean})=> + Math.sqrt( tx.transduce(tx.map((x) => Math.pow(x - mean, 2)), tx.add(), src) / - (src.length - 1) - ); - }, + (src.length - 1)), // compute 10th - 90th percentiles - percentiles: ($) => { - const sorted = $("sorted"); + percentiles: ({sorted}) => { return tx.transduce( tx.map((x) => sorted[Math.floor(x / 100 * sorted.length)]), tx.push(), @@ -138,15 +161,15 @@ const stats = { // inject some source data to analyze -// Note: we wrap the data as function to avoid `resolveMap` +// Note: we wrap the data as function to avoid `resolve` // attempting to resolve each array item as well. this is // purely for performance reasons and would also work without // wrapping. // Note 2: If the `stats` graph is meant to be re-usable in // the future you MUST use the spread operator to create a -// shallow copy, because `resolveMap` mutates the given object -resolveMap({...stats, src: () => [ 1, 6, 7, 2, 4, 11, -3 ]}) +// shallow copy, because `resolve` mutates the given object +resolve({...stats, src: () => [ 1, 6, 7, 2, 4, 11, -3 ]}) // { // mean: 4, // range: 14, @@ -162,9 +185,9 @@ resolveMap({...stats, src: () => [ 1, 6, 7, 2, 4, 11, -3 ]}) ### Theme configuration ```typescript -import { resolveMap } from "@thi.ng/resolve-map"; +import { resolve } from "@thi.ng/resolve-map"; -resolveMap({ +resolve({ colors: { bg: "white", text: "black", @@ -177,13 +200,13 @@ resolveMap({ bg: "@/colors/text", label: "@/colors/bg", // resolve with abs path inside fn - fontsize: (resolve) => `${resolve("/main/fontsizes/0")}px`, + fontsize: ($) => `${$("/main/fontsizes/0")}px`, }, buttonPrimary: { bg: "@/colors/selected", label: "@/button/label", // resolve with relative path inside fn - fontsize: (resolve) => `${resolve("../main/fontsizes/2")}px`, + fontsize: ($) => `${$("../main/fontsizes/2")}px`, } }); // { diff --git a/packages/resolve-map/package.json b/packages/resolve-map/package.json index 9c1c6e365b..b819cd5cac 100644 --- a/packages/resolve-map/package.json +++ b/packages/resolve-map/package.json @@ -1,10 +1,14 @@ { "name": "@thi.ng/resolve-map", - "version": "2.0.5", + "version": "3.0.2", "description": "DAG resolution of vanilla objects & arrays with internally linked values", "main": "./index.js", "typings": "./index.d.ts", - "repository": "https://github.com/thi-ng/umbrella", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/resolve-map", "author": "Karsten Schmidt ", "license": "Apache-2.0", "scripts": { @@ -22,9 +26,9 @@ "typescript": "^2.8.3" }, "dependencies": { - "@thi.ng/checks": "^1.5.3", - "@thi.ng/errors": "^0.1.3", - "@thi.ng/paths": "^1.3.8" + "@thi.ng/checks": "^1.5.5", + "@thi.ng/errors": "^0.1.4", + "@thi.ng/paths": "^1.3.10" }, "keywords": [ "configuration", diff --git a/packages/resolve-map/src/index.ts b/packages/resolve-map/src/index.ts index cb5b4eadbb..72b81846d2 100644 --- a/packages/resolve-map/src/index.ts +++ b/packages/resolve-map/src/index.ts @@ -7,47 +7,80 @@ import { getIn, mutIn } from "@thi.ng/paths"; const SEMAPHORE = Symbol("SEMAPHORE"); +const RE_ARGS = /^(function\s+\w+)?\s*\(\{([\w\s,]+)\}/ + +export type ResolveFn = (path: string) => any; + +export type LookupPath = PropertyKey[]; + /** - * Visits all key-value pairs in depth-first order for given object or - * array, expands any reference values, mutates the original object and - * returns it. Cyclic references are not allowed or checked for and if - * present will cause a stack overflow error. However, refs pointing to - * other refs are recursively resolved (again, provided there are no - * cycles). + * Visits all key-value pairs or array items in depth-first order, + * expands any reference values, mutates the original object and returns + * it. Cyclic references are not allowed and will throw an error. + * However, refs pointing to other refs are recursively resolved (again, + * provided there are no cycles). * * Reference values are special strings representing lookup paths of * other values in the object and are prefixed with `@` for relative * refs or `@/` for absolute refs and both using `/` as path separator * (Note: trailing slashes are NOT allowed!). Relative refs are resolved - * from currently visited object and support "../" prefixes to access - * any parent levels. Absolute refs are always resolved from the root - * level (the original object passed to this function). + * from the currently visited object and support "../" prefixes to + * access any parent levels. Absolute refs are always resolved from the + * root level (the original object passed to this function). * * ```ts - * resolveMap({a: 1, b: {c: "@d", d: "@/a"} }) + * // `c` references sibling `d` + * // `d` references parent `a` + * resolve({a: 1, b: {c: "@d", d: "@/a"} }) * // { a: 1, b: { c: 1, d: 1 } } * ``` * - * If a value is a function, it is called with a single arg `resolve`, a - * function which accepts a path (**without `@` prefix**) to look up - * other values. The return value of the user provided function is used - * as final value for that key. This mechanism can be used to compute + * Any function values are called using two possible conventions: + * + * 1) If the user function uses ES6 object destructuring for its first + * argument, the given object keys are resolved prior to calling the + * function and the resolved values provided as first argument + * (object) and a general `resolve` function as second argument. + * 2) If no de-structure form is found in the function's arguments, the + * function is only called with `resolve` as argument. + * + * **Important:** Since ES6 var names can't contain special characters, + * destructured keys can ALWAYS only be looked up as siblings of the + * currently processed key. + * + * The `resolve` function provided as arg to the user function accepts a + * path (**without `@` prefix**) to look up any other values in the root + * object. + * + * ``` + * // `c` uses ES6 destructuring form to look up `a` & `b` values + * // `d` uses provided resolve fn arg `$` to look up `c` + * resolve({a: 1, b: 2, c: ({a,b}) => a + b, d: ($) => $("c") }) + * // { a: 1, b: 2, c: 3, d: 3 } + * + * // last item references item @ index = 2 + * resolve([1,2, ($) => $("0") + $("1"), "@2"]) + * // [1, 2, 3, 3] + * ``` + * + * The return value of the user provided function is used as final value + * for that key in the object. This mechanism can be used to compute * derived values of other values stored anywhere in the root object. * **Function values will always be called only once.** Therefore, in - * order to associate a function as value to a key, it needs to be + * order to associate a function as final value to a key, it MUST be * wrapped with an additional function, as shown for the `e` key in the * example below. Similarly, if an actual string value should happen to * start with `@`, it needs to be wrapped in a function (see `f` key * below). * - * ```ts + * ``` * // `a` is derived from 1st array element in `b.d` * // `b.c` is looked up from `b.d[0]` * // `b.d[1]` is derived from calling `e(2)` * // `e` is a wrapped function - * res = resolveMap({ - * a: (resolve) => resolve("b/c") * 100, - * b: { c: "@d/0", d: [2, (resolve) => resolve("../../e")(2) ] }, + * res = resolve({ + * a: ($) => $("b/c") * 100, + * b: { c: "@d/0", d: [2, ($) => $("../../e")(2) ] }, * e: () => (x) => x * 10, * f: () => "@foo", * }) @@ -57,72 +90,188 @@ const SEMAPHORE = Symbol("SEMAPHORE"); * // 20 * ``` * - * `resolveMap` mutates the original object and returns it. User code - * should NEVER provide any of the optional args (these are only used - * for internal recursion purposes). - * - * @param obj + * @param root */ -export const resolveMap = (obj: any, root?: any, path: PropertyKey[] = [], resolved: any = {}) => { +export const resolve = (root: any) => { + if (isPlainObject(root)) { + return resolveMap(root); + } else if (isArray(root)) { + return resolveArray(root); + } + return root; +}; + +const resolveMap = (obj: any, root?: any, path: LookupPath = [], resolved: any = {}, stack: string[] = []) => { root = root || obj; for (let k in obj) { - _resolve(root, [...path, k], resolved); + _resolve(root, [...path, k], resolved, stack); } return obj; -} +}; + +const resolveArray = (arr: any[], root?: any, path: LookupPath = [], resolved: any = {}, stack: string[] = []) => { + root = root || arr; + for (let k = 0, n = arr.length; k < n; k++) { + _resolve(root, [...path, k], resolved, stack); + } + return arr; +}; /** - * Like `resolveMap`, but for arrays. + * The actual recursive resolution mechanism. Takes root object, key + * path, helper object for marking visited keys and a stack of currently + * active lookups. The latter is used for cycle detection and `_resolve` + * will throw an error if a cycle has been detected. * - * @param arr * @param root * @param path * @param resolved + * @param stack */ -const resolveArray = (arr: any[], root?: any, path: PropertyKey[] = [], resolved: any = {}) => { - root = root || arr; - for (let k = 0, n = arr.length; k < n; k++) { - _resolve(root, [...path, k], resolved); +const _resolve = (root: any, path: LookupPath, resolved: any, stack: string[]) => { + const pathID = path.join("/"); + if (stack.indexOf(pathID) >= 0) { + illegalArgs(`cyclic references not allowed: ${pathID}`); } - return arr; -} - -const _resolve = (root: any, path: PropertyKey[], resolved: any) => { - let v = getIn(root, path), rv = SEMAPHORE; - const pp = path.join("/"); - if (!resolved[pp]) { - if (isString(v) && v.charAt(0) === "@") { - rv = _resolve(root, absPath(path, v), resolved); - } else if (isPlainObject(v)) { - resolveMap(v, root, path, resolved); + // console.log(pp, resolved[pp], stack); + let v = getIn(root, path); + if (!resolved[pathID]) { + let res = SEMAPHORE; + stack.push(pathID); + if (isPlainObject(v)) { + resolveMap(v, root, path, resolved, stack); } else if (isArray(v)) { - resolveArray(v, root, path, resolved); + resolveArray(v, root, path, resolved, stack); + } else if (isString(v) && v.charAt(0) === "@") { + res = _resolve(root, absPath(path, v), resolved, stack); } else if (isFunction(v)) { - rv = v((p: string) => _resolve(root, absPath(path, p, 0), resolved)); + res = resolveFunction(v, (p: string) => _resolve(root, absPath(path, p, 0), resolved, stack), pathID, resolved); + } else if (v === undefined) { + v = resolvePath(root, path, resolved, stack); } - if (rv !== SEMAPHORE) { - mutIn(root, path, rv); - v = rv; + if (res !== SEMAPHORE) { + mutIn(root, path, res); + v = res; } - resolved[pp] = true; + resolved[pathID] = true; + stack.pop(); } return v; -} +}; -const absPath = (curr: PropertyKey[], q: string, idx = 1): PropertyKey[] => { - if (q.charAt(idx) === "/") { - return q.substr(idx + 1).split("/"); +/** + * Repeatedly calls `_resolve` by stepwise descending along given path + * and returns final value. This is to ensure full resolution of deeper + * values created by functions at intermediate tree levels. + * + * E.g. given: + * + * ``` + * {a: () => ({b: {c: 1}}), d: "@/a/b/c" } + * => + * { a: { b: { c: 1 } }, d: 1 } + * ``` + * + * @param root + * @param path + * @param resolved + */ +const resolvePath = (root: any, path: LookupPath, resolved: any, stack: string[] = []) => { + // temporarily remove current path to avoid cycle detection + let pathID = stack.pop() + let v; + for (let i = 1, n = path.length; i <= n; i++) { + v = _resolve(root, path.slice(0, i), resolved, stack); + } + // restore + stack.push(pathID); + return v; +}; + +/** + * Resolution helper for function values. Checks if the user function + * uses ES6 object destructuring for its first argument and if so + * resolves the given keys before calling the function and provides + * their values as first arg. If no de-structure form is found, calls + * function only with `resolve` as argument. + * + * If the user function returns an array or plain object, all of its + * nested values are marked as resolved. + * + * See `resolve` comments for further details. + * + * @param fn + * @param resolve + * @param pathID current base path for marking + * @param resolved + */ +const resolveFunction = (fn: (x: any, r?: ResolveFn) => any, resolve: ResolveFn, pathID: string, resolved: any) => { + const match = RE_ARGS.exec(fn.toString()); + let res; + if (match) { + const args = match[2] + .replace(/\s/g, "") + .split(/,/g) + .reduce((acc, k) => (acc[k] = resolve(k), acc), {}); + res = fn(args, resolve); + } else { + res = fn(resolve); + + } + markResolved(res, pathID, resolved); + return res; +}; + +const markResolved = (v: any, path: string, resolved: any) => { + resolved[path] = true; + if (isPlainObject(v)) { + markObjResolved(v, path, resolved); + } + else if (isArray(v)) { + markArrayResolved(v, path, resolved); + } +}; + +const markObjResolved = (obj: any, path: string, resolved: any) => { + let v, p; + for (let k in obj) { + v = obj[k]; + p = path + "/" + k; + markResolved(v, p, resolved); + } +}; + +const markArrayResolved = (arr: any[], path: string, resolved: any) => { + let v, p; + for (let i = 0, n = arr.length; i < n; i++) { + v = arr[i]; + p = path + "/" + i; + markResolved(v, p, resolved); + } +}; + +/** + * Takes the path for the current key and a lookup path string. Converts + * the possibly relative lookup path into its absolute form. + * + * @param curr + * @param path + * @param idx + */ +export const absPath = (curr: LookupPath, path: string, idx = 1): PropertyKey[] => { + if (path.charAt(idx) === "/") { + return path.substr(idx + 1).split("/"); } curr = curr.slice(0, curr.length - 1); - const sub = q.substr(idx).split("/"); + const sub = path.substr(idx).split("/"); for (let i = 0, n = sub.length; i < n; i++) { if (sub[i] === "..") { - !curr.length && illegalArgs(`invalid lookup path`); + !curr.length && illegalArgs(`invalid lookup path: ${path}`); curr.pop(); } else { return curr.concat(sub.slice(i)); } } - !curr.length && illegalArgs(`invalid lookup path`); + !curr.length && illegalArgs(`invalid lookup path: ${path}`); return curr; -} +}; diff --git a/packages/resolve-map/test/index.ts b/packages/resolve-map/test/index.ts index ba4bcca815..7d12ee0324 100644 --- a/packages/resolve-map/test/index.ts +++ b/packages/resolve-map/test/index.ts @@ -1,57 +1,60 @@ +import * as tx from "@thi.ng/transducers"; import * as assert from "assert"; -import { resolveMap } from "../src/index"; + +import { resolve } from "../src/index"; describe("resolve-map", () => { it("simple", () => { assert.deepEqual( - resolveMap({ a: 1, b: "@a" }), + resolve({ a: 1, b: "@a" }), { a: 1, b: 1 } ); }); it("linked refs", () => { assert.deepEqual( - resolveMap({ a: "@c", b: "@a", c: 1 }), + resolve({ a: "@c", b: "@a", c: 1 }), { a: 1, b: 1, c: 1 } ); }); it("array refs", () => { assert.deepEqual( - resolveMap({ a: "@c/1", b: "@a", c: [1, 2] }), + resolve({ a: "@c/1", b: "@a", c: [1, 2] }), { a: 2, b: 2, c: [1, 2] } ); }); it("abs vs rel refs", () => { assert.deepEqual( - resolveMap({ a1: { b: 1, c: "@b" }, a2: { b: 2, c: "@b" }, a3: { b: 3, c: "@/a1/b" } }), + resolve({ a1: { b: 1, c: "@b" }, a2: { b: 2, c: "@b" }, a3: { b: 3, c: "@/a1/b" } }), { a1: { b: 1, c: 1 }, a2: { b: 2, c: 2 }, a3: { b: 3, c: 1 } } ); }); it("rel parent refs", () => { assert.deepEqual( - resolveMap({ a: { b: { c: "@../c/d", d: "@c", e: "@/c/d" }, c: { d: 1 } }, c: { d: 10 } }), + resolve({ a: { b: { c: "@../c/d", d: "@c", e: "@/c/d" }, c: { d: 1 } }, c: { d: 10 } }), { a: { b: { c: 1, d: 1, e: 10 }, c: { d: 1 } }, c: { d: 10 } } ); }) it("cycles", () => { - assert.throws(() => resolveMap({ a: "@a" })); - assert.throws(() => resolveMap({ a: { b: "@b" } })); - assert.throws(() => resolveMap({ a: { b: "@/a" } })); - assert.throws(() => resolveMap({ a: { b: "@/a/b" } })); - assert.throws(() => resolveMap({ a: "@b", b: "@a" })); + assert.throws(() => resolve({ a: "@a" })); + assert.throws(() => resolve({ a: { b: "@b" } })); + // console.log(resolve({ a: { b: "@/a" } })); + assert.throws(() => resolve({ a: { b: "@/a" } })); + assert.throws(() => resolve({ a: { b: "@/a/b" } })); + assert.throws(() => resolve({ a: "@b", b: "@a" })); }); it("function refs", () => { assert.deepEqual( - resolveMap({ a: (x) => x("b/c") * 10, b: { c: "@d", d: "@/e" }, e: () => 1 }), + resolve({ a: (x) => x("b/c") * 10, b: { c: "@d", d: "@/e" }, e: () => 1 }), { a: 10, b: { c: 1, d: 1 }, e: 1 } ); - const res = resolveMap({ a: (x) => x("b/c")() * 10, b: { c: "@d", d: "@/e" }, e: () => () => 1 }); + const res = resolve({ a: (x) => x("b/c")() * 10, b: { c: "@d", d: "@/e" }, e: () => () => 1 }); assert.equal(res.a, 10); assert.strictEqual(res.b.c, res.e); assert.strictEqual(res.b.d, res.e); @@ -61,9 +64,50 @@ describe("resolve-map", () => { it("function resolves only once", () => { let n = 0; assert.deepEqual( - resolveMap({ a: (x) => x("b/c"), b: { c: "@d", d: "@/e" }, e: () => (n++ , 1) }), + resolve({ a: (x) => x("b/c"), b: { c: "@d", d: "@/e" }, e: () => (n++ , 1) }), { a: 1, b: { c: 1, d: 1 }, e: 1 } ); assert.equal(n, 1); - }) + }); + + it("destructure", () => { + const stats = { + // sequence average + mean: ({ src }) => tx.reduce(tx.mean(), src), + // sequence range + range: ({ min, max }) => max - min, + // computes sequence min val + min: ({ src }) => tx.reduce(tx.min(), src), + // computes sequence max val + max: ({ src }) => tx.reduce(tx.max(), src), + // sorted copy + sorted: ({ src }) => [...src].sort((a, b) => a - b), + // standard deviation + sd: ({ src, mean }) => + Math.sqrt( + tx.transduce(tx.map((x: number) => Math.pow(x - mean, 2)), tx.add(), src) / + (src.length - 1)), + // compute 10th - 90th percentiles + percentiles: ({ sorted }) => { + return tx.transduce( + tx.map((x: number) => sorted[Math.floor(x / 100 * sorted.length)]), + tx.push(), + tx.range(10, 100, 5) + ); + } + }; + assert.deepEqual( + resolve({ ...stats, src: () => [1, 6, 7, 2, 4, 11, -3] }), + { + mean: 4, + range: 14, + min: -3, + max: 11, + sorted: [-3, 1, 2, 4, 6, 7, 11], + sd: 4.546060565661952, + percentiles: [-3, 1, 1, 1, 2, 2, 2, 4, 4, 4, 6, 6, 6, 7, 7, 7, 11, 11], + src: [1, 6, 7, 2, 4, 11, -3] + } + ); + }); }); diff --git a/packages/rle-pack/CHANGELOG.md b/packages/rle-pack/CHANGELOG.md index 6c3f62c1c2..05bcead5ec 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.22](https://github.com/thi-ng/umbrella/compare/@thi.ng/rle-pack@0.2.21...@thi.ng/rle-pack@0.2.22) (2018-06-21) + + + + +**Note:** Version bump only for package @thi.ng/rle-pack + ## [0.2.21](https://github.com/thi-ng/umbrella/compare/@thi.ng/rle-pack@0.2.20...@thi.ng/rle-pack@0.2.21) (2018-05-14) diff --git a/packages/rle-pack/package.json b/packages/rle-pack/package.json index 675483c4d3..9d4d4bd616 100644 --- a/packages/rle-pack/package.json +++ b/packages/rle-pack/package.json @@ -1,10 +1,14 @@ { "name": "@thi.ng/rle-pack", - "version": "0.2.21", + "version": "0.2.22", "description": "Binary run-length encoding packer w/ flexible repeat bit widths", "main": "./index.js", "typings": "./index.d.ts", - "repository": "https://github.com/thi-ng/umbrella", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/rle-pack", "author": "Karsten Schmidt ", "license": "Apache-2.0", "scripts": { @@ -25,7 +29,7 @@ "typescript": "^2.8.3" }, "dependencies": { - "@thi.ng/bitstream": "^0.4.12" + "@thi.ng/bitstream": "^0.4.13" }, "keywords": [ "binary", diff --git a/packages/router/CHANGELOG.md b/packages/router/CHANGELOG.md index 7326be2e2c..38dee7deb6 100644 --- a/packages/router/CHANGELOG.md +++ b/packages/router/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.18](https://github.com/thi-ng/umbrella/compare/@thi.ng/router@0.1.17...@thi.ng/router@0.1.18) (2018-06-21) + + + + +**Note:** Version bump only for package @thi.ng/router + + +## [0.1.17](https://github.com/thi-ng/umbrella/compare/@thi.ng/router@0.1.16...@thi.ng/router@0.1.17) (2018-06-18) + + + + +**Note:** Version bump only for package @thi.ng/router + + +## [0.1.16](https://github.com/thi-ng/umbrella/compare/@thi.ng/router@0.1.15...@thi.ng/router@0.1.16) (2018-05-14) + + + + +**Note:** Version bump only for package @thi.ng/router + ## [0.1.15](https://github.com/thi-ng/umbrella/compare/@thi.ng/router@0.1.14...@thi.ng/router@0.1.15) (2018-05-14) diff --git a/packages/router/package.json b/packages/router/package.json index c5b9983bab..10e98f14bc 100644 --- a/packages/router/package.json +++ b/packages/router/package.json @@ -1,10 +1,14 @@ { "name": "@thi.ng/router", - "version": "0.1.15", + "version": "0.1.18", "description": "Generic router for browser & non-browser based applications", "main": "./index.js", "typings": "./index.d.ts", - "repository": "https://github.com/thi-ng/umbrella", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/router", "author": "Karsten Schmidt ", "license": "Apache-2.0", "scripts": { @@ -23,10 +27,10 @@ "typescript": "^2.8.3" }, "dependencies": { - "@thi.ng/api": "^4.0.2", - "@thi.ng/checks": "^1.5.3", - "@thi.ng/equiv": "^0.1.2", - "@thi.ng/errors": "^0.1.3" + "@thi.ng/api": "^4.0.4", + "@thi.ng/checks": "^1.5.5", + "@thi.ng/equiv": "^0.1.5", + "@thi.ng/errors": "^0.1.4" }, "keywords": [ "declarative", diff --git a/packages/rstream-csp/CHANGELOG.md b/packages/rstream-csp/CHANGELOG.md index e0c0209d16..b9c95246e3 100644 --- a/packages/rstream-csp/CHANGELOG.md +++ b/packages/rstream-csp/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. + +## [0.1.78](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-csp@0.1.77...@thi.ng/rstream-csp@0.1.78) (2018-06-21) + + + + +**Note:** Version bump only for package @thi.ng/rstream-csp + + +## [0.1.77](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-csp@0.1.76...@thi.ng/rstream-csp@0.1.77) (2018-06-19) + + + + +**Note:** Version bump only for package @thi.ng/rstream-csp + + +## [0.1.76](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-csp@0.1.75...@thi.ng/rstream-csp@0.1.76) (2018-06-18) + + + + +**Note:** Version bump only for package @thi.ng/rstream-csp + + +## [0.1.75](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-csp@0.1.74...@thi.ng/rstream-csp@0.1.75) (2018-05-30) + + + + +**Note:** Version bump only for package @thi.ng/rstream-csp + + +## [0.1.74](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-csp@0.1.73...@thi.ng/rstream-csp@0.1.74) (2018-05-20) + + + + +**Note:** Version bump only for package @thi.ng/rstream-csp + + +## [0.1.73](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-csp@0.1.72...@thi.ng/rstream-csp@0.1.73) (2018-05-14) + + + + +**Note:** Version bump only for package @thi.ng/rstream-csp + ## [0.1.72](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-csp@0.1.71...@thi.ng/rstream-csp@0.1.72) (2018-05-14) diff --git a/packages/rstream-csp/package.json b/packages/rstream-csp/package.json index f5cb46be6a..b1e6bbc0b1 100644 --- a/packages/rstream-csp/package.json +++ b/packages/rstream-csp/package.json @@ -1,10 +1,14 @@ { "name": "@thi.ng/rstream-csp", - "version": "0.1.72", + "version": "0.1.78", "description": "@thi.ng/csp bridge module for @thi.ng/rstream", "main": "./index.js", "typings": "./index.d.ts", - "repository": "https://github.com/thi-ng/umbrella", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/rstream-csp", "author": "Karsten Schmidt ", "license": "Apache-2.0", "scripts": { @@ -24,8 +28,8 @@ "typescript": "^2.8.3" }, "dependencies": { - "@thi.ng/csp": "^0.3.41", - "@thi.ng/rstream": "^1.6.13" + "@thi.ng/csp": "^0.3.45", + "@thi.ng/rstream": "^1.8.0" }, "keywords": [ "bridge", diff --git a/packages/rstream-dot/CHANGELOG.md b/packages/rstream-dot/CHANGELOG.md index 96909a4b6b..280414f472 100644 --- a/packages/rstream-dot/CHANGELOG.md +++ b/packages/rstream-dot/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. + +## [0.2.17](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-dot@0.2.16...@thi.ng/rstream-dot@0.2.17) (2018-06-21) + + + + +**Note:** Version bump only for package @thi.ng/rstream-dot + + +## [0.2.16](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-dot@0.2.15...@thi.ng/rstream-dot@0.2.16) (2018-06-19) + + + + +**Note:** Version bump only for package @thi.ng/rstream-dot + + +## [0.2.15](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-dot@0.2.14...@thi.ng/rstream-dot@0.2.15) (2018-06-18) + + + + +**Note:** Version bump only for package @thi.ng/rstream-dot + + +## [0.2.14](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-dot@0.2.13...@thi.ng/rstream-dot@0.2.14) (2018-05-30) + + + + +**Note:** Version bump only for package @thi.ng/rstream-dot + + +## [0.2.13](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-dot@0.2.12...@thi.ng/rstream-dot@0.2.13) (2018-05-20) + + + + +**Note:** Version bump only for package @thi.ng/rstream-dot + + +## [0.2.12](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-dot@0.2.11...@thi.ng/rstream-dot@0.2.12) (2018-05-14) + + + + +**Note:** Version bump only for package @thi.ng/rstream-dot + ## [0.2.11](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-dot@0.2.10...@thi.ng/rstream-dot@0.2.11) (2018-05-14) diff --git a/packages/rstream-dot/package.json b/packages/rstream-dot/package.json index 8252842c2c..6f97d99720 100644 --- a/packages/rstream-dot/package.json +++ b/packages/rstream-dot/package.json @@ -1,10 +1,14 @@ { "name": "@thi.ng/rstream-dot", - "version": "0.2.11", + "version": "0.2.17", "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", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/rstream-dot", "author": "Karsten Schmidt ", "license": "Apache-2.0", "scripts": { @@ -24,7 +28,7 @@ "typescript": "^2.8.3" }, "dependencies": { - "@thi.ng/rstream": "^1.6.13" + "@thi.ng/rstream": "^1.8.0" }, "keywords": [ "conversion", diff --git a/packages/rstream-gestures/CHANGELOG.md b/packages/rstream-gestures/CHANGELOG.md index f4399bee44..e7e2db6ff8 100644 --- a/packages/rstream-gestures/CHANGELOG.md +++ b/packages/rstream-gestures/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. + +## [0.3.14](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-gestures@0.3.13...@thi.ng/rstream-gestures@0.3.14) (2018-06-21) + + + + +**Note:** Version bump only for package @thi.ng/rstream-gestures + + +## [0.3.13](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-gestures@0.3.12...@thi.ng/rstream-gestures@0.3.13) (2018-06-19) + + + + +**Note:** Version bump only for package @thi.ng/rstream-gestures + + +## [0.3.12](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-gestures@0.3.11...@thi.ng/rstream-gestures@0.3.12) (2018-06-18) + + + + +**Note:** Version bump only for package @thi.ng/rstream-gestures + + +## [0.3.11](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-gestures@0.3.10...@thi.ng/rstream-gestures@0.3.11) (2018-05-30) + + + + +**Note:** Version bump only for package @thi.ng/rstream-gestures + + +## [0.3.10](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-gestures@0.3.9...@thi.ng/rstream-gestures@0.3.10) (2018-05-20) + + + + +**Note:** Version bump only for package @thi.ng/rstream-gestures + + +## [0.3.9](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-gestures@0.3.8...@thi.ng/rstream-gestures@0.3.9) (2018-05-14) + + + + +**Note:** Version bump only for package @thi.ng/rstream-gestures + ## [0.3.8](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-gestures@0.3.7...@thi.ng/rstream-gestures@0.3.8) (2018-05-14) diff --git a/packages/rstream-gestures/package.json b/packages/rstream-gestures/package.json index d3be430e9e..734843895d 100644 --- a/packages/rstream-gestures/package.json +++ b/packages/rstream-gestures/package.json @@ -1,10 +1,14 @@ { "name": "@thi.ng/rstream-gestures", - "version": "0.3.8", + "version": "0.3.14", "description": "Unified mouse, mouse wheel & single-touch event stream abstraction", "main": "./index.js", "typings": "./index.d.ts", - "repository": "https://github.com/thi-ng/umbrella", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/rstream-gestures", "author": "Karsten Schmidt ", "license": "Apache-2.0", "scripts": { @@ -24,9 +28,9 @@ "typescript": "^2.8.3" }, "dependencies": { - "@thi.ng/api": "^4.0.2", - "@thi.ng/rstream": "^1.6.13", - "@thi.ng/transducers": "^1.10.1" + "@thi.ng/api": "^4.0.4", + "@thi.ng/rstream": "^1.8.0", + "@thi.ng/transducers": "^1.11.1" }, "keywords": [ "dataflow", diff --git a/packages/rstream-graph/CHANGELOG.md b/packages/rstream-graph/CHANGELOG.md index 806be879b4..28006105c3 100644 --- a/packages/rstream-graph/CHANGELOG.md +++ b/packages/rstream-graph/CHANGELOG.md @@ -3,6 +3,110 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [2.1.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-graph@2.0.3...@thi.ng/rstream-graph@2.1.0) (2018-06-21) + + +### Features + +* **rstream-graph:** add stop(), fix `const` inputs, update docs/readme ([d0b1e5c](https://github.com/thi-ng/umbrella/commit/d0b1e5c)) + + + + + +## [2.0.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-graph@2.0.2...@thi.ng/rstream-graph@2.0.3) (2018-06-19) + + + + +**Note:** Version bump only for package @thi.ng/rstream-graph + + +## [2.0.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-graph@2.0.1...@thi.ng/rstream-graph@2.0.2) (2018-06-18) + + + + +**Note:** Version bump only for package @thi.ng/rstream-graph + + +## [2.0.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-graph@2.0.0...@thi.ng/rstream-graph@2.0.1) (2018-06-07) + + +### Bug Fixes + +* **rstream-graph:** rename `resolveMap` => `resolve` due to upstream changes ([0fc2305](https://github.com/thi-ng/umbrella/commit/0fc2305)) + + + + + +# [2.0.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-graph@1.1.2...@thi.ng/rstream-graph@2.0.0) (2018-06-06) + + +### Features + +* **rstream-graph:** add full/optional support for multiple node outputs ([f2e0df2](https://github.com/thi-ng/umbrella/commit/f2e0df2)) +* **rstream-graph:** update NodeOutput, support multiple handlers ([be21c4c](https://github.com/thi-ng/umbrella/commit/be21c4c)) + + +### BREAKING CHANGES + +* **rstream-graph:** update NodeSpec format & graph initialization + +- add new types/interfaces +- non-destructive initGraph() behavior +- update & refactor nodeFromSpec() +- update addNode/removeNode() +- update tests & docs + + + + + +## [1.1.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-graph@1.1.1...@thi.ng/rstream-graph@1.1.2) (2018-05-30) + + + + +**Note:** Version bump only for package @thi.ng/rstream-graph + + +## [1.1.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-graph@1.1.0...@thi.ng/rstream-graph@1.1.1) (2018-05-21) + + + + +**Note:** Version bump only for package @thi.ng/rstream-graph + + +# [1.1.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-graph@1.0.17...@thi.ng/rstream-graph@1.1.0) (2018-05-21) + + +### Features + +* **rstream-graph:** update types, initGraph(), node1(), add tests ([0818498](https://github.com/thi-ng/umbrella/commit/0818498)) + + + + + +## [1.0.17](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-graph@1.0.16...@thi.ng/rstream-graph@1.0.17) (2018-05-20) + + + + +**Note:** Version bump only for package @thi.ng/rstream-graph + + +## [1.0.16](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-graph@1.0.15...@thi.ng/rstream-graph@1.0.16) (2018-05-14) + + + + +**Note:** Version bump only for package @thi.ng/rstream-graph + ## [1.0.15](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-graph@1.0.14...@thi.ng/rstream-graph@1.0.15) (2018-05-14) diff --git a/packages/rstream-graph/README.md b/packages/rstream-graph/README.md index 3595044cc9..7823a7c973 100644 --- a/packages/rstream-graph/README.md +++ b/packages/rstream-graph/README.md @@ -9,10 +9,17 @@ This project is part of the Declarative, reactive dataflow graph construction using [@thi.ng/rstream](https://github.com/thi-ng/umbrella/tree/master/packages/rstream), -[@thi.ng/atom](https://github.com/thi-ng/umbrella/tree/master/packages/atom) and [@thi.ng/transducers](https://github.com/thi-ng/umbrella/tree/master/packages/transducers) +[@thi.ng/atom](https://github.com/thi-ng/umbrella/tree/master/packages/atom) +and +[@thi.ng/transducers](https://github.com/thi-ng/umbrella/tree/master/packages/transducers) primitives. -Stream subscription types act as graph nodes and attached transducers as graph edges, transforming data for downstream consumers / nodes. Theoretically, allows cycles and is not restricted to DAG topologies, but care must be taken to avoid CPU hogging (user's responsibility). +Stream subscription types act as graph nodes and attached transducers as +graph edges, transforming data for downstream consumers / nodes. +Theoretically, allows cycles and is not restricted to DAG topologies, +but care must be taken to avoid CPU hogging if those cycles are causing +synchronous computation loops (it the user's responsibility to avoid +these). ## Installation @@ -34,10 +41,10 @@ yarn add @thi.ng/rstream-graph Small(ish), fully commented projects can be found in the `/examples` folder: -* **Dataflow circles** - - [Source](https://github.com/thi-ng/umbrella/tree/master/examples/rstream-dataflow), - [Live version](http://demo.thi.ng/umbrella/rstream-dataflow) * **SVG grid gen** - + [Source](https://github.com/thi-ng/umbrella/tree/master/examples/rstream-grid), + [Live version](http://demo.thi.ng/umbrella/rstream-grid) +* **Dataflow circles** - [Source](https://github.com/thi-ng/umbrella/tree/master/examples/rstream-dataflow), [Live version](http://demo.thi.ng/umbrella/rstream-dataflow) @@ -67,7 +74,7 @@ const graph = rsg.initGraph(state, { mul: { fn: rsg.mul, ins: { - a: { stream: "add" }, + a: { stream: "/add/node" }, b: { stream: () => rs.fromIterable([10, 20, 30]) } }, } @@ -87,7 +94,89 @@ setTimeout(() => state.resetIn("a", 10), 1000); // result: 360 ``` -Please documentation in the source code for further details. +## Graph specification + +A dataflow graph spec is a plain object where keys are node names and +their values are `NodeSpec`s, defining a node's inputs, outputs and the +operation to be applied to produce one or more result streams. + +```ts +interface NodeSpec { + fn: NodeFactory; + ins: IObjectOf; + outs?: IObjectOf; +} +``` + +Specification for a single "node" in the dataflow graph. Nodes here are +actually just wrappers of streams / subscriptions (or generally any form +of +[@thi.ng/rstream](https://github.com/thi-ng/umbrella/tree/master/packages/rstream) +`ISubscribable`), usually with an associated transducer to transform / +combine the inputs and produce values for the node's result stream. + +The `fn` function is responsible to produce such a stream transformer +construct and the library provides several general purpose helpers for +that purpose. The keys used to specify inputs in the `ins` object are +dictated by the actual node `fn` used. Most node functions with multiple +inputs will be implemented as +[`StreamSync`](https://github.com/thi-ng/umbrella/tree/master/packages/rstream/src/stream-sync.ts) +instances and the input IDs are used to locally rename input streams +within the `StreamSync` container. Alo see `initGraph` and +`nodeFromSpec` (in +[`/src/nodes.ts`](https://github.com/thi-ng/umbrella/tree/master/packages/rstream-graph/src/nodes.ts) +for more details how these specs are compiled into stream constructs. + +Specification for a single input, which can be given in different ways: + +1) Create a stream of value changes at given path in state + [Atom](https://github.com/thi-ng/umbrella/e/master/packages/atom) + (passed to `initGraph`): + +```ts +{ path: "nested.src.path" } +{ path: ["nested", "src", "path"] } +``` + +2) Reference path to another node's output in the GraphSpec object. See + [@thi.ng/resolve-map](https://github.com/thi-ng/umbrella/tree/master/packages/resolve-map) + for details. + +```ts +{ stream: "/node-id/node" } // main node output +{ stream: "/node-id/outs/foo" } // specific output +``` + +3) Reference another node indirectly. The passed in `resolve` function + can be used to lookup other nodes, with the same logic as above. E.g. + the following spec looks up the main output of node "abc" and adds a + transformed subscription, which is then used as input for current + node. + +```ts +{ stream: (resolve) => + resolve("/abc/node").subscribe(map(x => x * 10)) } +``` + +4) Provide an external input stream: + +```ts +{ stream: () => fromIterable([1,2,3], 500) } +``` + +5) Single value input stream: + +```ts +{ const: 1 } +{ const: () => 1 } +``` + +If the optional `xform` key is given, a subscription with the given +transducer is added to the input and then used as input instead. This is +allows for further pre-processing of inputs. + +Please see detailed documentation in the source code & test cases for +further details. ## Authors diff --git a/packages/rstream-graph/package.json b/packages/rstream-graph/package.json index dff0c1b1a4..db7129c991 100644 --- a/packages/rstream-graph/package.json +++ b/packages/rstream-graph/package.json @@ -1,10 +1,14 @@ { "name": "@thi.ng/rstream-graph", - "version": "1.0.15", + "version": "2.1.0", "description": "Declarative dataflow graph construction for @thi.ng/rstream", "main": "./index.js", "typings": "./index.d.ts", - "repository": "https://github.com/thi-ng/umbrella", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/rstream-graph", "author": "Karsten Schmidt ", "license": "Apache-2.0", "scripts": { @@ -24,13 +28,13 @@ "typescript": "^2.8.3" }, "dependencies": { - "@thi.ng/api": "^4.0.2", - "@thi.ng/checks": "^1.5.3", - "@thi.ng/errors": "^0.1.3", - "@thi.ng/paths": "^1.3.8", - "@thi.ng/resolve-map": "^2.0.5", - "@thi.ng/rstream": "^1.6.13", - "@thi.ng/transducers": "^1.10.1" + "@thi.ng/api": "^4.0.4", + "@thi.ng/checks": "^1.5.5", + "@thi.ng/errors": "^0.1.4", + "@thi.ng/paths": "^1.3.10", + "@thi.ng/resolve-map": "^3.0.2", + "@thi.ng/rstream": "^1.8.0", + "@thi.ng/transducers": "^1.11.1" }, "keywords": [ "compute", diff --git a/packages/rstream-graph/src/api.ts b/packages/rstream-graph/src/api.ts index 6b5392d691..bda295f8ba 100644 --- a/packages/rstream-graph/src/api.ts +++ b/packages/rstream-graph/src/api.ts @@ -1,35 +1,56 @@ import { IObjectOf } from "@thi.ng/api/api"; +import { Path } from "@thi.ng/paths"; import { ISubscribable } from "@thi.ng/rstream/api"; import { Transducer } from "@thi.ng/transducers/api"; -export type NodeFactory = (src: IObjectOf>, id: string) => ISubscribable; +/** + * A function which constructs and returns an `ISubscribable` using + * given object of inputs and node ID. See `node()` and `node1()`. + */ +export type NodeFactory = (src: NodeInputs, id: string) => ISubscribable; + +export type NodeInputs = IObjectOf>; +export type NodeOutputs = IObjectOf>; +export type Graph = IObjectOf; + +export interface Node { + ins: NodeInputs; + outs: NodeOutputs; + node: ISubscribable; +} /** * A dataflow graph spec is simply an object where keys are node names - * and their values are NodeSpec's, defining inputs and the operation to - * be applied to produce a result stream. + * and their values are `NodeSpec`s, defining a node's inputs, outputs + * and the operation to be applied to produce one or more result + * streams. */ -export type GraphSpec = IObjectOf; +export type GraphSpec = IObjectOf< + NodeSpec | + Node | + ((resolve: (path: string) => any) => Node)>; /** * Specification for a single "node" in the dataflow graph. Nodes here - * are actually streams (or just generally any form of @thi.ng/rstream - * subscription), usually with an associated transducer to transform / - * combine the inputs and produce values for the node's result stream. + * are actually just wrappers of streams / subscriptions (or generally + * any form of thi.ng/rstream `ISubscribable`), usually with an + * associated transducer to transform / 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. + * The `fn` function is responsible to produce such a stream transformer + * 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 will be 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) + * Alo see `initGraph` and `nodeFromSpec` (in /src/nodes.ts) for more + * details how these specs are compiled into stream constructs. */ export interface NodeSpec { fn: NodeFactory; - ins: IObjectOf; - out?: NodeOutput; + ins: IObjectOf; + outs?: IObjectOf; } /** @@ -41,21 +62,26 @@ export interface NodeSpec { * * ``` * { path: "nested.src.path" } + * { path: ["nested", "src", "path"] } * ``` * - * 2) Reference another node in the GraphSpec object: + * 2) Reference path to another node's output in the GraphSpec object. + * See `@thi.ng/resolve-map` for details. * * ``` - * { stream: "node-id" } + * { stream: "/node-id/node" } // main node output + * { stream: "/node-id/outs/foo" } // specific output * ``` * * 3) Reference another node indirectly. The passed in `resolve` - * function can be used to lookup other nodes, e.g. the following - * spec looks up node "src" and adds a transformed subscription, - * which is then used as input for current node + * function can be used to lookup other nodes, with the same logic as + * above. E.g. the following spec looks up the main output of node + * "abc" and adds a transformed subscription, which is then used as + * input for current node. * * ``` - * { stream: (resolve) => resolve("src").subscribe(map(x => x * 10)) } + * { stream: (resolve) => + * resolve("/abc/node").subscribe(map(x => x * 10)) } * ``` * * 4) Provide an external input stream: @@ -68,17 +94,20 @@ export interface NodeSpec { * * ``` * { const: 1 } + * { const: () => 1 } * ``` * - * 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 `xform` is given, a subscription with the given + * transducer is added to the input and then used as input instead. */ -export interface NodeInput { +export interface NodeInputSpec { id?: string; - path?: string; + path?: Path; stream?: string | ((resolve) => ISubscribable); - const?: any; + const?: any | ((resolve) => any); xform?: Transducer; } -export type NodeOutput = string | ((node: ISubscribable) => void); +export type NodeOutputSpec = Path | NodeOutputFn; + +export type NodeOutputFn = (node: ISubscribable, id: PropertyKey) => ISubscribable; diff --git a/packages/rstream-graph/src/graph.ts b/packages/rstream-graph/src/graph.ts index 3e18841d41..c263043a02 100644 --- a/packages/rstream-graph/src/graph.ts +++ b/packages/rstream-graph/src/graph.ts @@ -1,89 +1,204 @@ import { IObjectOf } from "@thi.ng/api/api"; import { IAtom } from "@thi.ng/atom/api"; +import { isFunction } from "@thi.ng/checks/is-function"; +import { isPlainObject } from "@thi.ng/checks/is-plain-object"; import { isString } from "@thi.ng/checks/is-string"; import { illegalArgs } from "@thi.ng/errors/illegal-arguments"; -import { resolveMap } from "@thi.ng/resolve-map"; +import { getIn } from "@thi.ng/paths"; +import { absPath, resolve } from "@thi.ng/resolve-map"; import { ISubscribable } from "@thi.ng/rstream/api"; import { fromIterableSync } from "@thi.ng/rstream/from/iterable"; import { fromView } from "@thi.ng/rstream/from/view"; import { StreamSync, sync } from "@thi.ng/rstream/stream-sync"; -import { Subscription } from "@thi.ng/rstream/subscription"; import { Transducer } from "@thi.ng/transducers/api"; -import { NodeFactory, NodeSpec } from "./api"; +import { + Graph, + GraphSpec, + Node, + NodeFactory, + NodeInputs, + NodeInputSpec, + NodeOutputs, + NodeOutputSpec, + NodeSpec +} from "./api"; /** - * Dataflow graph initialization function. Takes an object of - * NodeSpec's, calls `nodeFromSpec` for each and then recursively - * resolves references via `@thi.ng/resolve-map/resolveMap`. Returns - * updated graph object (mutates in-place, original specs are replaced - * by stream constructs). + * Dataflow graph initialization function. Takes a state Atom (or `null` + * if not needed) and an object of `NodeSpec` values or functions + * returning `Node` objects. Calls `nodeFromSpec` for each spec and then + * recursively resolves references via thi.ng/resolve-map `resolve`. + * Returns new initialized graph object of `Node` objects and + * `@thi.ng/rstream` stream constructs. Does NOT mutate original + * `GraphSpec` object. * * @param state - * @param nodes + * @param spec */ -export const initGraph = (state: IAtom, nodes: IObjectOf): IObjectOf> => { - for (let id in nodes) { - (nodes)[id] = nodeFromSpec(state, nodes[id], id); +export const initGraph = (state: IAtom, spec: GraphSpec): Graph => { + const res: Graph = {} + for (let id in spec) { + const n = spec[id]; + if (isNodeSpec(n)) { + res[id] = nodeFromSpec(state, spec[id], id); + } else { + res[id] = n; + } } - return resolveMap(nodes); + return resolve(res); }; +const isNodeSpec = (x: any): x is NodeSpec => + isPlainObject(x) && isFunction((x).fn); + /** - * Transforms a single NodeSpec into a lookup function for `resolveMap` + * Transforms a single `NodeSpec` into a lookup function for `resolve` * (which is called from `initGraph`). When that function is called, * recursively resolves all specified input streams and calls this - * spec's `fn` to produce a new stream from these inputs. If the spec - * includes the optional `out` key, it also executes the provided - * function, or if the value is a string, adds a subscription to this - * node's result stream which then updates the provide state atom at the - * path defined by `out`. Returns an ISubscribable. + * spec's `fn` to produce a new stream from these inputs. + * + * If the spec includes the optional `outs` keys, it also creates the + * subscriptions for each of the given output keys, which then can be + * used as inputs by other nodes. Each value in the `outs` subspec can + * be a function or state path (string/number/array, see thi.ng/paths). + * Functions are called with this node's constructed stream/subscribable + * and the output id and must return a new `ISubscribable`. For path + * values a subscription is added to this node's result stream which + * then updates the provided state atom at the path given. + * + * Non-function output specs subs assume the raw node output value is an + * object from which the different output keys are being extracted. The + * special `*` output key can be used to handle the entire node output + * value. This is useful/required for non-object node result values. + * + * ``` + * out: { + * // fn output spec + * // creates new sub which uses `pick` transducer to + * // select key `a` from main node output (assumed to be object) + * a: (node, id) => node.subscribe({}, pick(id)), + * + * // yields sub of `b` key's values extracted from main output + * // and also stores them at given path in state atom + * b: "foo.b" + * + * // yields sub with same value as main node output and + * // stores vals in state atom at given path + * "*": "foo.main" + * } + * ``` * * See `api.ts` for further details and possible spec variations. * + * @param state * @param spec + * @param id */ -const nodeFromSpec = (state: IAtom, spec: NodeSpec, id: string) => (resolve) => { - const src: IObjectOf> = {}; - for (let id in spec.ins) { +const nodeFromSpec = (state: IAtom, spec: NodeSpec, id: string) => + (resolve) => { + const ins = prepareNodeInputs(spec.ins, state, resolve); + const node = spec.fn(ins, id); + const outs = prepareNodeOutputs(spec.outs, node, state, id); + return { ins, node, outs }; + }; + +const prepareNodeInputs = (ins: IObjectOf, state: IAtom, resolve: (x: string) => any) => { + const res: NodeInputs = {}; + if (!ins) return res; + for (let id in ins) { let s; - const i = spec.ins[id]; + const i = ins[id]; if (i.path) { s = fromView(state, i.path); - } else if (i.stream) { - s = isString(i.stream) ? - resolve(i.stream) : - (i).stream(resolve); - } else if (i.const) { - s = fromIterableSync([i.const]); - } else { - illegalArgs(`invalid node spec`); + } + else if (i.stream) { + s = isString(i.stream) ? resolve(i.stream) : i.stream(resolve); + } + else if (i.const) { + s = fromIterableSync([isFunction(i.const) ? i.const(resolve) : i.const], false); + } + else { + illegalArgs(`invalid node input: ${id}`); } if (i.xform) { s = s.subscribe(i.xform, id); } - src[id] = s; + res[id] = s; } - const node = spec.fn(src, id); - if (spec.out) { - if (isString(spec.out)) { - ((path) => node.subscribe({ next: (x) => state.resetIn(path, x) }, `out-${id}`))(spec.out); + return res; +} + +const prepareNodeOutputs = (outs: IObjectOf, node: ISubscribable, state: IAtom, nodeID: string) => { + const res: NodeOutputs = {}; + if (!outs) return res; + for (let id in outs) { + const o = outs[id]; + if (isFunction(o)) { + res[id] = o(node, id); + } else if (id == "*") { + res[id] = ((path) => node.subscribe({ + next: (x) => state.resetIn(path, x) + }, `out-${nodeID}`))(o); } else { - spec.out(node); + res[id] = ((path, id) => node.subscribe({ + next: (x) => state.resetIn(path, x[id]) + }, `out-${nodeID}-${id}`))(o, id); } } - return node; + return res; }; -export const addNode = (graph: IObjectOf>, state: IAtom, id: string, spec: NodeSpec) => - graph[id] = nodeFromSpec(state, spec, id)((nodeID) => graph[nodeID]); - -export const removeNode = (graph: IObjectOf>, id: string) => { +/** + * Compiles given `NodeSpec` and adds it to graph. Returns compiled + * `Node` object for the given spec. Throws error if the graph already + * contains a node with given `id`. + * + * @param graph + * @param state + * @param id + * @param spec + */ +export const addNode = (graph: Graph, state: IAtom, id: string, spec: NodeSpec): Node => { if (graph[id]) { - graph[id].unsubscribe(); + illegalArgs(`graph already contains a node with ID: ${id}`); + } + return graph[id] = nodeFromSpec(state, spec, id)( + (path) => getIn(graph, absPath([id], path)) + ); +} + +/** + * Calls `.unsubscribe()` on given node and all of its outputs, then + * removes it from graph. Returns `false` if no node exists for given + * `id`. + * + * @param graph + * @param id + */ +export const removeNode = (graph: Graph, id: string) => { + const node = graph[id]; + if (node) { + node.node.unsubscribe(); + for (let id in node.outs) { + node.outs[id].unsubscribe(); + } delete graph[id]; return true; } + return false; +}; + +/** + * Calls `.unsubscribe()` on all nodes in the graph, causing all related + * streams & subscriptions to terminate. + * + * @param graph + */ +export const stop = (graph: Graph) => { + for (let id in graph) { + graph[id].node.unsubscribe(); + } }; /** @@ -109,10 +224,12 @@ export const node = (xform: Transducer, any>, inputIDs?: string[] * @param xform * @param inputID */ -export const node1 = (xform: Transducer, inputID = "src"): NodeFactory => - (src: IObjectOf>, id: string): Subscription => { +export const node1 = (xform?: Transducer, inputID = "src"): NodeFactory => + (src: IObjectOf>, id: string): ISubscribable => { ensureInputs(src, [inputID], id); - return src[inputID].subscribe(xform, id); + return xform ? + src[inputID].subscribe(xform, id) : + src[inputID].subscribe(null, id); }; /** diff --git a/packages/rstream-graph/src/nodes/extract.ts b/packages/rstream-graph/src/nodes/extract.ts index 4a36e0403a..1df94cfa92 100644 --- a/packages/rstream-graph/src/nodes/extract.ts +++ b/packages/rstream-graph/src/nodes/extract.ts @@ -8,6 +8,9 @@ import { node1 } from "../graph"; * Nested value extraction node. Higher order function. * * Inputs: 1 + * + * @param path value lookup path + * @param inputID default: `src` */ 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 9409c8ca62..ff7a1255fa 100644 --- a/packages/rstream-graph/src/nodes/math.ts +++ b/packages/rstream-graph/src/nodes/math.ts @@ -39,7 +39,7 @@ export const mul: NodeFactory = node( /** * Subtraction node. * - * Inputs: 2 + * Inputs: `a`, `b` */ export const sub: NodeFactory = node(map((ports: IObjectOf) => ports.a - ports.b), ["a", "b"]); @@ -47,7 +47,7 @@ export const sub: NodeFactory = /** * Division node. * - * Inputs: 2 + * Inputs: `a`, `b` */ export const div: NodeFactory = node(map((ports: IObjectOf) => ports.a / ports.b), ["a", "b"]); diff --git a/packages/rstream-graph/test/index.ts b/packages/rstream-graph/test/index.ts index a9e7b95e00..461a548328 100644 --- a/packages/rstream-graph/test/index.ts +++ b/packages/rstream-graph/test/index.ts @@ -1,6 +1,75 @@ -// import * as assert from "assert"; -// import * as rsg from "../src/index"; +import { Atom } from "@thi.ng/atom"; +import * as rs from "@thi.ng/rstream"; +import { map } from "@thi.ng/transducers/xform/map"; +import * as assert from "assert"; + +import * as rsg from "../src"; describe("rstream-graph", () => { - it("tests pending"); + it("basic", (done) => { + const acc = []; + const state = new Atom({ a: 1, b: 2 }); + const graph = rsg.initGraph(state, { + foo: () => ({ + node: rs.fromIterable([2]), + ins: {}, + outs: {} + }), + bar: ($) => ({ + node: $("/foo/node").transform(map((x: number) => x * 10)), + ins: {}, + outs: {} + }), + add: { + fn: rsg.add, + ins: { + a: { path: "a" }, + b: { path: "b" } + }, + outs: { + alt: (n) => n.subscribe({}) // identical to main out, testing only + } + }, + mul: { + fn: rsg.mul, + ins: { + a: { stream: "/add/outs/alt" }, + b: { stream: () => rs.fromIterable([10, 20, 30]) }, + c: { stream: "/bar/node" } + }, + outs: { + baz: (n, id) => n.subscribe({ next: (x) => state.resetIn(["foo", id], x) }) + } + }, + res: { + ins: { + src: { stream: "/mul/node" } + }, + fn: rsg.node1(map((x: number) => ({ x: x, x2: x * 2 }))), + outs: { + "*": "res" + } + }, + res2: { + ins: { + src: { stream: "/res/node" } + }, + fn: rsg.node1(), + outs: { + x: "res2.x", + } + } + }); + graph.mul.node.subscribe({ next: (x) => acc.push(x) }); + setTimeout(() => { + state.resetIn("a", 10); + console.log(graph); + assert.deepEqual(acc, [600, 1200, 1800, 7200]); + assert.deepEqual( + state.deref(), + { a: 10, b: 2, foo: { baz: 7200 }, res: { x: 7200, x2: 14400 }, res2: { x: 7200 } } + ); + done(); + }, 10); + }); }); diff --git a/packages/rstream-log/CHANGELOG.md b/packages/rstream-log/CHANGELOG.md index 24db589f7f..1323c200db 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.29](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-log@1.0.28...@thi.ng/rstream-log@1.0.29) (2018-06-21) + + + + +**Note:** Version bump only for package @thi.ng/rstream-log + + +## [1.0.28](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-log@1.0.27...@thi.ng/rstream-log@1.0.28) (2018-06-19) + + + + +**Note:** Version bump only for package @thi.ng/rstream-log + + +## [1.0.27](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-log@1.0.26...@thi.ng/rstream-log@1.0.27) (2018-06-18) + + + + +**Note:** Version bump only for package @thi.ng/rstream-log + + +## [1.0.26](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-log@1.0.25...@thi.ng/rstream-log@1.0.26) (2018-05-30) + + + + +**Note:** Version bump only for package @thi.ng/rstream-log + + +## [1.0.25](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-log@1.0.24...@thi.ng/rstream-log@1.0.25) (2018-05-20) + + + + +**Note:** Version bump only for package @thi.ng/rstream-log + + +## [1.0.24](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-log@1.0.23...@thi.ng/rstream-log@1.0.24) (2018-05-14) + + + + +**Note:** Version bump only for package @thi.ng/rstream-log + ## [1.0.23](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-log@1.0.22...@thi.ng/rstream-log@1.0.23) (2018-05-14) diff --git a/packages/rstream-log/package.json b/packages/rstream-log/package.json index e11ab56255..9bb9fd48a1 100644 --- a/packages/rstream-log/package.json +++ b/packages/rstream-log/package.json @@ -1,10 +1,14 @@ { "name": "@thi.ng/rstream-log", - "version": "1.0.23", + "version": "1.0.29", "description": "Structured, multilevel & hierarchical loggers based on @thi.ng/rstream", "main": "./index.js", "typings": "./index.d.ts", - "repository": "https://github.com/thi-ng/umbrella", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/rstream-log", "author": "Karsten Schmidt ", "license": "Apache-2.0", "scripts": { @@ -24,11 +28,11 @@ "typescript": "^2.8.3" }, "dependencies": { - "@thi.ng/api": "^4.0.2", - "@thi.ng/checks": "^1.5.3", - "@thi.ng/errors": "^0.1.3", - "@thi.ng/rstream": "^1.6.13", - "@thi.ng/transducers": "^1.10.1" + "@thi.ng/api": "^4.0.4", + "@thi.ng/checks": "^1.5.5", + "@thi.ng/errors": "^0.1.4", + "@thi.ng/rstream": "^1.8.0", + "@thi.ng/transducers": "^1.11.1" }, "keywords": [ "ES6", diff --git a/packages/rstream-query/CHANGELOG.md b/packages/rstream-query/CHANGELOG.md index 61958170cc..bbbf69808f 100644 --- a/packages/rstream-query/CHANGELOG.md +++ b/packages/rstream-query/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. + +## [0.3.16](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-query@0.3.15...@thi.ng/rstream-query@0.3.16) (2018-06-21) + + + + +**Note:** Version bump only for package @thi.ng/rstream-query + + +## [0.3.15](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-query@0.3.14...@thi.ng/rstream-query@0.3.15) (2018-06-19) + + + + +**Note:** Version bump only for package @thi.ng/rstream-query + + +## [0.3.14](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-query@0.3.13...@thi.ng/rstream-query@0.3.14) (2018-06-18) + + + + +**Note:** Version bump only for package @thi.ng/rstream-query + + +## [0.3.13](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-query@0.3.12...@thi.ng/rstream-query@0.3.13) (2018-05-30) + + + + +**Note:** Version bump only for package @thi.ng/rstream-query + + +## [0.3.12](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-query@0.3.11...@thi.ng/rstream-query@0.3.12) (2018-05-20) + + + + +**Note:** Version bump only for package @thi.ng/rstream-query + + +## [0.3.11](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-query@0.3.10...@thi.ng/rstream-query@0.3.11) (2018-05-14) + + + + +**Note:** Version bump only for package @thi.ng/rstream-query + ## [0.3.10](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream-query@0.3.9...@thi.ng/rstream-query@0.3.10) (2018-05-14) diff --git a/packages/rstream-query/package.json b/packages/rstream-query/package.json index 82f80c6098..87e495caeb 100644 --- a/packages/rstream-query/package.json +++ b/packages/rstream-query/package.json @@ -1,10 +1,14 @@ { "name": "@thi.ng/rstream-query", - "version": "0.3.10", + "version": "0.3.16", "description": "@thi.ng/rstream based triple store & reactive query engine", "main": "./index.js", "typings": "./index.d.ts", - "repository": "https://github.com/thi-ng/umbrella", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/rstream-query", "author": "Karsten Schmidt ", "license": "Apache-2.0", "scripts": { @@ -24,14 +28,14 @@ "typescript": "^2.8.3" }, "dependencies": { - "@thi.ng/api": "^4.0.2", - "@thi.ng/associative": "^0.5.5", - "@thi.ng/checks": "^1.5.3", - "@thi.ng/equiv": "^0.1.2", - "@thi.ng/errors": "^0.1.3", - "@thi.ng/rstream": "^1.6.13", - "@thi.ng/rstream-dot": "^0.2.11", - "@thi.ng/transducers": "^1.10.1" + "@thi.ng/api": "^4.0.4", + "@thi.ng/associative": "^0.5.8", + "@thi.ng/checks": "^1.5.5", + "@thi.ng/equiv": "^0.1.5", + "@thi.ng/errors": "^0.1.4", + "@thi.ng/rstream": "^1.8.0", + "@thi.ng/rstream-dot": "^0.2.17", + "@thi.ng/transducers": "^1.11.1" }, "keywords": [ "dataflow", diff --git a/packages/rstream/CHANGELOG.md b/packages/rstream/CHANGELOG.md index 8c48692f5b..49df2db48b 100644 --- a/packages/rstream/CHANGELOG.md +++ b/packages/rstream/CHANGELOG.md @@ -3,6 +3,66 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [1.8.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream@1.7.3...@thi.ng/rstream@1.8.0) (2018-06-21) + + +### Features + +* **rstream:** option to avoid auto-closing `fromInterval()`, add docs ([cc5b736](https://github.com/thi-ng/umbrella/commit/cc5b736)) + + + + + +## [1.7.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream@1.7.2...@thi.ng/rstream@1.7.3) (2018-06-19) + + + + +**Note:** Version bump only for package @thi.ng/rstream + + +## [1.7.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream@1.7.1...@thi.ng/rstream@1.7.2) (2018-06-18) + + + + +**Note:** Version bump only for package @thi.ng/rstream + + +## [1.7.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream@1.7.0...@thi.ng/rstream@1.7.1) (2018-05-30) + + + + +**Note:** Version bump only for package @thi.ng/rstream + + +# [1.7.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream@1.6.14...@thi.ng/rstream@1.7.0) (2018-05-20) + + +### Bug Fixes + +* **rstream:** minor update PubSub topic fn return type ([cbc600e](https://github.com/thi-ng/umbrella/commit/cbc600e)) + + +### Features + +* **rstream:** re-implement bisect() using PubSub, update tests ([846aaf9](https://github.com/thi-ng/umbrella/commit/846aaf9)) +* **rstream:** update resolve(), update subscribe() overrides ([23fdd39](https://github.com/thi-ng/umbrella/commit/23fdd39)) + + + + + +## [1.6.14](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream@1.6.13...@thi.ng/rstream@1.6.14) (2018-05-14) + + + + +**Note:** Version bump only for package @thi.ng/rstream + ## [1.6.13](https://github.com/thi-ng/umbrella/compare/@thi.ng/rstream@1.6.12...@thi.ng/rstream@1.6.13) (2018-05-14) diff --git a/packages/rstream/package.json b/packages/rstream/package.json index 009032e384..98754347b2 100644 --- a/packages/rstream/package.json +++ b/packages/rstream/package.json @@ -1,10 +1,14 @@ { "name": "@thi.ng/rstream", - "version": "1.6.13", + "version": "1.8.0", "description": "Reactive multi-tap streams, dataflow & transformation pipeline constructs", "main": "./index.js", "typings": "./index.d.ts", - "repository": "https://github.com/thi-ng/umbrella", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/rstream", "author": "Karsten Schmidt ", "license": "Apache-2.0", "scripts": { @@ -24,13 +28,13 @@ "typescript": "^2.8.3" }, "dependencies": { - "@thi.ng/api": "^4.0.2", - "@thi.ng/associative": "^0.5.5", - "@thi.ng/atom": "^1.3.12", - "@thi.ng/checks": "^1.5.3", - "@thi.ng/errors": "^0.1.3", - "@thi.ng/paths": "^1.3.8", - "@thi.ng/transducers": "^1.10.1" + "@thi.ng/api": "^4.0.4", + "@thi.ng/associative": "^0.5.8", + "@thi.ng/atom": "^1.4.2", + "@thi.ng/checks": "^1.5.5", + "@thi.ng/errors": "^0.1.4", + "@thi.ng/paths": "^1.3.10", + "@thi.ng/transducers": "^1.11.1" }, "keywords": [ "datastructure", diff --git a/packages/rstream/src/api.ts b/packages/rstream/src/api.ts index 1622cee818..ecde79300e 100644 --- a/packages/rstream/src/api.ts +++ b/packages/rstream/src/api.ts @@ -25,8 +25,10 @@ export interface ISubscribable extends IDeref, IID { - subscribe(xform: Transducer, id?: string): Subscription; subscribe(sub: Partial>, xform: Transducer, id?: string): Subscription; + // subscribe, C>(sub: S): S; + subscribe(sub: Subscription): Subscription; + subscribe(xform: Transducer, id?: string): Subscription; subscribe(sub: Partial>, id?: string): Subscription; unsubscribe(sub?: Partial>): boolean; getState(): State; diff --git a/packages/rstream/src/from/interval.ts b/packages/rstream/src/from/interval.ts index 381e35f5ec..f1dc1106f3 100644 --- a/packages/rstream/src/from/interval.ts +++ b/packages/rstream/src/from/interval.ts @@ -1,5 +1,14 @@ import { Stream } from "../stream"; +/** + * Returns a new `Stream` which emits a monotonically increasing counter + * value at given `delay` interval, up to an optionally defined max + * value (default: ∞), after which the stream is closed. The stream only + * starts when the first subscriber becomes available. + * + * @param delay + * @param count + */ export function fromInterval(delay: number, count = Number.POSITIVE_INFINITY) { return new Stream((stream) => { let i = 0; diff --git a/packages/rstream/src/from/iterable.ts b/packages/rstream/src/from/iterable.ts index 2126bdedbf..ea839f13a6 100644 --- a/packages/rstream/src/from/iterable.ts +++ b/packages/rstream/src/from/iterable.ts @@ -1,6 +1,18 @@ import { Stream } from "../stream"; -export function fromIterable(src: Iterable, delay = 0) { +/** + * Creates a new `Stream` of given iterable which asynchronously calls + * `.next()` for each item of the iterable when the first (and in this + * case the only one) subscriber becomes available. The values are + * processed via `setInterval()` using the given `delay` value (default: + * 0). Once the iterable is exhausted (if finite), then calls `.done()` + * by default, but can be avoided by passing `false` as last argument. + * + * @param src + * @param delay + * @param close + */ +export function fromIterable(src: Iterable, delay = 0, close = true) { return new Stream( (stream) => { const iter = src[Symbol.iterator](); @@ -8,7 +20,7 @@ export function fromIterable(src: Iterable, delay = 0) { let val: IteratorResult; if ((val = iter.next()).done) { clearInterval(id); - stream.done(); + close && stream.done(); } else { stream.next(val.value); } @@ -19,13 +31,23 @@ export function fromIterable(src: Iterable, delay = 0) { ); } -export function fromIterableSync(src: Iterable) { +/** + * Creates a new `Stream` of given iterable which synchronously calls + * `.next()` for each item of the iterable when the first (and in this + * case the only one) subscriber becomes available. Once the iterable is + * exhausted (MUST be finite!), then calls `.done()` by default, but can + * be avoided by passing `false` as last argument. + * + * @param src + * @param close + */ +export function fromIterableSync(src: Iterable, close = true) { return new Stream( (stream) => { for (let s of src) { stream.next(s); } - stream.done(); + close && stream.done(); return null; }, `iterable-${Stream.NEXT_ID++}` diff --git a/packages/rstream/src/pubsub.ts b/packages/rstream/src/pubsub.ts index 62b0ff888d..e0df7022ef 100644 --- a/packages/rstream/src/pubsub.ts +++ b/packages/rstream/src/pubsub.ts @@ -51,7 +51,7 @@ export interface PubSubOpts { */ export class PubSub extends Subscription { - topicfn: (x: B) => PropertyKey; + topicfn: (x: B) => any; topics: EquivMap>; constructor(opts?: PubSubOpts) { @@ -81,8 +81,10 @@ export class PubSub extends Subscription { return null; } - subscribeTopic(topicID: any, sub: Partial>, id?: string): Subscription; subscribeTopic(topicID: any, tx: Transducer, id?: string): Subscription; + // subscribeTopic, C>(topicID: any, sub: S): S; + subscribeTopic(topicID: any, sub: Subscription): Subscription; + subscribeTopic(topicID: any, sub: Partial>, id?: string): Subscription; subscribeTopic(topicID: any, sub: any, id?: string): Subscription { let t = this.topics.get(topicID); if (!t) { @@ -108,7 +110,6 @@ export class PubSub extends Subscription { return super.unsubscribe(); } unsupported(); - return false; } done() { diff --git a/packages/rstream/src/stream.ts b/packages/rstream/src/stream.ts index 25fe200173..56fc04ff8b 100644 --- a/packages/rstream/src/stream.ts +++ b/packages/rstream/src/stream.ts @@ -46,9 +46,11 @@ export class Stream extends Subscription this.src = src; } - subscribe(sub: Partial>, id?: string): Subscription + subscribe(sub: Partial>, xform: Transducer, id?: string): Subscription; + // subscribe, C>(sub: S): S; + subscribe(sub: Subscription): Subscription; subscribe(xform: Transducer, id?: string): Subscription; - subscribe(sub: Partial>, xform: Transducer, id?: string): Subscription + subscribe(sub: Partial>, id?: string): Subscription; subscribe(...args: any[]) { const wrapped = super.subscribe.apply(this, args); if (this.subs.size === 1) { diff --git a/packages/rstream/src/subs/bisect.ts b/packages/rstream/src/subs/bisect.ts index ce788445ec..4a6ac6e058 100644 --- a/packages/rstream/src/subs/bisect.ts +++ b/packages/rstream/src/subs/bisect.ts @@ -1,17 +1,41 @@ import { Predicate } from "@thi.ng/api/api"; import { ISubscriber } from "../api"; -import { Subscription } from "../subscription"; +import { PubSub } from "../pubsub"; -export function bisect(pred: Predicate, a?: ISubscriber, b?: ISubscriber) { - return new Subscription({ - next(x) { - const sub = pred(x) ? a : b; - sub.next && sub.next(x); - }, - done() { - a.done && a.done(); - b.done && b.done(); - } - }); +/** + * Returns a new `PubSub` instance using given predicate `pred` as + * boolean topic function and `a` & `b` as subscribers for truthy (`a`) + * and falsey `b` values. + * + * ``` + * rs.fromIterable([1,2,3,4]).subscribe( + * rs.bisect( + * (x) => !(x & 1), + * { next: (x) => console.log("even", x) }, + * { next: (x) => console.log("odd", x) } + * ) + * ); + * ``` + * + * If `a` or `b` need to be subscribed to directly, then `a` / `b` MUST + * be converted into a `Subscription` instance (if not already) and a + * reference kept prior to calling `bisect()`. + * + * ``` + * const even = new rs.Subscription({next: (x) => console.log("even", x) }); + * const odd = new rs.Subscription({next: (x) => console.log("odd", x) }); + * + * rs.fromIterable([1,2,3,4]).subscribe(rs.bisect((x) => !(x & 1), even, odd)); + * ``` + * + * @param pred + * @param a + * @param b + */ +export function bisect(pred: Predicate, a?: ISubscriber, b?: ISubscriber): PubSub { + const sub = new PubSub({ topic: pred }); + sub.subscribeTopic(true, a); + sub.subscribeTopic(false, b); + return sub; } diff --git a/packages/rstream/src/subs/resolve.ts b/packages/rstream/src/subs/resolve.ts index 4d32653420..dec967adc2 100644 --- a/packages/rstream/src/subs/resolve.ts +++ b/packages/rstream/src/subs/resolve.ts @@ -1,12 +1,19 @@ +import { IID } from "@thi.ng/api/api"; import { DEBUG, State } from "../api"; import { Subscription } from "../subscription"; +export interface ResolverOpts extends IID { + fail: (e: any) => void; +} + export class Resolver extends Subscription, T> { protected outstanding = 0; + protected fail: (e: any) => void; - constructor(id?: string) { - super(null, null, null, id || `resolve-${Subscription.NEXT_ID++}`); + constructor(opts: Partial = {}) { + super(null, null, null, opts.id || `resolve-${Subscription.NEXT_ID++}`); + this.fail = opts.fail; } next(x: Promise) { @@ -22,7 +29,7 @@ export class Resolver extends Subscription, T> { DEBUG && console.log(`resolved value in ${State[this.state]} state (${x})`); } }, - (e) => this.error(e) + (e) => (this.fail || this.error)(e) ); } @@ -33,6 +40,6 @@ export class Resolver extends Subscription, T> { } } -export function resolve(id?: string) { - return new Resolver(id); -} \ No newline at end of file +export function resolve(opts?: Partial) { + return new Resolver(opts); +} diff --git a/packages/rstream/src/subscription.ts b/packages/rstream/src/subscription.ts index 93359b86a4..e8669fa882 100644 --- a/packages/rstream/src/subscription.ts +++ b/packages/rstream/src/subscription.ts @@ -57,9 +57,11 @@ export class Subscription implements * Creates new child subscription with given subscriber and/or * transducer and optional subscription ID. */ - subscribe(sub: Partial>, id?: string): Subscription + subscribe(sub: Partial>, xform: Transducer, id?: string): Subscription; + // subscribe, C>(sub: S): S; + subscribe(sub: Subscription): Subscription; subscribe(xform: Transducer, id?: string): Subscription; - subscribe(sub: Partial>, xform: Transducer, id?: string): Subscription + subscribe(sub: Partial>, id?: string): Subscription; subscribe(...args: any[]) { this.ensureState(); let sub, xform, id; diff --git a/packages/rstream/test/bisect.ts b/packages/rstream/test/bisect.ts index 1dcb03ecb0..58c58abbb5 100644 --- a/packages/rstream/test/bisect.ts +++ b/packages/rstream/test/bisect.ts @@ -13,11 +13,12 @@ describe("bisect", () => { it("raw subscribers", (done) => { const odds = [], evens = []; src.subscribe( - rs.bisect((x) => !!(x & 1), + rs.bisect((x) => !!(x & 1), { next(x) { odds.push(x) } }, { next(x) { evens.push(x) } } ) - ).subscribe({ + ); + src.subscribe({ done() { assert.deepEqual(odds, [1, 3]); assert.deepEqual(evens, [2, 4]); @@ -37,14 +38,14 @@ describe("bisect", () => { tx.map(x => x * 100) ); let doneCount = 0; - src.subscribe(rs.bisect((x) => !!(x & 1), subo, sube)) - .subscribe({ - done() { - assert.deepEqual(odds, [10, 30]); - assert.deepEqual(evens, [200, 400]); - assert.equal(doneCount, 2); - done(); - } - }); + src.subscribe(rs.bisect((x) => !!(x & 1), subo, sube)); + src.subscribe({ + done() { + assert.deepEqual(odds, [10, 30]); + assert.deepEqual(evens, [200, 400]); + assert.equal(doneCount, 2); + done(); + } + }); }); }); \ No newline at end of file diff --git a/packages/sax/.npmignore b/packages/sax/.npmignore new file mode 100644 index 0000000000..d703bda97a --- /dev/null +++ b/packages/sax/.npmignore @@ -0,0 +1,10 @@ +build +coverage +dev +doc +src* +test +.nyc_output +tsconfig.json +*.tgz +*.html diff --git a/packages/sax/CHANGELOG.md b/packages/sax/CHANGELOG.md new file mode 100644 index 0000000000..ed94deaafc --- /dev/null +++ b/packages/sax/CHANGELOG.md @@ -0,0 +1,65 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + + +## [0.3.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/sax@0.3.1...@thi.ng/sax@0.3.2) (2018-06-21) + + + + +**Note:** Version bump only for package @thi.ng/sax + + +## [0.3.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/sax@0.3.0...@thi.ng/sax@0.3.1) (2018-06-20) + + + + +**Note:** Version bump only for package @thi.ng/sax + + +# [0.3.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/sax@0.2.0...@thi.ng/sax@0.3.0) (2018-06-20) + + +### Features + +* **sax:** add children & trim opts, add CDATA support ([882f394](https://github.com/thi-ng/umbrella/commit/882f394)) + + + + + +# [0.2.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/sax@0.1.1...@thi.ng/sax@0.2.0) (2018-06-19) + + +### Features + +* **sax:** add support for escape seqs, minor optimizations ([e824b6b](https://github.com/thi-ng/umbrella/commit/e824b6b)) + + + + + +## [0.1.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/sax@0.1.0...@thi.ng/sax@0.1.1) (2018-06-18) + + +### Bug Fixes + +* **sax:** correct docs in readme ([0e4662d](https://github.com/thi-ng/umbrella/commit/0e4662d)) + + + + + +# 0.1.0 (2018-06-18) + + +### Features + +* **sax:** add entity support, update result format, update states ([0f2fcdf](https://github.com/thi-ng/umbrella/commit/0f2fcdf)) +* **sax:** add support for proc & doctype elements, update `end` results ([a4766a5](https://github.com/thi-ng/umbrella/commit/a4766a5)) +* **sax:** emit child elements with `end` results, support comments ([3dea954](https://github.com/thi-ng/umbrella/commit/3dea954)) +* **sax:** initial import ([dce189f](https://github.com/thi-ng/umbrella/commit/dce189f)) +* **sax:** update error handling, add parse() wrapper, add FSMOpts ([64f2378](https://github.com/thi-ng/umbrella/commit/64f2378)) diff --git a/packages/sax/LICENSE b/packages/sax/LICENSE new file mode 100644 index 0000000000..8dada3edaf --- /dev/null +++ b/packages/sax/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/sax/README.md b/packages/sax/README.md new file mode 100644 index 0000000000..871d632f45 --- /dev/null +++ b/packages/sax/README.md @@ -0,0 +1,188 @@ +# @thi.ng/sax + +[![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/sax.svg)](https://www.npmjs.com/package/@thi.ng/sax) + +This project is part of the +[@thi.ng/umbrella](https://github.com/thi-ng/umbrella/) monorepo. + +## About + +[@thi.ng/transducers](https://github.com/thi-ng/umbrella/tree/master/packages/transducers)-based, +[SAX](https://en.wikipedia.org/wiki/Simple_API_for_XML)-like, +non-validating, [configurable](#parser-options), speedy & tiny XML +parser (~1.8KB gzipped). + +Unlike the classic event-driven approach of SAX, this parser is +implemented as a transducer function, transforming an XML input into a +stream of SAX-event-like objects. Being a transducer, the parser can be +used in novel ways as part of a larger processing pipeline and can be +composed with other pre or post-processing steps, e.g. to filter or +transform element / attribute values or only do partial parsing with +early termination based on some condition. + +## Installation + +``` +yarn add @thi.ng/sax +``` + +## Dependencies + +- [@thi.ng/transducers](https://github.com/thi-ng/umbrella/tree/master/packages/transducers) +- [@thi.ng/transducers-fsm](https://github.com/thi-ng/umbrella/tree/master/packages/transducers-fsm) + +## Usage examples + +```ts +import * as sax from "@thi.ng/sax"; +import * as tx from "@thi.ng/transducers"; + +src=` + + + + + ccc + dd + + + +` + +doc = [...tx.iterator(sax.parse(), src)] + +// (see description of `type` values further below) + +// [ { type: 0, +// tag: 'xml', +// attribs: { version: '1.0', encoding: 'utf-8' } }, +// { type: 1, body: 'foo bar' }, +// { type: 2, body: ' comment ' }, +// { type: 4, tag: 'a', attribs: {} }, +// { type: 6, tag: 'a', body: '\n ' }, +// { type: 4, tag: 'b1', attribs: {} }, +// { type: 6, tag: 'b1', body: '\n ' }, +// { type: 4, tag: 'c', attribs: { x: '23', y: '42' } }, +// { type: 6, tag: 'c', body: 'ccc\n ' }, +// { type: 4, tag: 'd', attribs: {} }, +// { type: 6, tag: 'd', body: 'dd' }, +// { type: 5, tag: 'd', attribs: {}, children: [], body: 'dd' }, +// { type: 5, +// tag: 'c', +// attribs: { x: '23', y: '42' }, +// children: [ [Object] ], +// body: 'ccc\n ' }, +// { type: 5, +// tag: 'b1', +// attribs: {}, +// children: [ [Object] ], +// body: '\n ' }, +// { type: 4, tag: 'b2', attribs: { foo: 'bar' } }, +// { type: 5, tag: 'b2', attribs: { foo: 'bar' } }, +// { type: 5, +// tag: 'a', +// attribs: {}, +// children: [ [Object], [Object] ], +// body: '\n ' } ] +``` + +### Partial parsing & result post-processing + +As mentioned earlier, the transducer nature of this parser allows for +its easy integration into larger transformation pipelines. The next +example parses an SVG file, then extracts and selectively applies +transformations to only the `` elements in the first group +(``) element. + +Given the composed transducer below, parsing stops immediately after the +first `` element is complete. This is because the `matchFirst()` +transducer will cause early termination once that element has been +processed. + +```ts +svg=` + + + + + + + + + + + + +`; + +[...tx.iterator( + tx.comp( + // transform into parse events (see parser options below) + sax.parse({ children: true }), + // match 1st group end + tx.matchFirst((e) => e.type == sax.Type.ELEM_END && e.tag == "g"), + // extract group's children + tx.mapcat((e) => e.children), + // select circles only + tx.filter((e) => e.tag == "circle"), + // transform attributes + tx.map((e)=> [e.tag, { + ...e.attribs, + cx: parseFloat(e.attribs.cy), + cy: parseFloat(e.attribs.cy), + r: parseFloat(e.attribs.r), + }]) + ), + svg +)] +// [ [ 'circle', { cx: 150, cy: 150, r: 50 } ], +// [ 'circle', { cx: 150, cy: 150, r: 50 } ], +// [ 'circle', { cx: 150, cy: 150, fill: 'rgba(0,255,255,0.25)', r: 100, stroke: '#ff0000' } ] ] +``` + +### Error handling + +If the parser encounters a syntax error, an error event value incl. a +description and input position will be produced (but no JS error will be +thrown) and the entire transducer pipeline stopped. + +```ts +[...tx.iterator(sax.parse(), `a`)] +// [ { type: 7, body: 'unexpected char: \'a\' @ pos 1' } ] + +[...tx.iterator(sax.parse(), ``)] +// [ { type: 4, tag: 'a', attribs: {} }, +// { type: 4, tag: 'b', attribs: {} }, +// { type: 7, body: 'unmatched tag: c @ pos 7' } ] +``` + +## Emitted result type IDs + +The `type` key in each emitted result object is a TypeScript enum with the following values: + +| ID | Enum | Description | +|----|-------------------|-----------------------------------------------| +| 0 | `Type.PROC` | Processing instruction incl. attribs | +| 1 | `Type.DOCTYPE` | Doctype declaration body | +| 2 | `Type.COMMENT` | Comment body | +| 3 | `Type.CDATA` | CDATA content | +| 4 | `Type.ELEM_START` | Element start incl. attributes | +| 5 | `Type.ELEM_END` | Element end incl. attributes, body & children | +| 6 | `Type.ELEM_BODY` | Element text body | +| 7 | `Type.ERROR` | Parse error description | + +## Parser options + +| Option | Type | Default | Description | +|------------|-----------|---------|--------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `children` | `boolean` | `true` | If `true`, recursively includes children elements in `ELEM_END` events. For very large documents, this should be disabled to save (or even fit into) memory. | +| `entities` | `boolean` | `false` | If `true`, unescape standard XML entities in body text and attrib values. | +| `trim` | `boolean` | `false` | If `true`, trims element body, comments and CDATA content. If the remaining string is empty, no event will be generated for this value. | + +## Authors + +- Karsten Schmidt + +## License + +© 2018 Karsten Schmidt // Apache Software License 2.0 diff --git a/packages/sax/package.json b/packages/sax/package.json new file mode 100644 index 0000000000..2af5e9092b --- /dev/null +++ b/packages/sax/package.json @@ -0,0 +1,47 @@ +{ + "name": "@thi.ng/sax", + "version": "0.3.2", + "description": "Transducer-based, SAX-like, non-validating, speedy & tiny XML parser", + "main": "./index.js", + "typings": "./index.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/sax", + "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.2.0", + "@types/node": "^10.0.6", + "mocha": "^5.1.1", + "nyc": "^11.7.1", + "typedoc": "^0.11.1", + "typescript": "^2.8.3" + }, + "dependencies": { + "@thi.ng/api": "^4.0.4", + "@thi.ng/transducers": "^1.11.1", + "@thi.ng/transducers-fsm": "^0.2.2" + }, + "keywords": [ + "ES6", + "FSM", + "parser", + "SAX", + "transducers", + "typescript", + "XML" + ], + "publishConfig": { + "access": "public" + } +} diff --git a/packages/sax/src/index.ts b/packages/sax/src/index.ts new file mode 100644 index 0000000000..38accfcc61 --- /dev/null +++ b/packages/sax/src/index.ts @@ -0,0 +1,508 @@ +import { IObjectOf } from "@thi.ng/api"; +import { fsm, FSMState, FSMStateMap } from "@thi.ng/transducers-fsm"; +import { Transducer } from "@thi.ng/transducers/api"; + +export interface ParseOpts { + /** + * If `true`, unescape standard XML entities in body text and attrib + * values. + * + * Default: false + */ + entities: boolean; + /** + * If `true`, include children tags in `ELEM_END` events. For very + * large documents, this should be disabled to save (or even fit + * into) memory. + * + * Default: true + */ + children: boolean; + /** + * If `true`, trims element body, comments and CDATA content. If the + * remaining string is empty, no event will be generated for this + * value. + * + * Default: false + */ + trim: boolean; +} + +export interface ParseElement { + tag: string; + attribs: IObjectOf; + body: string; + children: ParseElement[]; +} + +export interface ParseEvent extends Partial { + type: Type; +} + +export enum Type { + /** + * XML processing instruction event + */ + PROC, + /** + * XML Doctype element event + */ + DOCTYPE, + /** + * XML comment element event + */ + COMMENT, + /** + * XML CDATA content event + */ + CDATA, + /** + * XML element start (incl. attribs) event + */ + ELEM_START, + /** + * XML element end event (possibly incl. already parsed children) + */ + ELEM_END, + /** + * XML element body (text) event + */ + ELEM_BODY, + /** + * Parser error event + */ + ERROR, +} + +interface ParseState extends FSMState { + scope: any[]; + tag: string; + attribs: any; + body: string; + name: string; + val: string; + pos: number; + quote: string; + phase: number; + isProc: boolean; + opts: Partial; +} + +enum State { + WAIT, + ERROR, + MAYBE_ELEM, + ELEM_START, + ELEM_END, + ELEM_SINGLE, + ELEM_BODY, + MAYBE_ATTRIB, + ATTRIB_NAME, + ATTRIB_VAL_START, + ATTRIB_VALUE, + MAYBE_INSTRUCTION, + COMMENT, + COMMENT_BODY, + DOCTYPE, + PROC_DECL, + PROC_END, + CDATA, + // H_CHAR, + // U_CHAR, +} + +const ENTITIES = { + "&": "&", + "<": "<", + ">": ">", + """: '"', + "'": "'", +}; + +const ENTITY_RE = new RegExp(`(${Object.keys(ENTITIES).join("|")})`, "g"); + +const ESCAPE_SEQS = { + "n": "\n", + "r": "\r", + "t": "\t", + "v": "\v", + "f": "\f", + "b": "\b", + "\"": "\"", + "'": "'", + "\\": "\\" +}; + +/** + * Returns XML parser transducer, optionally configured with given + * options. + * + * @param opts + */ +export const parse = (opts?: Partial): Transducer => + fsm({ + states: PARSER, + init: () => ({ + state: State.WAIT, + scope: [], + pos: 0, + opts: { + children: true, + entities: false, + trim: false, + ...opts + }, + }), + terminate: State.ERROR + }); + +const isWS = (x: string) => { + const c = x.charCodeAt(0); + return c === 0x20 || // space + c === 0x09 || // tab + c === 0x0a || // LF + c === 0x0d; // CR +}; + +const isTagChar = (x: string) => { + const c = x.charCodeAt(0); + return (c >= 0x41 && c <= 0x5a) || // A-Z + (c >= 0x61 && c <= 0x7a) || // a-z + (c >= 0x30 && c <= 0x39) || // 0-9 + c == 0x2d || // - + c == 0x5f || // _ + c == 0x3a; // : +}; + +const error = (s: ParseState, body: string) => { + s.state = State.ERROR; + return [{ type: Type.ERROR, body }]; +}; + +const illegalEscape = (s: ParseState, ch: string) => + error(s, `illegal escape sequence: \\${ch} @ pos ${s.pos - 1}`); + +const unexpected = (s: ParseState, x: string) => + error(s, `unexpected char: '${x}' @ pos ${s.pos}`); + +const replaceEntities = (x: string) => x.replace(ENTITY_RE, (y) => ENTITIES[y]); + +const PARSER: FSMStateMap = { + + [State.ERROR]: () => { }, + + [State.WAIT]: (state, ch) => { + state.pos++; + if (!isWS(ch)) { + if (ch === "<") { + state.state = State.MAYBE_ELEM; + } else { + return unexpected(state, ch); + } + } + }, + + [State.MAYBE_ELEM]: (state, ch) => { + state.pos++; + if (ch === "/") { + if (state.scope.length == 0) { + return unexpected(state, ch); + } + state.state = State.ELEM_END; + state.tag = ""; + } else if (isTagChar(ch)) { + state.state = State.ELEM_START; + state.tag = ch; + state.attribs = {}; + } else if (ch === "!") { + state.state = State.MAYBE_INSTRUCTION; + } else if (ch === "?") { + state.state = State.PROC_DECL; + state.phase = 0; + state.tag = ""; + state.body = ""; + } else { + return unexpected(state, ch); + } + }, + + [State.ELEM_START]: (state, ch) => { + state.pos++; + if (isTagChar(ch)) { + state.tag += ch; + } else if (isWS(ch)) { + state.state = State.MAYBE_ATTRIB; + } else if (ch === ">") { + state.state = State.ELEM_BODY; + state.scope.push({ tag: state.tag, attribs: state.attribs, children: [] }); + state.body = ""; + return [{ type: Type.ELEM_START, tag: state.tag, attribs: state.attribs }]; + } else if (ch === "/") { + state.state = State.ELEM_SINGLE; + } else { + return unexpected(state, ch); + } + }, + + [State.ELEM_END]: (state, ch) => { + state.pos++; + if (isTagChar(ch)) { + state.tag += ch; + } else if (ch === ">") { + const scope = state.scope; + const n = scope.length; + if (n > 0 && state.tag === scope[n - 1].tag) { + const res = scope.pop(); + if (n > 1 && state.opts.children) { + scope[n - 2].children.push(res); + } + state.state = State.WAIT; + return [{ type: Type.ELEM_END, ...res }]; + } else { + return error(state, `unmatched tag: '${state.tag}' @ pos ${state.pos - state.tag.length - 2}`); + } + } + }, + + [State.ELEM_SINGLE]: (state, ch) => { + state.pos++; + if (ch === ">") { + state.state = State.WAIT; + const n = state.scope.length; + const res = { tag: state.tag, attribs: state.attribs }; + if (n > 0 && state.opts.children) { + state.scope[n - 1].children.push(res); + } + return [ + { type: Type.ELEM_START, ...res }, + { type: Type.ELEM_END, ...res } + ]; + } else { + return unexpected(state, ch); + } + }, + + [State.ELEM_BODY]: (state, ch) => { + state.pos++; + let b = state.body; + if (ch === "<") { + let res; + const t = state.tag; + state.state = State.MAYBE_ELEM; + state.tag = ""; + if (b.length > 0) { + if (state.opts.trim) { + b = b.trim(); + if (!b.length) { + return; + } + } + if (state.opts.entities) { + b = replaceEntities(b); + } + state.scope[state.scope.length - 1].body = b; + res = [{ type: Type.ELEM_BODY, tag: t, body: b }]; + } + return res; + } else { + if (b.charAt(b.length - 1) === "\\") { + const e = ESCAPE_SEQS[ch]; + if (e !== undefined) { + state.body = b.substr(0, b.length - 1) + e; + return; + } else { + return illegalEscape(state, ch); + } + } + state.body += ch; + } + }, + + [State.MAYBE_ATTRIB]: (state, ch) => { + state.pos++; + if (isTagChar(ch)) { + state.state = State.ATTRIB_NAME; + state.name = ch; + state.val = ""; + } else { + if (state.isProc) { + if (ch === "?") { + state.state = State.PROC_END; + } else if (!isWS(ch)) { + return unexpected(state, ch); + } + } else { + if (ch === ">") { + state.state = State.ELEM_BODY; + state.scope.push({ tag: state.tag, attribs: state.attribs, children: [] }); + state.body = ""; + return [{ type: Type.ELEM_START, tag: state.tag, attribs: state.attribs }]; + } else if (ch === "/") { + state.state = State.ELEM_SINGLE; + } else if (!isWS(ch)) { + return unexpected(state, ch); + } + } + } + }, + + [State.ATTRIB_NAME]: (state, ch) => { + state.pos++; + if (isTagChar(ch)) { + state.name += ch; + } else if (ch === "=") { + state.state = State.ATTRIB_VAL_START; + } else { + return unexpected(state, ch); + } + }, + + [State.ATTRIB_VAL_START]: (state, ch) => { + state.pos++; + if (ch === "\"" || ch === "'") { + state.state = State.ATTRIB_VALUE; + state.quote = ch; + } else { + return unexpected(state, ch); + } + }, + + [State.ATTRIB_VALUE]: (state, ch) => { + state.pos++; + let v = state.val; + if (v.charAt(v.length - 1) == "\\") { + const e = ESCAPE_SEQS[ch]; + if (e !== undefined) { + state.val = v.substr(0, v.length - 1) + e; + return; + } else { + return illegalEscape(state, ch); + } + } + if (ch !== state.quote) { + state.val += ch; + return; + } + if (state.opts.entities) { + v = replaceEntities(v); + } + state.attribs[state.name] = v; + state.state = State.MAYBE_ATTRIB; + }, + + [State.MAYBE_INSTRUCTION]: (state, ch) => { + state.pos++; + if (ch === "-") { + state.state = State.COMMENT; + } else if (ch === "D") { + state.state = State.DOCTYPE; + state.phase = 1; + state.body = ""; + } else if (ch === "[") { + state.state = State.CDATA; + state.phase = 1; + state.body = ""; + } else { + return unexpected(state, ch); + } + }, + + [State.COMMENT]: (state, ch) => { + state.pos++; + if (ch === "-") { + state.state = State.COMMENT_BODY; + state.body = ""; + } else { + return unexpected(state, ch); + } + }, + + [State.COMMENT_BODY]: (state, ch) => { + state.pos++; + if (ch === ">") { + const n = state.body.length; + if (state.body.substr(n - 2) !== "--") { + return unexpected(state, ch); + } + state.state = State.WAIT; + let b = state.body.substr(0, n - 2); + if (state.opts.trim) { + b = b.trim(); + if (!b.length) { + return; + } + } + return [{ type: Type.COMMENT, body: b }]; + } else { + state.body += ch; + } + }, + + [State.DOCTYPE]: (state, ch) => { + state.pos++; + if (state.phase < 8) { + if (ch === "DOCTYPE "[state.phase]) { + state.phase++; + } else { + return unexpected(state, ch); + } + } else if (ch === ">") { + state.state = State.WAIT; + return [{ type: Type.DOCTYPE, body: state.body.trim() }]; + } else { + state.body += ch; + } + }, + + [State.CDATA]: (state, ch) => { + state.pos++; + if (state.phase < 7) { + if (ch === "[CDATA["[state.phase]) { + state.phase++; + } else { + return unexpected(state, ch); + } + } else if (ch === ">") { + const n = state.body.length; + if (state.body.substr(n - 2) !== "]]") { + state.body += ch; + return; + } + state.state = State.WAIT; + let b = state.body.substr(0, n - 2); + if (state.opts.trim) { + b = b.trim(); + if (!b.length) { + return; + } + } + return [{ type: Type.CDATA, body: b }]; + } else { + state.body += ch; + } + }, + + [State.PROC_DECL]: (state, ch) => { + state.pos++; + if (isTagChar(ch)) { + state.tag += ch; + } else if (isWS(ch)) { + state.state = State.MAYBE_ATTRIB; + state.isProc = true; + state.attribs = {}; + } else { + return unexpected(state, ch); + } + }, + + [State.PROC_END]: (state, ch) => { + state.pos++; + if (ch === ">") { + state.state = State.WAIT; + state.isProc = false; + return [{ type: Type.PROC, tag: state.tag, attribs: state.attribs }]; + } else { + return unexpected(state, ch); + } + }, +}; diff --git a/packages/sax/test/index.ts b/packages/sax/test/index.ts new file mode 100644 index 0000000000..4a797bb803 --- /dev/null +++ b/packages/sax/test/index.ts @@ -0,0 +1,6 @@ +// import * as assert from "assert"; +// import * as sax from "../src/index"; + +describe("sax", () => { + it("tests pending"); +}); diff --git a/packages/sax/test/tsconfig.json b/packages/sax/test/tsconfig.json new file mode 100644 index 0000000000..bcf29ace54 --- /dev/null +++ b/packages/sax/test/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "../build" + }, + "include": [ + "./**/*.ts", + "../src/**/*.ts" + ] +} diff --git a/packages/sax/tsconfig.json b/packages/sax/tsconfig.json new file mode 100644 index 0000000000..bd6481a5a6 --- /dev/null +++ b/packages/sax/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": [ + "./src/**/*.ts" + ] +} diff --git a/packages/transducers-fsm/.npmignore b/packages/transducers-fsm/.npmignore new file mode 100644 index 0000000000..d703bda97a --- /dev/null +++ b/packages/transducers-fsm/.npmignore @@ -0,0 +1,10 @@ +build +coverage +dev +doc +src* +test +.nyc_output +tsconfig.json +*.tgz +*.html diff --git a/packages/transducers-fsm/CHANGELOG.md b/packages/transducers-fsm/CHANGELOG.md new file mode 100644 index 0000000000..81bcfffe27 --- /dev/null +++ b/packages/transducers-fsm/CHANGELOG.md @@ -0,0 +1,39 @@ +# 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/transducers-fsm@0.2.1...@thi.ng/transducers-fsm@0.2.2) (2018-06-21) + + + + +**Note:** Version bump only for package @thi.ng/transducers-fsm + + +## [0.2.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/transducers-fsm@0.2.0...@thi.ng/transducers-fsm@0.2.1) (2018-06-20) + + + + +**Note:** Version bump only for package @thi.ng/transducers-fsm + + +# [0.2.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/transducers-fsm@0.1.0...@thi.ng/transducers-fsm@0.2.0) (2018-06-19) + + +### Features + +* **transducers-fsm:** support multiple results, add tests, update readme ([a9ca135](https://github.com/thi-ng/umbrella/commit/a9ca135)) + + + + + +# 0.1.0 (2018-06-18) + + +### Features + +* **transducers-fsm:** inital import ([7c3c290](https://github.com/thi-ng/umbrella/commit/7c3c290)) diff --git a/packages/transducers-fsm/LICENSE b/packages/transducers-fsm/LICENSE new file mode 100644 index 0000000000..8dada3edaf --- /dev/null +++ b/packages/transducers-fsm/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/transducers-fsm/README.md b/packages/transducers-fsm/README.md new file mode 100644 index 0000000000..50b081d0fc --- /dev/null +++ b/packages/transducers-fsm/README.md @@ -0,0 +1,148 @@ +# @thi.ng/transducers-fsm + +[![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/transducers-fsm.svg)](https://www.npmjs.com/package/@thi.ng/transducers-fsm) + +This project is part of the +[@thi.ng/umbrella](https://github.com/thi-ng/umbrella/) monorepo. + +## About + +This package provides a single function, a general purpose [Finite State +Machine](https://en.wikipedia.org/wiki/Finite-state_machine) transducer, +which acts as useful & lightweight mechanism to provide +context-sensitive processing capabilities as part of a transducer +transformation pipeline. + +## Installation + +``` +yarn add @thi.ng/transducers-fsm +``` + +## Dependencies + +- [@thi.ng/transducers](https://github.com/thi-ng/umbrella/tree/master/packages/transducers) + +## Usage examples + +For a real world example, the +[@thi.ng/sax](https://github.com/thi-ng/umbrella/tree/master/packages/sax) +package provides a SAX-like XML parser transducer, built around the FSM +provided here. + +### 3-state FSM + +The following example defines a simple FSM with 3 states: + +- `skip` +- `take` +- `done` + +The FSM always starts in the `skip` state. + +The FSM alternates between skipping or consuming (passing through) 5 +inputs as long as each input is < 20. Once an input is >= 20, the FSM +switches into the `done` state, which has been declared as a *terminal* +state and once entered will cause processing to terminate (also see API +description further below). + +```ts +const testFSM = fsm.fsm({ + + // initial state initializer + // (called before processing 1st input) + init: () => ({ state: "skip", count: 0 }), + + // terminal state ID + terminate: "done", + + // individual state handlers + states: { + // skip state + skip: (state, x) => { + if (x < 20) { + if (++state.count > 5) { + state.state = "take"; + state.count = 1; + return [x]; + } + } else { + state.state = "done"; + } + }, + + // take state + take: (state, x) => { + if (x < 20) { + if (++state.count > 5) { + state.state = "skip"; + state.count = 1; + } else { + return [x]; + } + } else { + state.state = "done"; + } + }, + + // terminal state, ignore inputs + done: () => { }, + }, +}); + +[...tx.iterator(testFSM, tx.range(100))] +// [ 5, 6, 7, 8, 9, 15, 16, 17, 18, 19 ] + +// Use FSM as part of composed transducers... + +[...tx.iterator(tx.comp(tx.takeNth(2), testFSM), tx.range(100))] +// [ 10, 12, 14, 16, 18 ] + +[ + ...tx.iterator( + tx.comp( + tx.mapcat((x) => x.split(/[,\s]+/g)), + tx.map((x) => parseInt(x)), + testFSM, + tx.filter(tx.odd), + ), + ["9,8,7,6", "14 1 0 17 15 16", "19,23,12,42,4"] + ) +] +// [ 1, 17, 15 ] +``` + +## API + +### `fsm(opts: FSMOpts): Transducer` + +Finite State Machine transducer. Takes an FSM configuration object and +returns a transducer, which processes inputs using the provided state +handler functions, which in turn can produce any number of outputs per +consumed input. + +Before processing the first input, the FSM state is initialized by +calling the user provided `init()` function, which MUST return a state +object with at least a `state` key, whose value is used for dynamic +(i.e. stateful) dispatch during input processing. This state object is +passed with each input value to the current state handler, which is +expected to mutate this object, e.g. to cause state changes based on +given inputs. + +If a state handler needs to "emit" results for downstream processing, it +can return an array of values. Any such values are passed on +(individually, not as array) to the next reducer in the chain. If a +state handler returns `null` or `undefined`, further downstream +processing of the current input is skipped. + +Regardless of return value, if a state handler has caused a state change +to the configured `terminate` state, processing is terminated (by calling +`ensureReduced()`) and no further inputs will be consumed. + +## Authors + +- Karsten Schmidt + +## License + +© 2018 Karsten Schmidt // Apache Software License 2.0 diff --git a/packages/transducers-fsm/package.json b/packages/transducers-fsm/package.json new file mode 100644 index 0000000000..b825ef6d81 --- /dev/null +++ b/packages/transducers-fsm/package.json @@ -0,0 +1,44 @@ +{ + "name": "@thi.ng/transducers-fsm", + "version": "0.2.2", + "description": "Transducer-based Finite State Machine transformer", + "main": "./index.js", + "typings": "./index.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/transducers-fsm", + "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.2.0", + "@types/node": "^10.0.6", + "mocha": "^5.1.1", + "nyc": "^11.7.1", + "typedoc": "^0.11.1", + "typescript": "^2.8.3" + }, + "dependencies": { + "@thi.ng/api": "^4.0.4", + "@thi.ng/transducers": "^1.11.1" + }, + "keywords": [ + "ES6", + "finite state machine", + "FSM", + "transducers", + "typescript" + ], + "publishConfig": { + "access": "public" + } +} diff --git a/packages/transducers-fsm/src/index.ts b/packages/transducers-fsm/src/index.ts new file mode 100644 index 0000000000..08cef2db2d --- /dev/null +++ b/packages/transducers-fsm/src/index.ts @@ -0,0 +1,117 @@ +import { IObjectOf } from "@thi.ng/api/api"; +import { Reducer, Transducer } from "@thi.ng/transducers/api"; +import { comp } from "@thi.ng/transducers/func/comp"; +import { compR } from "@thi.ng/transducers/func/compr"; +import { ensureReduced, isReduced } from "@thi.ng/transducers/reduced"; + +export interface FSMState { + state: PropertyKey; +} + +export type FSMStateMap = IObjectOf>; +export type FSMHandler = (state: T, input: A) => B | null | void; + +export interface FSMOpts { + states: FSMStateMap; + terminate: PropertyKey; + init: () => T; +} + +/** + * Finite State Machine transducer. Takes an FSM configuration object + * and returns a transducer, which processes inputs using the provided + * state handler functions, which in turn can return any number of + * outputs per consumed input. + * + * Before processing the first input, the FSM state is initialized by + * calling the user provided `init()` function, which MUST return a + * state object with at least a `state` key, whose value is used for + * dynamic (i.e. stateful) dispatch during input processing. This state + * object is passed with each input value to the current state handler, + * which is expected to mutate this object, e.g. to cause state changes + * based on given inputs. + * + * If a state handler needs to "emit" results for downstream processing, + * it can return an array of values. Any such values are passed on + * (individually, not as array) to the next reducer in the chain. If a + * state handler returns `null` or `undefined`, further downstream + * processing of the current input is skipped. + * + * Regardless of return value, if a state handler has caused a state + * change to the configured `terminal` state, processing is terminated + * (by calling `ensureReduced()`) and no further inputs will be + * consumed. + * + * ``` + * testFSM = { + * states: { + * skip: (state, x) => { + * if (x < 20) { + * if (++state.count > 5) { + * state.state = "take"; + * state.count = 1; + * return [x]; + * } + * } else { + * state.state = "done"; + * } + * }, + * take: (state, x) => { + * if (x < 20) { + * if (++state.count > 5) { + * state.state = "skip"; + * state.count = 1; + * } else { + * return [x]; + * } + * } else { + * state.state = "done"; + * } + * }, + * done: () => { }, + * }, + * terminate: "done", + * init: () => ({ state: "skip", count: 0 }) + * } + * + * [...tx.iterator(fsm.fsm(testFSM), tx.range(100))] + * // [ 5, 6, 7, 8, 9, 15, 16, 17, 18, 19 ] + * + * // as part of composed transducers... + * + * [...tx.iterator( + * tx.comp(tx.takeNth(2), fsm.fsm(testFSM)), + * tx.range(100))] + * // [ 10, 12, 14, 16, 18 ] + * + * [...tx.iterator( + * tx.comp(fsm.fsm(testFSM), tx.map((x) => x * 10)), + * tx.range(100))] + * // [ 50, 60, 70, 80, 90, 150, 160, 170, 180, 190 ] + * ``` + * + * @param opts + */ +export function fsm(opts: FSMOpts): Transducer { + return comp((rfn: Reducer) => { + const states = opts.states; + const state = opts.init(); + const r = rfn[2]; + return compR(rfn, + (acc, x) => { + const res = states[state.state](state, x); + if (res != null) { + for (let i = 0, n = (res).length; i < n; i++) { + acc = r(acc, res[i]); + if (isReduced(acc)) { + break; + } + } + } + if (state.state === opts.terminate) { + return ensureReduced(acc); + } + return acc; + }); + }); +} diff --git a/packages/transducers-fsm/test/index.ts b/packages/transducers-fsm/test/index.ts new file mode 100644 index 0000000000..83054b548a --- /dev/null +++ b/packages/transducers-fsm/test/index.ts @@ -0,0 +1,50 @@ +import * as assert from "assert"; +import * as tx from "@thi.ng/transducers"; +import { fsm } from "../src/index"; + +describe("transducers-fsm", () => { + it("readme example", () => { + const testFSM = fsm({ + states: { + skip: (state, x) => { + if (x < 20) { + if (++state.count > 5) { + state.state = "take"; + state.count = 1; + return [x]; + } + } else { + state.state = "done"; + } + }, + take: (state, x) => { + if (x < 20) { + if (++state.count > 5) { + state.state = "skip"; + state.count = 1; + } else { + return [x]; + } + } else { + state.state = "done"; + } + }, + done: () => { }, + }, + terminate: "done", + init: () => ({ state: "skip", count: 0 }) + }); + assert.deepEqual( + [...tx.iterator(testFSM, tx.range(100))], + [5, 6, 7, 8, 9, 15, 16, 17, 18, 19] + ); + assert.deepEqual( + [...tx.iterator(tx.comp(tx.takeNth(2), testFSM), tx.range(100))], + [10, 12, 14, 16, 18] + ); + assert.deepEqual( + [...tx.iterator(tx.comp(testFSM, tx.map((x: number) => x * 10)), tx.range(100))], + [50, 60, 70, 80, 90, 150, 160, 170, 180, 190] + ); + }); +}); diff --git a/packages/transducers-fsm/test/tsconfig.json b/packages/transducers-fsm/test/tsconfig.json new file mode 100644 index 0000000000..bcf29ace54 --- /dev/null +++ b/packages/transducers-fsm/test/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "../build" + }, + "include": [ + "./**/*.ts", + "../src/**/*.ts" + ] +} diff --git a/packages/transducers-fsm/tsconfig.json b/packages/transducers-fsm/tsconfig.json new file mode 100644 index 0000000000..bd6481a5a6 --- /dev/null +++ b/packages/transducers-fsm/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": [ + "./src/**/*.ts" + ] +} diff --git a/packages/transducers/CHANGELOG.md b/packages/transducers/CHANGELOG.md index 8dc5e2f327..954027b8dd 100644 --- a/packages/transducers/CHANGELOG.md +++ b/packages/transducers/CHANGELOG.md @@ -3,6 +3,41 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [1.11.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/transducers@1.11.0...@thi.ng/transducers@1.11.1) (2018-06-21) + + + + +**Note:** Version bump only for package @thi.ng/transducers + + +# [1.11.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/transducers@1.10.3...@thi.ng/transducers@1.11.0) (2018-06-19) + + +### Features + +* **transducers:** add matchFirst()/matchLast() xforms, update readme ([bc261e5](https://github.com/thi-ng/umbrella/commit/bc261e5)) + + + + + +## [1.10.3](https://github.com/thi-ng/umbrella/compare/@thi.ng/transducers@1.10.2...@thi.ng/transducers@1.10.3) (2018-06-18) + + + + +**Note:** Version bump only for package @thi.ng/transducers + + +## [1.10.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/transducers@1.10.1...@thi.ng/transducers@1.10.2) (2018-05-14) + + + + +**Note:** Version bump only for package @thi.ng/transducers + ## [1.10.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/transducers@1.10.0...@thi.ng/transducers@1.10.1) (2018-05-14) diff --git a/packages/transducers/README.md b/packages/transducers/README.md index a151ea9efe..7bca74279f 100644 --- a/packages/transducers/README.md +++ b/packages/transducers/README.md @@ -8,7 +8,7 @@ This project is part of the ## About Lightweight transducer and supporting generators / iterator -implementations for ES6 / TypeScript (~8.4KB gzipped, full lib). +implementations for ES6 / TypeScript (~8.5KB gzipped, full lib). ## TOC @@ -34,12 +34,14 @@ though the implementation does heavily differ (also in contrast to some other JS based implementations) and dozens of less common, but generally highly useful operators have been added. See full list below. -The -[@thi.ng/rstream](https://github.com/thi-ng/umbrella/tree/master/packages/rstream) -& -[@thi.ng/csp](https://github.com/thi-ng/umbrella/tree/master/packages/csp) -partner modules provide related functionality, supplementing features of -this library and depending on it. +### Related functionality / packages + +- [@thi.ng/csp](https://github.com/thi-ng/umbrella/tree/master/packages/csp) +- [@thi.ng/rstream](https://github.com/thi-ng/umbrella/tree/master/packages/rstream) +- [@thi.ng/rstream-graph](https://github.com/thi-ng/umbrella/tree/master/packages/rstream-graph) +- [@thi.ng/rstream-log](https://github.com/thi-ng/umbrella/tree/master/packages/rstream-log) +- [@thi.ng/sax](https://github.com/thi-ng/umbrella/tree/master/packages/sax) +- [@thi.ng/transducers-fsm](https://github.com/thi-ng/umbrella/tree/master/packages/transducers-fsm) Since 0.8.0 this project largely supersedes the [@thi.ng/iterators](https://github.com/thi-ng/umbrella/tree/master/packages/iterators) @@ -675,6 +677,10 @@ itself. Returns nothing. #### `mapVals(fn: (v: A) => B, copy = true): Transducer, IObjectOf>` +#### `matchFirst(pred: Predicate): Transducer` + +#### `matchLast(pred: Predicate): Transducer` + #### `movingAverage(n: number): Transducer` #### `movingMedian(n: number, key?: ((x: A) => B), cmp?: Comparator): Transducer` diff --git a/packages/transducers/package.json b/packages/transducers/package.json index 937aba7bda..98645a96f0 100644 --- a/packages/transducers/package.json +++ b/packages/transducers/package.json @@ -1,10 +1,14 @@ { "name": "@thi.ng/transducers", - "version": "1.10.1", + "version": "1.11.1", "description": "Lightweight transducer implementations for ES6 / TypeScript", "main": "./index.js", "typings": "./index.d.ts", - "repository": "https://github.com/thi-ng/umbrella", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/transducers", "author": "Karsten Schmidt ", "license": "Apache-2.0", "scripts": { @@ -24,11 +28,11 @@ "typescript": "^2.8.3" }, "dependencies": { - "@thi.ng/api": "^4.0.2", - "@thi.ng/checks": "^1.5.3", - "@thi.ng/compare": "^0.1.2", - "@thi.ng/equiv": "^0.1.2", - "@thi.ng/errors": "^0.1.3" + "@thi.ng/api": "^4.0.4", + "@thi.ng/checks": "^1.5.5", + "@thi.ng/compare": "^0.1.4", + "@thi.ng/equiv": "^0.1.5", + "@thi.ng/errors": "^0.1.4" }, "keywords": [ "ES6", diff --git a/packages/transducers/src/index.ts b/packages/transducers/src/index.ts index ec66127ecc..f6682ae689 100644 --- a/packages/transducers/src/index.ts +++ b/packages/transducers/src/index.ts @@ -59,6 +59,8 @@ export * from "./xform/map-nth"; export * from "./xform/map-vals"; export * from "./xform/map"; export * from "./xform/mapcat"; +export * from "./xform/match-first"; +export * from "./xform/match-last"; export * from "./xform/moving-average"; export * from "./xform/moving-median"; export * from "./xform/multiplex"; diff --git a/packages/transducers/src/xform/match-first.ts b/packages/transducers/src/xform/match-first.ts new file mode 100644 index 0000000000..d12e256640 --- /dev/null +++ b/packages/transducers/src/xform/match-first.ts @@ -0,0 +1,20 @@ +import { Predicate } from "@thi.ng/api"; + +import { Transducer } from "../api"; +import { comp } from "../func/comp"; +import { filter } from "./filter"; +import { take } from "./take"; + +/** + * Transducer composition / syntax sugar for: + * + * comp(filter(pred), take(1)) + * + * Yields none or only the first value which passed the predicate check + * and then causes early termination. + * + * @param pred + */ +export function matchFirst(pred: Predicate): Transducer { + return comp(filter(pred), take(1)); +} diff --git a/packages/transducers/src/xform/match-last.ts b/packages/transducers/src/xform/match-last.ts new file mode 100644 index 0000000000..d6c6ad06ff --- /dev/null +++ b/packages/transducers/src/xform/match-last.ts @@ -0,0 +1,19 @@ +import { Predicate } from "@thi.ng/api"; + +import { Transducer } from "../api"; +import { comp } from "../func/comp"; +import { filter } from "./filter"; +import { takeLast } from "./take-last"; + +/** + * Transducer composition / syntax sugar for: + * + * comp(filter(pred), takeLast(1)) + * + * Yields none or only the last value which passed the predicate check. + * + * @param pred + */ +export function matchLast(pred: Predicate): Transducer { + return comp(filter(pred), takeLast(1)); +} diff --git a/packages/unionstruct/CHANGELOG.md b/packages/unionstruct/CHANGELOG.md index 58e03e42df..069c12b95e 100644 --- a/packages/unionstruct/CHANGELOG.md +++ b/packages/unionstruct/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.11](https://github.com/thi-ng/umbrella/compare/@thi.ng/unionstruct@0.1.10...@thi.ng/unionstruct@0.1.11) (2018-06-21) + + + + +**Note:** Version bump only for package @thi.ng/unionstruct + ## [0.1.10](https://github.com/thi-ng/umbrella/compare/@thi.ng/unionstruct@0.1.9...@thi.ng/unionstruct@0.1.10) (2018-05-13) diff --git a/packages/unionstruct/package.json b/packages/unionstruct/package.json index 1d1097e59c..de288bc28e 100644 --- a/packages/unionstruct/package.json +++ b/packages/unionstruct/package.json @@ -1,10 +1,14 @@ { "name": "@thi.ng/unionstruct", - "version": "0.1.10", + "version": "0.1.11", "description": "C-style struct, union and bitfield views of ArrayBuffers", "main": "./index.js", "typings": "./index.d.ts", - "repository": "https://github.com/thi-ng/umbrella", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/unionstruct", "author": "Karsten Schmidt ", "license": "Apache-2.0", "scripts": { diff --git a/scripts/make-module b/scripts/make-module index ce9b426676..9351d03d4a 100755 --- a/scripts/make-module +++ b/scripts/make-module @@ -31,7 +31,11 @@ cat << EOF > $MODULE/package.json "description": "TODO", "main": "./index.js", "typings": "./index.d.ts", - "repository": "https://github.com/thi-ng/umbrella", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/$1", "author": "$AUTHOR <$EMAIL>", "license": "Apache-2.0", "scripts": { @@ -51,7 +55,7 @@ cat << EOF > $MODULE/package.json "typescript": "^2.8.3" }, "dependencies": { - "@thi.ng/api": "^2.3.2" + "@thi.ng/api": "^4.0.3" }, "keywords": [ "ES6",