From 7349ef435e46f7e5100fcbe0813eb41159dee04e Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Tue, 15 May 2018 03:53:07 +0100 Subject: [PATCH 01/54] test(hdom): fix/update lifecycle test --- packages/hdom/test/index.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) 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 ); }); From 577bdde8e20f8cc69ff64f63a41a12c06c61d0fa Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Tue, 15 May 2018 04:41:34 +0100 Subject: [PATCH 02/54] docs: add discord badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 398737ad49..740510f055 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ [![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 From 09d531a7c2afe79a8e9ca7bb0e268871ff589419 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Tue, 15 May 2018 04:55:26 +0100 Subject: [PATCH 03/54] feat(exmples): add hdom-dropdown-fuzzy (unfinished) --- examples/hdom-dropdown-fuzzy/.gitignore | 3 + examples/hdom-dropdown-fuzzy/README.md | 18 ++ examples/hdom-dropdown-fuzzy/index.html | 25 ++ examples/hdom-dropdown-fuzzy/package.json | 26 ++ examples/hdom-dropdown-fuzzy/src/config.ts | 237 ++++++++++++++++++ examples/hdom-dropdown-fuzzy/src/dropdown.ts | 75 ++++++ examples/hdom-dropdown-fuzzy/src/fuzzy.ts | 41 +++ examples/hdom-dropdown-fuzzy/src/index.ts | 44 ++++ examples/hdom-dropdown-fuzzy/src/input.ts | 43 ++++ examples/hdom-dropdown-fuzzy/tsconfig.json | 9 + .../hdom-dropdown-fuzzy/webpack.config.js | 18 ++ 11 files changed, 539 insertions(+) create mode 100644 examples/hdom-dropdown-fuzzy/.gitignore create mode 100644 examples/hdom-dropdown-fuzzy/README.md create mode 100644 examples/hdom-dropdown-fuzzy/index.html create mode 100644 examples/hdom-dropdown-fuzzy/package.json create mode 100644 examples/hdom-dropdown-fuzzy/src/config.ts create mode 100644 examples/hdom-dropdown-fuzzy/src/dropdown.ts create mode 100644 examples/hdom-dropdown-fuzzy/src/fuzzy.ts create mode 100644 examples/hdom-dropdown-fuzzy/src/index.ts create mode 100644 examples/hdom-dropdown-fuzzy/src/input.ts create mode 100644 examples/hdom-dropdown-fuzzy/tsconfig.json create mode 100644 examples/hdom-dropdown-fuzzy/webpack.config.js 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..74a37796ba --- /dev/null +++ b/examples/hdom-dropdown-fuzzy/README.md @@ -0,0 +1,18 @@ +# hdom-dropdown-fuzzy + +[Live demo](http://demo.thi.ng/umbrella/hdom-dropdown-fuzzy/) + +``` +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..5df7a41ba9 --- /dev/null +++ b/examples/hdom-dropdown-fuzzy/src/config.ts @@ -0,0 +1,237 @@ +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 ba" + }, + 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" }, + } +}; diff --git a/examples/hdom-dropdown-fuzzy/src/dropdown.ts b/examples/hdom-dropdown-fuzzy/src/dropdown.ts new file mode 100644 index 0000000000..7c07f8252d --- /dev/null +++ b/examples/hdom-dropdown-fuzzy/src/dropdown.ts @@ -0,0 +1,75 @@ +import { IObjectOf } from "@thi.ng/api/api"; +import { ReadonlyAtom } from "@thi.ng/atom/api"; +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; + onmouseover: EventListener; + onmouseleave: EventListener; +} + +export interface DropdownState { + open: boolean; + hover: boolean; + selected: any; + items: [any, any][]; +} + +export interface DropdownTheme { + root: IObjectOf; + bodyOpen: IObjectOf; + bodyClosed: IObjectOf; + item: IObjectOf; + itemSelected: 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, + [appLink, { ...hattribs, ...ui.itemSelected }, opts.ontoggle, opts.openLabel || opts.hoverLabel], + ["div", ui.bodyOpen, + state.items.map( + (x) => appLink(null, x[0] === state.selected ? ui.itemSelected : ui.item, opts.onchange(x[0]), x[1]) + )]] : + ["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..6e7e122358 --- /dev/null +++ b/examples/hdom-dropdown-fuzzy/src/fuzzy.ts @@ -0,0 +1,41 @@ +import { IView } from "@thi.ng/atom/api"; +import { EV_SET_VALUE } from "@thi.ng/interceptors/api"; +import { filterFuzzy, iterator } from "@thi.ng/transducers"; +import { dropdownListeners, DropdownState } from "./dropdown"; + +export interface FuzzyArgs { + state: IView; + filter: IView; + dropdown: any; + input: any; + hoverLabel: any; + placeholder: string; +} + +export const fuzzyDropdown = (ctx, opts: FuzzyArgs) => { + 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]]), + oncancel: () => ctx.bus.dispatch([EV_SET_VALUE, [opts.state.path + ".open", false]]), + onconfirm: () => ctx.bus.dispatch([EV_SET_VALUE, [opts.state.path + ".open", false]]) + }]; + return () => { + const state = { ...opts.state.deref() }; + if (opts.filter) { + state.items = [ + ...iterator( + filterFuzzy( + opts.filter.deref().toLowerCase(), + (x: [any, any]) => x[1].toLowerCase()), + state.items) + ]; + } + return [opts.dropdown, { + ...dropdownListeners(ctx, opts.state.path), + openLabel: filterInput, + hoverLabel: opts.hoverLabel, + state + }]; + }; +}; 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..a9dbb9da0c --- /dev/null +++ b/examples/hdom-dropdown-fuzzy/src/input.ts @@ -0,0 +1,43 @@ +import { Path } from "@thi.ng/api/api"; +import { getIn } from "@thi.ng/paths"; +import { IView } from "@thi.ng/atom/api"; + +export interface InputArgs { + state: IView; + orig: IView; + attribs: any; + placeholder: string; + oninput: EventListener; + oncancel: EventListener; + onconfirm: EventListener; +} + +export function cancelableInput(themeCtxPath: Path) { + return (_, args: InputArgs) => [{ + init: (el) => el.focus(), + render: (ctx) => + ["input", + { + ...getIn(ctx, themeCtxPath), + ...args.attribs, + type: "text", + oninput: args.oninput, + onkeydown: (e: KeyboardEvent) => { + switch (e.key) { + case "Escape": + args.oncancel && args.oncancel(e); + (e.target).blur(); + break; + case "Enter": + args.onconfirm && args.onconfirm(e); + (e.target).blur(); + break; + default: + } + }, + placeholder: args.placeholder, + value: args.state + } + ] + }]; +} 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: "." + } +}; From db58ebc7cdab88460dc1a2dc6ee6d62864bc9cb8 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Tue, 15 May 2018 13:07:47 +0100 Subject: [PATCH 04/54] feat(examples): add fuzzy match highlighting --- examples/hdom-dropdown-fuzzy/README.md | 3 ++ examples/hdom-dropdown-fuzzy/src/config.ts | 4 +++ examples/hdom-dropdown-fuzzy/src/dropdown.ts | 21 ++++++++--- examples/hdom-dropdown-fuzzy/src/fuzzy.ts | 38 ++++++++++++++++---- examples/hdom-dropdown-fuzzy/src/input.ts | 2 +- 5 files changed, 57 insertions(+), 11 deletions(-) diff --git a/examples/hdom-dropdown-fuzzy/README.md b/examples/hdom-dropdown-fuzzy/README.md index 74a37796ba..861253b871 100644 --- a/examples/hdom-dropdown-fuzzy/README.md +++ b/examples/hdom-dropdown-fuzzy/README.md @@ -2,6 +2,9 @@ [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 diff --git a/examples/hdom-dropdown-fuzzy/src/config.ts b/examples/hdom-dropdown-fuzzy/src/config.ts index 5df7a41ba9..e6959b9b6b 100644 --- a/examples/hdom-dropdown-fuzzy/src/config.ts +++ b/examples/hdom-dropdown-fuzzy/src/config.ts @@ -233,5 +233,9 @@ export const theme = { }, 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 index 7c07f8252d..09c4628c33 100644 --- a/examples/hdom-dropdown-fuzzy/src/dropdown.ts +++ b/examples/hdom-dropdown-fuzzy/src/dropdown.ts @@ -1,5 +1,6 @@ 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"; @@ -18,6 +19,7 @@ export interface DropdownArgs { attribs: IObjectOf; hoverLabel: any; openLabel: any; + noItems: any; onmouseover: EventListener; onmouseleave: EventListener; } @@ -26,15 +28,18 @@ export interface DropdownState { open: boolean; hover: boolean; selected: any; - items: [any, 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) { @@ -49,9 +54,17 @@ export function dropdown(themeCtxPath: Path) { ["div", ui.root, [appLink, { ...hattribs, ...ui.itemSelected }, opts.ontoggle, opts.openLabel || opts.hoverLabel], ["div", ui.bodyOpen, - state.items.map( - (x) => appLink(null, x[0] === state.selected ? ui.itemSelected : ui.item, opts.onchange(x[0]), x[1]) - )]] : + state.items.length ? + state.items.map( + (x) => + ["a", + { + ...x[0] === state.selected ? ui.itemSelected : ui.item, + 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 ? diff --git a/examples/hdom-dropdown-fuzzy/src/fuzzy.ts b/examples/hdom-dropdown-fuzzy/src/fuzzy.ts index 6e7e122358..597ead30c0 100644 --- a/examples/hdom-dropdown-fuzzy/src/fuzzy.ts +++ b/examples/hdom-dropdown-fuzzy/src/fuzzy.ts @@ -1,7 +1,11 @@ import { IView } from "@thi.ng/atom/api"; import { EV_SET_VALUE } from "@thi.ng/interceptors/api"; -import { filterFuzzy, iterator } from "@thi.ng/transducers"; -import { dropdownListeners, DropdownState } from "./dropdown"; +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; @@ -22,12 +26,14 @@ export const fuzzyDropdown = (ctx, opts: FuzzyArgs) => { }]; return () => { const state = { ...opts.state.deref() }; - if (opts.filter) { + const filter = opts.filter.deref().toLowerCase(); + if (filter) { state.items = [ ...iterator( - filterFuzzy( - opts.filter.deref().toLowerCase(), - (x: [any, any]) => x[1].toLowerCase()), + comp( + filterFuzzy(filter, (x: DropdownItem) => x[1].toLowerCase()), + map(highlightMatches(filter, ctx.theme.fuzzy)) + ), state.items) ]; } @@ -35,7 +41,27 @@ export const fuzzyDropdown = (ctx, opts: FuzzyArgs) => { ...dropdownListeners(ctx, opts.state.path), openLabel: filterInput, hoverLabel: opts.hoverLabel, + noItems: "no matches", state }]; }; }; + +const highlightMatches = (filter: string, attribs: any) => { + filter = filter.toLowerCase(); + return ([id, x]: DropdownItem) => { + const res: any[] = []; + let prev = -1; + for (let i = 0, j = 0, n = x.length - 1, m = filter.length; 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(["span", attribs, c]); + prev = i; + j++; + } + } + res.push(x.substr(prev + 1)); + return [id, res]; + }; +} diff --git a/examples/hdom-dropdown-fuzzy/src/input.ts b/examples/hdom-dropdown-fuzzy/src/input.ts index a9dbb9da0c..14537aa425 100644 --- a/examples/hdom-dropdown-fuzzy/src/input.ts +++ b/examples/hdom-dropdown-fuzzy/src/input.ts @@ -1,6 +1,6 @@ import { Path } from "@thi.ng/api/api"; -import { getIn } from "@thi.ng/paths"; import { IView } from "@thi.ng/atom/api"; +import { getIn } from "@thi.ng/paths"; export interface InputArgs { state: IView; From 81b596f6e8597ffefd74a9fd2403c441e8058f37 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Tue, 15 May 2018 13:52:20 +0100 Subject: [PATCH 05/54] refactor(examples): update highlightMatches() --- examples/hdom-dropdown-fuzzy/src/fuzzy.ts | 34 +++++++++++------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/examples/hdom-dropdown-fuzzy/src/fuzzy.ts b/examples/hdom-dropdown-fuzzy/src/fuzzy.ts index 597ead30c0..1e49d165b7 100644 --- a/examples/hdom-dropdown-fuzzy/src/fuzzy.ts +++ b/examples/hdom-dropdown-fuzzy/src/fuzzy.ts @@ -27,12 +27,12 @@ export const fuzzyDropdown = (ctx, opts: FuzzyArgs) => { return () => { const state = { ...opts.state.deref() }; const filter = opts.filter.deref().toLowerCase(); - if (filter) { + if (filter && state.open) { state.items = [ ...iterator( comp( filterFuzzy(filter, (x: DropdownItem) => x[1].toLowerCase()), - map(highlightMatches(filter, ctx.theme.fuzzy)) + map(([id, x]) => [id, highlightMatches(x, filter, ctx.theme.fuzzy)]) ), state.items) ]; @@ -47,21 +47,19 @@ export const fuzzyDropdown = (ctx, opts: FuzzyArgs) => { }; }; -const highlightMatches = (filter: string, attribs: any) => { +const highlightMatches = (x: string, filter: string, attribs: any) => { filter = filter.toLowerCase(); - return ([id, x]: DropdownItem) => { - const res: any[] = []; - let prev = -1; - for (let i = 0, j = 0, n = x.length - 1, m = filter.length; 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(["span", attribs, c]); - prev = i; - j++; - } + 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(["span", attribs, c]); + prev = i; + j++; } - res.push(x.substr(prev + 1)); - return [id, res]; - }; -} + } + prev < n && res.push(x.substr(prev + 1)); + return res; +}; From 2482b16e0a6f0a8b53f1a8f6b214ba1e4cbe1f7c Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Tue, 15 May 2018 22:45:46 +0100 Subject: [PATCH 06/54] fix(hdom): delay init() lifecycle call to ensure children are available - update diffElement() --- packages/hdom/src/diff.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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) { From 7e1028a26b80f7946618b13fd84d650be017008d Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Tue, 15 May 2018 22:50:29 +0100 Subject: [PATCH 07/54] fix(examples): tab handling in fuzzy dropdown --- examples/hdom-dropdown-fuzzy/src/config.ts | 2 +- examples/hdom-dropdown-fuzzy/src/dropdown.ts | 3 +- examples/hdom-dropdown-fuzzy/src/fuzzy.ts | 16 +++--- examples/hdom-dropdown-fuzzy/src/input.ts | 57 +++++++++++--------- 4 files changed, 45 insertions(+), 33 deletions(-) diff --git a/examples/hdom-dropdown-fuzzy/src/config.ts b/examples/hdom-dropdown-fuzzy/src/config.ts index e6959b9b6b..815dfc3342 100644 --- a/examples/hdom-dropdown-fuzzy/src/config.ts +++ b/examples/hdom-dropdown-fuzzy/src/config.ts @@ -212,7 +212,7 @@ export const theme = { class: "fl w-100 w-50-ns w-33-l pa2" }, input: { - class: "bg-transparent w-100 ba" + class: "bg-transparent w-100 bn pa2" }, dd: { root: { class: "" }, diff --git a/examples/hdom-dropdown-fuzzy/src/dropdown.ts b/examples/hdom-dropdown-fuzzy/src/dropdown.ts index 09c4628c33..44640e3708 100644 --- a/examples/hdom-dropdown-fuzzy/src/dropdown.ts +++ b/examples/hdom-dropdown-fuzzy/src/dropdown.ts @@ -51,7 +51,7 @@ export function dropdown(themeCtxPath: Path) { onmouseleave: opts.onmouseleave, }; return state.open ? - ["div", ui.root, + ["div", { ...ui.root, onkeydown: (e) => console.log(e) }, [appLink, { ...hattribs, ...ui.itemSelected }, opts.ontoggle, opts.openLabel || opts.hoverLabel], ["div", ui.bodyOpen, state.items.length ? @@ -60,6 +60,7 @@ export function dropdown(themeCtxPath: Path) { ["a", { ...x[0] === state.selected ? ui.itemSelected : ui.item, + href: "#", onclick: opts.onchange(x[0]), }, ...(isString(x[1]) ? [x[1]] : x[1])] diff --git a/examples/hdom-dropdown-fuzzy/src/fuzzy.ts b/examples/hdom-dropdown-fuzzy/src/fuzzy.ts index 1e49d165b7..c96e5e4ede 100644 --- a/examples/hdom-dropdown-fuzzy/src/fuzzy.ts +++ b/examples/hdom-dropdown-fuzzy/src/fuzzy.ts @@ -17,12 +17,13 @@ export interface FuzzyArgs { } 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]]), - oncancel: () => ctx.bus.dispatch([EV_SET_VALUE, [opts.state.path + ".open", false]]), - onconfirm: () => ctx.bus.dispatch([EV_SET_VALUE, [opts.state.path + ".open", false]]) + oncancel: close, + onconfirm: close, }]; return () => { const state = { ...opts.state.deref() }; @@ -32,7 +33,11 @@ export const fuzzyDropdown = (ctx, opts: FuzzyArgs) => { ...iterator( comp( filterFuzzy(filter, (x: DropdownItem) => x[1].toLowerCase()), - map(([id, x]) => [id, highlightMatches(x, filter, ctx.theme.fuzzy)]) + map(([id, x]) => + [ + id, + highlightMatches((y) => ["span", ctx.theme.fuzzy, y], x, filter) + ]) ), state.items) ]; @@ -47,15 +52,14 @@ export const fuzzyDropdown = (ctx, opts: FuzzyArgs) => { }; }; -const highlightMatches = (x: string, filter: string, attribs: any) => { - filter = filter.toLowerCase(); +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(["span", attribs, c]); + res.push(fn(c)); prev = i; j++; } diff --git a/examples/hdom-dropdown-fuzzy/src/input.ts b/examples/hdom-dropdown-fuzzy/src/input.ts index 14537aa425..b80259b47a 100644 --- a/examples/hdom-dropdown-fuzzy/src/input.ts +++ b/examples/hdom-dropdown-fuzzy/src/input.ts @@ -10,34 +10,41 @@ export interface InputArgs { oninput: EventListener; oncancel: EventListener; onconfirm: EventListener; + onblur: EventListener; } export function cancelableInput(themeCtxPath: Path) { - return (_, args: InputArgs) => [{ - init: (el) => el.focus(), - render: (ctx) => - ["input", - { - ...getIn(ctx, themeCtxPath), - ...args.attribs, - type: "text", - oninput: args.oninput, - onkeydown: (e: KeyboardEvent) => { - switch (e.key) { - case "Escape": - args.oncancel && args.oncancel(e); - (e.target).blur(); - break; - case "Enter": - args.onconfirm && args.onconfirm(e); - (e.target).blur(); - break; - default: - } + return { + init: (el: HTMLElement) => (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 }, - placeholder: args.placeholder, - value: args.state - } + ], + ["i.absolute.fas.fa-times-circle.gray.f7", + { style: { right: "0.5rem", top: "0.25rem" } }] ] - }]; + }; } From 0c29250350265b82108bb53d9d09cc661f8925ce Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Wed, 16 May 2018 00:17:32 +0100 Subject: [PATCH 08/54] feat(examples): add input clearing --- examples/hdom-dropdown-fuzzy/src/fuzzy.ts | 1 + examples/hdom-dropdown-fuzzy/src/input.ts | 19 ++++++++++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/examples/hdom-dropdown-fuzzy/src/fuzzy.ts b/examples/hdom-dropdown-fuzzy/src/fuzzy.ts index c96e5e4ede..dc36c3e4b8 100644 --- a/examples/hdom-dropdown-fuzzy/src/fuzzy.ts +++ b/examples/hdom-dropdown-fuzzy/src/fuzzy.ts @@ -22,6 +22,7 @@ export const fuzzyDropdown = (ctx, opts: FuzzyArgs) => { 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, }]; diff --git a/examples/hdom-dropdown-fuzzy/src/input.ts b/examples/hdom-dropdown-fuzzy/src/input.ts index b80259b47a..847ca34f1f 100644 --- a/examples/hdom-dropdown-fuzzy/src/input.ts +++ b/examples/hdom-dropdown-fuzzy/src/input.ts @@ -10,12 +10,14 @@ export interface InputArgs { oninput: EventListener; oncancel: EventListener; onconfirm: EventListener; + onclear: EventListener; onblur: EventListener; } export function cancelableInput(themeCtxPath: Path) { + let input; return { - init: (el: HTMLElement) => (el.firstChild).focus(), + init: (el: HTMLElement) => (input = el.firstChild).focus(), render: (ctx, args: InputArgs) => ["span.relative", ["input", @@ -43,8 +45,19 @@ export function cancelableInput(themeCtxPath: Path) { value: args.state }, ], - ["i.absolute.fas.fa-times-circle.gray.f7", - { style: { right: "0.5rem", top: "0.25rem" } }] + 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 ] }; } From 0e103c50f43977a01eb7acd4513ff15f8a25e9cf Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Wed, 16 May 2018 00:26:28 +0100 Subject: [PATCH 09/54] Publish - @thi.ng/hdom@3.0.23 --- packages/hdom/CHANGELOG.md | 11 +++++++++++ packages/hdom/package.json | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/hdom/CHANGELOG.md b/packages/hdom/CHANGELOG.md index a8036eff0d..2f2d7b2bba 100644 --- a/packages/hdom/CHANGELOG.md +++ b/packages/hdom/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [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) diff --git a/packages/hdom/package.json b/packages/hdom/package.json index 2a27756cac..bae8cb565c 100644 --- a/packages/hdom/package.json +++ b/packages/hdom/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/hdom", - "version": "3.0.22", + "version": "3.0.23", "description": "Lightweight vanilla ES6 UI component & virtual DOM system", "main": "./index.js", "typings": "./index.d.ts", From 23fdd39808b2f4f5b1ba0ed7fae1e49d2a4030a7 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Sat, 19 May 2018 19:06:24 -0700 Subject: [PATCH 10/54] feat(rstream): update resolve(), update subscribe() overrides - replace resolve() opt `id` arg w/ `ResolveOpts` object - if `fail` option is given use as Promise failure handler instead of calling `this.error()` and thereby stopping stream - add new override for actual child `Subscription`s, fixes generics - update `subscribe()` for Subscription, Stream, PubSub --- packages/rstream/src/pubsub.ts | 3 ++- packages/rstream/src/stream.ts | 5 +++-- packages/rstream/src/subs/resolve.ts | 19 +++++++++++++------ packages/rstream/src/subscription.ts | 5 +++-- 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/packages/rstream/src/pubsub.ts b/packages/rstream/src/pubsub.ts index 62b0ff888d..561dbe2a2b 100644 --- a/packages/rstream/src/pubsub.ts +++ b/packages/rstream/src/pubsub.ts @@ -81,8 +81,9 @@ export class PubSub extends Subscription { return null; } - subscribeTopic(topicID: any, sub: Partial>, id?: string): Subscription; subscribeTopic(topicID: any, tx: Transducer, id?: string): Subscription; + 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) { diff --git a/packages/rstream/src/stream.ts b/packages/rstream/src/stream.ts index 25fe200173..64ebeb11f8 100644 --- a/packages/rstream/src/stream.ts +++ b/packages/rstream/src/stream.ts @@ -46,9 +46,10 @@ export class Stream extends Subscription this.src = src; } - subscribe(sub: Partial>, id?: string): Subscription + subscribe(sub: Partial>, xform: Transducer, id?: string): Subscription; + 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/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..aefe5d3997 100644 --- a/packages/rstream/src/subscription.ts +++ b/packages/rstream/src/subscription.ts @@ -57,9 +57,10 @@ 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(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; From cbc600e73c801683c1b5c9a12bb3d737b51a5c8d Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Sun, 20 May 2018 13:05:33 -0700 Subject: [PATCH 11/54] fix(rstream): minor update PubSub topic fn return type --- packages/rstream/src/pubsub.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/rstream/src/pubsub.ts b/packages/rstream/src/pubsub.ts index 561dbe2a2b..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) { @@ -82,6 +82,7 @@ export class PubSub extends 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 { @@ -109,7 +110,6 @@ export class PubSub extends Subscription { return super.unsubscribe(); } unsupported(); - return false; } done() { From 846aaf97c7b3daa18ccd7f28f6f77e4fbbc6f7ab Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Sun, 20 May 2018 13:54:49 -0700 Subject: [PATCH 12/54] feat(rstream): re-implement bisect() using PubSub, update tests --- packages/rstream/src/subs/bisect.ts | 48 +++++++++++++++++++++-------- packages/rstream/test/bisect.ts | 23 +++++++------- 2 files changed, 48 insertions(+), 23 deletions(-) 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/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 From 773829e435eb0885de5a0c358d60b563373449a7 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Sun, 20 May 2018 13:58:15 -0700 Subject: [PATCH 13/54] minor(rstream): add alt subscribe() overrides (disabled) --- packages/rstream/src/api.ts | 4 +++- packages/rstream/src/stream.ts | 1 + packages/rstream/src/subscription.ts | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) 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/stream.ts b/packages/rstream/src/stream.ts index 64ebeb11f8..56fc04ff8b 100644 --- a/packages/rstream/src/stream.ts +++ b/packages/rstream/src/stream.ts @@ -47,6 +47,7 @@ export class Stream extends 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; diff --git a/packages/rstream/src/subscription.ts b/packages/rstream/src/subscription.ts index aefe5d3997..e8669fa882 100644 --- a/packages/rstream/src/subscription.ts +++ b/packages/rstream/src/subscription.ts @@ -58,6 +58,7 @@ export class Subscription implements * transducer and optional subscription ID. */ 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; From c880a63386769776046478887968d31f26b1a177 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Sun, 20 May 2018 14:55:06 -0700 Subject: [PATCH 14/54] Publish - @thi.ng/rstream-csp@0.1.74 - @thi.ng/rstream-dot@0.2.13 - @thi.ng/rstream-gestures@0.3.10 - @thi.ng/rstream-graph@1.0.17 - @thi.ng/rstream-log@1.0.25 - @thi.ng/rstream-query@0.3.12 - @thi.ng/rstream@1.7.0 --- packages/rstream-csp/CHANGELOG.md | 8 ++++++++ packages/rstream-csp/package.json | 4 ++-- packages/rstream-dot/CHANGELOG.md | 8 ++++++++ packages/rstream-dot/package.json | 4 ++-- packages/rstream-gestures/CHANGELOG.md | 8 ++++++++ packages/rstream-gestures/package.json | 4 ++-- packages/rstream-graph/CHANGELOG.md | 8 ++++++++ packages/rstream-graph/package.json | 4 ++-- packages/rstream-log/CHANGELOG.md | 8 ++++++++ packages/rstream-log/package.json | 4 ++-- packages/rstream-query/CHANGELOG.md | 8 ++++++++ packages/rstream-query/package.json | 6 +++--- packages/rstream/CHANGELOG.md | 17 +++++++++++++++++ packages/rstream/package.json | 2 +- 14 files changed, 79 insertions(+), 14 deletions(-) diff --git a/packages/rstream-csp/CHANGELOG.md b/packages/rstream-csp/CHANGELOG.md index da01631b48..0777902e5d 100644 --- a/packages/rstream-csp/CHANGELOG.md +++ b/packages/rstream-csp/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.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) diff --git a/packages/rstream-csp/package.json b/packages/rstream-csp/package.json index 21d55dc306..2e4b02ae66 100644 --- a/packages/rstream-csp/package.json +++ b/packages/rstream-csp/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rstream-csp", - "version": "0.1.73", + "version": "0.1.74", "description": "@thi.ng/csp bridge module for @thi.ng/rstream", "main": "./index.js", "typings": "./index.d.ts", @@ -25,7 +25,7 @@ }, "dependencies": { "@thi.ng/csp": "^0.3.42", - "@thi.ng/rstream": "^1.6.14" + "@thi.ng/rstream": "^1.7.0" }, "keywords": [ "bridge", diff --git a/packages/rstream-dot/CHANGELOG.md b/packages/rstream-dot/CHANGELOG.md index 212baf6c50..a7afee4675 100644 --- a/packages/rstream-dot/CHANGELOG.md +++ b/packages/rstream-dot/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.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) diff --git a/packages/rstream-dot/package.json b/packages/rstream-dot/package.json index feb7b303de..15ad0badae 100644 --- a/packages/rstream-dot/package.json +++ b/packages/rstream-dot/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rstream-dot", - "version": "0.2.12", + "version": "0.2.13", "description": "Graphviz DOT conversion of @thi.ng/rstream dataflow graph topologies", "main": "./index.js", "typings": "./index.d.ts", @@ -24,7 +24,7 @@ "typescript": "^2.8.3" }, "dependencies": { - "@thi.ng/rstream": "^1.6.14" + "@thi.ng/rstream": "^1.7.0" }, "keywords": [ "conversion", diff --git a/packages/rstream-gestures/CHANGELOG.md b/packages/rstream-gestures/CHANGELOG.md index 746a950694..d7fc32c699 100644 --- a/packages/rstream-gestures/CHANGELOG.md +++ b/packages/rstream-gestures/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.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) diff --git a/packages/rstream-gestures/package.json b/packages/rstream-gestures/package.json index f068d0b351..ddf96172cb 100644 --- a/packages/rstream-gestures/package.json +++ b/packages/rstream-gestures/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rstream-gestures", - "version": "0.3.9", + "version": "0.3.10", "description": "Unified mouse, mouse wheel & single-touch event stream abstraction", "main": "./index.js", "typings": "./index.d.ts", @@ -25,7 +25,7 @@ }, "dependencies": { "@thi.ng/api": "^4.0.3", - "@thi.ng/rstream": "^1.6.14", + "@thi.ng/rstream": "^1.7.0", "@thi.ng/transducers": "^1.10.2" }, "keywords": [ diff --git a/packages/rstream-graph/CHANGELOG.md b/packages/rstream-graph/CHANGELOG.md index 7ec67374d3..13e9624d96 100644 --- a/packages/rstream-graph/CHANGELOG.md +++ b/packages/rstream-graph/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [1.0.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) diff --git a/packages/rstream-graph/package.json b/packages/rstream-graph/package.json index e6ed88e0dc..aa76ca2d07 100644 --- a/packages/rstream-graph/package.json +++ b/packages/rstream-graph/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rstream-graph", - "version": "1.0.16", + "version": "1.0.17", "description": "Declarative dataflow graph construction for @thi.ng/rstream", "main": "./index.js", "typings": "./index.d.ts", @@ -29,7 +29,7 @@ "@thi.ng/errors": "^0.1.3", "@thi.ng/paths": "^1.3.8", "@thi.ng/resolve-map": "^2.0.5", - "@thi.ng/rstream": "^1.6.14", + "@thi.ng/rstream": "^1.7.0", "@thi.ng/transducers": "^1.10.2" }, "keywords": [ diff --git a/packages/rstream-log/CHANGELOG.md b/packages/rstream-log/CHANGELOG.md index 16a8ddd3d9..c8b85fa062 100644 --- a/packages/rstream-log/CHANGELOG.md +++ b/packages/rstream-log/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [1.0.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) diff --git a/packages/rstream-log/package.json b/packages/rstream-log/package.json index 0481ac9604..aacd711caf 100644 --- a/packages/rstream-log/package.json +++ b/packages/rstream-log/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rstream-log", - "version": "1.0.24", + "version": "1.0.25", "description": "Structured, multilevel & hierarchical loggers based on @thi.ng/rstream", "main": "./index.js", "typings": "./index.d.ts", @@ -27,7 +27,7 @@ "@thi.ng/api": "^4.0.3", "@thi.ng/checks": "^1.5.3", "@thi.ng/errors": "^0.1.3", - "@thi.ng/rstream": "^1.6.14", + "@thi.ng/rstream": "^1.7.0", "@thi.ng/transducers": "^1.10.2" }, "keywords": [ diff --git a/packages/rstream-query/CHANGELOG.md b/packages/rstream-query/CHANGELOG.md index 90640713c5..c73c07b39c 100644 --- a/packages/rstream-query/CHANGELOG.md +++ b/packages/rstream-query/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.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) diff --git a/packages/rstream-query/package.json b/packages/rstream-query/package.json index 9b269e555c..67e6d5a504 100644 --- a/packages/rstream-query/package.json +++ b/packages/rstream-query/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rstream-query", - "version": "0.3.11", + "version": "0.3.12", "description": "@thi.ng/rstream based triple store & reactive query engine", "main": "./index.js", "typings": "./index.d.ts", @@ -29,8 +29,8 @@ "@thi.ng/checks": "^1.5.3", "@thi.ng/equiv": "^0.1.3", "@thi.ng/errors": "^0.1.3", - "@thi.ng/rstream": "^1.6.14", - "@thi.ng/rstream-dot": "^0.2.12", + "@thi.ng/rstream": "^1.7.0", + "@thi.ng/rstream-dot": "^0.2.13", "@thi.ng/transducers": "^1.10.2" }, "keywords": [ diff --git a/packages/rstream/CHANGELOG.md b/packages/rstream/CHANGELOG.md index ad23245f1b..0a9d3ae801 100644 --- a/packages/rstream/CHANGELOG.md +++ b/packages/rstream/CHANGELOG.md @@ -3,6 +3,23 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [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) diff --git a/packages/rstream/package.json b/packages/rstream/package.json index 4e94b8371a..91c7cf44b8 100644 --- a/packages/rstream/package.json +++ b/packages/rstream/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rstream", - "version": "1.6.14", + "version": "1.7.0", "description": "Reactive multi-tap streams, dataflow & transformation pipeline constructs", "main": "./index.js", "typings": "./index.d.ts", From 0818498a2d5197c03300522662d0dd50b0b8af35 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Sun, 20 May 2018 18:11:08 -0700 Subject: [PATCH 15/54] feat(rstream-graph): update types, initGraph(), node1(), add tests - allow pre-existing subscribables in GraphSpec - update initGraph() to only process NodeSpec values - update NodeSpec to use `Path` instead of `string` for state paths - update NodeSpec `const` inputs to support value factory fns - make xform arg to node1() optional - update doc strings --- packages/rstream-graph/src/api.ts | 24 ++++++++------ packages/rstream-graph/src/graph.ts | 34 +++++++++++--------- packages/rstream-graph/src/nodes/extract.ts | 3 ++ packages/rstream-graph/src/nodes/math.ts | 4 +-- packages/rstream-graph/test/index.ts | 35 +++++++++++++++++++-- 5 files changed, 71 insertions(+), 29 deletions(-) diff --git a/packages/rstream-graph/src/api.ts b/packages/rstream-graph/src/api.ts index 6b5392d691..5791b7f0ec 100644 --- a/packages/rstream-graph/src/api.ts +++ b/packages/rstream-graph/src/api.ts @@ -1,4 +1,5 @@ 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"; @@ -6,10 +7,10 @@ export type NodeFactory = (src: IObjectOf>, id: string) => /** * 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 either `ISubscribable`s or NodeSpec's, defining + * inputs and the operation to be applied to produce a result stream. */ -export type GraphSpec = IObjectOf; +export type GraphSpec = IObjectOf | NodeSpec>; /** * Specification for a single "node" in the dataflow graph. Nodes here @@ -41,18 +42,22 @@ 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 in the GraphSpec object. See + * `@thi.ng/resolve-map` for details. * * ``` - * { stream: "node-id" } + * { stream: "/path/to/node-id" } // absolute + * { stream: "../../path/to/node-id" } // relative + * { stream: "node-id" } // sibling * ``` * * 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 + * which is then used as input for current node. * * ``` * { stream: (resolve) => resolve("src").subscribe(map(x => x * 10)) } @@ -68,6 +73,7 @@ export interface NodeSpec { * * ``` * { const: 1 } + * { const: () => 1 } * ``` * * If the optional `xform` is given, a subscription with the transducer @@ -75,10 +81,10 @@ export interface NodeSpec { */ export interface NodeInput { 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 NodeOutput = Path | ((node: ISubscribable) => void); diff --git a/packages/rstream-graph/src/graph.ts b/packages/rstream-graph/src/graph.ts index 3e18841d41..bffb588805 100644 --- a/packages/rstream-graph/src/graph.ts +++ b/packages/rstream-graph/src/graph.ts @@ -1,5 +1,7 @@ import { IObjectOf } from "@thi.ng/api/api"; import { IAtom } from "@thi.ng/atom/api"; +import { implementsFunction } from "@thi.ng/checks/implements-function"; +import { isFunction } from "@thi.ng/checks/is-function"; import { isString } from "@thi.ng/checks/is-string"; import { illegalArgs } from "@thi.ng/errors/illegal-arguments"; import { resolveMap } from "@thi.ng/resolve-map"; @@ -7,10 +9,9 @@ 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 { GraphSpec, NodeFactory, NodeSpec } from "./api"; /** * Dataflow graph initialization function. Takes an object of @@ -22,9 +23,12 @@ import { NodeFactory, NodeSpec } from "./api"; * @param state * @param nodes */ -export const initGraph = (state: IAtom, nodes: IObjectOf): IObjectOf> => { +export const initGraph = (state: IAtom, nodes: GraphSpec): IObjectOf> => { for (let id in nodes) { - (nodes)[id] = nodeFromSpec(state, nodes[id], id); + const n = nodes[id]; + if (!implementsFunction(n, "subscribe")) { + (nodes)[id] = nodeFromSpec(state, nodes[id], id); + } } return resolveMap(nodes); }; @@ -51,13 +55,11 @@ const nodeFromSpec = (state: IAtom, spec: NodeSpec, id: string) => (resolve if (i.path) { s = fromView(state, i.path); } else if (i.stream) { - s = isString(i.stream) ? - resolve(i.stream) : - (i).stream(resolve); + s = isString(i.stream) ? resolve(i.stream) : i.stream(resolve); } else if (i.const) { - s = fromIterableSync([i.const]); + s = fromIterableSync([isFunction(i.const) ? i.const(resolve) : i.const]); } else { - illegalArgs(`invalid node spec`); + illegalArgs(`invalid node input: ${id}`); } if (i.xform) { s = s.subscribe(i.xform, id); @@ -66,10 +68,12 @@ const nodeFromSpec = (state: IAtom, spec: NodeSpec, id: string) => (resolve } 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); - } else { + if (isFunction(spec.out)) { spec.out(node); + } else { + ((path) => node.subscribe({ + next: (x) => state.resetIn(path, x) + }, `out-${id}`))(spec.out); } } return node; @@ -109,10 +113,10 @@ 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..db0363191f 100644 --- a/packages/rstream-graph/test/index.ts +++ b/packages/rstream-graph/test/index.ts @@ -1,6 +1,35 @@ -// import * as assert from "assert"; -// import * as rsg from "../src/index"; +import * as assert from "assert"; +import { Atom } from "@thi.ng/atom"; +import * as rs from "@thi.ng/rstream"; +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: rs.fromIterable([2]), + add: { + fn: rsg.add, + ins: { + a: { path: "a" }, + b: { path: "b" } + }, + }, + mul: { + fn: rsg.mul, + ins: { + a: { stream: "add" }, + b: { stream: () => rs.fromIterable([10, 20, 30]) }, + c: { stream: "foo" } + }, + } + }); + graph.mul.subscribe({ next: (x) => acc.push(x) }); + setTimeout(() => { + state.resetIn("a", 10); + assert.deepEqual(acc, [60, 120, 180, 720]); + done(); + }, 10); + }); }); From b756098978fe422cf62866ee5238befe5039e1c0 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Sun, 20 May 2018 18:15:39 -0700 Subject: [PATCH 16/54] Publish - @thi.ng/rstream-graph@1.1.0 --- packages/rstream-graph/CHANGELOG.md | 11 +++++++++++ packages/rstream-graph/package.json | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/rstream-graph/CHANGELOG.md b/packages/rstream-graph/CHANGELOG.md index 13e9624d96..898fe43ed5 100644 --- a/packages/rstream-graph/CHANGELOG.md +++ b/packages/rstream-graph/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [1.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) diff --git a/packages/rstream-graph/package.json b/packages/rstream-graph/package.json index aa76ca2d07..b13a06dca2 100644 --- a/packages/rstream-graph/package.json +++ b/packages/rstream-graph/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rstream-graph", - "version": "1.0.17", + "version": "1.1.0", "description": "Declarative dataflow graph construction for @thi.ng/rstream", "main": "./index.js", "typings": "./index.d.ts", From ad56421a11e6f6d7705343907083630b5eb5ebe1 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Sun, 20 May 2018 18:40:13 -0700 Subject: [PATCH 17/54] refactor(rstream-graph): allow fn vals in GraphSpec too, update test --- packages/rstream-graph/src/api.ts | 15 ++++++++++++--- packages/rstream-graph/src/graph.ts | 4 ++-- packages/rstream-graph/test/index.ts | 9 ++++++--- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/packages/rstream-graph/src/api.ts b/packages/rstream-graph/src/api.ts index 5791b7f0ec..d0ad0272c2 100644 --- a/packages/rstream-graph/src/api.ts +++ b/packages/rstream-graph/src/api.ts @@ -3,14 +3,23 @@ import { Path } from "@thi.ng/paths"; import { ISubscribable } from "@thi.ng/rstream/api"; import { Transducer } from "@thi.ng/transducers/api"; +/** + * A function which constructs and returns an `ISubscribable` using + * given object of inputs and node ID. See `node()` and `node1()`. + */ export type NodeFactory = (src: IObjectOf>, id: string) => ISubscribable; /** * A dataflow graph spec is simply an object where keys are node names - * and their values are either `ISubscribable`s or NodeSpec's, defining - * inputs and the operation to be applied to produce a result stream. + * and their values are either pre-existing @thi.ng/rstream + * `ISubscribable`s, functions returning `ISubscribable`s or + * `NodeSpec`s, defining inputs and the operation to be applied to + * produce a result stream. */ -export type GraphSpec = IObjectOf | NodeSpec>; +export type GraphSpec = IObjectOf< + NodeSpec | + ISubscribable | + ((resolve: (path: string) => any) => ISubscribable)>; /** * Specification for a single "node" in the dataflow graph. Nodes here diff --git a/packages/rstream-graph/src/graph.ts b/packages/rstream-graph/src/graph.ts index bffb588805..f74ae6cbb6 100644 --- a/packages/rstream-graph/src/graph.ts +++ b/packages/rstream-graph/src/graph.ts @@ -1,7 +1,7 @@ import { IObjectOf } from "@thi.ng/api/api"; import { IAtom } from "@thi.ng/atom/api"; -import { implementsFunction } from "@thi.ng/checks/implements-function"; 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"; @@ -26,7 +26,7 @@ import { GraphSpec, NodeFactory, NodeSpec } from "./api"; export const initGraph = (state: IAtom, nodes: GraphSpec): IObjectOf> => { for (let id in nodes) { const n = nodes[id]; - if (!implementsFunction(n, "subscribe")) { + if (isPlainObject(n)) { (nodes)[id] = nodeFromSpec(state, nodes[id], id); } } diff --git a/packages/rstream-graph/test/index.ts b/packages/rstream-graph/test/index.ts index db0363191f..6918075d79 100644 --- a/packages/rstream-graph/test/index.ts +++ b/packages/rstream-graph/test/index.ts @@ -1,6 +1,8 @@ -import * as assert from "assert"; 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", () => { @@ -9,6 +11,7 @@ describe("rstream-graph", () => { const state = new Atom({ a: 1, b: 2 }); const graph = rsg.initGraph(state, { foo: rs.fromIterable([2]), + bar: ($) => $("foo").transform(map((x: number) => x * 10)), add: { fn: rsg.add, ins: { @@ -21,14 +24,14 @@ describe("rstream-graph", () => { ins: { a: { stream: "add" }, b: { stream: () => rs.fromIterable([10, 20, 30]) }, - c: { stream: "foo" } + c: { stream: "bar" } }, } }); graph.mul.subscribe({ next: (x) => acc.push(x) }); setTimeout(() => { state.resetIn("a", 10); - assert.deepEqual(acc, [60, 120, 180, 720]); + assert.deepEqual(acc, [600, 1200, 1800, 7200]); done(); }, 10); }); From 44998ca0187a2b279aa35bcc74ca940489b15b80 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Sun, 20 May 2018 18:40:44 -0700 Subject: [PATCH 18/54] Publish - @thi.ng/rstream-graph@1.1.1 --- packages/rstream-graph/CHANGELOG.md | 8 ++++++++ packages/rstream-graph/package.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/rstream-graph/CHANGELOG.md b/packages/rstream-graph/CHANGELOG.md index 898fe43ed5..999386a76c 100644 --- a/packages/rstream-graph/CHANGELOG.md +++ b/packages/rstream-graph/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [1.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) diff --git a/packages/rstream-graph/package.json b/packages/rstream-graph/package.json index b13a06dca2..535680dbb1 100644 --- a/packages/rstream-graph/package.json +++ b/packages/rstream-graph/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rstream-graph", - "version": "1.1.0", + "version": "1.1.1", "description": "Declarative dataflow graph construction for @thi.ng/rstream", "main": "./index.js", "typings": "./index.d.ts", From 94225989315c69bfe565748fcfe569270c4f153e Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Tue, 29 May 2018 14:36:38 +0100 Subject: [PATCH 19/54] feat(atom): add INotify impl for History - add EVENT_UNDO/REDO/RECORD events - emit events from `undo()`, `redo()`, `record()` --- packages/atom/src/api.ts | 5 +++- packages/atom/src/history.ts | 48 +++++++++++++++++++++++++++++++++--- 2 files changed, 48 insertions(+), 5 deletions(-) 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..7d2273009d 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,19 @@ 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 the restored state provided as event + * value. 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 res = this.state.reset(this.history.pop()); + this.notify({ id: History.EVENT_UNDO, value: res }); + return res; } } @@ -81,11 +97,19 @@ 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 the restored state provided as event + * value. 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 res = this.state.reset(this.future.pop()); + this.notify({ id: History.EVENT_REDO, value: res }); + return res; } } @@ -145,6 +169,9 @@ export class History implements * If no `state` is given, uses the wrapped atom's current state * value. * + * If recording succeeded, the `History.EVENT_RECORD` event is + * emitted with the recorded state provided as event value. + * * @param state */ record(state?: T) { @@ -159,9 +186,11 @@ export class History implements state = this.state.deref(); if (!n || this.changed(history[n - 1], state)) { history.push(state); + this.notify({ id: History.EVENT_RECORD, value: state }); } } else { history.push(state); + this.notify({ id: History.EVENT_RECORD, value: state }); } this.future.length = 0; } @@ -214,4 +243,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 { + } } From 7ac6227dcb8b2ce3c17bbf01d74f7087fb06f117 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Wed, 30 May 2018 17:40:05 +0100 Subject: [PATCH 20/54] feat(atom): provide prev/curr states to history event listeners - update undo()/redo() event value (now an object w/ `prev`/`curr` keys/states) - refactor/fix record(), only expunge history and truncate future if actually recording new state --- packages/atom/src/history.ts | 51 +++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/packages/atom/src/history.ts b/packages/atom/src/history.ts index 7d2273009d..97a1b9a52e 100644 --- a/packages/atom/src/history.ts +++ b/packages/atom/src/history.ts @@ -77,17 +77,19 @@ export class History implements * there's no history. * * If undo was possible, the `History.EVENT_UNDO` event is emitted - * after the restoration with the restored state provided as event - * value. This allows for additional state handling to be executed, - * e.g. application of the Command pattern. See `addListener()` for + * 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()); - const res = this.state.reset(this.history.pop()); - this.notify({ id: History.EVENT_UNDO, value: res }); - return res; + 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; } } @@ -99,17 +101,19 @@ export class History implements * there's no future (so sad!). * * If redo was possible, the `History.EVENT_REDO` event is emitted - * after the restoration with the restored state provided as event - * value. This allows for additional state handling to be executed, - * e.g. application of the Command pattern. See `addListener()` for + * 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()); - const res = this.state.reset(this.future.pop()); - this.notify({ id: History.EVENT_REDO, value: res }); - return res; + 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; } } @@ -164,10 +168,10 @@ 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. @@ -177,22 +181,21 @@ export class History implements 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); - this.notify({ id: History.EVENT_RECORD, value: 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; } /** From 67c7e17dd02e0604bdbc082b7cd7e80e5db893bc Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Wed, 30 May 2018 17:43:46 +0100 Subject: [PATCH 21/54] Publish - @thi.ng/atom@1.4.0 - @thi.ng/hdom@3.0.24 - @thi.ng/hiccup-svg@1.0.3 - @thi.ng/hiccup@2.0.3 - @thi.ng/interceptors@1.8.3 - @thi.ng/rstream-csp@0.1.75 - @thi.ng/rstream-dot@0.2.14 - @thi.ng/rstream-gestures@0.3.11 - @thi.ng/rstream-graph@1.1.2 - @thi.ng/rstream-log@1.0.26 - @thi.ng/rstream-query@0.3.13 - @thi.ng/rstream@1.7.1 --- packages/atom/CHANGELOG.md | 12 ++++++++++++ packages/atom/package.json | 2 +- packages/hdom/CHANGELOG.md | 8 ++++++++ packages/hdom/package.json | 6 +++--- packages/hiccup-svg/CHANGELOG.md | 8 ++++++++ packages/hiccup-svg/package.json | 4 ++-- packages/hiccup/CHANGELOG.md | 8 ++++++++ packages/hiccup/package.json | 4 ++-- packages/interceptors/CHANGELOG.md | 8 ++++++++ packages/interceptors/package.json | 4 ++-- packages/rstream-csp/CHANGELOG.md | 8 ++++++++ packages/rstream-csp/package.json | 4 ++-- packages/rstream-dot/CHANGELOG.md | 8 ++++++++ packages/rstream-dot/package.json | 4 ++-- packages/rstream-gestures/CHANGELOG.md | 8 ++++++++ packages/rstream-gestures/package.json | 4 ++-- packages/rstream-graph/CHANGELOG.md | 8 ++++++++ packages/rstream-graph/package.json | 4 ++-- packages/rstream-log/CHANGELOG.md | 8 ++++++++ packages/rstream-log/package.json | 4 ++-- packages/rstream-query/CHANGELOG.md | 8 ++++++++ packages/rstream-query/package.json | 6 +++--- packages/rstream/CHANGELOG.md | 8 ++++++++ packages/rstream/package.json | 4 ++-- 24 files changed, 125 insertions(+), 25 deletions(-) diff --git a/packages/atom/CHANGELOG.md b/packages/atom/CHANGELOG.md index cd67c9b219..bb5a79ae98 100644 --- a/packages/atom/CHANGELOG.md +++ b/packages/atom/CHANGELOG.md @@ -3,6 +3,18 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [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) diff --git a/packages/atom/package.json b/packages/atom/package.json index 2679b9a02b..fac8eba32f 100644 --- a/packages/atom/package.json +++ b/packages/atom/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/atom", - "version": "1.3.13", + "version": "1.4.0", "description": "Mutable wrapper for immutable values", "main": "./index.js", "typings": "./index.d.ts", diff --git a/packages/hdom/CHANGELOG.md b/packages/hdom/CHANGELOG.md index 2f2d7b2bba..6f769485ac 100644 --- a/packages/hdom/CHANGELOG.md +++ b/packages/hdom/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. + +## [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) diff --git a/packages/hdom/package.json b/packages/hdom/package.json index bae8cb565c..33432a6c2e 100644 --- a/packages/hdom/package.json +++ b/packages/hdom/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/hdom", - "version": "3.0.23", + "version": "3.0.24", "description": "Lightweight vanilla ES6 UI component & virtual DOM system", "main": "./index.js", "typings": "./index.d.ts", @@ -16,7 +16,7 @@ "test": "rm -rf build && tsc -p test && nyc mocha build/test/*.js" }, "devDependencies": { - "@thi.ng/atom": "^1.3.13", + "@thi.ng/atom": "^1.4.0", "@types/mocha": "^5.2.0", "@types/node": "^10.0.6", "mocha": "^5.1.1", @@ -29,7 +29,7 @@ "@thi.ng/checks": "^1.5.3", "@thi.ng/diff": "^1.0.17", "@thi.ng/equiv": "^0.1.3", - "@thi.ng/hiccup": "^2.0.2", + "@thi.ng/hiccup": "^2.0.3", "@thi.ng/iterators": "^4.1.16" }, "keywords": [ diff --git a/packages/hiccup-svg/CHANGELOG.md b/packages/hiccup-svg/CHANGELOG.md index a9f84ebe79..3d0b80d373 100644 --- a/packages/hiccup-svg/CHANGELOG.md +++ b/packages/hiccup-svg/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [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) diff --git a/packages/hiccup-svg/package.json b/packages/hiccup-svg/package.json index 987f21cbf1..ba5ab1c4fb 100644 --- a/packages/hiccup-svg/package.json +++ b/packages/hiccup-svg/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/hiccup-svg", - "version": "1.0.2", + "version": "1.0.3", "description": "SVG element functions for @thi.ng/hiccup & @thi.ng/hdom", "main": "./index.js", "typings": "./index.d.ts", @@ -24,7 +24,7 @@ "typescript": "^2.8.3" }, "dependencies": { - "@thi.ng/hiccup": "^2.0.2" + "@thi.ng/hiccup": "^2.0.3" }, "keywords": [ "components", diff --git a/packages/hiccup/CHANGELOG.md b/packages/hiccup/CHANGELOG.md index dad111fd9a..2fe8c60dd9 100644 --- a/packages/hiccup/CHANGELOG.md +++ b/packages/hiccup/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [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) diff --git a/packages/hiccup/package.json b/packages/hiccup/package.json index a602d97172..8b509efc12 100644 --- a/packages/hiccup/package.json +++ b/packages/hiccup/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/hiccup", - "version": "2.0.2", + "version": "2.0.3", "description": "HTML/SVG/XML serialization of nested data structures, iterables & closures", "main": "./index.js", "typings": "./index.d.ts", @@ -16,7 +16,7 @@ "test": "rm -rf build && tsc -p test && nyc mocha build/test/*.js" }, "devDependencies": { - "@thi.ng/atom": "^1.3.13", + "@thi.ng/atom": "^1.4.0", "@types/mocha": "^5.2.0", "@types/node": "^10.0.6", "mocha": "^5.1.1", diff --git a/packages/interceptors/CHANGELOG.md b/packages/interceptors/CHANGELOG.md index d4506b04dd..167e779fd8 100644 --- a/packages/interceptors/CHANGELOG.md +++ b/packages/interceptors/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [1.8.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) diff --git a/packages/interceptors/package.json b/packages/interceptors/package.json index 170f6f7876..957d0472c1 100644 --- a/packages/interceptors/package.json +++ b/packages/interceptors/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/interceptors", - "version": "1.8.2", + "version": "1.8.3", "description": "Interceptor based event bus, side effect & immutable state handling", "main": "./index.js", "typings": "./index.d.ts", @@ -25,7 +25,7 @@ }, "dependencies": { "@thi.ng/api": "^4.0.3", - "@thi.ng/atom": "^1.3.13", + "@thi.ng/atom": "^1.4.0", "@thi.ng/checks": "^1.5.3", "@thi.ng/errors": "^0.1.3", "@thi.ng/paths": "^1.3.8" diff --git a/packages/rstream-csp/CHANGELOG.md b/packages/rstream-csp/CHANGELOG.md index 0777902e5d..9c9561b3ac 100644 --- a/packages/rstream-csp/CHANGELOG.md +++ b/packages/rstream-csp/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.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) diff --git a/packages/rstream-csp/package.json b/packages/rstream-csp/package.json index 2e4b02ae66..e6a889edb9 100644 --- a/packages/rstream-csp/package.json +++ b/packages/rstream-csp/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rstream-csp", - "version": "0.1.74", + "version": "0.1.75", "description": "@thi.ng/csp bridge module for @thi.ng/rstream", "main": "./index.js", "typings": "./index.d.ts", @@ -25,7 +25,7 @@ }, "dependencies": { "@thi.ng/csp": "^0.3.42", - "@thi.ng/rstream": "^1.7.0" + "@thi.ng/rstream": "^1.7.1" }, "keywords": [ "bridge", diff --git a/packages/rstream-dot/CHANGELOG.md b/packages/rstream-dot/CHANGELOG.md index a7afee4675..047b057fab 100644 --- a/packages/rstream-dot/CHANGELOG.md +++ b/packages/rstream-dot/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.2.14](https://github.com/thi-ng/umbrella/compare/@thi.ng/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) diff --git a/packages/rstream-dot/package.json b/packages/rstream-dot/package.json index 15ad0badae..01ff2ccf5a 100644 --- a/packages/rstream-dot/package.json +++ b/packages/rstream-dot/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rstream-dot", - "version": "0.2.13", + "version": "0.2.14", "description": "Graphviz DOT conversion of @thi.ng/rstream dataflow graph topologies", "main": "./index.js", "typings": "./index.d.ts", @@ -24,7 +24,7 @@ "typescript": "^2.8.3" }, "dependencies": { - "@thi.ng/rstream": "^1.7.0" + "@thi.ng/rstream": "^1.7.1" }, "keywords": [ "conversion", diff --git a/packages/rstream-gestures/CHANGELOG.md b/packages/rstream-gestures/CHANGELOG.md index d7fc32c699..b0ffd7577b 100644 --- a/packages/rstream-gestures/CHANGELOG.md +++ b/packages/rstream-gestures/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.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) diff --git a/packages/rstream-gestures/package.json b/packages/rstream-gestures/package.json index ddf96172cb..cae891fdd0 100644 --- a/packages/rstream-gestures/package.json +++ b/packages/rstream-gestures/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rstream-gestures", - "version": "0.3.10", + "version": "0.3.11", "description": "Unified mouse, mouse wheel & single-touch event stream abstraction", "main": "./index.js", "typings": "./index.d.ts", @@ -25,7 +25,7 @@ }, "dependencies": { "@thi.ng/api": "^4.0.3", - "@thi.ng/rstream": "^1.7.0", + "@thi.ng/rstream": "^1.7.1", "@thi.ng/transducers": "^1.10.2" }, "keywords": [ diff --git a/packages/rstream-graph/CHANGELOG.md b/packages/rstream-graph/CHANGELOG.md index 999386a76c..de5cee41a4 100644 --- a/packages/rstream-graph/CHANGELOG.md +++ b/packages/rstream-graph/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [1.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) diff --git a/packages/rstream-graph/package.json b/packages/rstream-graph/package.json index 535680dbb1..ed3902b62f 100644 --- a/packages/rstream-graph/package.json +++ b/packages/rstream-graph/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rstream-graph", - "version": "1.1.1", + "version": "1.1.2", "description": "Declarative dataflow graph construction for @thi.ng/rstream", "main": "./index.js", "typings": "./index.d.ts", @@ -29,7 +29,7 @@ "@thi.ng/errors": "^0.1.3", "@thi.ng/paths": "^1.3.8", "@thi.ng/resolve-map": "^2.0.5", - "@thi.ng/rstream": "^1.7.0", + "@thi.ng/rstream": "^1.7.1", "@thi.ng/transducers": "^1.10.2" }, "keywords": [ diff --git a/packages/rstream-log/CHANGELOG.md b/packages/rstream-log/CHANGELOG.md index c8b85fa062..7468d403b4 100644 --- a/packages/rstream-log/CHANGELOG.md +++ b/packages/rstream-log/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [1.0.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) diff --git a/packages/rstream-log/package.json b/packages/rstream-log/package.json index aacd711caf..f416432589 100644 --- a/packages/rstream-log/package.json +++ b/packages/rstream-log/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rstream-log", - "version": "1.0.25", + "version": "1.0.26", "description": "Structured, multilevel & hierarchical loggers based on @thi.ng/rstream", "main": "./index.js", "typings": "./index.d.ts", @@ -27,7 +27,7 @@ "@thi.ng/api": "^4.0.3", "@thi.ng/checks": "^1.5.3", "@thi.ng/errors": "^0.1.3", - "@thi.ng/rstream": "^1.7.0", + "@thi.ng/rstream": "^1.7.1", "@thi.ng/transducers": "^1.10.2" }, "keywords": [ diff --git a/packages/rstream-query/CHANGELOG.md b/packages/rstream-query/CHANGELOG.md index c73c07b39c..6acb051833 100644 --- a/packages/rstream-query/CHANGELOG.md +++ b/packages/rstream-query/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.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) diff --git a/packages/rstream-query/package.json b/packages/rstream-query/package.json index 67e6d5a504..42e2f84186 100644 --- a/packages/rstream-query/package.json +++ b/packages/rstream-query/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rstream-query", - "version": "0.3.12", + "version": "0.3.13", "description": "@thi.ng/rstream based triple store & reactive query engine", "main": "./index.js", "typings": "./index.d.ts", @@ -29,8 +29,8 @@ "@thi.ng/checks": "^1.5.3", "@thi.ng/equiv": "^0.1.3", "@thi.ng/errors": "^0.1.3", - "@thi.ng/rstream": "^1.7.0", - "@thi.ng/rstream-dot": "^0.2.13", + "@thi.ng/rstream": "^1.7.1", + "@thi.ng/rstream-dot": "^0.2.14", "@thi.ng/transducers": "^1.10.2" }, "keywords": [ diff --git a/packages/rstream/CHANGELOG.md b/packages/rstream/CHANGELOG.md index 0a9d3ae801..dbdef8e3be 100644 --- a/packages/rstream/CHANGELOG.md +++ b/packages/rstream/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [1.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) diff --git a/packages/rstream/package.json b/packages/rstream/package.json index 91c7cf44b8..c491c4390b 100644 --- a/packages/rstream/package.json +++ b/packages/rstream/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rstream", - "version": "1.7.0", + "version": "1.7.1", "description": "Reactive multi-tap streams, dataflow & transformation pipeline constructs", "main": "./index.js", "typings": "./index.d.ts", @@ -26,7 +26,7 @@ "dependencies": { "@thi.ng/api": "^4.0.3", "@thi.ng/associative": "^0.5.6", - "@thi.ng/atom": "^1.3.13", + "@thi.ng/atom": "^1.4.0", "@thi.ng/checks": "^1.5.3", "@thi.ng/errors": "^0.1.3", "@thi.ng/paths": "^1.3.8", From de48e13407fde97ade7abc24514ecd0adb1aa773 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Tue, 5 Jun 2018 14:47:08 +0100 Subject: [PATCH 22/54] build: add yarn bootstrap --- package.json | 1 + 1 file changed, 1 insertion(+) 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", From be21c4c20a5cb8e3885cfcb3501b876cb373900d Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Wed, 6 Jun 2018 02:55:12 +0100 Subject: [PATCH 23/54] feat(rstream-graph): update NodeOutput, support multiple handlers - extract prepareNodeOutputs() --- packages/rstream-graph/src/api.ts | 5 ++++- packages/rstream-graph/src/graph.ts | 33 ++++++++++++++++++++++------- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/packages/rstream-graph/src/api.ts b/packages/rstream-graph/src/api.ts index d0ad0272c2..f4224cb158 100644 --- a/packages/rstream-graph/src/api.ts +++ b/packages/rstream-graph/src/api.ts @@ -96,4 +96,7 @@ export interface NodeInput { xform?: Transducer; } -export type NodeOutput = Path | ((node: ISubscribable) => void); +export type NodeOutput = + Path | + ((node: ISubscribable) => void) | + IObjectOf) => void)>; diff --git a/packages/rstream-graph/src/graph.ts b/packages/rstream-graph/src/graph.ts index f74ae6cbb6..de68ef78ae 100644 --- a/packages/rstream-graph/src/graph.ts +++ b/packages/rstream-graph/src/graph.ts @@ -11,7 +11,7 @@ import { fromView } from "@thi.ng/rstream/from/view"; import { StreamSync, sync } from "@thi.ng/rstream/stream-sync"; import { Transducer } from "@thi.ng/transducers/api"; -import { GraphSpec, NodeFactory, NodeSpec } from "./api"; +import { GraphSpec, NodeFactory, NodeSpec, NodeOutput } from "./api"; /** * Dataflow graph initialization function. Takes an object of @@ -67,16 +67,33 @@ const nodeFromSpec = (state: IAtom, spec: NodeSpec, id: string) => (resolve src[id] = s; } const node = spec.fn(src, id); - if (spec.out) { - if (isFunction(spec.out)) { - spec.out(node); - } else { + prepareNodeOutputs(spec.out, node, state, id); + return node; +}; + +const prepareNodeOutputs = (out: NodeOutput, node: ISubscribable, state: IAtom, id: string) => { + if (out) { + if (isFunction(out)) { + out(node); + } + else if (isPlainObject(out)) { + for (let oid in out) { + const o = out[oid]; + if (isFunction(o)) { + o(node); + } else { + ((path, oid) => node.subscribe({ + next: (x) => state.resetIn(path, x[oid]) + }, `out-${id}-${oid}`))(o, oid); + } + } + } + else { ((path) => node.subscribe({ next: (x) => state.resetIn(path, x) - }, `out-${id}`))(spec.out); + }, `out-${id}`))(out); } } - return node; }; export const addNode = (graph: IObjectOf>, state: IAtom, id: string, spec: NodeSpec) => @@ -137,4 +154,4 @@ export const ensureInputs = (src: IObjectOf>, inputIDs: strin illegalArgs(`node "${nodeID}": missing input(s): ${missing.join(", ")}`); } } -}; +}; \ No newline at end of file From dc6e0acc9229444f2b95bd1e1320a9561a9019c7 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Wed, 6 Jun 2018 12:00:52 +0100 Subject: [PATCH 24/54] refactor(resolve-map): export absPath(), add LookupPath type alias --- packages/resolve-map/src/index.ts | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/packages/resolve-map/src/index.ts b/packages/resolve-map/src/index.ts index cb5b4eadbb..5c1b08cd53 100644 --- a/packages/resolve-map/src/index.ts +++ b/packages/resolve-map/src/index.ts @@ -7,6 +7,8 @@ import { getIn, mutIn } from "@thi.ng/paths"; const SEMAPHORE = Symbol("SEMAPHORE"); +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 @@ -63,13 +65,13 @@ const SEMAPHORE = Symbol("SEMAPHORE"); * * @param obj */ -export const resolveMap = (obj: any, root?: any, path: PropertyKey[] = [], resolved: any = {}) => { +export const resolveMap = (obj: any, root?: any, path: LookupPath = [], resolved: any = {}) => { root = root || obj; for (let k in obj) { _resolve(root, [...path, k], resolved); } return obj; -} +}; /** * Like `resolveMap`, but for arrays. @@ -79,15 +81,15 @@ export const resolveMap = (obj: any, root?: any, path: PropertyKey[] = [], resol * @param path * @param resolved */ -const resolveArray = (arr: any[], root?: any, path: PropertyKey[] = [], resolved: any = {}) => { +const resolveArray = (arr: any[], root?: any, path: LookupPath = [], resolved: any = {}) => { root = root || arr; for (let k = 0, n = arr.length; k < n; k++) { _resolve(root, [...path, k], resolved); } return arr; -} +}; -const _resolve = (root: any, path: PropertyKey[], resolved: any) => { +const _resolve = (root: any, path: LookupPath, resolved: any) => { let v = getIn(root, path), rv = SEMAPHORE; const pp = path.join("/"); if (!resolved[pp]) { @@ -107,9 +109,17 @@ const _resolve = (root: any, path: PropertyKey[], resolved: any) => { resolved[pp] = true; } return v; -} +}; -const absPath = (curr: PropertyKey[], q: string, idx = 1): PropertyKey[] => { +/** + * 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 q + * @param idx + */ +export const absPath = (curr: LookupPath, q: string, idx = 1): PropertyKey[] => { if (q.charAt(idx) === "/") { return q.substr(idx + 1).split("/"); } @@ -125,4 +135,4 @@ const absPath = (curr: PropertyKey[], q: string, idx = 1): PropertyKey[] => { } !curr.length && illegalArgs(`invalid lookup path`); return curr; -} +}; From f2e0df251fbd35e5687cf20b7c7eb940e4498bcd Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Wed, 6 Jun 2018 13:38:30 +0100 Subject: [PATCH 25/54] feat(rstream-graph): add full/optional support for multiple node outputs BREAKING CHANGE: update NodeSpec format & graph initialization - add new types/interfaces - non-destructive initGraph() behavior - update & refactor nodeFromSpec() - update addNode/removeNode() - update tests & docs --- packages/rstream-graph/src/api.ts | 33 +++-- packages/rstream-graph/src/graph.ts | 200 +++++++++++++++++++-------- packages/rstream-graph/test/index.ts | 47 ++++++- 3 files changed, 203 insertions(+), 77 deletions(-) diff --git a/packages/rstream-graph/src/api.ts b/packages/rstream-graph/src/api.ts index f4224cb158..06e65229fa 100644 --- a/packages/rstream-graph/src/api.ts +++ b/packages/rstream-graph/src/api.ts @@ -7,7 +7,17 @@ import { Transducer } from "@thi.ng/transducers/api"; * A function which constructs and returns an `ISubscribable` using * given object of inputs and node ID. See `node()` and `node1()`. */ -export type NodeFactory = (src: IObjectOf>, id: string) => ISubscribable; +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 @@ -18,8 +28,8 @@ export type NodeFactory = (src: IObjectOf>, id: string) => */ export type GraphSpec = IObjectOf< NodeSpec | - ISubscribable | - ((resolve: (path: string) => any) => ISubscribable)>; + Node | + ((resolve: (path: string) => any) => Node)>; /** * Specification for a single "node" in the dataflow graph. Nodes here @@ -33,13 +43,13 @@ export type GraphSpec = IObjectOf< * are implemented as `StreamSync` instances and the input IDs are used * to locally rename input streams within the `StreamSync` container. * - * See `initGraph` and `nodeFromSpec` for more details (in - * /src/nodes.ts) + * 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; } /** @@ -88,7 +98,7 @@ export interface NodeSpec { * If the optional `xform` is given, a subscription with the transducer * is added to the input and then used as input instead. */ -export interface NodeInput { +export interface NodeInputSpec { id?: string; path?: Path; stream?: string | ((resolve) => ISubscribable); @@ -96,7 +106,6 @@ export interface NodeInput { xform?: Transducer; } -export type NodeOutput = - Path | - ((node: ISubscribable) => void) | - IObjectOf) => 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 de68ef78ae..4c7a2de448 100644 --- a/packages/rstream-graph/src/graph.ts +++ b/packages/rstream-graph/src/graph.ts @@ -4,107 +4,185 @@ 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, resolveMap } 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 { Transducer } from "@thi.ng/transducers/api"; -import { GraphSpec, NodeFactory, NodeSpec, NodeOutput } from "./api"; +import { + Graph, + GraphSpec, + 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 `resolveMap`. + * 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: GraphSpec): IObjectOf> => { - for (let id in nodes) { - const n = nodes[id]; - if (isPlainObject(n)) { - (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 resolveMap(res); }; +const isNodeSpec = (x: any): x is NodeSpec => + isPlainObject(x) && isFunction((x).fn); + /** - * Transforms a single NodeSpec into a lookup function for `resolveMap` - * (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. + * Transforms a single `NodeSpec` into a lookup function for + * `resolveMap` (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 `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. + * + * ``` + * out: { + * // fn output spec + * // creates new sub which uses `pick` transducer to + * // select key `a` from main node output + * 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 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) { + } + else if (i.stream) { s = isString(i.stream) ? resolve(i.stream) : i.stream(resolve); - } else if (i.const) { + } + else if (i.const) { s = fromIterableSync([isFunction(i.const) ? i.const(resolve) : i.const]); - } else { + } + 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); - prepareNodeOutputs(spec.out, node, state, id); - return node; -}; + return res; +} -const prepareNodeOutputs = (out: NodeOutput, node: ISubscribable, state: IAtom, id: string) => { - if (out) { - if (isFunction(out)) { - out(node); - } - else if (isPlainObject(out)) { - for (let oid in out) { - const o = out[oid]; - if (isFunction(o)) { - o(node); - } else { - ((path, oid) => node.subscribe({ - next: (x) => state.resetIn(path, x[oid]) - }, `out-${id}-${oid}`))(o, oid); - } - } - } - else { - ((path) => node.subscribe({ +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-${id}`))(out); + }, `out-${nodeID}`))(o); + } else { + res[id] = ((path, id) => node.subscribe({ + next: (x) => state.resetIn(path, x[id]) + }, `out-${nodeID}-${id}`))(o, id); } } + 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) => { if (graph[id]) { - graph[id].unsubscribe(); + illegalArgs(`graph already contains a node with ID: ${id}`); + } + 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; }; /** @@ -133,7 +211,9 @@ export const node = (xform: Transducer, any>, inputIDs?: string[] export const node1 = (xform?: Transducer, inputID = "src"): NodeFactory => (src: IObjectOf>, id: string): ISubscribable => { ensureInputs(src, [inputID], id); - return xform ? src[inputID].subscribe(xform, id) : src[inputID].subscribe(null, id); + return xform ? + src[inputID].subscribe(xform, id) : + src[inputID].subscribe(null, id); }; /** @@ -154,4 +234,4 @@ export const ensureInputs = (src: IObjectOf>, inputIDs: strin illegalArgs(`node "${nodeID}": missing input(s): ${missing.join(", ")}`); } } -}; \ No newline at end of file +}; diff --git a/packages/rstream-graph/test/index.ts b/packages/rstream-graph/test/index.ts index 6918075d79..461a548328 100644 --- a/packages/rstream-graph/test/index.ts +++ b/packages/rstream-graph/test/index.ts @@ -10,28 +10,65 @@ describe("rstream-graph", () => { const acc = []; const state = new Atom({ a: 1, b: 2 }); const graph = rsg.initGraph(state, { - foo: rs.fromIterable([2]), - bar: ($) => $("foo").transform(map((x: number) => x * 10)), + 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" }, + a: { stream: "/add/outs/alt" }, b: { stream: () => rs.fromIterable([10, 20, 30]) }, - c: { stream: "bar" } + 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.subscribe({ next: (x) => acc.push(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); }); From 558f4f8cbe3a843bc107a213e0dfa1c7de28a913 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Wed, 6 Jun 2018 14:36:36 +0100 Subject: [PATCH 26/54] fix(resolve-map): add private _resolveDeep - fixes resolution issue if a function dynamically created deep values --- packages/resolve-map/src/index.ts | 32 +++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/packages/resolve-map/src/index.ts b/packages/resolve-map/src/index.ts index 5c1b08cd53..ea64deb9c3 100644 --- a/packages/resolve-map/src/index.ts +++ b/packages/resolve-map/src/index.ts @@ -81,7 +81,7 @@ export const resolveMap = (obj: any, root?: any, path: LookupPath = [], resolved * @param path * @param resolved */ -const resolveArray = (arr: any[], root?: any, path: LookupPath = [], resolved: any = {}) => { +const _resolveArray = (arr: any[], root?: any, path: LookupPath = [], resolved: any = {}) => { root = root || arr; for (let k = 0, n = arr.length; k < n; k++) { _resolve(root, [...path, k], resolved); @@ -90,7 +90,8 @@ const resolveArray = (arr: any[], root?: any, path: LookupPath = [], resolved: a }; const _resolve = (root: any, path: LookupPath, resolved: any) => { - let v = getIn(root, path), rv = SEMAPHORE; + let rv = SEMAPHORE; + let v = getIn(root, path); const pp = path.join("/"); if (!resolved[pp]) { if (isString(v) && v.charAt(0) === "@") { @@ -98,9 +99,9 @@ const _resolve = (root: any, path: LookupPath, resolved: any) => { } else if (isPlainObject(v)) { resolveMap(v, root, path, resolved); } else if (isArray(v)) { - resolveArray(v, root, path, resolved); + _resolveArray(v, root, path, resolved); } else if (isFunction(v)) { - rv = v((p: string) => _resolve(root, absPath(path, p, 0), resolved)); + rv = v((p: string) => _resolveDeep(root, absPath(path, p, 0), resolved)); } if (rv !== SEMAPHORE) { mutIn(root, path, rv); @@ -111,6 +112,29 @@ const _resolve = (root: any, path: LookupPath, resolved: any) => { return v; }; +/** + * Repeatedly calls `_resolve` stepwise descending along given path. + * This is to ensure resolution of deep values created by functions at + * parent 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 _resolveDeep = (root: any, path: LookupPath, resolved: any) => { + let v; + for (let i = 1, n = path.length; i <= n; i++) { + v = _resolve(root, path.slice(0, i), resolved); + } + return v; +}; + /** * Takes the path for the current key and a lookup path string. Converts * the possibly relative lookup path into its absolute form. From 1a09b61ef178a48e09b8b84b19f0afd1e019d202 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Wed, 6 Jun 2018 14:37:40 +0100 Subject: [PATCH 27/54] minor(rstream-graph): minor fix exported types --- packages/rstream-graph/src/graph.ts | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/packages/rstream-graph/src/graph.ts b/packages/rstream-graph/src/graph.ts index 4c7a2de448..81e9f4f0cc 100644 --- a/packages/rstream-graph/src/graph.ts +++ b/packages/rstream-graph/src/graph.ts @@ -15,6 +15,7 @@ import { Transducer } from "@thi.ng/transducers/api"; import { Graph, GraphSpec, + Node, NodeFactory, NodeInputs, NodeInputSpec, @@ -67,15 +68,15 @@ const isNodeSpec = (x: any): x is NodeSpec => * 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. + * 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 + * // 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 @@ -94,12 +95,13 @@ const isNodeSpec = (x: any): x is NodeSpec => * @param spec * @param id */ -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 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 = {}; @@ -157,11 +159,13 @@ const prepareNodeOutputs = (outs: IObjectOf, node: ISubscribable * @param id * @param spec */ -export const addNode = (graph: Graph, state: IAtom, id: string, spec: NodeSpec) => { +export const addNode = (graph: Graph, state: IAtom, id: string, spec: NodeSpec): Node => { if (graph[id]) { illegalArgs(`graph already contains a node with ID: ${id}`); } - graph[id] = nodeFromSpec(state, spec, id)((path) => getIn(graph, absPath([id], path))); + return graph[id] = nodeFromSpec(state, spec, id)( + (path) => getIn(graph, absPath([id], path)) + ); } /** From dd2cbd44d21d3041aae30334b2207d18467e3497 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Wed, 6 Jun 2018 14:38:11 +0100 Subject: [PATCH 28/54] refactor(examples): update rstream-graph examples --- examples/rstream-dataflow/src/index.ts | 28 +++++++++++++------------- examples/rstream-grid/src/dataflow.ts | 12 ++++++----- 2 files changed, 21 insertions(+), 19 deletions(-) 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; From 48c796f5cb7cdefe7f86a876c7829410158f4c06 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Wed, 6 Jun 2018 15:19:22 +0100 Subject: [PATCH 29/54] fix(resolve-map): also use _resolvePath for plain lookups, optimize - rename _resolveDeep => _resolvePath - update docs --- packages/resolve-map/src/index.ts | 40 ++++++++++++++++++------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/packages/resolve-map/src/index.ts b/packages/resolve-map/src/index.ts index ea64deb9c3..d6aa2c94e5 100644 --- a/packages/resolve-map/src/index.ts +++ b/packages/resolve-map/src/index.ts @@ -93,15 +93,16 @@ const _resolve = (root: any, path: LookupPath, resolved: any) => { let rv = SEMAPHORE; let v = getIn(root, path); const pp = path.join("/"); + console.log("resolve", pp, resolved[pp]); if (!resolved[pp]) { - if (isString(v) && v.charAt(0) === "@") { - rv = _resolve(root, absPath(path, v), resolved); - } else if (isPlainObject(v)) { + if (isPlainObject(v)) { resolveMap(v, root, path, resolved); } else if (isArray(v)) { _resolveArray(v, root, path, resolved); + } else if (isString(v) && v.charAt(0) === "@") { + rv = _resolvePath(root, absPath(path, v), resolved); } else if (isFunction(v)) { - rv = v((p: string) => _resolveDeep(root, absPath(path, p, 0), resolved)); + rv = v((p: string) => _resolvePath(root, absPath(path, p, 0), resolved)); } if (rv !== SEMAPHORE) { mutIn(root, path, rv); @@ -113,12 +114,16 @@ const _resolve = (root: any, path: LookupPath, resolved: any) => { }; /** - * Repeatedly calls `_resolve` stepwise descending along given path. - * This is to ensure resolution of deep values created by functions at - * parent tree levels. E.g. given: + * If the value at given path is still unresolved, 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. If the path is already marked + * as resolved, returns its value. + * + * E.g. given: * * ``` - * {a: () => ({b: {c: 1}}), d: ($) => $("/a/b/c") } + * {a: () => ({b: {c: 1}}), d: "@/a/b/c" } * => * { a: { b: { c: 1 } }, d: 1 } * ``` @@ -127,7 +132,10 @@ const _resolve = (root: any, path: LookupPath, resolved: any) => { * @param path * @param resolved */ -const _resolveDeep = (root: any, path: LookupPath, resolved: any) => { +const _resolvePath = (root: any, path: LookupPath, resolved: any) => { + if (resolved[path.join("/")]) { + return getIn(root, path); + } let v; for (let i = 1, n = path.length; i <= n; i++) { v = _resolve(root, path.slice(0, i), resolved); @@ -140,23 +148,23 @@ const _resolveDeep = (root: any, path: LookupPath, resolved: any) => { * the possibly relative lookup path into its absolute form. * * @param curr - * @param q + * @param path * @param idx */ -export const absPath = (curr: LookupPath, q: string, idx = 1): PropertyKey[] => { - if (q.charAt(idx) === "/") { - return q.substr(idx + 1).split("/"); +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; }; From 720b1f189dbe4552160149715c308edf632be3c6 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Wed, 6 Jun 2018 15:33:26 +0100 Subject: [PATCH 30/54] docs(rstream-graph): update api docs & readme --- packages/rstream-graph/README.md | 10 ++++--- packages/rstream-graph/src/api.ts | 45 ++++++++++++++++--------------- 2 files changed, 31 insertions(+), 24 deletions(-) diff --git a/packages/rstream-graph/README.md b/packages/rstream-graph/README.md index 3595044cc9..4c91b3c7c6 100644 --- a/packages/rstream-graph/README.md +++ b/packages/rstream-graph/README.md @@ -12,7 +12,10 @@ Declarative, reactive dataflow graph construction using [@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 (user's responsibility). ## Installation @@ -67,7 +70,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 +90,8 @@ setTimeout(() => state.resetIn("a", 10), 1000); // result: 360 ``` -Please documentation in the source code for further details. +Please see documentation in the source code & test cases for further +details. ## Authors diff --git a/packages/rstream-graph/src/api.ts b/packages/rstream-graph/src/api.ts index 06e65229fa..811802b26e 100644 --- a/packages/rstream-graph/src/api.ts +++ b/packages/rstream-graph/src/api.ts @@ -23,8 +23,8 @@ export interface Node { * A dataflow graph spec is simply an object where keys are node names * and their values are either pre-existing @thi.ng/rstream * `ISubscribable`s, functions returning `ISubscribable`s or - * `NodeSpec`s, defining inputs and the operation to be applied to - * produce a result stream. + * `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< NodeSpec | @@ -33,15 +33,17 @@ export type GraphSpec = IObjectOf< /** * 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 streams / qsubscriptions (or just 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 are implemented as `StreamSync` 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) for more * details how these specs are compiled into stream constructs. @@ -64,22 +66,23 @@ export interface NodeSpec { * { path: ["nested", "src", "path"] } * ``` * - * 2) Reference path to another node in the GraphSpec object. See - * `@thi.ng/resolve-map` for details. + * 2) Reference path to another node's output in the GraphSpec object. + * See `@thi.ng/resolve-map` for details. * * ``` - * { stream: "/path/to/node-id" } // absolute - * { stream: "../../path/to/node-id" } // relative - * { stream: "node-id" } // sibling + * { 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: @@ -95,8 +98,8 @@ export interface NodeSpec { * { 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 NodeInputSpec { id?: string; From 93b40b7824421078698f278a7dc849af172362bc Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Wed, 6 Jun 2018 15:40:24 +0100 Subject: [PATCH 31/54] Publish - @thi.ng/resolve-map@2.0.6 - @thi.ng/rstream-graph@2.0.0 --- packages/resolve-map/CHANGELOG.md | 16 ++++++++++++++-- packages/resolve-map/package.json | 2 +- packages/rstream-graph/CHANGELOG.md | 23 +++++++++++++++++++++++ packages/rstream-graph/package.json | 4 ++-- 4 files changed, 40 insertions(+), 5 deletions(-) diff --git a/packages/resolve-map/CHANGELOG.md b/packages/resolve-map/CHANGELOG.md index a5176bc1a6..8014867fe0 100644 --- a/packages/resolve-map/CHANGELOG.md +++ b/packages/resolve-map/CHANGELOG.md @@ -3,7 +3,19 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. - + +## [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 +23,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/package.json b/packages/resolve-map/package.json index 9c1c6e365b..0c1a114272 100644 --- a/packages/resolve-map/package.json +++ b/packages/resolve-map/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/resolve-map", - "version": "2.0.5", + "version": "2.0.6", "description": "DAG resolution of vanilla objects & arrays with internally linked values", "main": "./index.js", "typings": "./index.d.ts", diff --git a/packages/rstream-graph/CHANGELOG.md b/packages/rstream-graph/CHANGELOG.md index de5cee41a4..02b99a26d1 100644 --- a/packages/rstream-graph/CHANGELOG.md +++ b/packages/rstream-graph/CHANGELOG.md @@ -3,6 +3,29 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [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) diff --git a/packages/rstream-graph/package.json b/packages/rstream-graph/package.json index ed3902b62f..43c793ff7e 100644 --- a/packages/rstream-graph/package.json +++ b/packages/rstream-graph/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rstream-graph", - "version": "1.1.2", + "version": "2.0.0", "description": "Declarative dataflow graph construction for @thi.ng/rstream", "main": "./index.js", "typings": "./index.d.ts", @@ -28,7 +28,7 @@ "@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/resolve-map": "^2.0.6", "@thi.ng/rstream": "^1.7.1", "@thi.ng/transducers": "^1.10.2" }, From 57f1ed5fd8576d2b299ddb17d231b4508147e4b9 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Wed, 6 Jun 2018 23:39:50 +0100 Subject: [PATCH 32/54] feat(resolve-map): add ES6 destructuring shorthands for function vals - add _resolveFunction() - add tests - update docs & readme --- packages/resolve-map/README.md | 64 +++++++++++++++++----------- packages/resolve-map/src/index.ts | 67 ++++++++++++++++++++++++++---- packages/resolve-map/test/index.ts | 33 ++++++++++++++- 3 files changed, 132 insertions(+), 32 deletions(-) diff --git a/packages/resolve-map/README.md b/packages/resolve-map/README.md index 27381be083..f7f40e11b2 100644 --- a/packages/resolve-map/README.md +++ b/packages/resolve-map/README.md @@ -47,22 +47,43 @@ resolveMap({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 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). +If a value is a function, it is 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 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 are ALWAYS looked up as siblings of the currently +processed value. + +```ts +// `c` uses ES6 destructuring form to look up `a` & `b` values +{a: 1, b: 2, c: ({a,b}) => a + b } +=> +// { a: 1, b: 2, c: 3 } +``` + +The single arg `resolve` function accepts a path (**without `@` +prefix**) to look up any other values in the object. 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 +// `f` is wrapped to ignore `@` prefix res = resolveMap({ a: (resolve) => resolve("b/c") * 100, b: { c: "@d/0", d: [2, (resolve) => resolve("../../e")(2) ] }, @@ -107,27 +128,22 @@ import * as tx from "@thi.ng/transducers"; // provided later 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(), diff --git a/packages/resolve-map/src/index.ts b/packages/resolve-map/src/index.ts index d6aa2c94e5..08db37b2a5 100644 --- a/packages/resolve-map/src/index.ts +++ b/packages/resolve-map/src/index.ts @@ -7,6 +7,10 @@ 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[]; /** @@ -30,13 +34,35 @@ export type LookupPath = PropertyKey[]; * // { 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 + * If a value is a function, it is 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 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 are ALWAYS looked up as siblings of the currently + * processed value. + * + * ``` + * // `c` uses ES6 destructuring form to look up `a` & `b` values + * {a: 1, b: 2, c: ({a,b}) => a + b } + * => + * // { a: 1, b: 2, c: 3 } + * ``` + * + * The single arg `resolve` function accepts a path (**without `@` + * prefix**) to look up any other values in the object. + * + * 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 @@ -93,8 +119,10 @@ const _resolve = (root: any, path: LookupPath, resolved: any) => { let rv = SEMAPHORE; let v = getIn(root, path); const pp = path.join("/"); - console.log("resolve", pp, resolved[pp]); if (!resolved[pp]) { + if (pp.length > 1) { + resolved[path.slice(0, path.length - 1).join("/")] = true; + } if (isPlainObject(v)) { resolveMap(v, root, path, resolved); } else if (isArray(v)) { @@ -102,7 +130,7 @@ const _resolve = (root: any, path: LookupPath, resolved: any) => { } else if (isString(v) && v.charAt(0) === "@") { rv = _resolvePath(root, absPath(path, v), resolved); } else if (isFunction(v)) { - rv = v((p: string) => _resolvePath(root, absPath(path, p, 0), resolved)); + rv = _resolveFunction(v, (p: string) => _resolvePath(root, absPath(path, p, 0), resolved)); } if (rv !== SEMAPHORE) { mutIn(root, path, rv); @@ -113,6 +141,31 @@ const _resolve = (root: any, path: LookupPath, resolved: any) => { 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. + * + * See `resolveMap` comments for further details. + * + * @param fn + * @param resolve + */ +const _resolveFunction = (fn: (x: any, r?: ResolveFn) => any, resolve: ResolveFn) => { + const match = RE_ARGS.exec(fn.toString()); + if (match) { + const args = {}; + for (let k of match[2].replace(/\s/g, "").split(/,/g)) { + args[k] = resolve(k); + } + return fn(args, resolve); + } else { + return fn(resolve); + } +}; + /** * If the value at given path is still unresolved, repeatedly calls * `_resolve` by stepwise descending along given path and returns final diff --git a/packages/resolve-map/test/index.ts b/packages/resolve-map/test/index.ts index ba4bcca815..616b73cf0a 100644 --- a/packages/resolve-map/test/index.ts +++ b/packages/resolve-map/test/index.ts @@ -1,4 +1,6 @@ +import * as tx from "@thi.ng/transducers"; import * as assert from "assert"; + import { resolveMap } from "../src/index"; describe("resolve-map", () => { @@ -65,5 +67,34 @@ describe("resolve-map", () => { { 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) + ); + } + }; + console.log(resolveMap({ ...stats, src: () => [1, 6, 7, 2, 4, 11, -3] })); + }); }); From e61c3b59affe34d403fc3c64b4dea7e8b616f2b7 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Thu, 7 Jun 2018 03:06:14 +0100 Subject: [PATCH 33/54] feat(resolve-map): add cycle detection, fix edge cases - add `resolve()` as main user function - make `resolveMap()` private - update _resolve() to keep track of active lookups - implement cycle detection - add markResolved() helper - update resolveFunction() to mark all of its nested values as resolved - refactor resolvePath() - update docs/readme - add/update tests BREAKING CHANGE: `resolveMap()` renamed to `resolve()`, update docs --- packages/resolve-map/README.md | 107 ++++++------- packages/resolve-map/src/index.ts | 236 ++++++++++++++++++----------- packages/resolve-map/test/index.ts | 43 ++++-- 3 files changed, 229 insertions(+), 157 deletions(-) diff --git a/packages/resolve-map/README.md b/packages/resolve-map/README.md index f7f40e11b2..9f3143e887 100644 --- a/packages/resolve-map/README.md +++ b/packages/resolve-map/README.md @@ -25,58 +25,64 @@ supported. ## API -### `resolveMap(obj)` - -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). - -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). +### `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 -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 using two possible conventions: +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 and a - general `resolve` function as second argument. + 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 are ALWAYS looked up as siblings of the currently -processed value. +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. ```ts // `c` uses ES6 destructuring form to look up `a` & `b` values -{a: 1, b: 2, c: ({a,b}) => a + b } -=> -// { a: 1, b: 2, c: 3 } +// `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 single arg `resolve` function accepts a path (**without `@` -prefix**) to look up any other values in the object. 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). +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` @@ -84,9 +90,9 @@ a function (see `f` key below). // `b.d[1]` is derived from calling `e(2)` // `e` is a wrapped function // `f` is wrapped to ignore `@` prefix -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", }) @@ -115,17 +121,16 @@ 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: ({src}) => tx.reduce(tx.mean(), src), @@ -161,8 +166,8 @@ const stats = { // 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, @@ -178,9 +183,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", @@ -193,13 +198,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/src/index.ts b/packages/resolve-map/src/index.ts index 08db37b2a5..72b81846d2 100644 --- a/packages/resolve-map/src/index.ts +++ b/packages/resolve-map/src/index.ts @@ -14,49 +14,54 @@ 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 using two possible - * conventions: + * 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 and a - * general `resolve` function as second argument. + * 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 are ALWAYS looked up as siblings of the currently - * processed value. + * 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 - * {a: 1, b: 2, c: ({a,b}) => a + b } - * => - * // { a: 1, b: 2, c: 3 } - * ``` + * // `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 } * - * The single arg `resolve` function accepts a path (**without `@` - * prefix**) to look up any other values in the object. + * // 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 @@ -68,14 +73,14 @@ export type LookupPath = PropertyKey[]; * 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", * }) @@ -85,59 +90,101 @@ export type LookupPath = PropertyKey[]; * // 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: LookupPath = [], 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: LookupPath = [], 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: LookupPath, resolved: any) => { - let rv = SEMAPHORE; + // console.log(pp, resolved[pp], stack); let v = getIn(root, path); - const pp = path.join("/"); - if (!resolved[pp]) { - if (pp.length > 1) { - resolved[path.slice(0, path.length - 1).join("/")] = true; - } + if (!resolved[pathID]) { + let res = SEMAPHORE; + stack.push(pathID); if (isPlainObject(v)) { - resolveMap(v, root, path, resolved); + 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) === "@") { - rv = _resolvePath(root, absPath(path, v), resolved); + res = _resolve(root, absPath(path, v), resolved, stack); } else if (isFunction(v)) { - rv = _resolveFunction(v, (p: string) => _resolvePath(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; +}; + +/** + * 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; }; @@ -148,52 +195,59 @@ const _resolve = (root: any, path: LookupPath, resolved: any) => { * their values as first arg. If no de-structure form is found, calls * function only with `resolve` as argument. * - * See `resolveMap` comments for further details. + * 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) => { +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 = {}; - for (let k of match[2].replace(/\s/g, "").split(/,/g)) { - args[k] = resolve(k); - } - return fn(args, resolve); + const args = match[2] + .replace(/\s/g, "") + .split(/,/g) + .reduce((acc, k) => (acc[k] = resolve(k), acc), {}); + res = fn(args, resolve); } else { - return fn(resolve); + res = fn(resolve); + } + markResolved(res, pathID, resolved); + return res; }; -/** - * If the value at given path is still unresolved, 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. If the path is already marked - * as resolved, returns its value. - * - * 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) => { - if (resolved[path.join("/")]) { - return getIn(root, path); +const markResolved = (v: any, path: string, resolved: any) => { + resolved[path] = true; + if (isPlainObject(v)) { + markObjResolved(v, path, resolved); } - let v; - for (let i = 1, n = path.length; i <= n; i++) { - v = _resolve(root, path.slice(0, i), 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); } - return v; }; /** diff --git a/packages/resolve-map/test/index.ts b/packages/resolve-map/test/index.ts index 616b73cf0a..7d12ee0324 100644 --- a/packages/resolve-map/test/index.ts +++ b/packages/resolve-map/test/index.ts @@ -1,59 +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); @@ -63,7 +64,7 @@ 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); @@ -95,6 +96,18 @@ describe("resolve-map", () => { ); } }; - console.log(resolveMap({ ...stats, src: () => [1, 6, 7, 2, 4, 11, -3] })); + 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] + } + ); }); }); From 0fc2305eb1565338d119513c6365b5215376d503 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Thu, 7 Jun 2018 03:19:10 +0100 Subject: [PATCH 34/54] fix(rstream-graph): rename `resolveMap` => `resolve` due to upstream changes --- packages/rstream-graph/src/graph.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/rstream-graph/src/graph.ts b/packages/rstream-graph/src/graph.ts index 81e9f4f0cc..a934e34579 100644 --- a/packages/rstream-graph/src/graph.ts +++ b/packages/rstream-graph/src/graph.ts @@ -5,7 +5,7 @@ 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 { getIn } from "@thi.ng/paths"; -import { absPath, resolveMap } from "@thi.ng/resolve-map"; +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"; @@ -28,7 +28,7 @@ import { * 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 `resolveMap`. + * 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. @@ -46,17 +46,17 @@ export const initGraph = (state: IAtom, spec: GraphSpec): Graph => { res[id] = n; } } - return resolveMap(res); + 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` (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. + * 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 `outs` keys, it also creates the * subscriptions for each of the given output keys, which then can be From 0beaffb16aac986cdd2b43049b11bfc25379bd86 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Thu, 7 Jun 2018 03:19:39 +0100 Subject: [PATCH 35/54] docs(resolve-map): update readme --- packages/resolve-map/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/resolve-map/README.md b/packages/resolve-map/README.md index 9f3143e887..6a6fe11f8e 100644 --- a/packages/resolve-map/README.md +++ b/packages/resolve-map/README.md @@ -159,7 +159,7 @@ 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. From e0535b4cb98eaf3c6d662cea942d074788a57431 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Thu, 7 Jun 2018 03:22:52 +0100 Subject: [PATCH 36/54] docs(resolve-map): update readme --- packages/resolve-map/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/resolve-map/README.md b/packages/resolve-map/README.md index 6a6fe11f8e..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 From b358c1a76c8f4f714c5fa5f55e5c94062e7afcff Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Thu, 7 Jun 2018 03:23:11 +0100 Subject: [PATCH 37/54] Publish - @thi.ng/resolve-map@3.0.0 - @thi.ng/rstream-graph@2.0.1 --- packages/resolve-map/CHANGELOG.md | 21 +++++++++++++++++++-- packages/resolve-map/package.json | 2 +- packages/rstream-graph/CHANGELOG.md | 11 +++++++++++ packages/rstream-graph/package.json | 4 ++-- 4 files changed, 33 insertions(+), 5 deletions(-) diff --git a/packages/resolve-map/CHANGELOG.md b/packages/resolve-map/CHANGELOG.md index 8014867fe0..bbb46a148b 100644 --- a/packages/resolve-map/CHANGELOG.md +++ b/packages/resolve-map/CHANGELOG.md @@ -3,7 +3,24 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. - + +# [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) @@ -15,7 +32,7 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline - + ## [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) diff --git a/packages/resolve-map/package.json b/packages/resolve-map/package.json index 0c1a114272..8c2b53e663 100644 --- a/packages/resolve-map/package.json +++ b/packages/resolve-map/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/resolve-map", - "version": "2.0.6", + "version": "3.0.0", "description": "DAG resolution of vanilla objects & arrays with internally linked values", "main": "./index.js", "typings": "./index.d.ts", diff --git a/packages/rstream-graph/CHANGELOG.md b/packages/rstream-graph/CHANGELOG.md index 02b99a26d1..9fea6418f2 100644 --- a/packages/rstream-graph/CHANGELOG.md +++ b/packages/rstream-graph/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [2.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) diff --git a/packages/rstream-graph/package.json b/packages/rstream-graph/package.json index 43c793ff7e..7db6960785 100644 --- a/packages/rstream-graph/package.json +++ b/packages/rstream-graph/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rstream-graph", - "version": "2.0.0", + "version": "2.0.1", "description": "Declarative dataflow graph construction for @thi.ng/rstream", "main": "./index.js", "typings": "./index.d.ts", @@ -28,7 +28,7 @@ "@thi.ng/checks": "^1.5.3", "@thi.ng/errors": "^0.1.3", "@thi.ng/paths": "^1.3.8", - "@thi.ng/resolve-map": "^2.0.6", + "@thi.ng/resolve-map": "^3.0.0", "@thi.ng/rstream": "^1.7.1", "@thi.ng/transducers": "^1.10.2" }, From 244bf213e96166df69571771b8b98cdd82c64e39 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Fri, 8 Jun 2018 12:12:41 +0100 Subject: [PATCH 38/54] feat(hiccup-css): add class scoping support - add CSSOpts.scope field - update formatRule() to inject class suffixing transducer if needed --- packages/hiccup-css/src/api.ts | 4 ++++ packages/hiccup-css/src/impl.ts | 17 ++++++++++++----- 2 files changed, 16 insertions(+), 5 deletions(-) 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, From 8d6e6c8685ebd1da8eec36110025e56797823be8 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Fri, 8 Jun 2018 12:15:46 +0100 Subject: [PATCH 39/54] feat(hiccup-css): add injectStyleSheet() --- packages/hiccup-css/src/index.ts | 1 + packages/hiccup-css/src/inject.ts | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 packages/hiccup-css/src/inject.ts 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; +}; From e1a8c915171d7749c6bb281485f8c91d9b1b337c Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Fri, 8 Jun 2018 12:28:44 +0100 Subject: [PATCH 40/54] docs(hiccup-css): update readme --- packages/hiccup-css/README.md | 37 ++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) 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 From f13138a4e1a7128fc3fc6b3cc05ab0c0d10a5eaf Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Fri, 8 Jun 2018 12:29:05 +0100 Subject: [PATCH 41/54] Publish - @thi.ng/hiccup-css@0.2.0 --- packages/hiccup-css/CHANGELOG.md | 12 ++++++++++++ packages/hiccup-css/package.json | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/hiccup-css/CHANGELOG.md b/packages/hiccup-css/CHANGELOG.md index 92d5fb0432..0bb397a6c9 100644 --- a/packages/hiccup-css/CHANGELOG.md +++ b/packages/hiccup-css/CHANGELOG.md @@ -3,6 +3,18 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [0.2.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/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) diff --git a/packages/hiccup-css/package.json b/packages/hiccup-css/package.json index bcb138ba9f..d34a21bcfa 100644 --- a/packages/hiccup-css/package.json +++ b/packages/hiccup-css/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/hiccup-css", - "version": "0.1.24", + "version": "0.2.0", "description": "CSS from nested JS data structures", "main": "./index.js", "typings": "./index.d.ts", From dce189f968295d71d5bf221b99060f514706a052 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Sun, 17 Jun 2018 11:35:19 +0100 Subject: [PATCH 42/54] feat(sax): initial import --- packages/sax/.npmignore | 10 ++ packages/sax/LICENSE | 201 ++++++++++++++++++++++++++++++++++++ packages/sax/package.json | 37 +++++++ packages/sax/src/index.ts | 203 +++++++++++++++++++++++++++++++++++++ packages/sax/tsconfig.json | 9 ++ 5 files changed, 460 insertions(+) create mode 100644 packages/sax/.npmignore create mode 100644 packages/sax/LICENSE create mode 100644 packages/sax/package.json create mode 100644 packages/sax/src/index.ts create mode 100644 packages/sax/tsconfig.json 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/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/package.json b/packages/sax/package.json new file mode 100644 index 0000000000..2331dcedeb --- /dev/null +++ b/packages/sax/package.json @@ -0,0 +1,37 @@ +{ + "name": "@thi.ng/sax", + "version": "0.0.1", + "description": "TODO", + "main": "./index.js", + "typings": "./index.d.ts", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn run clean && tsc --declaration", + "clean": "rm -rf *.js *.d.ts .nyc_output build coverage doc", + "cover": "yarn test && nyc report --reporter=lcov", + "doc": "node_modules/.bin/typedoc --mode modules --out doc src", + "pub": "yarn run build && yarn publish --access public", + "test": "rm -rf build && tsc -p test && nyc mocha build/test/*.js" + }, + "devDependencies": { + "@types/mocha": "^5.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.3", + "@thi.ng/transducers": "^1.10.2" + }, + "keywords": [ + "ES6", + "typescript" + ], + "publishConfig": { + "access": "public" + } +} \ No newline at end of file diff --git a/packages/sax/src/index.ts b/packages/sax/src/index.ts new file mode 100644 index 0000000000..6cd70b240b --- /dev/null +++ b/packages/sax/src/index.ts @@ -0,0 +1,203 @@ +import { Event, IObjectOf } from "@thi.ng/api"; +import { Transducer, Reducer } from "@thi.ng/transducers/api"; + +export interface FSMState { + state: PropertyKey; +} + +export type FSMStateMap = IObjectOf>; +export type FSMHandler = (state: T, input: A) => B; + +export function fsm(states: FSMStateMap, initial: () => T): Transducer { + return (rfn: Reducer) => { + let state = initial(); + const r = rfn[2]; + return [ + rfn[0], + (acc) => rfn[1](acc), + (acc, x) => { + // console.log(state.state, x, state); + const res = states[state.state](state, x); + if (res) { + acc = r(acc, res); + } + return acc; + }]; + } +} + +const isWS = (x: string) => + x == " " || x == "\t" || x == "\n" || x == "\r"; + +const isTagChar = (x: string) => + (x >= "A" && x <= "Z") || + (x >= "a" && x <= "z") || + (x >= "0" && x <= "9") || + x == "-" || + x == "_" || + x == ":"; + +export interface ParseState extends FSMState { + scope: string[]; + tag: string; + body: string; + attribs: any; + phase: number; + name: string; + val: string; +} + +export enum State { + WAIT, + ELEM, + ELEM_BODY, + ATTRIB, +} + +const unexpected = (x) => { throw new Error(`unexpected char: ${x}`); }; + +export const PARSER: FSMStateMap = { + [State.WAIT]: (s: ParseState, x: string) => { + if (isWS(x)) { + return null; + } else if (x == "<") { + s.state = State.ELEM; + s.phase = 0; + } else { + unexpected(x); + } + }, + + [State.ELEM]: (s: ParseState, x: string) => { + switch (s.phase) { + case 0: + if (x == "/") { + if (s.scope.length == 0) { + unexpected(x); + } + s.tag = ""; + s.phase = 1; + } else if (isTagChar(x)) { + s.tag = x; + s.attribs = {}; + s.phase = 2; + } else { + unexpected(x); + } + break; + // end + case 1: + if (isTagChar(x)) { + s.tag += x; + } else if (x == ">") { + if (s.tag == s.scope[s.scope.length - 1]) { + const res = { id: "end", value: { tag: s.tag } }; + s.scope.pop(); + s.state = State.WAIT; + return res; + } else { + throw new Error(`unmatched end tag: ${s.tag}`); + } + } + break; + // start + case 2: + if (isTagChar(x)) { + s.tag += x; + } else if (isWS(x)) { + s.state = State.ATTRIB; + s.phase = 0; + } else if (x == ">") { + const res = { id: "elem", value: { tag: s.tag, attribs: s.attribs } }; + s.scope.push(s.tag); + s.state = State.ELEM_BODY; + s.body = ""; + return res; + } else if (x == "/") { + s.phase = 3; + } else { + unexpected(x); + } + break; + case 3: + if (x == ">") { + s.state = State.WAIT; + return { id: "start", value: { tag: s.tag, attribs: s.attribs } }; + } else { + unexpected(x); + } + break; + } + }, + + + [State.ELEM_BODY]: (s: ParseState, x: string) => { + if (x == "<") { + const res = s.body.length > 0 ? + { id: "body", value: s.body } : + undefined; + s.state = State.ELEM; + s.tag = ""; + s.phase = 0; + return res; + } else { + s.body += x; + } + }, + + [State.ATTRIB]: (s: ParseState, x: string) => { + switch (s.phase) { + // ws + case 0: + if (isTagChar(x)) { + s.name = x; + s.val = ""; + s.phase = 2; + } else if (x == ">") { + const res = { id: "elem", value: { tag: s.tag, attribs: s.attribs } }; + s.scope.push(s.tag); + s.state = State.ELEM_BODY; + s.body = ""; + return res; + } else if (x == "/") { + s.phase = 1; + } else if (!isWS(x)) { + unexpected(x); + } + break; + // self-closing + case 1: + if (x == ">") { + s.state = State.WAIT; + return { id: "elem", value: { tag: s.tag, attribs: s.attribs } }; + } else { + unexpected(x); + } + break; + // attrib name + case 2: + if (isTagChar(x)) { + s.name += x; + } else if (x == "=") { + s.phase = 3; + } else { + unexpected(x); + } + break; + case 3: + if (x == "\"") { + s.phase = 4; + } else { + unexpected(x); + } + break; + case 4: + if (x == "\"") { + s.attribs[s.name] = s.val; + s.phase = 0; + } else { + s.val += x; + } + } + } +} \ No newline at end of file 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" + ] +} From 74f7d02ef65faef75d1ddcd1f1469772b98e6a18 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Sun, 17 Jun 2018 12:02:19 +0100 Subject: [PATCH 43/54] refactor(sax): extract parser sub-states --- packages/sax/src/index.ts | 240 ++++++++++++++++++-------------------- 1 file changed, 114 insertions(+), 126 deletions(-) diff --git a/packages/sax/src/index.ts b/packages/sax/src/index.ts index 6cd70b240b..c34ab9223c 100644 --- a/packages/sax/src/index.ts +++ b/packages/sax/src/index.ts @@ -6,7 +6,7 @@ export interface FSMState { } export type FSMStateMap = IObjectOf>; -export type FSMHandler = (state: T, input: A) => B; +export type FSMHandler = (state: T, input: A) => B | void; export function fsm(states: FSMStateMap, initial: () => T): Transducer { return (rfn: Reducer) => { @@ -37,167 +37,155 @@ const isTagChar = (x: string) => x == "_" || x == ":"; +const unexpected = (x) => { throw new Error(`unexpected char: ${x}`); }; + export interface ParseState extends FSMState { scope: string[]; tag: string; body: string; attribs: any; - phase: number; name: string; val: string; } -export enum State { +enum State { WAIT, - ELEM, + MAYBE_ELEM, + ELEM_START, + ELEM_END, + ELEM_SINGLE, ELEM_BODY, - ATTRIB, + MAYBE_ATTRIB, + ATTRIB_NAME, + ATTRIB_VAL_START, + ATTRIB_VALUE } -const unexpected = (x) => { throw new Error(`unexpected char: ${x}`); }; export const PARSER: FSMStateMap = { [State.WAIT]: (s: ParseState, x: string) => { - if (isWS(x)) { - return null; - } else if (x == "<") { - s.state = State.ELEM; - s.phase = 0; + if (!isWS(x)) { + if (x == "<") { + s.state = State.MAYBE_ELEM; + } else { + unexpected(x); + } + } + }, + + [State.MAYBE_ELEM]: (s: ParseState, x: string) => { + if (x == "/") { + if (s.scope.length == 0) { + unexpected(x); + } + s.state = State.ELEM_END; + s.tag = ""; + } else if (isTagChar(x)) { + s.state = State.ELEM_START; + s.tag = x; + s.attribs = {}; } else { unexpected(x); } }, - [State.ELEM]: (s: ParseState, x: string) => { - switch (s.phase) { - case 0: - if (x == "/") { - if (s.scope.length == 0) { - unexpected(x); - } - s.tag = ""; - s.phase = 1; - } else if (isTagChar(x)) { - s.tag = x; - s.attribs = {}; - s.phase = 2; - } else { - unexpected(x); - } - break; - // end - case 1: - if (isTagChar(x)) { - s.tag += x; - } else if (x == ">") { - if (s.tag == s.scope[s.scope.length - 1]) { - const res = { id: "end", value: { tag: s.tag } }; - s.scope.pop(); - s.state = State.WAIT; - return res; - } else { - throw new Error(`unmatched end tag: ${s.tag}`); - } - } - break; - // start - case 2: - if (isTagChar(x)) { - s.tag += x; - } else if (isWS(x)) { - s.state = State.ATTRIB; - s.phase = 0; - } else if (x == ">") { - const res = { id: "elem", value: { tag: s.tag, attribs: s.attribs } }; - s.scope.push(s.tag); - s.state = State.ELEM_BODY; - s.body = ""; - return res; - } else if (x == "/") { - s.phase = 3; - } else { - unexpected(x); - } - break; - case 3: - if (x == ">") { - s.state = State.WAIT; - return { id: "start", value: { tag: s.tag, attribs: s.attribs } }; - } else { - unexpected(x); - } - break; + [State.ELEM_START]: (s: ParseState, x: string) => { + if (isTagChar(x)) { + s.tag += x; + } else if (isWS(x)) { + s.state = State.MAYBE_ATTRIB; + } else if (x == ">") { + const res = { id: "elem", value: { tag: s.tag, attribs: s.attribs } }; + s.state = State.ELEM_BODY; + s.scope.push(s.tag); + s.body = ""; + return res; + } else if (x == "/") { + s.state = State.ELEM_SINGLE; + } else { + unexpected(x); } }, + [State.ELEM_END]: (s: ParseState, x: string) => { + if (isTagChar(x)) { + s.tag += x; + } else if (x == ">") { + if (s.tag == s.scope[s.scope.length - 1]) { + const res = { id: "end", value: { tag: s.tag } }; + s.scope.pop(); + s.state = State.WAIT; + return res; + } else { + throw new Error(`unmatched end tag: ${s.tag}`); + } + } + }, + + [State.ELEM_SINGLE]: (s: ParseState, x: string) => { + if (x == ">") { + s.state = State.WAIT; + return { id: "start", value: { tag: s.tag, attribs: s.attribs } }; + } else { + unexpected(x); + } + }, [State.ELEM_BODY]: (s: ParseState, x: string) => { if (x == "<") { const res = s.body.length > 0 ? { id: "body", value: s.body } : undefined; - s.state = State.ELEM; + s.state = State.MAYBE_ELEM; s.tag = ""; - s.phase = 0; return res; } else { s.body += x; } }, - [State.ATTRIB]: (s: ParseState, x: string) => { - switch (s.phase) { - // ws - case 0: - if (isTagChar(x)) { - s.name = x; - s.val = ""; - s.phase = 2; - } else if (x == ">") { - const res = { id: "elem", value: { tag: s.tag, attribs: s.attribs } }; - s.scope.push(s.tag); - s.state = State.ELEM_BODY; - s.body = ""; - return res; - } else if (x == "/") { - s.phase = 1; - } else if (!isWS(x)) { - unexpected(x); - } - break; - // self-closing - case 1: - if (x == ">") { - s.state = State.WAIT; - return { id: "elem", value: { tag: s.tag, attribs: s.attribs } }; - } else { - unexpected(x); - } - break; - // attrib name - case 2: - if (isTagChar(x)) { - s.name += x; - } else if (x == "=") { - s.phase = 3; - } else { - unexpected(x); - } - break; - case 3: - if (x == "\"") { - s.phase = 4; - } else { - unexpected(x); - } - break; - case 4: - if (x == "\"") { - s.attribs[s.name] = s.val; - s.phase = 0; - } else { - s.val += x; - } + [State.MAYBE_ATTRIB]: (s: ParseState, x: string) => { + if (isTagChar(x)) { + s.state = State.ATTRIB_NAME; + s.name = x; + s.val = ""; + } else if (x == ">") { + const res = { id: "elem", value: { tag: s.tag, attribs: s.attribs } }; + s.state = State.ELEM_BODY; + s.scope.push(s.tag); + s.body = ""; + return res; + } else if (x == "/") { + s.state = State.ELEM_SINGLE; + } else if (!isWS(x)) { + unexpected(x); } - } + }, + + [State.ATTRIB_NAME]: (s: ParseState, x: string) => { + if (isTagChar(x)) { + s.name += x; + } else if (x == "=") { + s.state = State.ATTRIB_VAL_START; + } else { + unexpected(x); + } + }, + + [State.ATTRIB_VAL_START]: (s: ParseState, x: string) => { + if (x == "\"") { + s.state = State.ATTRIB_VALUE; + } else { + unexpected(x); + } + }, + + [State.ATTRIB_VALUE]: (s: ParseState, x: string) => { + if (x == "\"") { + s.attribs[s.name] = s.val; + s.state = State.MAYBE_ATTRIB; + } else { + s.val += x; + } + }, } \ No newline at end of file From 3dea95494139931ad75dd29f749f8860b7063807 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Sun, 17 Jun 2018 15:54:16 +0100 Subject: [PATCH 44/54] feat(sax): emit child elements with `end` results, support comments --- packages/sax/src/index.ts | 87 +++++++++++++++++++++++++++++---------- 1 file changed, 66 insertions(+), 21 deletions(-) diff --git a/packages/sax/src/index.ts b/packages/sax/src/index.ts index c34ab9223c..eee00f0a4b 100644 --- a/packages/sax/src/index.ts +++ b/packages/sax/src/index.ts @@ -16,7 +16,7 @@ export function fsm(states: FSMStateMap, init rfn[0], (acc) => rfn[1](acc), (acc, x) => { - // console.log(state.state, x, state); + // console.log(x, State[state.state], state); const res = states[state.state](state, x); if (res) { acc = r(acc, res); @@ -40,12 +40,13 @@ const isTagChar = (x: string) => const unexpected = (x) => { throw new Error(`unexpected char: ${x}`); }; export interface ParseState extends FSMState { - scope: string[]; + scope: any[]; tag: string; body: string; attribs: any; name: string; val: string; + quote: string; } enum State { @@ -58,14 +59,16 @@ enum State { MAYBE_ATTRIB, ATTRIB_NAME, ATTRIB_VAL_START, - ATTRIB_VALUE + ATTRIB_VALUE, + MAYBE_INSTRUCTION, + COMMENT, + COMMENT_BODY, } - export const PARSER: FSMStateMap = { [State.WAIT]: (s: ParseState, x: string) => { if (!isWS(x)) { - if (x == "<") { + if (x === "<") { s.state = State.MAYBE_ELEM; } else { unexpected(x); @@ -74,7 +77,7 @@ export const PARSER: FSMStateMap = { }, [State.MAYBE_ELEM]: (s: ParseState, x: string) => { - if (x == "/") { + if (x === "/") { if (s.scope.length == 0) { unexpected(x); } @@ -84,6 +87,8 @@ export const PARSER: FSMStateMap = { s.state = State.ELEM_START; s.tag = x; s.attribs = {}; + } else if (x === "!") { + s.state = State.MAYBE_INSTRUCTION; } else { unexpected(x); } @@ -94,13 +99,13 @@ export const PARSER: FSMStateMap = { s.tag += x; } else if (isWS(x)) { s.state = State.MAYBE_ATTRIB; - } else if (x == ">") { + } else if (x === ">") { const res = { id: "elem", value: { tag: s.tag, attribs: s.attribs } }; s.state = State.ELEM_BODY; - s.scope.push(s.tag); + s.scope.push({ tag: s.tag, attribs: s.attribs, children: [] }); s.body = ""; return res; - } else if (x == "/") { + } else if (x === "/") { s.state = State.ELEM_SINGLE; } else { unexpected(x); @@ -110,10 +115,14 @@ export const PARSER: FSMStateMap = { [State.ELEM_END]: (s: ParseState, x: string) => { if (isTagChar(x)) { s.tag += x; - } else if (x == ">") { - if (s.tag == s.scope[s.scope.length - 1]) { - const res = { id: "end", value: { tag: s.tag } }; + } else if (x === ">") { + const n = s.scope.length; + if (n > 0 && s.tag === s.scope[n - 1].tag) { + const res = { id: "end", value: s.scope[n - 1] }; s.scope.pop(); + if (n > 1) { + s.scope[n - 2].children.push(res.value); + } s.state = State.WAIT; return res; } else { @@ -123,16 +132,20 @@ export const PARSER: FSMStateMap = { }, [State.ELEM_SINGLE]: (s: ParseState, x: string) => { - if (x == ">") { + if (x === ">") { s.state = State.WAIT; - return { id: "start", value: { tag: s.tag, attribs: s.attribs } }; + const n = s.scope.length; + if (n > 0) { + s.scope[n - 1].children.push({ tag: s.tag, attribs: s.attribs }); + } + return { id: "elem", value: { tag: s.tag, attribs: s.attribs } }; } else { unexpected(x); } }, [State.ELEM_BODY]: (s: ParseState, x: string) => { - if (x == "<") { + if (x === "<") { const res = s.body.length > 0 ? { id: "body", value: s.body } : undefined; @@ -149,13 +162,13 @@ export const PARSER: FSMStateMap = { s.state = State.ATTRIB_NAME; s.name = x; s.val = ""; - } else if (x == ">") { + } else if (x === ">") { const res = { id: "elem", value: { tag: s.tag, attribs: s.attribs } }; s.state = State.ELEM_BODY; - s.scope.push(s.tag); + s.scope.push({ tag: s.tag, attribs: s.attribs, children: [] }); s.body = ""; return res; - } else if (x == "/") { + } else if (x === "/") { s.state = State.ELEM_SINGLE; } else if (!isWS(x)) { unexpected(x); @@ -165,7 +178,7 @@ export const PARSER: FSMStateMap = { [State.ATTRIB_NAME]: (s: ParseState, x: string) => { if (isTagChar(x)) { s.name += x; - } else if (x == "=") { + } else if (x === "=") { s.state = State.ATTRIB_VAL_START; } else { unexpected(x); @@ -173,19 +186,51 @@ export const PARSER: FSMStateMap = { }, [State.ATTRIB_VAL_START]: (s: ParseState, x: string) => { - if (x == "\"") { + if (x === "\"" || x === "'") { s.state = State.ATTRIB_VALUE; + s.quote = x; } else { unexpected(x); } }, [State.ATTRIB_VALUE]: (s: ParseState, x: string) => { - if (x == "\"") { + if (x === s.quote) { s.attribs[s.name] = s.val; s.state = State.MAYBE_ATTRIB; } else { s.val += x; } }, + + [State.MAYBE_INSTRUCTION]: (s: ParseState, x: string) => { + if (x === "-") { + s.state = State.COMMENT; + } else { + unexpected(x); + } + }, + + [State.COMMENT]: (s: ParseState, x: string) => { + if (x === "-") { + s.state = State.COMMENT_BODY; + s.body = ""; + } else { + unexpected(x); + } + }, + + [State.COMMENT_BODY]: (s: ParseState, x: string) => { + if (x === ">") { + const n = s.body.length; + if (s.body.substr(n - 2) !== "--") { + unexpected(x); + } + s.state = State.WAIT; + return { id: "comment", value: s.body.substr(0, n - 2) }; + } else { + s.body += x; + } + }, + } \ No newline at end of file From a4766a54a1cb965b5fe31f2f2760e7610e8259ea Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Sun, 17 Jun 2018 22:18:55 +0100 Subject: [PATCH 45/54] feat(sax): add support for proc & doctype elements, update `end` results - `end` results now include element body as well --- packages/sax/src/index.ts | 65 +++++++++++++++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 6 deletions(-) diff --git a/packages/sax/src/index.ts b/packages/sax/src/index.ts index eee00f0a4b..fd35f984e9 100644 --- a/packages/sax/src/index.ts +++ b/packages/sax/src/index.ts @@ -47,6 +47,7 @@ export interface ParseState extends FSMState { name: string; val: string; quote: string; + phase: number; } enum State { @@ -63,6 +64,9 @@ enum State { MAYBE_INSTRUCTION, COMMENT, COMMENT_BODY, + DOCTYPE, + PROC_DECL, + PROC_END, } export const PARSER: FSMStateMap = { @@ -89,6 +93,10 @@ export const PARSER: FSMStateMap = { s.attribs = {}; } else if (x === "!") { s.state = State.MAYBE_INSTRUCTION; + } else if (x === "?") { + s.state = State.PROC_DECL; + s.phase = 0; + s.body = ""; } else { unexpected(x); } @@ -135,10 +143,11 @@ export const PARSER: FSMStateMap = { if (x === ">") { s.state = State.WAIT; const n = s.scope.length; + const res = { tag: s.tag, attribs: s.attribs }; if (n > 0) { - s.scope[n - 1].children.push({ tag: s.tag, attribs: s.attribs }); + s.scope[n - 1].children.push(res); } - return { id: "elem", value: { tag: s.tag, attribs: s.attribs } }; + return { id: "elem", value: res }; } else { unexpected(x); } @@ -146,9 +155,11 @@ export const PARSER: FSMStateMap = { [State.ELEM_BODY]: (s: ParseState, x: string) => { if (x === "<") { - const res = s.body.length > 0 ? - { id: "body", value: s.body } : - undefined; + let res; + if (s.body.length > 0) { + s.scope[s.scope.length - 1].body = s.body; + res = { id: "body", value: { tag: s.tag, body: s.body } } + } s.state = State.MAYBE_ELEM; s.tag = ""; return res; @@ -170,6 +181,8 @@ export const PARSER: FSMStateMap = { return res; } else if (x === "/") { s.state = State.ELEM_SINGLE; + } else if (s.tag === "xml" && x === "?") { + s.state = State.PROC_END; } else if (!isWS(x)) { unexpected(x); } @@ -206,6 +219,10 @@ export const PARSER: FSMStateMap = { [State.MAYBE_INSTRUCTION]: (s: ParseState, x: string) => { if (x === "-") { s.state = State.COMMENT; + } else if (x === "D") { + s.state = State.DOCTYPE; + s.phase = 1; + s.body = ""; } else { unexpected(x); } @@ -233,4 +250,40 @@ export const PARSER: FSMStateMap = { } }, -} \ No newline at end of file + [State.DOCTYPE]: (s: ParseState, x: string) => { + if (s.phase < 8) { + if (x === "DOCTYPE "[s.phase]) { + s.phase++; + } else { + unexpected(x); + } + } else if (x === ">") { + s.state = State.WAIT; + return { id: "doctype", value: s.body.trim() }; + } else { + s.body += x; + } + }, + + [State.PROC_DECL]: (s: ParseState, x: string) => { + if (x === "xml "[s.phase]) { + s.phase++; + if (s.phase == 4) { + s.state = State.MAYBE_ATTRIB; + s.tag = "xml"; + s.attribs = {}; + } + } else { + unexpected(x); + } + }, + + [State.PROC_END]: (s: ParseState, x: string) => { + if (x === ">") { + s.state = State.WAIT; + return { id: "proc", value: { tag: s.tag, attribs: s.attribs } }; + } else { + unexpected(x); + } + }, +}; From 64f237896040dbf490f4c5c47178fca074522944 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Sun, 17 Jun 2018 23:25:12 +0100 Subject: [PATCH 46/54] feat(sax): update error handling, add parse() wrapper, add FSMOpts - `unexpected()` does NOT throw error anymore, but triggers new ERROR FSM state - add input `pos` counter to ParserState, use for error messages - add `parse()` transducer wrapper - update `fsm()` transducer to accept new `FSMOpts` --- packages/sax/src/index.ts | 105 +++++++++++++++++++++++++++----------- 1 file changed, 76 insertions(+), 29 deletions(-) diff --git a/packages/sax/src/index.ts b/packages/sax/src/index.ts index fd35f984e9..717cf0374c 100644 --- a/packages/sax/src/index.ts +++ b/packages/sax/src/index.ts @@ -1,5 +1,6 @@ import { Event, IObjectOf } from "@thi.ng/api"; import { Transducer, Reducer } from "@thi.ng/transducers/api"; +import { ensureReduced } from "@thi.ng/transducers/reduced"; export interface FSMState { state: PropertyKey; @@ -8,9 +9,16 @@ export interface FSMState { export type FSMStateMap = IObjectOf>; export type FSMHandler = (state: T, input: A) => B | void; -export function fsm(states: FSMStateMap, initial: () => T): Transducer { +export interface FSMOpts { + states: FSMStateMap; + terminate: PropertyKey; + init: () => T; +} + +export function fsm(opts: FSMOpts): Transducer { return (rfn: Reducer) => { - let state = initial(); + const states = opts.states; + const state = opts.init(); const r = rfn[2]; return [ rfn[0], @@ -20,26 +28,17 @@ export function fsm(states: FSMStateMap, init const res = states[state.state](state, x); if (res) { acc = r(acc, res); + if (state.state == opts.terminate) { + return ensureReduced(acc); + } } return acc; }]; } } -const isWS = (x: string) => - x == " " || x == "\t" || x == "\n" || x == "\r"; - -const isTagChar = (x: string) => - (x >= "A" && x <= "Z") || - (x >= "a" && x <= "z") || - (x >= "0" && x <= "9") || - x == "-" || - x == "_" || - x == ":"; - -const unexpected = (x) => { throw new Error(`unexpected char: ${x}`); }; - export interface ParseState extends FSMState { + pos: number; scope: any[]; tag: string; body: string; @@ -52,6 +51,7 @@ export interface ParseState extends FSMState { enum State { WAIT, + ERROR, MAYBE_ELEM, ELEM_START, ELEM_END, @@ -69,21 +69,54 @@ enum State { PROC_END, } +const isWS = (x: string) => + x == " " || x == "\t" || x == "\n" || x == "\r"; + +const isTagChar = (x: string) => + (x >= "A" && x <= "Z") || + (x >= "a" && x <= "z") || + (x >= "0" && x <= "9") || + x == "-" || + x == "_" || + x == ":"; + +const unexpected = (s: ParseState, x: string) => { + s.state = State.ERROR; + return { id: "error", value: `unexpected char: '${x}' @ pos: ${s.pos}` }; +}; + +export function parse() { + return fsm({ + states: PARSER, + init: () => ({ + state: State.WAIT, + scope: [], + pos: 0 + }), + terminate: State.ERROR + }); +} + export const PARSER: FSMStateMap = { + [State.ERROR]: () => { + }, + [State.WAIT]: (s: ParseState, x: string) => { + s.pos++; if (!isWS(x)) { if (x === "<") { s.state = State.MAYBE_ELEM; } else { - unexpected(x); + return unexpected(s, x); } } }, [State.MAYBE_ELEM]: (s: ParseState, x: string) => { + s.pos++; if (x === "/") { if (s.scope.length == 0) { - unexpected(x); + return unexpected(s, x); } s.state = State.ELEM_END; s.tag = ""; @@ -98,11 +131,12 @@ export const PARSER: FSMStateMap = { s.phase = 0; s.body = ""; } else { - unexpected(x); + return unexpected(s, x); } }, [State.ELEM_START]: (s: ParseState, x: string) => { + s.pos++; if (isTagChar(x)) { s.tag += x; } else if (isWS(x)) { @@ -116,11 +150,12 @@ export const PARSER: FSMStateMap = { } else if (x === "/") { s.state = State.ELEM_SINGLE; } else { - unexpected(x); + return unexpected(s, x); } }, [State.ELEM_END]: (s: ParseState, x: string) => { + s.pos++; if (isTagChar(x)) { s.tag += x; } else if (x === ">") { @@ -140,6 +175,7 @@ export const PARSER: FSMStateMap = { }, [State.ELEM_SINGLE]: (s: ParseState, x: string) => { + s.pos++; if (x === ">") { s.state = State.WAIT; const n = s.scope.length; @@ -149,11 +185,12 @@ export const PARSER: FSMStateMap = { } return { id: "elem", value: res }; } else { - unexpected(x); + return unexpected(s, x); } }, [State.ELEM_BODY]: (s: ParseState, x: string) => { + s.pos++; if (x === "<") { let res; if (s.body.length > 0) { @@ -169,6 +206,7 @@ export const PARSER: FSMStateMap = { }, [State.MAYBE_ATTRIB]: (s: ParseState, x: string) => { + s.pos++; if (isTagChar(x)) { s.state = State.ATTRIB_NAME; s.name = x; @@ -184,30 +222,33 @@ export const PARSER: FSMStateMap = { } else if (s.tag === "xml" && x === "?") { s.state = State.PROC_END; } else if (!isWS(x)) { - unexpected(x); + return unexpected(s, x); } }, [State.ATTRIB_NAME]: (s: ParseState, x: string) => { + s.pos++; if (isTagChar(x)) { s.name += x; } else if (x === "=") { s.state = State.ATTRIB_VAL_START; } else { - unexpected(x); + return unexpected(s, x); } }, [State.ATTRIB_VAL_START]: (s: ParseState, x: string) => { + s.pos++; if (x === "\"" || x === "'") { s.state = State.ATTRIB_VALUE; s.quote = x; } else { - unexpected(x); + return unexpected(s, x); } }, [State.ATTRIB_VALUE]: (s: ParseState, x: string) => { + s.pos++; if (x === s.quote) { s.attribs[s.name] = s.val; s.state = State.MAYBE_ATTRIB; @@ -217,6 +258,7 @@ export const PARSER: FSMStateMap = { }, [State.MAYBE_INSTRUCTION]: (s: ParseState, x: string) => { + s.pos++; if (x === "-") { s.state = State.COMMENT; } else if (x === "D") { @@ -224,24 +266,26 @@ export const PARSER: FSMStateMap = { s.phase = 1; s.body = ""; } else { - unexpected(x); + return unexpected(s, x); } }, [State.COMMENT]: (s: ParseState, x: string) => { + s.pos++; if (x === "-") { s.state = State.COMMENT_BODY; s.body = ""; } else { - unexpected(x); + return unexpected(s, x); } }, [State.COMMENT_BODY]: (s: ParseState, x: string) => { + s.pos++; if (x === ">") { const n = s.body.length; if (s.body.substr(n - 2) !== "--") { - unexpected(x); + return unexpected(s, x); } s.state = State.WAIT; return { id: "comment", value: s.body.substr(0, n - 2) }; @@ -251,11 +295,12 @@ export const PARSER: FSMStateMap = { }, [State.DOCTYPE]: (s: ParseState, x: string) => { + s.pos++; if (s.phase < 8) { if (x === "DOCTYPE "[s.phase]) { s.phase++; } else { - unexpected(x); + return unexpected(s, x); } } else if (x === ">") { s.state = State.WAIT; @@ -266,6 +311,7 @@ export const PARSER: FSMStateMap = { }, [State.PROC_DECL]: (s: ParseState, x: string) => { + s.pos++; if (x === "xml "[s.phase]) { s.phase++; if (s.phase == 4) { @@ -274,16 +320,17 @@ export const PARSER: FSMStateMap = { s.attribs = {}; } } else { - unexpected(x); + return unexpected(s, x); } }, [State.PROC_END]: (s: ParseState, x: string) => { + s.pos++; if (x === ">") { s.state = State.WAIT; return { id: "proc", value: { tag: s.tag, attribs: s.attribs } }; } else { - unexpected(x); + return unexpected(s, x); } }, }; From 7c3c29032afa56af2a1af7dcfcc535ddc37933ff Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Sun, 17 Jun 2018 23:41:55 +0100 Subject: [PATCH 47/54] feat(transducers-fsm): inital import --- packages/transducers-fsm/.npmignore | 10 + packages/transducers-fsm/LICENSE | 201 ++++++++++++++++++++ packages/transducers-fsm/README.md | 30 +++ packages/transducers-fsm/package.json | 37 ++++ packages/transducers-fsm/src/index.ts | 38 ++++ packages/transducers-fsm/test/index.ts | 6 + packages/transducers-fsm/test/tsconfig.json | 10 + packages/transducers-fsm/tsconfig.json | 9 + 8 files changed, 341 insertions(+) create mode 100644 packages/transducers-fsm/.npmignore create mode 100644 packages/transducers-fsm/LICENSE create mode 100644 packages/transducers-fsm/README.md create mode 100644 packages/transducers-fsm/package.json create mode 100644 packages/transducers-fsm/src/index.ts create mode 100644 packages/transducers-fsm/test/index.ts create mode 100644 packages/transducers-fsm/test/tsconfig.json create mode 100644 packages/transducers-fsm/tsconfig.json 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/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..29af98ff20 --- /dev/null +++ b/packages/transducers-fsm/README.md @@ -0,0 +1,30 @@ +# @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 + +TODO... + +## Installation + +``` +yarn add @thi.ng/transducers-fsm +``` + +## Usage examples + +```typescript +import * as transducers-fsm from "@thi.ng/transducers-fsm"; +``` + +## 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..4688c12c46 --- /dev/null +++ b/packages/transducers-fsm/package.json @@ -0,0 +1,37 @@ +{ + "name": "@thi.ng/transducers-fsm", + "version": "0.0.1", + "description": "Transducer based Finite State Machine transformer", + "main": "./index.js", + "typings": "./index.d.ts", + "repository": "https://github.com/thi-ng/umbrella", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn run clean && tsc --declaration", + "clean": "rm -rf *.js *.d.ts .nyc_output build coverage doc", + "cover": "yarn test && nyc report --reporter=lcov", + "doc": "node_modules/.bin/typedoc --mode modules --out doc src", + "pub": "yarn run build && yarn publish --access public", + "test": "rm -rf build && tsc -p test && nyc mocha build/test/*.js" + }, + "devDependencies": { + "@types/mocha": "^5.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.3", + "@thi.ng/transducers": "^1.10.2" + }, + "keywords": [ + "ES6", + "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..2b4b06948a --- /dev/null +++ b/packages/transducers-fsm/src/index.ts @@ -0,0 +1,38 @@ +import { IObjectOf } from "@thi.ng/api/api"; + +import { Reducer, Transducer } from "@thi.ng/transducers/api"; +import { compR } from "@thi.ng/transducers/func/compr"; +import { ensureReduced } from "@thi.ng/transducers/reduced"; + +export interface FSMState { + state: PropertyKey; +} + +export type FSMStateMap = IObjectOf>; +export type FSMHandler = (state: T, input: A) => B | void; + +export interface FSMOpts { + states: FSMStateMap; + terminate: PropertyKey; + init: () => T; +} + +export function fsm(opts: FSMOpts): Transducer { + return (rfn: Reducer) => { + const states = opts.states; + const state = opts.init(); + const r = rfn[2]; + return compR(rfn, + (acc, x) => { + // console.log(x, State[state.state], state); + const res = states[state.state](state, x); + if (res) { + acc = r(acc, res); + 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..5ea048babf --- /dev/null +++ b/packages/transducers-fsm/test/index.ts @@ -0,0 +1,6 @@ +// import * as assert from "assert"; +// import * as transducers-fsm from "../src/index"; + +describe("transducers-fsm", () => { + it("tests pending"); +}); 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" + ] +} From 56deb4556b4085a7d50ed8c74f094cc02056feb5 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Sun, 17 Jun 2018 23:44:04 +0100 Subject: [PATCH 48/54] refactor(sax): remove extracted FSM transducer & types, update readme --- packages/sax/README.md | 89 +++++++++++++++++++++++++++++++++ packages/sax/package.json | 12 +++-- packages/sax/src/index.ts | 68 ++++++------------------- packages/sax/test/index.ts | 6 +++ packages/sax/test/tsconfig.json | 10 ++++ 5 files changed, 130 insertions(+), 55 deletions(-) create mode 100644 packages/sax/README.md create mode 100644 packages/sax/test/index.ts create mode 100644 packages/sax/test/tsconfig.json diff --git a/packages/sax/README.md b/packages/sax/README.md new file mode 100644 index 0000000000..bc2349411a --- /dev/null +++ b/packages/sax/README.md @@ -0,0 +1,89 @@ +# @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, speedy & tiny XML parser (1.4KB 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. + +## Installation + +``` +yarn add @thi.ng/sax +``` + +## 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)] + +// [ { id: 'proc', value: { tag: 'xml', attribs: { version: '1.0', encoding: 'utf-8' } } }, +// { id: 'doctype', value: 'foo bar' }, +// { id: 'elem', value: { tag: 'a', attribs: {} } }, +// { id: 'body', value: { tag: 'a', body: '\n ' } }, +// { id: 'elem', value: { tag: 'b1', attribs: {} } }, +// { id: 'body', value: { tag: 'b1', body: '\n ' } }, +// { id: 'elem', value: { tag: 'c', attribs: { x: '23', y: '42' } } }, +// { id: 'body', value: { tag: 'c', body: 'ccc\n ' } }, +// { id: 'elem', value: { tag: 'd', attribs: {} } }, +// { id: 'body', value: { tag: 'd', body: 'dd' } }, +// { id: 'end', +// value: { tag: 'd', attribs: {}, children: [], body: 'dd' } }, +// { id: 'end', +// value: +// { tag: 'c', +// attribs: { x: '23', y: '42' }, +// children: [ { tag: 'd', attribs: {}, children: [], body: 'dd' } ], +// body: 'ccc\n ' } }, +// { id: 'end', +// value: +// { tag: 'b1', attribs: {}, children: [Array], body: '\n ' } }, +// { id: 'elem', value: { tag: 'b2', attribs: [Object] } }, +// { id: 'end', +// value: { tag: 'a', attribs: {}, children: [Array], body: '\n ' } } ] +``` + +## Emitted result type IDs + +| ID | Description | +|-----------|-----------------------------------------------| +| `body` | Element text body | +| `doctype` | Doctype declaration | +| `elem` | Element start incl. attributes | +| `end` | Element end incl. attributes, body & children | +| `proc` | Processing instruction incl. attribs | +| `error` | Parse error incl. description | + +## Authors + +- Karsten Schmidt + +## License + +© 2018 Karsten Schmidt // Apache Software License 2.0 diff --git a/packages/sax/package.json b/packages/sax/package.json index 2331dcedeb..65cf93eb2d 100644 --- a/packages/sax/package.json +++ b/packages/sax/package.json @@ -1,7 +1,7 @@ { "name": "@thi.ng/sax", "version": "0.0.1", - "description": "TODO", + "description": "Transducer-based, SAX-like, non-validating, speedy & tiny XML parser", "main": "./index.js", "typings": "./index.d.ts", "repository": "https://github.com/thi-ng/umbrella", @@ -25,11 +25,17 @@ }, "dependencies": { "@thi.ng/api": "^4.0.3", - "@thi.ng/transducers": "^1.10.2" + "@thi.ng/transducers": "^1.10.2", + "@thi.ng/transducers-fsm": "^0.0.1" }, "keywords": [ "ES6", - "typescript" + "FSM", + "parser", + "SAX", + "transducers", + "typescript", + "XML" ], "publishConfig": { "access": "public" diff --git a/packages/sax/src/index.ts b/packages/sax/src/index.ts index 717cf0374c..c0bdf8fcac 100644 --- a/packages/sax/src/index.ts +++ b/packages/sax/src/index.ts @@ -1,43 +1,8 @@ -import { Event, IObjectOf } from "@thi.ng/api"; -import { Transducer, Reducer } from "@thi.ng/transducers/api"; -import { ensureReduced } from "@thi.ng/transducers/reduced"; +import { Event } from "@thi.ng/api"; +import * as fsm from "@thi.ng/transducers-fsm"; +import { Transducer } from "@thi.ng/transducers/api"; -export interface FSMState { - state: PropertyKey; -} - -export type FSMStateMap = IObjectOf>; -export type FSMHandler = (state: T, input: A) => B | void; - -export interface FSMOpts { - states: FSMStateMap; - terminate: PropertyKey; - init: () => T; -} - -export function fsm(opts: FSMOpts): Transducer { - return (rfn: Reducer) => { - const states = opts.states; - const state = opts.init(); - const r = rfn[2]; - return [ - rfn[0], - (acc) => rfn[1](acc), - (acc, x) => { - // console.log(x, State[state.state], state); - const res = states[state.state](state, x); - if (res) { - acc = r(acc, res); - if (state.state == opts.terminate) { - return ensureReduced(acc); - } - } - return acc; - }]; - } -} - -export interface ParseState extends FSMState { +export interface ParseState extends fsm.FSMState { pos: number; scope: any[]; tag: string; @@ -69,6 +34,17 @@ enum State { PROC_END, } +export const parse = (): Transducer => + fsm.fsm({ + states: PARSER, + init: () => ({ + state: State.WAIT, + scope: [], + pos: 0 + }), + terminate: State.ERROR + }); + const isWS = (x: string) => x == " " || x == "\t" || x == "\n" || x == "\r"; @@ -85,19 +61,7 @@ const unexpected = (s: ParseState, x: string) => { return { id: "error", value: `unexpected char: '${x}' @ pos: ${s.pos}` }; }; -export function parse() { - return fsm({ - states: PARSER, - init: () => ({ - state: State.WAIT, - scope: [], - pos: 0 - }), - terminate: State.ERROR - }); -} - -export const PARSER: FSMStateMap = { +const PARSER: fsm.FSMStateMap = { [State.ERROR]: () => { }, 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" + ] +} From 0f2fcdffa8d8207d0ecebe3d58bdd788536a9ed3 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Mon, 18 Jun 2018 01:46:28 +0100 Subject: [PATCH 49/54] feat(sax): add entity support, update result format, update states - add ParseOpts, ParseElement, ParseEvent, Type - add XML entity replacement (optional) - update error handling (add `error()` helper) - update PROC_DECL state to support arbitrary instruction tags - --- packages/sax/src/index.ts | 392 ++++++++++++++++++++++---------------- 1 file changed, 225 insertions(+), 167 deletions(-) diff --git a/packages/sax/src/index.ts b/packages/sax/src/index.ts index c0bdf8fcac..017450f7d0 100644 --- a/packages/sax/src/index.ts +++ b/packages/sax/src/index.ts @@ -1,17 +1,44 @@ -import { Event } from "@thi.ng/api"; +import { IObjectOf } from "@thi.ng/api"; import * as fsm from "@thi.ng/transducers-fsm"; import { Transducer } from "@thi.ng/transducers/api"; -export interface ParseState extends fsm.FSMState { - pos: number; - scope: any[]; +export interface ParseOpts { + entities: boolean; +} + +export interface ParseElement { tag: string; + attribs: IObjectOf; body: string; + children: ParseElement[]; +} + +export interface ParseEvent extends Partial { + type: Type; +} + +export enum Type { + PROC, + DOCTYPE, + COMMENT, + ELEM_START, + ELEM_END, + ELEM_BODY, + ERROR, +} + +interface ParseState extends fsm.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 { @@ -34,13 +61,24 @@ enum State { PROC_END, } -export const parse = (): Transducer => +const ENTITIES = { + "&": "&", + "<": "<", + ">": ">", + """: '"', + "'": "'", +}; + +const ENTITY_RE = new RegExp(`(${Object.keys(ENTITIES).join("|")})`, "g"); + +export const parse = (opts: Partial = {}): Transducer => fsm.fsm({ states: PARSER, init: () => ({ state: State.WAIT, scope: [], - pos: 0 + pos: 0, + opts, }), terminate: State.ERROR }); @@ -56,245 +94,265 @@ const isTagChar = (x: string) => x == "_" || x == ":"; -const unexpected = (s: ParseState, x: string) => { +const error = (s: ParseState, body: string) => { s.state = State.ERROR; - return { id: "error", value: `unexpected char: '${x}' @ pos: ${s.pos}` }; + return { type: Type.ERROR, body }; }; -const PARSER: fsm.FSMStateMap = { - [State.ERROR]: () => { - }, +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: fsm.FSMStateMap = { + + [State.ERROR]: () => { }, - [State.WAIT]: (s: ParseState, x: string) => { - s.pos++; - if (!isWS(x)) { - if (x === "<") { - s.state = State.MAYBE_ELEM; + [State.WAIT]: (state, ch) => { + state.pos++; + if (!isWS(ch)) { + if (ch === "<") { + state.state = State.MAYBE_ELEM; } else { - return unexpected(s, x); + return unexpected(state, ch); } } }, - [State.MAYBE_ELEM]: (s: ParseState, x: string) => { - s.pos++; - if (x === "/") { - if (s.scope.length == 0) { - return unexpected(s, x); + [State.MAYBE_ELEM]: (state, ch) => { + state.pos++; + if (ch === "/") { + if (state.scope.length == 0) { + return unexpected(state, ch); } - s.state = State.ELEM_END; - s.tag = ""; - } else if (isTagChar(x)) { - s.state = State.ELEM_START; - s.tag = x; - s.attribs = {}; - } else if (x === "!") { - s.state = State.MAYBE_INSTRUCTION; - } else if (x === "?") { - s.state = State.PROC_DECL; - s.phase = 0; - s.body = ""; + 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(s, x); + return unexpected(state, ch); } }, - [State.ELEM_START]: (s: ParseState, x: string) => { - s.pos++; - if (isTagChar(x)) { - s.tag += x; - } else if (isWS(x)) { - s.state = State.MAYBE_ATTRIB; - } else if (x === ">") { - const res = { id: "elem", value: { tag: s.tag, attribs: s.attribs } }; - s.state = State.ELEM_BODY; - s.scope.push({ tag: s.tag, attribs: s.attribs, children: [] }); - s.body = ""; + [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 === ">") { + const res = { type: Type.ELEM_START, tag: state.tag, attribs: state.attribs }; + state.state = State.ELEM_BODY; + state.scope.push({ tag: state.tag, attribs: state.attribs, children: [] }); + state.body = ""; return res; - } else if (x === "/") { - s.state = State.ELEM_SINGLE; + } else if (ch === "/") { + state.state = State.ELEM_SINGLE; } else { - return unexpected(s, x); + return unexpected(state, ch); } }, - [State.ELEM_END]: (s: ParseState, x: string) => { - s.pos++; - if (isTagChar(x)) { - s.tag += x; - } else if (x === ">") { - const n = s.scope.length; - if (n > 0 && s.tag === s.scope[n - 1].tag) { - const res = { id: "end", value: s.scope[n - 1] }; - s.scope.pop(); + [State.ELEM_END]: (state, ch) => { + state.pos++; + if (isTagChar(ch)) { + state.tag += ch; + } else if (ch === ">") { + const n = state.scope.length; + if (n > 0 && state.tag === state.scope[n - 1].tag) { + const res = state.scope[n - 1]; + state.scope.pop(); if (n > 1) { - s.scope[n - 2].children.push(res.value); + state.scope[n - 2].children.push(res); } - s.state = State.WAIT; - return res; + state.state = State.WAIT; + return { type: Type.ELEM_END, ...res }; } else { - throw new Error(`unmatched end tag: ${s.tag}`); + error(state, state.tag); } } }, - [State.ELEM_SINGLE]: (s: ParseState, x: string) => { - s.pos++; - if (x === ">") { - s.state = State.WAIT; - const n = s.scope.length; - const res = { tag: s.tag, attribs: s.attribs }; + [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) { - s.scope[n - 1].children.push(res); + state.scope[n - 1].children.push(res); } - return { id: "elem", value: res }; + return { type: Type.ELEM_END, ...res }; } else { - return unexpected(s, x); + return unexpected(state, ch); } }, - [State.ELEM_BODY]: (s: ParseState, x: string) => { - s.pos++; - if (x === "<") { + [State.ELEM_BODY]: (state, ch) => { + state.pos++; + if (ch === "<") { let res; - if (s.body.length > 0) { - s.scope[s.scope.length - 1].body = s.body; - res = { id: "body", value: { tag: s.tag, body: s.body } } + if (state.body.length > 0) { + if (state.opts.entities) { + state.body = replaceEntities(state.body); + } + state.scope[state.scope.length - 1].body = state.body; + res = { type: Type.ELEM_BODY, tag: state.tag, body: state.body }; } - s.state = State.MAYBE_ELEM; - s.tag = ""; + state.state = State.MAYBE_ELEM; + state.tag = ""; return res; } else { - s.body += x; + state.body += ch; } }, - [State.MAYBE_ATTRIB]: (s: ParseState, x: string) => { - s.pos++; - if (isTagChar(x)) { - s.state = State.ATTRIB_NAME; - s.name = x; - s.val = ""; - } else if (x === ">") { - const res = { id: "elem", value: { tag: s.tag, attribs: s.attribs } }; - s.state = State.ELEM_BODY; - s.scope.push({ tag: s.tag, attribs: s.attribs, children: [] }); - s.body = ""; - return res; - } else if (x === "/") { - s.state = State.ELEM_SINGLE; - } else if (s.tag === "xml" && x === "?") { - s.state = State.PROC_END; - } else if (!isWS(x)) { - return unexpected(s, x); + [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 === ">") { + const res = { type: Type.ELEM_START, tag: state.tag, attribs: state.attribs }; + state.state = State.ELEM_BODY; + state.scope.push({ tag: state.tag, attribs: state.attribs, children: [] }); + state.body = ""; + return res; + } else if (ch === "/") { + state.state = State.ELEM_SINGLE; + } else if (!isWS(ch)) { + return unexpected(state, ch); + } + } } }, - [State.ATTRIB_NAME]: (s: ParseState, x: string) => { - s.pos++; - if (isTagChar(x)) { - s.name += x; - } else if (x === "=") { - s.state = State.ATTRIB_VAL_START; + [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(s, x); + return unexpected(state, ch); } }, - [State.ATTRIB_VAL_START]: (s: ParseState, x: string) => { - s.pos++; - if (x === "\"" || x === "'") { - s.state = State.ATTRIB_VALUE; - s.quote = x; + [State.ATTRIB_VAL_START]: (state, ch) => { + state.pos++; + if (ch === "\"" || ch === "'") { + state.state = State.ATTRIB_VALUE; + state.quote = ch; } else { - return unexpected(s, x); + return unexpected(state, ch); } }, - [State.ATTRIB_VALUE]: (s: ParseState, x: string) => { - s.pos++; - if (x === s.quote) { - s.attribs[s.name] = s.val; - s.state = State.MAYBE_ATTRIB; + [State.ATTRIB_VALUE]: (state, ch) => { + state.pos++; + if (ch === state.quote) { + if (state.opts.entities) { + state.val = replaceEntities(state.val); + } + state.attribs[state.name] = state.val; + state.state = State.MAYBE_ATTRIB; } else { - s.val += x; + state.val += ch; } }, - [State.MAYBE_INSTRUCTION]: (s: ParseState, x: string) => { - s.pos++; - if (x === "-") { - s.state = State.COMMENT; - } else if (x === "D") { - s.state = State.DOCTYPE; - s.phase = 1; - s.body = ""; + [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 { - return unexpected(s, x); + return unexpected(state, ch); } }, - [State.COMMENT]: (s: ParseState, x: string) => { - s.pos++; - if (x === "-") { - s.state = State.COMMENT_BODY; - s.body = ""; + [State.COMMENT]: (state, ch) => { + state.pos++; + if (ch === "-") { + state.state = State.COMMENT_BODY; + state.body = ""; } else { - return unexpected(s, x); + return unexpected(state, ch); } }, - [State.COMMENT_BODY]: (s: ParseState, x: string) => { - s.pos++; - if (x === ">") { - const n = s.body.length; - if (s.body.substr(n - 2) !== "--") { - return unexpected(s, x); + [State.COMMENT_BODY]: (state, ch) => { + state.pos++; + if (ch === ">") { + const n = state.body.length; + if (state.body.substr(n - 2) !== "--") { + return unexpected(state, ch); } - s.state = State.WAIT; - return { id: "comment", value: s.body.substr(0, n - 2) }; + state.state = State.WAIT; + return { type: Type.COMMENT, body: state.body.substr(0, n - 2) }; } else { - s.body += x; + state.body += ch; } }, - [State.DOCTYPE]: (s: ParseState, x: string) => { - s.pos++; - if (s.phase < 8) { - if (x === "DOCTYPE "[s.phase]) { - s.phase++; + [State.DOCTYPE]: (state, ch) => { + state.pos++; + if (state.phase < 8) { + if (ch === "DOCTYPE "[state.phase]) { + state.phase++; } else { - return unexpected(s, x); + return unexpected(state, ch); } - } else if (x === ">") { - s.state = State.WAIT; - return { id: "doctype", value: s.body.trim() }; + } else if (ch === ">") { + state.state = State.WAIT; + return { type: Type.DOCTYPE, body: state.body.trim() }; } else { - s.body += x; + state.body += ch; } }, - [State.PROC_DECL]: (s: ParseState, x: string) => { - s.pos++; - if (x === "xml "[s.phase]) { - s.phase++; - if (s.phase == 4) { - s.state = State.MAYBE_ATTRIB; - s.tag = "xml"; - s.attribs = {}; - } + [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(s, x); + return unexpected(state, ch); } }, - [State.PROC_END]: (s: ParseState, x: string) => { - s.pos++; - if (x === ">") { - s.state = State.WAIT; - return { id: "proc", value: { tag: s.tag, attribs: s.attribs } }; + [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(s, x); + return unexpected(state, ch); } }, }; From 23b9cc2e30e9082ed5d612934915835a809e1bd5 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Mon, 18 Jun 2018 03:32:45 +0100 Subject: [PATCH 50/54] docs(transducers-fsm): update docs & readme --- packages/transducers-fsm/README.md | 118 +++++++++++++++++++++++++- packages/transducers-fsm/package.json | 4 +- packages/transducers-fsm/src/index.ts | 84 ++++++++++++++++-- 3 files changed, 195 insertions(+), 11 deletions(-) diff --git a/packages/transducers-fsm/README.md b/packages/transducers-fsm/README.md index 29af98ff20..ec39afc278 100644 --- a/packages/transducers-fsm/README.md +++ b/packages/transducers-fsm/README.md @@ -7,7 +7,11 @@ This project is part of the ## About -TODO... +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 @@ -17,10 +21,118 @@ yarn add @thi.ng/transducers-fsm ## Usage examples -```typescript -import * as transducers-fsm from "@thi.ng/transducers-fsm"; +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 simple example defines a 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 terminate processing (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 + terminal: "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` + +The `fsm()` function takes an FSM configuration object and returns a +transducer, which processes inputs using the provided state handler +functions. + +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. + +Any values (apart from `undefined`) returned by a state handler are +passed on to the next reducer in the chain and so can be used to emit +results for downstream processing. If a state handler returns nothing +(or `undefined`), further downstream processing of the current input is +skipped. + +Regardless of return value, if a state handler has changed the state ID +to the configured `terminal` state, processing is terminated (by calling +`ensureReduced()`) and no further inputs will be consumed. + ## Authors - Karsten Schmidt diff --git a/packages/transducers-fsm/package.json b/packages/transducers-fsm/package.json index 4688c12c46..7e89fb9c8c 100644 --- a/packages/transducers-fsm/package.json +++ b/packages/transducers-fsm/package.json @@ -1,7 +1,7 @@ { "name": "@thi.ng/transducers-fsm", "version": "0.0.1", - "description": "Transducer based Finite State Machine transformer", + "description": "Transducer-based Finite State Machine transformer", "main": "./index.js", "typings": "./index.d.ts", "repository": "https://github.com/thi-ng/umbrella", @@ -34,4 +34,4 @@ "publishConfig": { "access": "public" } -} +} \ No newline at end of file diff --git a/packages/transducers-fsm/src/index.ts b/packages/transducers-fsm/src/index.ts index 2b4b06948a..bc2047e65a 100644 --- a/packages/transducers-fsm/src/index.ts +++ b/packages/transducers-fsm/src/index.ts @@ -17,6 +17,79 @@ export interface FSMOpts { init: () => T; } +/** + * Finite State Machine transducer. Takes an FSM configuration object + * and returns a transducer, which processes inputs using the provided + * state handler functions. + * + * 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. + * + * Any values (apart from `undefined`) returned by a state handler are + * passed on to the next reducer in the chain and so can be used to emit + * results for downstream processing. If a state handler returns nothing + * (or `undefined`), further downstream processing of the current input + * is skipped. + * + * Regardless of return value, if a state handler has changed the state + * ID 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: () => { }, + * }, + * terminal: "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 (rfn: Reducer) => { const states = opts.states; @@ -24,13 +97,12 @@ export function fsm(opts: FSMOpts): Transduce const r = rfn[2]; return compR(rfn, (acc, x) => { - // console.log(x, State[state.state], state); const res = states[state.state](state, x); - if (res) { - acc = r(acc, res); - if (state.state == opts.terminate) { - return ensureReduced(acc); - } + if (res !== undefined) { + acc = r(acc, res); + } + if (state.state === opts.terminate) { + return ensureReduced(acc); } return acc; }); From 3589e155e4803b1574b50c3389efa357dd5fdb5a Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Mon, 18 Jun 2018 03:33:22 +0100 Subject: [PATCH 51/54] fix(checks): isOdd() for negative values --- packages/checks/src/is-odd.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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; } From 42e229210c65eeff5d1986b4acabeb39d96ab910 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Mon, 18 Jun 2018 03:38:51 +0100 Subject: [PATCH 52/54] docs: update readmes (add/fix links) --- packages/rstream-graph/README.md | 6 +++--- packages/transducers/README.md | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/rstream-graph/README.md b/packages/rstream-graph/README.md index 4c91b3c7c6..3a0a9dbe67 100644 --- a/packages/rstream-graph/README.md +++ b/packages/rstream-graph/README.md @@ -37,10 +37,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) diff --git a/packages/transducers/README.md b/packages/transducers/README.md index a151ea9efe..7e8cadbff0 100644 --- a/packages/transducers/README.md +++ b/packages/transducers/README.md @@ -34,12 +34,12 @@ 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/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) From b5b263005fbb6b293bfd536accf563a544887228 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Mon, 18 Jun 2018 03:49:55 +0100 Subject: [PATCH 53/54] docs: update package docs (bench / dot) --- packages/bench/package.json | 4 ++-- packages/dot/package.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/bench/package.json b/packages/bench/package.json index 5bb16a2a19..cbd6376210 100644 --- a/packages/bench/package.json +++ b/packages/bench/package.json @@ -1,7 +1,7 @@ { "name": "@thi.ng/bench", "version": "0.1.1", - "description": "TODO", + "description": "Basic benchmarking helpers", "main": "./index.js", "typings": "./index.d.ts", "repository": "https://github.com/thi-ng/umbrella", @@ -32,4 +32,4 @@ "publishConfig": { "access": "public" } -} +} \ No newline at end of file diff --git a/packages/dot/package.json b/packages/dot/package.json index 32e69e370c..0419759c6c 100644 --- a/packages/dot/package.json +++ b/packages/dot/package.json @@ -1,7 +1,7 @@ { "name": "@thi.ng/dot", "version": "0.1.6", - "description": "TODO", + "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", @@ -34,4 +34,4 @@ "publishConfig": { "access": "public" } -} +} \ No newline at end of file From 278a86c2d3503568262c9dc21d64ea6b8cfd22a8 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Mon, 18 Jun 2018 03:50:29 +0100 Subject: [PATCH 54/54] Publish - @thi.ng/associative@0.5.7 - @thi.ng/atom@1.4.1 - @thi.ng/bench@0.1.2 - @thi.ng/cache@0.2.12 - @thi.ng/checks@1.5.4 - @thi.ng/csp@0.3.43 - @thi.ng/dcons@1.0.4 - @thi.ng/dgraph@0.2.7 - @thi.ng/diff@1.0.18 - @thi.ng/dot@0.1.7 - @thi.ng/equiv@0.1.4 - @thi.ng/hdom-components@2.1.7 - @thi.ng/hdom@3.0.25 - @thi.ng/hiccup-css@0.2.1 - @thi.ng/hiccup-svg@1.0.4 - @thi.ng/hiccup@2.0.4 - @thi.ng/interceptors@1.8.4 - @thi.ng/iterators@4.1.17 - @thi.ng/paths@1.3.9 - @thi.ng/pointfree-lang@0.2.14 - @thi.ng/pointfree@0.8.3 - @thi.ng/resolve-map@3.0.1 - @thi.ng/router@0.1.17 - @thi.ng/rstream-csp@0.1.76 - @thi.ng/rstream-dot@0.2.15 - @thi.ng/rstream-gestures@0.3.12 - @thi.ng/rstream-graph@2.0.2 - @thi.ng/rstream-log@1.0.27 - @thi.ng/rstream-query@0.3.14 - @thi.ng/rstream@1.7.2 - @thi.ng/sax@0.1.0 - @thi.ng/transducers-fsm@0.1.0 - @thi.ng/transducers@1.10.3 --- packages/associative/CHANGELOG.md | 8 ++++++++ packages/associative/package.json | 10 +++++----- packages/atom/CHANGELOG.md | 8 ++++++++ packages/atom/package.json | 8 ++++---- packages/bench/CHANGELOG.md | 8 ++++++++ packages/bench/package.json | 4 ++-- packages/cache/CHANGELOG.md | 8 ++++++++ packages/cache/package.json | 6 +++--- packages/checks/CHANGELOG.md | 11 +++++++++++ packages/checks/package.json | 2 +- packages/csp/CHANGELOG.md | 8 ++++++++ packages/csp/package.json | 8 ++++---- packages/dcons/CHANGELOG.md | 8 ++++++++ packages/dcons/package.json | 6 +++--- packages/dgraph/CHANGELOG.md | 8 ++++++++ packages/dgraph/package.json | 8 ++++---- packages/diff/CHANGELOG.md | 8 ++++++++ packages/diff/package.json | 4 ++-- packages/dot/CHANGELOG.md | 8 ++++++++ packages/dot/package.json | 6 +++--- packages/equiv/CHANGELOG.md | 8 ++++++++ packages/equiv/package.json | 4 ++-- packages/hdom-components/CHANGELOG.md | 8 ++++++++ packages/hdom-components/package.json | 6 +++--- packages/hdom/CHANGELOG.md | 8 ++++++++ packages/hdom/package.json | 14 +++++++------- packages/hiccup-css/CHANGELOG.md | 8 ++++++++ packages/hiccup-css/package.json | 6 +++--- packages/hiccup-svg/CHANGELOG.md | 8 ++++++++ packages/hiccup-svg/package.json | 4 ++-- packages/hiccup/CHANGELOG.md | 8 ++++++++ packages/hiccup/package.json | 6 +++--- packages/interceptors/CHANGELOG.md | 8 ++++++++ packages/interceptors/package.json | 8 ++++---- packages/iterators/CHANGELOG.md | 8 ++++++++ packages/iterators/package.json | 4 ++-- packages/paths/CHANGELOG.md | 8 ++++++++ packages/paths/package.json | 4 ++-- packages/pointfree-lang/CHANGELOG.md | 8 ++++++++ packages/pointfree-lang/package.json | 4 ++-- packages/pointfree/CHANGELOG.md | 8 ++++++++ packages/pointfree/package.json | 6 +++--- packages/resolve-map/CHANGELOG.md | 12 ++++++++++-- packages/resolve-map/package.json | 6 +++--- packages/router/CHANGELOG.md | 8 ++++++++ packages/router/package.json | 6 +++--- packages/rstream-csp/CHANGELOG.md | 8 ++++++++ packages/rstream-csp/package.json | 6 +++--- packages/rstream-dot/CHANGELOG.md | 8 ++++++++ packages/rstream-dot/package.json | 4 ++-- packages/rstream-gestures/CHANGELOG.md | 8 ++++++++ packages/rstream-gestures/package.json | 6 +++--- packages/rstream-graph/CHANGELOG.md | 8 ++++++++ packages/rstream-graph/package.json | 12 ++++++------ packages/rstream-log/CHANGELOG.md | 8 ++++++++ packages/rstream-log/package.json | 8 ++++---- packages/rstream-query/CHANGELOG.md | 8 ++++++++ packages/rstream-query/package.json | 14 +++++++------- packages/rstream/CHANGELOG.md | 8 ++++++++ packages/rstream/package.json | 12 ++++++------ packages/sax/CHANGELOG.md | 16 ++++++++++++++++ packages/sax/package.json | 8 ++++---- packages/transducers-fsm/CHANGELOG.md | 12 ++++++++++++ packages/transducers-fsm/package.json | 6 +++--- packages/transducers/CHANGELOG.md | 8 ++++++++ packages/transducers/package.json | 6 +++--- 66 files changed, 392 insertions(+), 113 deletions(-) create mode 100644 packages/sax/CHANGELOG.md create mode 100644 packages/transducers-fsm/CHANGELOG.md diff --git a/packages/associative/CHANGELOG.md b/packages/associative/CHANGELOG.md index 7a3fed4370..c2ed93ebd4 100644 --- a/packages/associative/CHANGELOG.md +++ b/packages/associative/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.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) diff --git a/packages/associative/package.json b/packages/associative/package.json index 1ed52db5c8..e60fdd9c7f 100644 --- a/packages/associative/package.json +++ b/packages/associative/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/associative", - "version": "0.5.6", + "version": "0.5.7", "description": "Alternative Set & Map data type implementations with customizable equality semantics & supporting operations", "main": "./index.js", "typings": "./index.d.ts", @@ -25,12 +25,12 @@ }, "dependencies": { "@thi.ng/api": "^4.0.3", - "@thi.ng/checks": "^1.5.3", + "@thi.ng/checks": "^1.5.4", "@thi.ng/compare": "^0.1.3", - "@thi.ng/dcons": "^1.0.3", - "@thi.ng/equiv": "^0.1.3", + "@thi.ng/dcons": "^1.0.4", + "@thi.ng/equiv": "^0.1.4", "@thi.ng/errors": "^0.1.3", - "@thi.ng/iterators": "^4.1.16" + "@thi.ng/iterators": "^4.1.17" }, "keywords": [ "data structures", diff --git a/packages/atom/CHANGELOG.md b/packages/atom/CHANGELOG.md index bb5a79ae98..a58ab17008 100644 --- a/packages/atom/CHANGELOG.md +++ b/packages/atom/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [1.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) diff --git a/packages/atom/package.json b/packages/atom/package.json index fac8eba32f..5dd2972363 100644 --- a/packages/atom/package.json +++ b/packages/atom/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/atom", - "version": "1.4.0", + "version": "1.4.1", "description": "Mutable wrapper for immutable values", "main": "./index.js", "typings": "./index.d.ts", @@ -25,10 +25,10 @@ }, "dependencies": { "@thi.ng/api": "^4.0.3", - "@thi.ng/checks": "^1.5.3", - "@thi.ng/equiv": "^0.1.3", + "@thi.ng/checks": "^1.5.4", + "@thi.ng/equiv": "^0.1.4", "@thi.ng/errors": "^0.1.3", - "@thi.ng/paths": "^1.3.8" + "@thi.ng/paths": "^1.3.9" }, "keywords": [ "cursor", diff --git a/packages/bench/CHANGELOG.md b/packages/bench/CHANGELOG.md index 92f1536d9f..5cad05bc54 100644 --- a/packages/bench/CHANGELOG.md +++ b/packages/bench/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.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 cbd6376210..ede26a14c9 100644 --- a/packages/bench/package.json +++ b/packages/bench/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/bench", - "version": "0.1.1", + "version": "0.1.2", "description": "Basic benchmarking helpers", "main": "./index.js", "typings": "./index.d.ts", @@ -32,4 +32,4 @@ "publishConfig": { "access": "public" } -} \ No newline at end of file +} diff --git a/packages/cache/CHANGELOG.md b/packages/cache/CHANGELOG.md index 89e6f45edc..a58eed2566 100644 --- a/packages/cache/CHANGELOG.md +++ b/packages/cache/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.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) diff --git a/packages/cache/package.json b/packages/cache/package.json index b3250bf07f..82c6d13469 100644 --- a/packages/cache/package.json +++ b/packages/cache/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/cache", - "version": "0.2.11", + "version": "0.2.12", "description": "In-memory cache implementations with ES6 Map-like API and different eviction strategies", "main": "./index.js", "typings": "./index.d.ts", @@ -25,8 +25,8 @@ }, "dependencies": { "@thi.ng/api": "^4.0.3", - "@thi.ng/dcons": "^1.0.3", - "@thi.ng/iterators": "^4.1.16" + "@thi.ng/dcons": "^1.0.4", + "@thi.ng/iterators": "^4.1.17" }, "keywords": [ "cache", diff --git a/packages/checks/CHANGELOG.md b/packages/checks/CHANGELOG.md index 00afe9f620..039f4eaf1a 100644 --- a/packages/checks/CHANGELOG.md +++ b/packages/checks/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [1.5.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..8a1646b8f3 100644 --- a/packages/checks/package.json +++ b/packages/checks/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/checks", - "version": "1.5.3", + "version": "1.5.4", "description": "Single-function sub-modules for type, feature & value checks", "main": "./index.js", "typings": "./index.d.ts", diff --git a/packages/csp/CHANGELOG.md b/packages/csp/CHANGELOG.md index 4d664e2ded..204bf97479 100644 --- a/packages/csp/CHANGELOG.md +++ b/packages/csp/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.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) diff --git a/packages/csp/package.json b/packages/csp/package.json index 878ee0fca2..95672725ca 100644 --- a/packages/csp/package.json +++ b/packages/csp/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/csp", - "version": "0.3.42", + "version": "0.3.43", "description": "ES6 promise based CSP implementation", "main": "./index.js", "typings": "./index.d.ts", @@ -29,10 +29,10 @@ }, "dependencies": { "@thi.ng/api": "^4.0.3", - "@thi.ng/checks": "^1.5.3", - "@thi.ng/dcons": "^1.0.3", + "@thi.ng/checks": "^1.5.4", + "@thi.ng/dcons": "^1.0.4", "@thi.ng/errors": "^0.1.3", - "@thi.ng/transducers": "^1.10.2" + "@thi.ng/transducers": "^1.10.3" }, "keywords": [ "async", diff --git a/packages/dcons/CHANGELOG.md b/packages/dcons/CHANGELOG.md index 517f642396..fa96111cc2 100644 --- a/packages/dcons/CHANGELOG.md +++ b/packages/dcons/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [1.0.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) diff --git a/packages/dcons/package.json b/packages/dcons/package.json index d740781b09..7b6795d267 100644 --- a/packages/dcons/package.json +++ b/packages/dcons/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/dcons", - "version": "1.0.3", + "version": "1.0.4", "description": "Comprehensive doubly linked list structure w/ iterator support", "main": "./index.js", "typings": "./index.d.ts", @@ -25,9 +25,9 @@ }, "dependencies": { "@thi.ng/api": "^4.0.3", - "@thi.ng/checks": "^1.5.3", + "@thi.ng/checks": "^1.5.4", "@thi.ng/compare": "^0.1.3", - "@thi.ng/equiv": "^0.1.3", + "@thi.ng/equiv": "^0.1.4", "@thi.ng/errors": "^0.1.3" }, "keywords": [ diff --git a/packages/dgraph/CHANGELOG.md b/packages/dgraph/CHANGELOG.md index 971fa0eaeb..221a59a5ce 100644 --- a/packages/dgraph/CHANGELOG.md +++ b/packages/dgraph/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.2.7](https://github.com/thi-ng/umbrella/compare/@thi.ng/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) diff --git a/packages/dgraph/package.json b/packages/dgraph/package.json index 6ba2cd68eb..55d7f10401 100644 --- a/packages/dgraph/package.json +++ b/packages/dgraph/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/dgraph", - "version": "0.2.6", + "version": "0.2.7", "description": "Type-agnostic directed acyclic graph (DAG) & graph operations", "main": "./index.js", "typings": "./index.d.ts", @@ -25,10 +25,10 @@ }, "dependencies": { "@thi.ng/api": "^4.0.3", - "@thi.ng/associative": "^0.5.6", - "@thi.ng/equiv": "^0.1.3", + "@thi.ng/associative": "^0.5.7", + "@thi.ng/equiv": "^0.1.4", "@thi.ng/errors": "^0.1.3", - "@thi.ng/iterators": "^4.1.16" + "@thi.ng/iterators": "^4.1.17" }, "keywords": [ "data structure", diff --git a/packages/diff/CHANGELOG.md b/packages/diff/CHANGELOG.md index 6c0580b709..f04d924c25 100644 --- a/packages/diff/CHANGELOG.md +++ b/packages/diff/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [1.0.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) diff --git a/packages/diff/package.json b/packages/diff/package.json index fb513daf82..b1262aed67 100644 --- a/packages/diff/package.json +++ b/packages/diff/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/diff", - "version": "1.0.17", + "version": "1.0.18", "description": "Array & object Diff", "main": "./index.js", "typings": "./index.d.ts", @@ -23,7 +23,7 @@ }, "dependencies": { "@thi.ng/api": "^4.0.3", - "@thi.ng/equiv": "^0.1.3" + "@thi.ng/equiv": "^0.1.4" }, "keywords": [ "array", diff --git a/packages/dot/CHANGELOG.md b/packages/dot/CHANGELOG.md index d566e3cd36..696b358cbc 100644 --- a/packages/dot/CHANGELOG.md +++ b/packages/dot/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.1.7](https://github.com/thi-ng/umbrella/compare/@thi.ng/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) diff --git a/packages/dot/package.json b/packages/dot/package.json index 0419759c6c..b6690e90d6 100644 --- a/packages/dot/package.json +++ b/packages/dot/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/dot", - "version": "0.1.6", + "version": "0.1.7", "description": "Graphviz DOM abstraction as vanilla JS objects & serialization to DOT format", "main": "./index.js", "typings": "./index.d.ts", @@ -25,7 +25,7 @@ }, "dependencies": { "@thi.ng/api": "^4.0.3", - "@thi.ng/checks": "^1.5.3" + "@thi.ng/checks": "^1.5.4" }, "keywords": [ "ES6", @@ -34,4 +34,4 @@ "publishConfig": { "access": "public" } -} \ No newline at end of file +} diff --git a/packages/equiv/CHANGELOG.md b/packages/equiv/CHANGELOG.md index 346bef1e22..a3a508bec9 100644 --- a/packages/equiv/CHANGELOG.md +++ b/packages/equiv/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/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) diff --git a/packages/equiv/package.json b/packages/equiv/package.json index b469119a2c..2d96a8334d 100644 --- a/packages/equiv/package.json +++ b/packages/equiv/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/equiv", - "version": "0.1.3", + "version": "0.1.4", "description": "Extensible deep equivalence checking for any data types", "main": "./index.js", "typings": "./index.d.ts", @@ -24,7 +24,7 @@ "typescript": "^2.8.3" }, "dependencies": { - "@thi.ng/checks": "^1.5.3" + "@thi.ng/checks": "^1.5.4" }, "keywords": [ "deep", diff --git a/packages/hdom-components/CHANGELOG.md b/packages/hdom-components/CHANGELOG.md index a1c1638971..0f060f870f 100644 --- a/packages/hdom-components/CHANGELOG.md +++ b/packages/hdom-components/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [2.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) diff --git a/packages/hdom-components/package.json b/packages/hdom-components/package.json index f66aad1188..62a9f397cd 100644 --- a/packages/hdom-components/package.json +++ b/packages/hdom-components/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/hdom-components", - "version": "2.1.6", + "version": "2.1.7", "description": "Raw, skinnable UI & SVG components for @thi.ng/hdom", "main": "./index.js", "typings": "./index.d.ts", @@ -25,8 +25,8 @@ }, "dependencies": { "@thi.ng/api": "^4.0.3", - "@thi.ng/checks": "^1.5.3", - "@thi.ng/iterators": "^4.1.16", + "@thi.ng/checks": "^1.5.4", + "@thi.ng/iterators": "^4.1.17", "@types/webgl2": "^0.0.3" }, "keywords": [ diff --git a/packages/hdom/CHANGELOG.md b/packages/hdom/CHANGELOG.md index 6f769485ac..fe16c20dff 100644 --- a/packages/hdom/CHANGELOG.md +++ b/packages/hdom/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. + +## [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) diff --git a/packages/hdom/package.json b/packages/hdom/package.json index 33432a6c2e..788cef9be0 100644 --- a/packages/hdom/package.json +++ b/packages/hdom/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/hdom", - "version": "3.0.24", + "version": "3.0.25", "description": "Lightweight vanilla ES6 UI component & virtual DOM system", "main": "./index.js", "typings": "./index.d.ts", @@ -16,7 +16,7 @@ "test": "rm -rf build && tsc -p test && nyc mocha build/test/*.js" }, "devDependencies": { - "@thi.ng/atom": "^1.4.0", + "@thi.ng/atom": "^1.4.1", "@types/mocha": "^5.2.0", "@types/node": "^10.0.6", "mocha": "^5.1.1", @@ -26,11 +26,11 @@ }, "dependencies": { "@thi.ng/api": "^4.0.3", - "@thi.ng/checks": "^1.5.3", - "@thi.ng/diff": "^1.0.17", - "@thi.ng/equiv": "^0.1.3", - "@thi.ng/hiccup": "^2.0.3", - "@thi.ng/iterators": "^4.1.16" + "@thi.ng/checks": "^1.5.4", + "@thi.ng/diff": "^1.0.18", + "@thi.ng/equiv": "^0.1.4", + "@thi.ng/hiccup": "^2.0.4", + "@thi.ng/iterators": "^4.1.17" }, "keywords": [ "browser", diff --git a/packages/hiccup-css/CHANGELOG.md b/packages/hiccup-css/CHANGELOG.md index 0bb397a6c9..bd620fec9e 100644 --- a/packages/hiccup-css/CHANGELOG.md +++ b/packages/hiccup-css/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.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) diff --git a/packages/hiccup-css/package.json b/packages/hiccup-css/package.json index d34a21bcfa..70e33f66a9 100644 --- a/packages/hiccup-css/package.json +++ b/packages/hiccup-css/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/hiccup-css", - "version": "0.2.0", + "version": "0.2.1", "description": "CSS from nested JS data structures", "main": "./index.js", "typings": "./index.d.ts", @@ -25,9 +25,9 @@ }, "dependencies": { "@thi.ng/api": "^4.0.3", - "@thi.ng/checks": "^1.5.3", + "@thi.ng/checks": "^1.5.4", "@thi.ng/errors": "^0.1.3", - "@thi.ng/transducers": "^1.10.2" + "@thi.ng/transducers": "^1.10.3" }, "keywords": [ "clojure", diff --git a/packages/hiccup-svg/CHANGELOG.md b/packages/hiccup-svg/CHANGELOG.md index 3d0b80d373..64cff12bcb 100644 --- a/packages/hiccup-svg/CHANGELOG.md +++ b/packages/hiccup-svg/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [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) diff --git a/packages/hiccup-svg/package.json b/packages/hiccup-svg/package.json index ba5ab1c4fb..001861dd6a 100644 --- a/packages/hiccup-svg/package.json +++ b/packages/hiccup-svg/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/hiccup-svg", - "version": "1.0.3", + "version": "1.0.4", "description": "SVG element functions for @thi.ng/hiccup & @thi.ng/hdom", "main": "./index.js", "typings": "./index.d.ts", @@ -24,7 +24,7 @@ "typescript": "^2.8.3" }, "dependencies": { - "@thi.ng/hiccup": "^2.0.3" + "@thi.ng/hiccup": "^2.0.4" }, "keywords": [ "components", diff --git a/packages/hiccup/CHANGELOG.md b/packages/hiccup/CHANGELOG.md index 2fe8c60dd9..9ca647facc 100644 --- a/packages/hiccup/CHANGELOG.md +++ b/packages/hiccup/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [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) diff --git a/packages/hiccup/package.json b/packages/hiccup/package.json index 8b509efc12..6a9d8238ff 100644 --- a/packages/hiccup/package.json +++ b/packages/hiccup/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/hiccup", - "version": "2.0.3", + "version": "2.0.4", "description": "HTML/SVG/XML serialization of nested data structures, iterables & closures", "main": "./index.js", "typings": "./index.d.ts", @@ -16,7 +16,7 @@ "test": "rm -rf build && tsc -p test && nyc mocha build/test/*.js" }, "devDependencies": { - "@thi.ng/atom": "^1.4.0", + "@thi.ng/atom": "^1.4.1", "@types/mocha": "^5.2.0", "@types/node": "^10.0.6", "mocha": "^5.1.1", @@ -25,7 +25,7 @@ "typescript": "^2.8.3" }, "dependencies": { - "@thi.ng/checks": "^1.5.3", + "@thi.ng/checks": "^1.5.4", "@thi.ng/errors": "^0.1.3" }, "keywords": [ diff --git a/packages/interceptors/CHANGELOG.md b/packages/interceptors/CHANGELOG.md index 167e779fd8..4503b87d3c 100644 --- a/packages/interceptors/CHANGELOG.md +++ b/packages/interceptors/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [1.8.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) diff --git a/packages/interceptors/package.json b/packages/interceptors/package.json index 957d0472c1..de0270d0c4 100644 --- a/packages/interceptors/package.json +++ b/packages/interceptors/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/interceptors", - "version": "1.8.3", + "version": "1.8.4", "description": "Interceptor based event bus, side effect & immutable state handling", "main": "./index.js", "typings": "./index.d.ts", @@ -25,10 +25,10 @@ }, "dependencies": { "@thi.ng/api": "^4.0.3", - "@thi.ng/atom": "^1.4.0", - "@thi.ng/checks": "^1.5.3", + "@thi.ng/atom": "^1.4.1", + "@thi.ng/checks": "^1.5.4", "@thi.ng/errors": "^0.1.3", - "@thi.ng/paths": "^1.3.8" + "@thi.ng/paths": "^1.3.9" }, "keywords": [ "ES6", diff --git a/packages/iterators/CHANGELOG.md b/packages/iterators/CHANGELOG.md index e52f8c08ec..977ac41499 100644 --- a/packages/iterators/CHANGELOG.md +++ b/packages/iterators/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. + +## [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) diff --git a/packages/iterators/package.json b/packages/iterators/package.json index 102904375a..cc797b7291 100644 --- a/packages/iterators/package.json +++ b/packages/iterators/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/iterators", - "version": "4.1.16", + "version": "4.1.17", "description": "clojure.core inspired, composable ES6 iterators & generators", "main": "./index.js", "typings": "./index.d.ts", @@ -25,7 +25,7 @@ }, "dependencies": { "@thi.ng/api": "^4.0.3", - "@thi.ng/dcons": "^1.0.3", + "@thi.ng/dcons": "^1.0.4", "@thi.ng/errors": "^0.1.3" }, "keywords": [ diff --git a/packages/paths/CHANGELOG.md b/packages/paths/CHANGELOG.md index 944a3cdad0..a1bf43a635 100644 --- a/packages/paths/CHANGELOG.md +++ b/packages/paths/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [1.3.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..8c135828ee 100644 --- a/packages/paths/package.json +++ b/packages/paths/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/paths", - "version": "1.3.8", + "version": "1.3.9", "description": "immutable, optimized path-based object property / array accessors", "main": "./index.js", "typings": "./index.d.ts", @@ -24,7 +24,7 @@ "typescript": "^2.8.3" }, "dependencies": { - "@thi.ng/checks": "^1.5.3", + "@thi.ng/checks": "^1.5.4", "@thi.ng/errors": "^0.1.3" }, "keywords": [ diff --git a/packages/pointfree-lang/CHANGELOG.md b/packages/pointfree-lang/CHANGELOG.md index 5bfd77f3df..6c97e1441a 100644 --- a/packages/pointfree-lang/CHANGELOG.md +++ b/packages/pointfree-lang/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.2.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) diff --git a/packages/pointfree-lang/package.json b/packages/pointfree-lang/package.json index e9732d671f..c0b14ff29b 100644 --- a/packages/pointfree-lang/package.json +++ b/packages/pointfree-lang/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/pointfree-lang", - "version": "0.2.13", + "version": "0.2.14", "description": "Forth style syntax layer/compiler for the @thi.ng/pointfree DSL", "main": "./index.js", "typings": "./index.d.ts", @@ -28,7 +28,7 @@ "dependencies": { "@thi.ng/api": "^4.0.3", "@thi.ng/errors": "^0.1.3", - "@thi.ng/pointfree": "^0.8.2" + "@thi.ng/pointfree": "^0.8.3" }, "keywords": [ "concatenative", diff --git a/packages/pointfree/CHANGELOG.md b/packages/pointfree/CHANGELOG.md index 29caa98b1b..d18370bb31 100644 --- a/packages/pointfree/CHANGELOG.md +++ b/packages/pointfree/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.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) diff --git a/packages/pointfree/package.json b/packages/pointfree/package.json index 036e3143dc..5727b6435c 100644 --- a/packages/pointfree/package.json +++ b/packages/pointfree/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/pointfree", - "version": "0.8.2", + "version": "0.8.3", "description": "Pointfree functional composition / Forth style stack execution engine", "main": "./index.js", "typings": "./index.d.ts", @@ -25,8 +25,8 @@ }, "dependencies": { "@thi.ng/api": "^4.0.3", - "@thi.ng/checks": "^1.5.3", - "@thi.ng/equiv": "^0.1.3", + "@thi.ng/checks": "^1.5.4", + "@thi.ng/equiv": "^0.1.4", "@thi.ng/errors": "^0.1.3" }, "keywords": [ diff --git a/packages/resolve-map/CHANGELOG.md b/packages/resolve-map/CHANGELOG.md index bbb46a148b..c3bf8b568a 100644 --- a/packages/resolve-map/CHANGELOG.md +++ b/packages/resolve-map/CHANGELOG.md @@ -3,7 +3,15 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. - + +## [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) @@ -20,7 +28,7 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline - + ## [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) diff --git a/packages/resolve-map/package.json b/packages/resolve-map/package.json index 8c2b53e663..7d81bf1d2a 100644 --- a/packages/resolve-map/package.json +++ b/packages/resolve-map/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/resolve-map", - "version": "3.0.0", + "version": "3.0.1", "description": "DAG resolution of vanilla objects & arrays with internally linked values", "main": "./index.js", "typings": "./index.d.ts", @@ -22,9 +22,9 @@ "typescript": "^2.8.3" }, "dependencies": { - "@thi.ng/checks": "^1.5.3", + "@thi.ng/checks": "^1.5.4", "@thi.ng/errors": "^0.1.3", - "@thi.ng/paths": "^1.3.8" + "@thi.ng/paths": "^1.3.9" }, "keywords": [ "configuration", diff --git a/packages/router/CHANGELOG.md b/packages/router/CHANGELOG.md index d1d124722e..1f514d90f7 100644 --- a/packages/router/CHANGELOG.md +++ b/packages/router/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.1.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) diff --git a/packages/router/package.json b/packages/router/package.json index 24c930fcae..6b09daadad 100644 --- a/packages/router/package.json +++ b/packages/router/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/router", - "version": "0.1.16", + "version": "0.1.17", "description": "Generic router for browser & non-browser based applications", "main": "./index.js", "typings": "./index.d.ts", @@ -24,8 +24,8 @@ }, "dependencies": { "@thi.ng/api": "^4.0.3", - "@thi.ng/checks": "^1.5.3", - "@thi.ng/equiv": "^0.1.3", + "@thi.ng/checks": "^1.5.4", + "@thi.ng/equiv": "^0.1.4", "@thi.ng/errors": "^0.1.3" }, "keywords": [ diff --git a/packages/rstream-csp/CHANGELOG.md b/packages/rstream-csp/CHANGELOG.md index 9c9561b3ac..db903e4ad2 100644 --- a/packages/rstream-csp/CHANGELOG.md +++ b/packages/rstream-csp/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.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) diff --git a/packages/rstream-csp/package.json b/packages/rstream-csp/package.json index e6a889edb9..141a2a6cb8 100644 --- a/packages/rstream-csp/package.json +++ b/packages/rstream-csp/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rstream-csp", - "version": "0.1.75", + "version": "0.1.76", "description": "@thi.ng/csp bridge module for @thi.ng/rstream", "main": "./index.js", "typings": "./index.d.ts", @@ -24,8 +24,8 @@ "typescript": "^2.8.3" }, "dependencies": { - "@thi.ng/csp": "^0.3.42", - "@thi.ng/rstream": "^1.7.1" + "@thi.ng/csp": "^0.3.43", + "@thi.ng/rstream": "^1.7.2" }, "keywords": [ "bridge", diff --git a/packages/rstream-dot/CHANGELOG.md b/packages/rstream-dot/CHANGELOG.md index 047b057fab..24f68764f5 100644 --- a/packages/rstream-dot/CHANGELOG.md +++ b/packages/rstream-dot/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.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) diff --git a/packages/rstream-dot/package.json b/packages/rstream-dot/package.json index 01ff2ccf5a..ff2c8a7e30 100644 --- a/packages/rstream-dot/package.json +++ b/packages/rstream-dot/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rstream-dot", - "version": "0.2.14", + "version": "0.2.15", "description": "Graphviz DOT conversion of @thi.ng/rstream dataflow graph topologies", "main": "./index.js", "typings": "./index.d.ts", @@ -24,7 +24,7 @@ "typescript": "^2.8.3" }, "dependencies": { - "@thi.ng/rstream": "^1.7.1" + "@thi.ng/rstream": "^1.7.2" }, "keywords": [ "conversion", diff --git a/packages/rstream-gestures/CHANGELOG.md b/packages/rstream-gestures/CHANGELOG.md index b0ffd7577b..cec0cff08f 100644 --- a/packages/rstream-gestures/CHANGELOG.md +++ b/packages/rstream-gestures/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.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) diff --git a/packages/rstream-gestures/package.json b/packages/rstream-gestures/package.json index cae891fdd0..39b6260ca4 100644 --- a/packages/rstream-gestures/package.json +++ b/packages/rstream-gestures/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rstream-gestures", - "version": "0.3.11", + "version": "0.3.12", "description": "Unified mouse, mouse wheel & single-touch event stream abstraction", "main": "./index.js", "typings": "./index.d.ts", @@ -25,8 +25,8 @@ }, "dependencies": { "@thi.ng/api": "^4.0.3", - "@thi.ng/rstream": "^1.7.1", - "@thi.ng/transducers": "^1.10.2" + "@thi.ng/rstream": "^1.7.2", + "@thi.ng/transducers": "^1.10.3" }, "keywords": [ "dataflow", diff --git a/packages/rstream-graph/CHANGELOG.md b/packages/rstream-graph/CHANGELOG.md index 9fea6418f2..2e33832cce 100644 --- a/packages/rstream-graph/CHANGELOG.md +++ b/packages/rstream-graph/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [2.0.2](https://github.com/thi-ng/umbrella/compare/@thi.ng/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) diff --git a/packages/rstream-graph/package.json b/packages/rstream-graph/package.json index 7db6960785..e54549aad2 100644 --- a/packages/rstream-graph/package.json +++ b/packages/rstream-graph/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rstream-graph", - "version": "2.0.1", + "version": "2.0.2", "description": "Declarative dataflow graph construction for @thi.ng/rstream", "main": "./index.js", "typings": "./index.d.ts", @@ -25,12 +25,12 @@ }, "dependencies": { "@thi.ng/api": "^4.0.3", - "@thi.ng/checks": "^1.5.3", + "@thi.ng/checks": "^1.5.4", "@thi.ng/errors": "^0.1.3", - "@thi.ng/paths": "^1.3.8", - "@thi.ng/resolve-map": "^3.0.0", - "@thi.ng/rstream": "^1.7.1", - "@thi.ng/transducers": "^1.10.2" + "@thi.ng/paths": "^1.3.9", + "@thi.ng/resolve-map": "^3.0.1", + "@thi.ng/rstream": "^1.7.2", + "@thi.ng/transducers": "^1.10.3" }, "keywords": [ "compute", diff --git a/packages/rstream-log/CHANGELOG.md b/packages/rstream-log/CHANGELOG.md index 7468d403b4..3320cd0b1f 100644 --- a/packages/rstream-log/CHANGELOG.md +++ b/packages/rstream-log/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [1.0.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) diff --git a/packages/rstream-log/package.json b/packages/rstream-log/package.json index f416432589..022928d7c4 100644 --- a/packages/rstream-log/package.json +++ b/packages/rstream-log/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rstream-log", - "version": "1.0.26", + "version": "1.0.27", "description": "Structured, multilevel & hierarchical loggers based on @thi.ng/rstream", "main": "./index.js", "typings": "./index.d.ts", @@ -25,10 +25,10 @@ }, "dependencies": { "@thi.ng/api": "^4.0.3", - "@thi.ng/checks": "^1.5.3", + "@thi.ng/checks": "^1.5.4", "@thi.ng/errors": "^0.1.3", - "@thi.ng/rstream": "^1.7.1", - "@thi.ng/transducers": "^1.10.2" + "@thi.ng/rstream": "^1.7.2", + "@thi.ng/transducers": "^1.10.3" }, "keywords": [ "ES6", diff --git a/packages/rstream-query/CHANGELOG.md b/packages/rstream-query/CHANGELOG.md index 6acb051833..095f98c465 100644 --- a/packages/rstream-query/CHANGELOG.md +++ b/packages/rstream-query/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.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) diff --git a/packages/rstream-query/package.json b/packages/rstream-query/package.json index 42e2f84186..18869bc531 100644 --- a/packages/rstream-query/package.json +++ b/packages/rstream-query/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rstream-query", - "version": "0.3.13", + "version": "0.3.14", "description": "@thi.ng/rstream based triple store & reactive query engine", "main": "./index.js", "typings": "./index.d.ts", @@ -25,13 +25,13 @@ }, "dependencies": { "@thi.ng/api": "^4.0.3", - "@thi.ng/associative": "^0.5.6", - "@thi.ng/checks": "^1.5.3", - "@thi.ng/equiv": "^0.1.3", + "@thi.ng/associative": "^0.5.7", + "@thi.ng/checks": "^1.5.4", + "@thi.ng/equiv": "^0.1.4", "@thi.ng/errors": "^0.1.3", - "@thi.ng/rstream": "^1.7.1", - "@thi.ng/rstream-dot": "^0.2.14", - "@thi.ng/transducers": "^1.10.2" + "@thi.ng/rstream": "^1.7.2", + "@thi.ng/rstream-dot": "^0.2.15", + "@thi.ng/transducers": "^1.10.3" }, "keywords": [ "dataflow", diff --git a/packages/rstream/CHANGELOG.md b/packages/rstream/CHANGELOG.md index dbdef8e3be..1fa0e19d7f 100644 --- a/packages/rstream/CHANGELOG.md +++ b/packages/rstream/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [1.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) diff --git a/packages/rstream/package.json b/packages/rstream/package.json index c491c4390b..bd86f94a18 100644 --- a/packages/rstream/package.json +++ b/packages/rstream/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/rstream", - "version": "1.7.1", + "version": "1.7.2", "description": "Reactive multi-tap streams, dataflow & transformation pipeline constructs", "main": "./index.js", "typings": "./index.d.ts", @@ -25,12 +25,12 @@ }, "dependencies": { "@thi.ng/api": "^4.0.3", - "@thi.ng/associative": "^0.5.6", - "@thi.ng/atom": "^1.4.0", - "@thi.ng/checks": "^1.5.3", + "@thi.ng/associative": "^0.5.7", + "@thi.ng/atom": "^1.4.1", + "@thi.ng/checks": "^1.5.4", "@thi.ng/errors": "^0.1.3", - "@thi.ng/paths": "^1.3.8", - "@thi.ng/transducers": "^1.10.2" + "@thi.ng/paths": "^1.3.9", + "@thi.ng/transducers": "^1.10.3" }, "keywords": [ "datastructure", diff --git a/packages/sax/CHANGELOG.md b/packages/sax/CHANGELOG.md new file mode 100644 index 0000000000..15657bc197 --- /dev/null +++ b/packages/sax/CHANGELOG.md @@ -0,0 +1,16 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + + +# 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/package.json b/packages/sax/package.json index 65cf93eb2d..fb7f176a27 100644 --- a/packages/sax/package.json +++ b/packages/sax/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/sax", - "version": "0.0.1", + "version": "0.1.0", "description": "Transducer-based, SAX-like, non-validating, speedy & tiny XML parser", "main": "./index.js", "typings": "./index.d.ts", @@ -25,8 +25,8 @@ }, "dependencies": { "@thi.ng/api": "^4.0.3", - "@thi.ng/transducers": "^1.10.2", - "@thi.ng/transducers-fsm": "^0.0.1" + "@thi.ng/transducers": "^1.10.3", + "@thi.ng/transducers-fsm": "^0.1.0" }, "keywords": [ "ES6", @@ -40,4 +40,4 @@ "publishConfig": { "access": "public" } -} \ No newline at end of file +} diff --git a/packages/transducers-fsm/CHANGELOG.md b/packages/transducers-fsm/CHANGELOG.md new file mode 100644 index 0000000000..4c6bec89fc --- /dev/null +++ b/packages/transducers-fsm/CHANGELOG.md @@ -0,0 +1,12 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + + +# 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/package.json b/packages/transducers-fsm/package.json index 7e89fb9c8c..6d0a54cb75 100644 --- a/packages/transducers-fsm/package.json +++ b/packages/transducers-fsm/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/transducers-fsm", - "version": "0.0.1", + "version": "0.1.0", "description": "Transducer-based Finite State Machine transformer", "main": "./index.js", "typings": "./index.d.ts", @@ -25,7 +25,7 @@ }, "dependencies": { "@thi.ng/api": "^4.0.3", - "@thi.ng/transducers": "^1.10.2" + "@thi.ng/transducers": "^1.10.3" }, "keywords": [ "ES6", @@ -34,4 +34,4 @@ "publishConfig": { "access": "public" } -} \ No newline at end of file +} diff --git a/packages/transducers/CHANGELOG.md b/packages/transducers/CHANGELOG.md index 61cdcea306..5bda6cc0a3 100644 --- a/packages/transducers/CHANGELOG.md +++ b/packages/transducers/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [1.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) diff --git a/packages/transducers/package.json b/packages/transducers/package.json index 5d9f080121..06c7529152 100644 --- a/packages/transducers/package.json +++ b/packages/transducers/package.json @@ -1,6 +1,6 @@ { "name": "@thi.ng/transducers", - "version": "1.10.2", + "version": "1.10.3", "description": "Lightweight transducer implementations for ES6 / TypeScript", "main": "./index.js", "typings": "./index.d.ts", @@ -25,9 +25,9 @@ }, "dependencies": { "@thi.ng/api": "^4.0.3", - "@thi.ng/checks": "^1.5.3", + "@thi.ng/checks": "^1.5.4", "@thi.ng/compare": "^0.1.3", - "@thi.ng/equiv": "^0.1.3", + "@thi.ng/equiv": "^0.1.4", "@thi.ng/errors": "^0.1.3" }, "keywords": [