├── .c8rc.json ├── .gitignore ├── CHANGELOG.md ├── eslint.config.cjs ├── examples ├── cycle.ts ├── debugging.ts ├── delay.ts ├── event-emitter.ts ├── index.ts ├── race.ts ├── reduce.ts ├── set-interval.ts └── tsconfig.json ├── gulpfile.js ├── index.d.ts ├── index.js ├── lib ├── Bud.d.ts ├── Bud.js ├── End.d.ts ├── End.js ├── Fin.d.ts ├── Fin.js ├── Many.d.ts ├── Many.js ├── Noop.d.ts ├── Noop.js ├── Nothing.d.ts ├── Nothing.js ├── Same.d.ts ├── Same.js ├── _ │ ├── Bud │ │ ├── Effects.js │ │ ├── debug.js │ │ ├── emit.js │ │ ├── inspect.browser.js │ │ ├── inspect.js │ │ ├── map.js │ │ ├── sample.js │ │ └── thru.js │ ├── Counter.js │ └── domain │ │ ├── Affected.js │ │ ├── Computation.js │ │ ├── Domain.js │ │ ├── Queue.js │ │ ├── index.js │ │ ├── order.js │ │ └── propagate.js ├── asap.d.ts ├── asap.js ├── capture.d.ts ├── capture.js ├── concat.d.ts ├── concat.js ├── drain.d.ts ├── drain.js ├── join.d.ts ├── join.js ├── merge.d.ts ├── merge.js ├── resource.d.ts ├── resource.js ├── tap.d.ts ├── tap.js ├── transfer.d.ts ├── transfer.js ├── turnoff.d.ts └── turnoff.js ├── license.txt ├── map ├── filter-by.d.ts ├── filter-by.js ├── filter.d.ts ├── filter.js ├── reduce.d.ts ├── reduce.js ├── uniq.d.ts ├── uniq.js ├── when.d.ts └── when.js ├── package.json ├── perf ├── .eslintrc.js ├── fluh.perf.js ├── flyd.perf.js ├── leak.js ├── most.perf.js ├── perf.js └── rxjs.perf.js ├── readme.md ├── test ├── .eslintrc.js ├── Bud.test.js ├── End.test.js ├── Many.test.js ├── cache.test.js ├── capture.test.js ├── debug.test.js ├── delay-defer-raf.test.js ├── delta.test.js ├── effects.test.js ├── filter.test.js ├── globals.js ├── index.test.js ├── join.test.js ├── linear.test.js ├── map.test.js ├── merge.test.js ├── parallel.test.js ├── play.js ├── promise.test.js ├── reduce.test.js ├── resource.test.js ├── tap.test.js ├── thru.test.js ├── triangle.test.js ├── turnoff.test.js ├── type │ ├── filter.test.ts │ ├── index.d.ts │ ├── join.test.ts │ ├── map.test.ts │ ├── merge.test.ts │ ├── reduce.test.ts │ └── tsconfig.json ├── uniq.test.js └── when.test.js ├── thru ├── defer.d.ts ├── defer.js ├── delay.d.ts ├── delay.js ├── delta.d.ts ├── delta.js ├── promise.d.ts ├── promise.js ├── raf.d.ts └── raf.js └── tsconfig.json /.c8rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "reporter": [ "text", "html" ], 3 | "all": true, 4 | "exclude": 5 | [ 6 | "lib/inspect.browser.js", 7 | "test/", 8 | "perf/", 9 | "coverage/", 10 | "release/", 11 | ".eslintrc.js", 12 | "gulpfile.js", 13 | "start.js" 14 | ] 15 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | node_modules/ 3 | release/ 4 | coverage/ 5 | test/graph.dot 6 | test/graph.svg 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## `1.0.0` — 2022-01-15 2 | * Added TypeScript definitions. 3 | * Added `reduce`. 4 | 5 | ## `0.6.0` — 2021-12-10 6 | * **Breaking change**: `filter_by` is moved to its own module `map/filter-by`. 7 | * Added `merge`. 8 | * Added `debug`. 9 | * `transfer` is now in index. 10 | * Added `delta`. 11 | * Added `promise`: `every`, `last` and `buffered` strategies. 12 | * Added `raf`. 13 | * `resource` is now in index. 14 | 15 | ## `0.5.0` — 2020-05-13 16 | * **Breaking change**: `on` now returns disposer, not the bud itself. 17 | * Added `uniq`. 18 | 19 | ## `0.4.0` — 2020-02-08 20 | * Added `sample`. 21 | * Added `filter_by`. 22 | * Added `turnoff`. 23 | * Added `resource`. 24 | 25 | ## `0.3.0` — 2019-12-26 26 | * **Breaking change**: `when_data_all` now handles errors like `Promise.all`. 27 | * Fixed not being possible to pass `undefined` as value to Bud, while readme allows that. 28 | * Better cleanup on `End` to allow gc at early stages. 29 | * Added `when_error`. 30 | * Added `tap`. 31 | 32 | ## `0.2.0` — 2019-11-30 33 | * **Breaking change**: now all effects run after all propagations, which means effects see all tip values in consistent state. Previously, effects run before propagating data further. 34 | 35 | ## `0.1.0` — 2019-11-25 36 | * Initial release, added basic features: `Bud`, `join`, special values `Nothing`, `Many`, `End`. 37 | * Atomicity. 38 | * `map`, `filter` and `when`. 39 | * `thru` and two high-order primitives: `defer` and `delay`. 40 | * Cache `order` for static graph. 41 | * Utilities: `Noop`, `Same`, `Fin`, `concat`, `drain`, `capture`. 42 | -------------------------------------------------------------------------------- /eslint.config.cjs: -------------------------------------------------------------------------------- 1 | 2 | var outlander = require('outlander/typescript') 3 | var globals = require('outlander/globals') 4 | 5 | 6 | module.exports = 7 | [ 8 | ...outlander, 9 | { 10 | languageOptions: 11 | { 12 | sourceType: 'module', 13 | globals: 14 | { 15 | ...globals.node, 16 | } 17 | } 18 | }, 19 | { 20 | rules: 21 | { 22 | }, 23 | }, 24 | { 25 | files: [ '**/*.js', '**/*.ts' ], 26 | }, 27 | { 28 | ignores: 29 | [ 30 | 'release/', 31 | 'examples/', 32 | 'test/', 33 | ], 34 | }, 35 | { 36 | files: [ 'test/**/*.js' ], 37 | languageOptions: 38 | { 39 | globals: 40 | { 41 | ...globals.mocha, 42 | ...globals.chai, 43 | spy: true, 44 | } 45 | } 46 | } 47 | ] 48 | -------------------------------------------------------------------------------- /examples/cycle.ts: -------------------------------------------------------------------------------- 1 | console.info('example: cycle') 2 | 3 | import { Bud } from 'fluh' 4 | import filter from 'fluh/map/filter' 5 | 6 | const init = Bud() 7 | const next = init.map(n => n + 1) 8 | 9 | /* it is possible to have a cycle, but exit condition should also present */ 10 | next.map(filter(n => n < 10)).on(init.emit) 11 | 12 | next.on(n => console.log('next', n)) 13 | 14 | /* start the cycle */ 15 | init.emit(0) 16 | -------------------------------------------------------------------------------- /examples/debugging.ts: -------------------------------------------------------------------------------- 1 | console.info('example: debugging') 2 | 3 | import { Bud } from 'fluh' 4 | import { join } from 'fluh' 5 | import { when_data_all } from 'fluh/map/when' 6 | import { End } from 'fluh' 7 | 8 | function identity (x: T) { 9 | return x 10 | } 11 | 12 | const source = Bud() 13 | const a = source.map(identity) 14 | const b = source.map(identity) 15 | const drain = join(a, b, when_data_all((a, b) => [a, b])) 16 | 17 | source.debug('source') 18 | a.debug() 19 | b.debug() 20 | drain.debug('drain') 21 | 22 | source 23 | .emit('Foo') 24 | .emit('Bar') 25 | .emit(End) 26 | -------------------------------------------------------------------------------- /examples/delay.ts: -------------------------------------------------------------------------------- 1 | console.info('example: delay') 2 | 3 | import { transfer } from 'fluh' 4 | import { Bud } from 'fluh' 5 | import { End } from 'fluh' 6 | 7 | import { Bud as T_Bud } from 'fluh/lib/Bud' 8 | 9 | const source = Bud() 10 | 11 | source 12 | .on((x) => console.log('sync', x)) 13 | 14 | source 15 | .thru(delay(16)) /* thru: (fn: (Bud) => Bud): Bud, may introduce async */ 16 | .on((x) => console.log('async', x)) 17 | 18 | source 19 | .emit(1) 20 | .emit(2) 21 | .emit(3) 22 | .emit(4) 23 | .emit(5) 24 | .emit(End) 25 | 26 | function delay (ms = 0) { 27 | return (bud_source: T_Bud) => { 28 | return transfer(bud_source, (value, emit) => { 29 | setTimeout(() => { 30 | console.info(`bring ${ms}ms delay; graph becomes async`) 31 | emit(value) 32 | }, ms) 33 | }) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/event-emitter.ts: -------------------------------------------------------------------------------- 1 | console.info('example: event-emitter') 2 | 3 | import { EventEmitter } from 'events' 4 | 5 | import { resource } from 'fluh' 6 | import { when_data } from 'fluh/map/when' 7 | import { End } from 'fluh' 8 | 9 | const emitter = new EventEmitter 10 | const source = fromEvent<{ x: number }>(emitter, 'click') /* source from EventEmitter */ 11 | 12 | source 13 | .on(console.log) 14 | 15 | source 16 | .on(when_data((e) => { if (e.x == 5) source.emit(End) })) 17 | 18 | emitter.emit('click', { x: 1 }) 19 | emitter.emit('click', { x: 2 }) 20 | emitter.emit('click', { x: 3 }) 21 | emitter.emit('click', { x: 4 }) 22 | emitter.emit('click', { x: 5 }) 23 | 24 | console.info('listeners:', emitter.listeners('click')) 25 | 26 | function fromEvent (emitter: EventEmitter, eventName: string) { 27 | return resource((emit) => { 28 | console.info('resource initialized') 29 | 30 | emitter.addListener(eventName, emit) 31 | 32 | return function disposer () { 33 | if (!emitter) return 34 | 35 | emitter.removeListener(eventName, emit) 36 | 37 | // @ts-ignore 38 | emitter = null 39 | // @ts-ignore 40 | emit = null 41 | 42 | console.info('resource received End and got disposed') 43 | } 44 | }) 45 | } 46 | -------------------------------------------------------------------------------- /examples/index.ts: -------------------------------------------------------------------------------- 1 | 2 | import './cycle' 3 | import './debugging' 4 | import './delay' 5 | import './event-emitter' 6 | import './race' 7 | import './reduce' 8 | import './set-interval' 9 | -------------------------------------------------------------------------------- /examples/race.ts: -------------------------------------------------------------------------------- 1 | console.info('example: race') 2 | 3 | import { Bud } from 'fluh' 4 | import { Bud as T_Bud } from 'fluh/lib/Bud' 5 | 6 | import transfer from 'fluh/lib/transfer' 7 | 8 | import { every } from 'fluh/thru/promise' 9 | import { last } from 'fluh/thru/promise' 10 | import { buffered } from 'fluh/thru/promise' 11 | 12 | function delay (bud: T_Bud) { 13 | return transfer(bud, (value, emit) => { 14 | const timeout = (10 - value) 15 | emit(new Promise(rs => { 16 | setTimeout(() => rs(value), (timeout * 25)) 17 | })) 18 | }) 19 | } 20 | 21 | const source = Bud() 22 | const delayed = source.thru(delay) 23 | 24 | const delayed_every = delayed.thru(every) 25 | const delayed_last = delayed.thru(last) 26 | const delayed_buffered = delayed.thru(buffered(3)) 27 | 28 | delayed_every.on(n => console.log(' EVERY', n)) 29 | delayed_last.on(n => console.log(' LAST', n)) 30 | delayed_buffered.on(n => console.log('BUFFERED', n)) 31 | 32 | source 33 | .emit(1) 34 | .emit(2) 35 | .emit(3) 36 | .emit(4) 37 | .emit(5) 38 | .emit(6) 39 | .emit(7) 40 | .emit(8) 41 | .emit(9) 42 | -------------------------------------------------------------------------------- /examples/reduce.ts: -------------------------------------------------------------------------------- 1 | console.info('example: reduce') 2 | 3 | import { when_data } from 'fluh/map/when' 4 | import reduce from 'fluh/map/reduce' 5 | import { Bud } from 'fluh' 6 | import { End } from 'fluh' 7 | 8 | const source = Bud() 9 | 10 | source 11 | .map(sum()) /* essentially works like a reduce */ 12 | .on(console.log) 13 | 14 | source 15 | .map(when_data(reduce(0, (a: number, b: number) => a + b))) /* explicit reduce */ 16 | .on((x) => console.log(x, '\n')) 17 | 18 | source 19 | .emit(1) 20 | .emit(2) 21 | .emit(3) 22 | .emit(4) 23 | .emit(5) 24 | .emit(End) 25 | 26 | function sum () { 27 | let total = 0 28 | return when_data((next: number) => { 29 | total += next 30 | return total 31 | }) 32 | } 33 | 34 | -------------------------------------------------------------------------------- /examples/set-interval.ts: -------------------------------------------------------------------------------- 1 | console.info('example: set-interval') 2 | 3 | import { resource } from 'fluh' 4 | import { when_data } from 'fluh/map/when' 5 | import { End } from 'fluh' 6 | 7 | const source = interval(50) 8 | 9 | source 10 | .map(when_data((x) => x * x - 1)) 11 | .on(console.log) 12 | 13 | source 14 | .on((x) => { if (x == 5) source.emit(End) }) 15 | 16 | function interval (ms: number) { 17 | return resource((emit) => { 18 | console.info('resource initialized') 19 | 20 | let next = 0 21 | let t = setInterval(() => { 22 | next++ 23 | emit(next) 24 | }, ms) 25 | 26 | return function disposer () { 27 | if (!t) return 28 | 29 | clearInterval(t) 30 | 31 | // @ts-ignore 32 | t = null 33 | // @ts-ignore 34 | emit = null 35 | 36 | console.info('resource received End and got disposed') 37 | } 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /examples/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": 3 | { 4 | "noEmit": true, 5 | "pretty": true, 6 | 7 | "strict": true, 8 | "target": "ES6", 9 | "lib": [ "ES6" ], 10 | 11 | "moduleResolution": "node" 12 | } 13 | } -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports.default = require('metalpipe/prefab')('library', require('gulp')) 3 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | 2 | export { default as Bud } from './lib/Bud' 3 | 4 | export { default as Nothing } from './lib/Nothing' 5 | export { default as Many } from './lib/Many' 6 | export { default as End } from './lib/End' 7 | 8 | export { default as Noop } from './lib/Noop' 9 | export { default as Same } from './lib/Same' 10 | export { default as Fin } from './lib/Fin' 11 | 12 | export { default as join } from './lib/join' 13 | export { default as merge } from './lib/merge' 14 | export { default as transfer } from './lib/transfer' 15 | export { default as turnoff } from './lib/turnoff' 16 | export { default as resource } from './lib/resource' 17 | 18 | export { default as asap } from './lib/asap' 19 | export { default as capture } from './lib/capture' 20 | export { default as concat } from './lib/concat' 21 | export { default as drain } from './lib/drain' 22 | export { default as tap } from './lib/tap' 23 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | export { default as Bud } from './lib/Bud' 3 | 4 | export { default as Nothing } from './lib/Nothing' 5 | export { default as Many } from './lib/Many' 6 | export { default as End } from './lib/End' 7 | 8 | export { default as Noop } from './lib/Noop' 9 | export { default as Same } from './lib/Same' 10 | export { default as Fin } from './lib/Fin' 11 | 12 | export { default as join } from './lib/join' 13 | export { default as merge } from './lib/merge' 14 | export { default as transfer } from './lib/transfer' 15 | export { default as turnoff } from './lib/turnoff' 16 | export { default as resource } from './lib/resource' 17 | 18 | export { default as asap } from './lib/asap' 19 | export { default as capture } from './lib/capture' 20 | export { default as concat } from './lib/concat' 21 | export { default as drain } from './lib/drain' 22 | export { default as tap } from './lib/tap' 23 | -------------------------------------------------------------------------------- /lib/Bud.d.ts: -------------------------------------------------------------------------------- 1 | 2 | import { InspectOptions } from 'util' 3 | 4 | import { Nothing } from './Nothing' 5 | import { Many } from './Many' 6 | 7 | 8 | export type Disposer = () => void 9 | 10 | export type OptionsDebug = InspectOptions & 11 | { 12 | label: string, 13 | } 14 | 15 | export type Product = (T | Many | Nothing) 16 | export type Producer = (value: T) => Product 17 | export type Transformer = (bud: Bud) => Bud 18 | 19 | export type Bud = 20 | { 21 | id: string, 22 | value: T_Init, 23 | sample (): T_Init, 24 | 25 | constructor: typeof Bud, 26 | 27 | emit (value: Product): Bud, 28 | on (fn: (value: T) => void): Disposer, 29 | map (fn: Producer): Bud, 30 | thru (fn: Transformer): Bud, 31 | 32 | debug (label: string, options?: InspectOptions): Disposer, 33 | debug (options?: OptionsDebug): Disposer, 34 | } 35 | 36 | declare const Bud: 37 | { 38 | (): Bud, 39 | (value: T): Bud, 40 | is (it: any): it is Bud, 41 | } 42 | 43 | export default Bud 44 | -------------------------------------------------------------------------------- /lib/Bud.js: -------------------------------------------------------------------------------- 1 | 2 | import def from 'def-prop' 3 | import val from 'def-prop/val' 4 | 5 | import Nothing from './Nothing' 6 | 7 | import domain from './_/domain/index.js' 8 | 9 | import sample from './_/Bud/sample' 10 | import emit from './_/Bud/emit' 11 | import Effects from './_/Bud/Effects' 12 | import map from './_/Bud/map' 13 | import thru from './_/Bud/thru' 14 | import debug from './_/Bud/debug' 15 | 16 | var kind = Symbol('Bud') 17 | 18 | 19 | export default function Bud (value) 20 | { 21 | var bud = 22 | { 23 | id: ('#' + domain.id++), 24 | value: Nothing, 25 | } 26 | 27 | def(bud, kind, val(Bud)) 28 | def(bud, 'constructor', val(Bud)) 29 | 30 | def(bud, 'sample', val(sample(bud))) 31 | 32 | def(bud, 'emit', val(emit(bud))) 33 | def(bud, 'on', val(Effects(bud))) 34 | def(bud, 'map', val(map(bud))) 35 | def(bud, 'thru', val(thru(bud))) 36 | 37 | def(bud, 'debug', val(debug(bud))) 38 | 39 | if (arguments.length) 40 | { 41 | bud.emit(value) 42 | } 43 | 44 | return bud 45 | } 46 | 47 | Bud.is = (value) => 48 | { 49 | return (kind in Object(value)) 50 | } 51 | -------------------------------------------------------------------------------- /lib/End.d.ts: -------------------------------------------------------------------------------- 1 | 2 | declare const End: unique symbol 3 | 4 | export default End 5 | 6 | export type End = typeof End 7 | -------------------------------------------------------------------------------- /lib/End.js: -------------------------------------------------------------------------------- 1 | 2 | export default Symbol('End') 3 | -------------------------------------------------------------------------------- /lib/Fin.d.ts: -------------------------------------------------------------------------------- 1 | 2 | import { End } from './End' 3 | 4 | export default function (): End 5 | -------------------------------------------------------------------------------- /lib/Fin.js: -------------------------------------------------------------------------------- 1 | 2 | import End from './End' 3 | 4 | 5 | export default function Fin () 6 | { 7 | return End 8 | } 9 | -------------------------------------------------------------------------------- /lib/Many.d.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Nothing } from './Nothing' 3 | 4 | export type Many = T[] & { readonly many: unique symbol } 5 | 6 | declare const Many: 7 | { 8 | (...args: (T | Nothing)[]): Many, 9 | last (value: T | Many): T, 10 | is (it: any): it is Many, 11 | } 12 | 13 | export default Many 14 | -------------------------------------------------------------------------------- /lib/Many.js: -------------------------------------------------------------------------------- 1 | 2 | import def from 'def-prop' 3 | import val from 'def-prop/val' 4 | 5 | var kind = Symbol('Many') 6 | 7 | 8 | export default function Many (...many) 9 | { 10 | def(many, kind, val(Many)) 11 | def(many, 'constructor', val(Many)) 12 | 13 | return many 14 | } 15 | 16 | Many.is = (it) => 17 | { 18 | return (kind in Object(it)) 19 | } 20 | -------------------------------------------------------------------------------- /lib/Noop.d.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Nothing } from './Nothing' 3 | 4 | export default function (): Nothing 5 | -------------------------------------------------------------------------------- /lib/Noop.js: -------------------------------------------------------------------------------- 1 | 2 | import Nothing from './Nothing' 3 | 4 | 5 | export default function Noop () 6 | { 7 | return Nothing 8 | } 9 | -------------------------------------------------------------------------------- /lib/Nothing.d.ts: -------------------------------------------------------------------------------- 1 | 2 | declare const Nothing: unique symbol 3 | 4 | export default Nothing 5 | 6 | export type Nothing = typeof Nothing 7 | -------------------------------------------------------------------------------- /lib/Nothing.js: -------------------------------------------------------------------------------- 1 | 2 | export default Symbol('Nothing') 3 | -------------------------------------------------------------------------------- /lib/Same.d.ts: -------------------------------------------------------------------------------- 1 | 2 | export default function (value: T): T 3 | -------------------------------------------------------------------------------- /lib/Same.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | export default function Same (value) 4 | { 5 | return value 6 | } 7 | -------------------------------------------------------------------------------- /lib/_/Bud/Effects.js: -------------------------------------------------------------------------------- 1 | /* eslint complexity: [ 2, 6 ] */ 2 | 3 | import def from 'def-prop' 4 | import val from 'def-prop/val' 5 | 6 | import Nothing from '../../Nothing' 7 | import End from '../../End' 8 | 9 | 10 | export default function Effects (bud) 11 | { 12 | var fns = null 13 | 14 | function on (fn) 15 | { 16 | var value = bud.value 17 | 18 | if (value !== Nothing) 19 | { 20 | fn(value) 21 | } 22 | 23 | if (value === End) 24 | { 25 | return disposer_noop() 26 | } 27 | 28 | if (! fns) 29 | { 30 | fns = fn 31 | } 32 | else if (typeof fns === 'function') 33 | { 34 | fns = [ fns, fn ] 35 | } 36 | else 37 | { 38 | fns.push(fn) 39 | } 40 | 41 | on.present = true 42 | 43 | return disposer(fn) 44 | } 45 | 46 | function disposer (fn) 47 | { 48 | return function ds () 49 | { 50 | if (! fn) { return } 51 | 52 | if (fns === fn) 53 | { 54 | fns = null 55 | on.present = false 56 | } 57 | else if (fns) 58 | { 59 | var index = fns.indexOf(fn) 60 | fns = fns.filter((_, fn_index) => (fn_index !== index)) 61 | 62 | if (fns.length === 1) 63 | { 64 | fns = fns[0] 65 | } 66 | } 67 | 68 | fn = null 69 | } 70 | } 71 | 72 | def(on, 'present', val(false, ':write')) 73 | 74 | def(on, 'emit', val(function emit () 75 | { 76 | // if (! fns) { return } 77 | 78 | var value = bud.value 79 | 80 | if (typeof fns === 'function') 81 | { 82 | fns(value) 83 | } 84 | else 85 | { 86 | var fnss = fns 87 | var i = 0 88 | var L = fnss.length 89 | 90 | for (; (i < L); i++) 91 | { 92 | var fn = fnss[i] 93 | 94 | fn(value) 95 | } 96 | } 97 | 98 | if (value === End) 99 | { 100 | fns = null 101 | } 102 | })) 103 | 104 | return on 105 | } 106 | 107 | 108 | function disposer_noop () 109 | { 110 | return function ds () {} 111 | } 112 | -------------------------------------------------------------------------------- /lib/_/Bud/debug.js: -------------------------------------------------------------------------------- 1 | 2 | import inspect from './inspect' 3 | 4 | 5 | export default function (bud) 6 | { 7 | return function debug (label, options) 8 | { 9 | if (arguments.length === 1) 10 | { 11 | if (typeof label === 'object') 12 | { 13 | ({ label, ...options } = label) 14 | } 15 | } 16 | 17 | return bud.on(value => 18 | { 19 | console.log((label || bud.id), inspect(value, options)) 20 | }) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/_/Bud/emit.js: -------------------------------------------------------------------------------- 1 | 2 | import Nothing from '../../Nothing' 3 | 4 | import domain from '../domain/index.js' 5 | 6 | var queue = domain.queue 7 | 8 | 9 | export default function (bud) 10 | { 11 | return function emit (value) 12 | { 13 | if (! arguments.length) 14 | { 15 | value = Nothing 16 | } 17 | 18 | queue(bud, value) 19 | 20 | return bud 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/_/Bud/inspect.browser.js: -------------------------------------------------------------------------------- 1 | 2 | export default function inspect (value /*, options */) 3 | { 4 | return value 5 | } 6 | -------------------------------------------------------------------------------- /lib/_/Bud/inspect.js: -------------------------------------------------------------------------------- 1 | 2 | import { inspect as util_inspect } from 'util' 3 | 4 | 5 | export default function inspect (value, options) 6 | { 7 | return util_inspect(value, { colors: true, depth: 2, ...options }) 8 | } 9 | -------------------------------------------------------------------------------- /lib/_/Bud/map.js: -------------------------------------------------------------------------------- 1 | 2 | import join from '../../join' 3 | 4 | 5 | export default function (bud) 6 | { 7 | return (fn) => join(bud, fn) 8 | } 9 | -------------------------------------------------------------------------------- /lib/_/Bud/sample.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | export default function (bud) 4 | { 5 | return () => 6 | { 7 | return bud.value 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/_/Bud/thru.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | export default function (bud) 4 | { 5 | return (fn) => 6 | { 7 | return fn(bud) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/_/Counter.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | export default function Counter () 4 | { 5 | var value = 1 6 | 7 | return 0, 8 | { 9 | current () 10 | { 11 | return value 12 | }, 13 | next () 14 | { 15 | return value++ 16 | }, 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/_/domain/Affected.js: -------------------------------------------------------------------------------- 1 | // TODO: try impr shortcut with affected count 2 | 3 | 4 | export default function Affected () 5 | { 6 | var current = 1 7 | 8 | function next () 9 | { 10 | current++ 11 | } 12 | 13 | var aff = new WeakMap 14 | 15 | function affect (bud) 16 | { 17 | aff.set(bud, current) 18 | } 19 | 20 | function is_now (bud) 21 | { 22 | /* worse performance: return (! (aff.get(bud) < op_current())) */ 23 | return (aff.get(bud) === current) 24 | } 25 | 26 | return { next, affect, is_now } 27 | } 28 | -------------------------------------------------------------------------------- /lib/_/domain/Computation.js: -------------------------------------------------------------------------------- 1 | /* eslint complexity: [ 2, 8 ] */ 2 | 3 | import End from '../../End' 4 | 5 | import Order from './order' 6 | import propagate from './propagate' 7 | 8 | 9 | export default function Computation (domain) 10 | { 11 | var W = new WeakMap 12 | 13 | var deps_reverse_get = domain.deps.reverse.get 14 | var affect = domain.affected.affect 15 | var affected_now = domain.affected.is_now 16 | 17 | return function comp (bud) 18 | { 19 | var tick = domain.id 20 | var frame = W.get(bud) 21 | 22 | if (frame && frame.tick === tick) 23 | { 24 | return frame 25 | } 26 | 27 | var order = Order(bud, domain) 28 | var frame = Frame(bud, order) 29 | frame.tick = tick 30 | frame.order = order 31 | 32 | W.set(bud, frame) 33 | return frame 34 | } 35 | 36 | function Frame (bud, order) 37 | { 38 | var L = order.length 39 | 40 | switch (L) 41 | { 42 | case 0: 43 | return function frame (value) 44 | { 45 | if (! propagate(affect, bud, value)) { return } 46 | 47 | bud.on.present && bud.on.emit() 48 | 49 | var been_finalized = finalize(bud) 50 | if (been_finalized) 51 | { 52 | domain.id++ 53 | } 54 | } 55 | 56 | case 1: 57 | var order_only = order[0] 58 | var compute_only = deps_reverse_get(order_only).compute 59 | 60 | return function frame (value) 61 | { 62 | if (! propagate(affect, bud, value)) { return } 63 | 64 | value = compute_only() 65 | propagate(affect, order_only, value) 66 | 67 | bud.on.present && bud.on.emit() 68 | effects(order_only) 69 | 70 | var been_finalized = finalize(bud) 71 | var been_finalized = (finalize(order_only) || been_finalized) 72 | 73 | if (been_finalized) 74 | { 75 | domain.id++ 76 | } 77 | } 78 | 79 | default: 80 | var computes = order.map(bud => deps_reverse_get(bud).compute) 81 | 82 | return function frame (value) 83 | { 84 | if (! propagate(affect, bud, value)) { return } 85 | 86 | for (var n = 0; (n < L); n++) 87 | { 88 | var dep = order[n] 89 | value = computes[n]() 90 | 91 | propagate(affect, dep, value) 92 | } 93 | 94 | bud.on.present && bud.on.emit() 95 | for (var n = 0; (n < L); n++) 96 | { 97 | effects(order[n]) 98 | } 99 | 100 | var been_finalized = finalize(bud) 101 | for (var n = 0; (n < L); n++) 102 | { 103 | been_finalized = (finalize(order[n]) || been_finalized) 104 | } 105 | 106 | if (been_finalized) 107 | { 108 | domain.id++ 109 | } 110 | } 111 | } 112 | } 113 | 114 | function effects (bud) 115 | { 116 | if (affected_now(bud)) 117 | { 118 | bud.on.present && bud.on.emit() 119 | } 120 | } 121 | 122 | function finalize (bud) 123 | { 124 | if (bud.value !== End) { return false } 125 | 126 | domain.deps.finalize(bud) 127 | return true 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /lib/_/domain/Domain.js: -------------------------------------------------------------------------------- 1 | 2 | import End from '../../End' 3 | 4 | import Affected from './Affected' 5 | import Computation from './Computation' 6 | import Queue from './Queue' 7 | 8 | 9 | export default function Domain () 10 | { 11 | var domain = 12 | { 13 | id: 1, 14 | deps: Deps(), 15 | affected: Affected(), 16 | comp: null, 17 | queue: null, 18 | state, 19 | } 20 | 21 | domain.comp = Computation(domain) 22 | domain.queue = Queue(domain) 23 | 24 | function state (bud) 25 | { 26 | var direct = domain.deps.direct.get(bud) 27 | var reverse = domain.deps.reverse.get_test(bud) 28 | var order = domain.comp(bud).order 29 | 30 | return { direct, reverse, order } 31 | } 32 | 33 | return domain 34 | } 35 | 36 | 37 | function Deps () 38 | { 39 | var d = 40 | { 41 | direct: Direct(), 42 | reverse: null, 43 | 44 | register, 45 | finalize, 46 | } 47 | 48 | d.reverse = Reverse(d.direct) 49 | 50 | function register (bud_new, bud_deps, compute) 51 | { 52 | var deps = [] 53 | 54 | for (var bud of bud_deps) 55 | { 56 | if (bud.value !== End) 57 | { 58 | d.direct.add(bud, bud_new) 59 | 60 | deps.push(bud) 61 | } 62 | } 63 | 64 | d.reverse.add(bud_new, { compute, deps }) 65 | } 66 | 67 | function finalize (bud) 68 | { 69 | d.direct.finalize(bud) 70 | d.reverse.finalize(bud) 71 | } 72 | 73 | return d 74 | } 75 | 76 | 77 | function Direct () 78 | { 79 | var D = new WeakMap 80 | 81 | var direct = 82 | { 83 | add, 84 | remove, 85 | get, 86 | finalize, 87 | } 88 | 89 | function add (bud, dep) 90 | { 91 | get(bud).push(dep) 92 | } 93 | 94 | function remove (bud, dep) 95 | { 96 | var deps = get(bud) 97 | var index = deps.indexOf(dep) 98 | if (~ index) 99 | { 100 | deps.splice(index, 1) 101 | } 102 | } 103 | 104 | function get (bud) 105 | { 106 | if (! D.has(bud)) 107 | { 108 | D.set(bud, []) 109 | } 110 | 111 | return D.get(bud) 112 | } 113 | 114 | function finalize (bud) 115 | { 116 | D.delete(bud) 117 | } 118 | 119 | return direct 120 | } 121 | 122 | 123 | function Reverse (direct) 124 | { 125 | var W = new WeakMap 126 | 127 | var reverse = 128 | { 129 | add, 130 | get, 131 | get_test, 132 | finalize, 133 | } 134 | 135 | function add (bud, { compute, deps }) 136 | { 137 | W.set(bud, { compute, deps }) 138 | } 139 | 140 | function get (bud) 141 | { 142 | return W.get(bud) 143 | } 144 | 145 | function get_test (bud) 146 | { 147 | var rev = get(bud) 148 | return (rev && rev.deps) 149 | } 150 | 151 | function finalize (bud) 152 | { 153 | var rev = W.get(bud) 154 | 155 | if (! rev) { return } 156 | 157 | W.delete(bud) 158 | 159 | var deps = rev.deps 160 | var L = deps.length 161 | 162 | for (var n = 0; (n < L); n++) 163 | { 164 | direct.remove(deps[n], bud) 165 | } 166 | 167 | /* deps.splice(0) */ 168 | } 169 | 170 | return reverse 171 | } 172 | -------------------------------------------------------------------------------- /lib/_/domain/Queue.js: -------------------------------------------------------------------------------- 1 | /* eslint max-depth: [ 2, 4 ] */ 2 | 3 | export default function Queue (domain) 4 | { 5 | var hot = false 6 | var que_que = [] 7 | 8 | var next = domain.affected.next 9 | var comp = domain.comp 10 | 11 | function queue (bud, value) 12 | { 13 | if (! hot) 14 | { 15 | hot = true 16 | 17 | try 18 | { 19 | for (;;) 20 | { 21 | next() 22 | comp(bud)(value) 23 | 24 | if (! que_que.length) break 25 | 26 | bud = que_que[0] 27 | value = que_que[1] 28 | 29 | que_que.splice(0, 2) 30 | } 31 | } 32 | finally 33 | { 34 | hot = false 35 | } 36 | } 37 | else 38 | { 39 | que_que.push(bud, value) 40 | } 41 | } 42 | 43 | return queue 44 | } 45 | -------------------------------------------------------------------------------- /lib/_/domain/index.js: -------------------------------------------------------------------------------- 1 | 2 | import Domain from './Domain' 3 | 4 | export default Domain() 5 | -------------------------------------------------------------------------------- /lib/_/domain/order.js: -------------------------------------------------------------------------------- 1 | // TODO: optimizable in non-recursive cycle analogue 2 | 3 | 4 | export default function order (bud, domain) 5 | { 6 | var bare = order_bare(bud, domain) 7 | 8 | var present = new Set 9 | var total = [] 10 | 11 | walk_deps(bare, present, total) 12 | 13 | return total 14 | } 15 | 16 | function order_bare (bud, domain) 17 | { 18 | return [ bud, domain.deps.direct.get(bud).map(bud => order_bare(bud, domain)) ] 19 | } 20 | 21 | function walk_deps (bare, present, total) 22 | { 23 | var deps = bare[1] 24 | 25 | for (var n = (deps.length - 1); (n >= 0); n--) 26 | { 27 | walk(deps[n], present, total) 28 | } 29 | } 30 | 31 | function walk (bare, present, total) 32 | { 33 | walk_deps(bare, present, total) 34 | 35 | var bud = bare[0] 36 | 37 | if (! present.has(bud)) 38 | { 39 | present.add(bud) 40 | 41 | total.unshift(bud) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/_/domain/propagate.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable complexity */ 2 | 3 | import Nothing from '../../Nothing' 4 | import Many from '../../Many' 5 | import End from '../../End' 6 | 7 | 8 | export default function propagate (affect, bud, value) 9 | { 10 | if (bud.value === End) 11 | { 12 | return false 13 | } 14 | 15 | if (value === Nothing) 16 | { 17 | return false 18 | } 19 | 20 | if (! Many.is(value)) 21 | { 22 | // inline: 23 | // return propagate_first(affect, bud, value) 24 | 25 | bud.value = value 26 | 27 | affect(bud) 28 | 29 | return true 30 | } 31 | 32 | var L = value.length 33 | var been_updated = false 34 | 35 | for (var n = 0; (n < L); n++) 36 | { 37 | var value_next = value[n] 38 | 39 | if (! been_updated) 40 | { 41 | // inline: 42 | // been_updated = propagate_first(affect, bud, value_next) 43 | 44 | if (value_next === Nothing) 45 | { 46 | continue 47 | } 48 | 49 | been_updated = true 50 | 51 | bud.value = value_next 52 | 53 | affect(bud) 54 | } 55 | else 56 | { 57 | /* schedule rest propagations from Many(_, ...rest) */ 58 | bud.emit(value_next) 59 | } 60 | } 61 | 62 | return been_updated 63 | } 64 | 65 | /* 66 | function propagate_first (affect, bud, value) 67 | { 68 | if (value === Nothing) 69 | { 70 | return false 71 | } 72 | 73 | bud.value = value 74 | 75 | affect(bud) 76 | 77 | return true 78 | } 79 | */ 80 | -------------------------------------------------------------------------------- /lib/asap.d.ts: -------------------------------------------------------------------------------- 1 | 2 | export default function (fn: () => R): Promise 3 | -------------------------------------------------------------------------------- /lib/asap.js: -------------------------------------------------------------------------------- 1 | 2 | export default function asap (fn) 3 | { 4 | return Promise.resolve().then(fn) 5 | } 6 | -------------------------------------------------------------------------------- /lib/capture.d.ts: -------------------------------------------------------------------------------- 1 | 2 | export default function (fn: () => R): (R | unknown) 3 | -------------------------------------------------------------------------------- /lib/capture.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | export default function capture (fn) 4 | { 5 | return (...args) => 6 | { 7 | try 8 | { 9 | return fn(...args) 10 | } 11 | catch (e) 12 | { 13 | return e 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/concat.d.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Bud } from './Bud' 3 | 4 | export default function (bud: Bud): Promise 5 | -------------------------------------------------------------------------------- /lib/concat.js: -------------------------------------------------------------------------------- 1 | 2 | import drain from './drain' 3 | 4 | 5 | export default function concat (bud) 6 | { 7 | var buffer = [] 8 | 9 | bud.on(value => buffer.push(value)) 10 | 11 | return drain(bud).then(() => buffer) 12 | } 13 | -------------------------------------------------------------------------------- /lib/drain.d.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Bud } from './Bud' 3 | 4 | export default function (bud: Bud): Promise 5 | -------------------------------------------------------------------------------- /lib/drain.js: -------------------------------------------------------------------------------- 1 | 2 | import End from './End' 3 | 4 | 5 | export default function drain (bud) 6 | { 7 | return new Promise(rs => 8 | { 9 | bud.on(value => 10 | { 11 | if (value === End) 12 | { 13 | rs() 14 | } 15 | }) 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /lib/join.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | /* eslint-disable max-params */ 3 | 4 | import { Bud } from './Bud' 5 | import { Product } from './Bud' 6 | 7 | export type Producer = (...values: T) => Product 8 | 9 | declare function join (bud: Bud, fn: Producer<[ T ], R>): Bud 10 | declare function join (bud1: Bud, bud2: Bud, fn: Producer<[ T1, T2 ], R>): Bud 11 | declare function join (bud1: Bud, bud2: Bud, bud3: Bud, fn: Producer<[ T1, T2, T3 ], R>): Bud 12 | declare function join (bud1: Bud, bud2: Bud, bud3: Bud, bud4: Bud, fn: Producer<[ T1, T2, T3, T4 ], R>): Bud 13 | declare function join (bud1: Bud, bud2: Bud, bud3: Bud, bud4: Bud, bud5: Bud, fn: Producer<[ T1, T2, T3, T4, T5 ], R>): Bud 14 | declare function join (fn: Producer, ...buds: { [ Key in keyof T ]: Bud }): Bud 15 | 16 | export default join 17 | -------------------------------------------------------------------------------- /lib/join.js: -------------------------------------------------------------------------------- 1 | /* eslint complexity: [ 2, 9 ] */ 2 | 3 | import Nothing from './Nothing' 4 | import End from './End' 5 | 6 | import domain from './_/domain/index.js' 7 | 8 | var affected_now = domain.affected.is_now 9 | 10 | 11 | export default function join (...args) 12 | { 13 | if (typeof args[0] === 'function') 14 | { 15 | var buds = args.slice(1) 16 | var fn = args[0] 17 | } 18 | else 19 | { 20 | var buds = args.slice(0, -1) 21 | var fn = args[args.length - 1] 22 | } 23 | 24 | var value = collect(buds, fn, true) 25 | var next = buds[0].constructor(value) 26 | 27 | if (value !== End) 28 | { 29 | /* compute function: */ 30 | var compute = Collect(buds, fn) 31 | 32 | /* collect direct & reverse deps: */ 33 | domain.deps.register(next, buds, compute) 34 | } 35 | 36 | return next 37 | } 38 | 39 | 40 | function Collect (buds, fn) 41 | { 42 | if (buds.length === 1) 43 | { 44 | var bud = buds[0] 45 | 46 | return () => 47 | { 48 | return collect_only(bud, fn) 49 | } 50 | } 51 | else 52 | { 53 | return () => 54 | { 55 | return collect(buds, fn) 56 | } 57 | } 58 | } 59 | 60 | 61 | function collect_only (bud, fn, any_touched = false) 62 | { 63 | var value = bud.value 64 | 65 | if (value === Nothing) 66 | { 67 | return Nothing 68 | } 69 | if (! (any_touched || affected_now(bud))) 70 | { 71 | return Nothing 72 | } 73 | 74 | return fn(value) 75 | } 76 | 77 | function collect (buds, fn, any_touched = false) 78 | { 79 | var L = buds.length 80 | var args = [] 81 | 82 | for (var n = 0; (n < L); n++) 83 | { 84 | var bud = buds[n] 85 | var value = bud.value 86 | 87 | if (value === Nothing) 88 | { 89 | return Nothing 90 | } 91 | 92 | any_touched || (any_touched = affected_now(bud)) 93 | 94 | args.push(value) 95 | } 96 | 97 | if (! any_touched) 98 | { 99 | return Nothing 100 | } 101 | 102 | return fn(...args) 103 | } 104 | -------------------------------------------------------------------------------- /lib/merge.d.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Bud } from './Bud' 3 | import { Product } from './Bud' 4 | 5 | type Union = T[number] 6 | 7 | export type Producer = (...values: T) => Product 8 | 9 | export default function 10 | ( 11 | ...buds: { [ Key in keyof T ]: Bud } 12 | ) 13 | : Bud> 14 | -------------------------------------------------------------------------------- /lib/merge.js: -------------------------------------------------------------------------------- 1 | /* eslint complexity: [ 2, 10 ] */ 2 | 3 | import Nothing from './Nothing' 4 | import Many from './Many' 5 | import End from './End' 6 | 7 | import domain from './_/domain/index.js' 8 | 9 | var affected_now = domain.affected.is_now 10 | 11 | 12 | export default function merge (...buds) 13 | { 14 | var bud1 = buds[0] 15 | 16 | var value = pick(buds, true) 17 | var next = bud1.constructor(value) 18 | 19 | if (value !== End) 20 | { 21 | /* compute function: */ 22 | var compute = Pick(buds) 23 | 24 | /* collect direct & reverse deps: */ 25 | domain.deps.register(next, buds, compute) 26 | } 27 | 28 | return next 29 | } 30 | 31 | 32 | function Pick (buds) 33 | { 34 | return () => 35 | { 36 | return pick(buds) 37 | } 38 | } 39 | 40 | 41 | function pick (buds, any_touched = false) 42 | { 43 | var L = buds.length 44 | 45 | var first = Nothing 46 | var all = [] 47 | var y1 = 0 48 | var y2 = 0 49 | 50 | for (var n = 0; (n < L); n++) 51 | { 52 | var bud = buds[n] 53 | var value = bud.value 54 | 55 | if (value === Nothing) 56 | { 57 | continue 58 | } 59 | if (! (any_touched || affected_now(bud))) 60 | { 61 | continue 62 | } 63 | 64 | y2 = y1 65 | y1 = 1 66 | 67 | y2 || (first = value) 68 | y2 && all.push(value) 69 | } 70 | 71 | switch (y1 + y2) 72 | { 73 | case 0: return Nothing 74 | case 1: return first 75 | default: return Many(first, ...all) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /lib/resource.d.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Bud } from './Bud' 3 | import { End } from './End' 4 | import { Disposer } from './Bud' 5 | 6 | export type Emitter = (emit: Bud['emit'], bud: Bud) => Disposer 7 | 8 | export default function (fn: Emitter): Bud 9 | -------------------------------------------------------------------------------- /lib/resource.js: -------------------------------------------------------------------------------- 1 | 2 | import Bud from './Bud' 3 | import tap from './tap' 4 | import { when_end } from '../map/when' 5 | 6 | 7 | export default function resource (fn) 8 | { 9 | return Bud().thru(tap(bud => 10 | { 11 | var disposer = fn(bud.emit, bud) 12 | 13 | bud.on(when_end(disposer)) 14 | })) 15 | } 16 | -------------------------------------------------------------------------------- /lib/tap.d.ts: -------------------------------------------------------------------------------- 1 | 2 | export default function (fn: (...args: Args) => void): 3 | (...args: Args) => Args[0] 4 | -------------------------------------------------------------------------------- /lib/tap.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | export default function tap (fn) 4 | { 5 | return (...args) => (fn(...args), args[0]) 6 | } 7 | -------------------------------------------------------------------------------- /lib/transfer.d.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Bud } from './Bud' 3 | 4 | export type Emitter = Bud['emit'] 5 | export type TransformerEmit = (value: T, emit: Emitter) => void 6 | 7 | export default function (bud: Bud, fn: TransformerEmit): Bud 8 | -------------------------------------------------------------------------------- /lib/transfer.js: -------------------------------------------------------------------------------- 1 | 2 | import tap from './tap' 3 | import { when_end } from '../map/when' 4 | 5 | 6 | export default function transfer (bud_source, fn) 7 | { 8 | return bud_source.constructor() 9 | .thru(tap(bud => 10 | { 11 | var ds = bud_source.on(value => 12 | { 13 | fn(value, bud.emit) 14 | }) 15 | 16 | bud.on(when_end(ds)) 17 | })) 18 | } 19 | -------------------------------------------------------------------------------- /lib/turnoff.d.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Bud } from './Bud' 3 | import { Disposer } from './Bud' 4 | 5 | import { End } from './End' 6 | 7 | export default function (bud: Bud, bud_target: Bud): Disposer 8 | -------------------------------------------------------------------------------- /lib/turnoff.js: -------------------------------------------------------------------------------- 1 | 2 | import { when_end } from '../map/when' 3 | 4 | 5 | export default function turnoff (bud, bud_target) 6 | { 7 | return bud.on(when_end(bud_target.emit)) 8 | } 9 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright © 2022, Strider. 2 | https://github.com/StreetStrider 3 | 4 | Permission to use, copy, modify, and/or distribute this software for any 5 | purpose with or without fee is hereby granted, provided that the above 6 | copyright notice and this permission notice appear in all copies. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 11 | SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 13 | OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 14 | CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | -------------------------------------------------------------------------------- /map/filter-by.d.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Bud } from '../lib/Bud' 3 | import { Producer } from '../lib/Bud' 4 | 5 | export default function (bud: Bud): Producer 6 | -------------------------------------------------------------------------------- /map/filter-by.js: -------------------------------------------------------------------------------- 1 | 2 | import filter from './filter' 3 | 4 | 5 | export default function filter_by (bud) 6 | { 7 | return filter(bud.sample) 8 | } 9 | -------------------------------------------------------------------------------- /map/filter.d.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Producer } from '../lib/Bud' 3 | 4 | export type PredicateType = (x: Item) => x is Kind 5 | export type Predicate = (x: Item) => boolean 6 | 7 | export default function (pred: PredicateType): Producer 8 | export default function (pred: Predicate): Producer 9 | -------------------------------------------------------------------------------- /map/filter.js: -------------------------------------------------------------------------------- 1 | 2 | import Same from '../lib/Same' 3 | import Noop from '../lib/Noop' 4 | 5 | import { base_when } from './when' 6 | 7 | 8 | export default function filter (pred) 9 | { 10 | return base_when(pred, Same, Noop) 11 | } 12 | -------------------------------------------------------------------------------- /map/reduce.d.ts: -------------------------------------------------------------------------------- 1 | 2 | type Reducer = (memo: R, next: T) => R 3 | 4 | export default function (memo: R, reducer: Reducer): (next: T) => R 5 | -------------------------------------------------------------------------------- /map/reduce.js: -------------------------------------------------------------------------------- 1 | 2 | export default function reduce (memo, reducer) 3 | { 4 | return (next) => 5 | { 6 | return (memo = reducer(memo, next)) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /map/uniq.d.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Producer } from '../lib/Bud' 3 | 4 | export default function (): Producer 5 | -------------------------------------------------------------------------------- /map/uniq.js: -------------------------------------------------------------------------------- 1 | 2 | import Nothing from '../lib/Nothing' 3 | 4 | 5 | export default function uniq () 6 | { 7 | var recent = Nothing 8 | 9 | return (value) => 10 | { 11 | if (value === recent) { return Nothing } 12 | 13 | recent = value 14 | 15 | return value 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /map/when.d.ts: -------------------------------------------------------------------------------- 1 | 2 | import { End } from '../lib/End' 3 | 4 | export type Predicate = (...args: Args) => boolean 5 | 6 | export function base_when 7 | ( 8 | pred: Predicate, 9 | fn_true: (...args: Args) => True, 10 | fn_false: (...args: Args) => False, 11 | ) 12 | : 13 | (...args: Args) => (True & False) 14 | 15 | export function When 16 | ( 17 | pred: Predicate, 18 | fn_false_default?: (...args: Args) => False, 19 | ): 20 | ( 21 | fn_true: (...args: Args) => True, 22 | fn_false: (...args: Args) => False, 23 | ) 24 | => 25 | (...args: Args) => (True & False) 26 | 27 | 28 | export type NoData = (Error | End) 29 | 30 | export function when_data , R> (fn_true: (value: T) => R) 31 | : 32 | (value: Out) => (Extract | R) 33 | 34 | export function when_end (fn_true: (value: End) => R) 35 | : 36 | (value: T) => (Exclude | R) 37 | 38 | export function when_error (fn_true: (value: Error) => R) 39 | : 40 | (value: T) => (Exclude | R) 41 | 42 | 43 | type Union = T[number] 44 | 45 | export function when_data_all 46 | < 47 | Outs extends any[], 48 | Ins extends { [ Key in keyof Outs ]: Exclude }, 49 | R 50 | > 51 | (fn_true: (...args: Ins) => R) 52 | : 53 | (...values: Outs) => (Extract, NoData> | R) 54 | -------------------------------------------------------------------------------- /map/when.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | export function base_when (pred, fn_true, fn_false) 4 | { 5 | return (...args) => 6 | { 7 | if (pred(...args)) 8 | { 9 | return fn_true(...args) 10 | } 11 | else 12 | { 13 | return fn_false(...args) 14 | } 15 | } 16 | } 17 | 18 | 19 | import Same from '../lib/Same' 20 | 21 | export function When (pred, fn_false_default = Same) 22 | { 23 | return (fn_true, fn_false = fn_false_default) => 24 | { 25 | return base_when(pred, fn_true, fn_false) 26 | } 27 | } 28 | 29 | 30 | export var when_data = When(is_data) 31 | 32 | export var when_end = When(is_end) 33 | 34 | export var when_error = When(is_error) 35 | 36 | 37 | export function when_data_all (fn_true) 38 | { 39 | return (...args) => 40 | { 41 | if (args.some(is_end)) 42 | { 43 | return End 44 | } 45 | 46 | var error = args.find(is_error) 47 | 48 | if (error) 49 | { 50 | return error 51 | } 52 | 53 | return fn_true(...args) 54 | } 55 | } 56 | 57 | 58 | import End from '../lib/End' 59 | 60 | function is_end (value) 61 | { 62 | return (value === End) 63 | } 64 | 65 | function is_error (value) 66 | { 67 | return (value instanceof Error) 68 | } 69 | 70 | function is_data (value) 71 | { 72 | if (value === End) { return false } 73 | if (value instanceof Error) { return false } 74 | return true 75 | } 76 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | 4 | "name": "fluh", 5 | "version": "1.0.0", 6 | 7 | "description": "simple and easy functional reactive library with atomic push strategy", 8 | 9 | "author": "Strider ", 10 | "license": "ISC", 11 | 12 | "keywords": 13 | [ 14 | "frp", "reactive", "functional-programming", "streams", 15 | "flyd", 16 | "node", "browser" 17 | ], 18 | 19 | "homepage": "https://github.com/StreetStrider/fluh", 20 | "repository": { "url": "https://github.com/StreetStrider/fluh.git", "type": "git" }, 21 | "bugs": { "url": "https://github.com/StreetStrider/fluh/issues" }, 22 | 23 | "engines": 24 | { 25 | "node": ">= 8.3" 26 | }, 27 | 28 | "scripts": 29 | { 30 | "start": "node -r esm -r console-ultimate test/play", 31 | 32 | "st": "eslint", 33 | "unit": "mocha -r esm -r console-ultimate -r test/globals test/*.test.js", 34 | "ts": "tsc && npm run dts && tsc -p examples/tsconfig.json", 35 | "dts": "dtslint --expectOnly --localTs ./node_modules/typescript/lib test/type", 36 | "test": "npm run st && npm run ts && npm run unit", 37 | 38 | "cover": "c8 npm run unit", 39 | 40 | "perf": "gulp --final --to perf && node release/perf/perf/perf", 41 | "leak": "node --inspect --expose-gc perf/leak", 42 | 43 | "final": "gulp --final --to npm", 44 | 45 | "example": "cd examples; ts-node -r esm -r console-ultimate", 46 | "examples": "cd examples; ts-node -r esm -r console-ultimate ./", 47 | 48 | "depcruise": "depcruise --no-config --exclude '^(node_modules|release|test|perf|coverage|examples|.*\\.d\\.ts|eslint|gulp)' --output-type dot . > test/graph.dot", 49 | "diagrams": "graphviz -Tsvg -otest/graph.svg test/graph.dot", 50 | "graph": "npm run depcruise && npm run diagrams && open-cli test/graph.svg ##### < http://viz-js.com/ >", 51 | 52 | "skott": "skott -m webapp -n -t -ig '@(release|test|perf|coverage|examples)/**/*' -ig '*eslint*' -ig '*gulp*'" 53 | }, 54 | 55 | "files": 56 | [ 57 | "lib/", 58 | "map/", 59 | "thru/", 60 | "index.js", 61 | "index.d.ts" 62 | ], 63 | 64 | "browser": 65 | { 66 | "./lib/_/Bud/inspect.js": "./lib/_/Bud/inspect.browser.js" 67 | }, 68 | 69 | "dependencies": 70 | { 71 | "def-prop": 72 | "2" 73 | }, 74 | 75 | "devDependencies": 76 | { 77 | "fluh": 78 | "./", 79 | 80 | 81 | "esm": 82 | "^3.2", 83 | 84 | "console-ultimate": 85 | "3", 86 | 87 | "raf": 88 | "3", 89 | 90 | 91 | "gulp": 92 | "4", 93 | 94 | "rollup": 95 | "3.29.5", 96 | 97 | "metalpipe": 98 | "https://github.com/StrangeTransistor/metalpipe.git#6ea7e3221c465fab3724d88eac5207479d591145", 99 | 100 | "metalpipe": 101 | "~/Projects/metalpipe", 102 | 103 | 104 | "eslint": 105 | "9", 106 | 107 | "outlander": 108 | "StrangeTransistor/outlander#3.0.0", 109 | 110 | "eslint-plugin-node": 111 | "11", 112 | 113 | "@typescript-eslint/parser": 114 | "8.32.1", 115 | 116 | "@typescript-eslint/eslint-plugin": 117 | "8.32.1", 118 | 119 | "typescript": 120 | "5.8.3", 121 | 122 | "dtslint": 123 | "4", 124 | 125 | "ts-node": 126 | "10", 127 | 128 | "@types/node": 129 | "22.15.24", 130 | 131 | 132 | "mocha": 133 | "8", 134 | 135 | "chai": 136 | "4", 137 | 138 | "sinon": 139 | "11", 140 | 141 | 142 | "c8": 143 | "7", 144 | 145 | 146 | "benny": 147 | "^3.7", 148 | 149 | 150 | "dependency-cruiser": 151 | "16", 152 | 153 | "diagrams": 154 | "0.11", 155 | 156 | "graphviz-cli": 157 | "2", 158 | 159 | "open-cli": 160 | "8", 161 | 162 | "skott": 163 | "0.35", 164 | 165 | 166 | "flyd": 167 | "0.2", 168 | 169 | "rxjs": 170 | "7", 171 | 172 | "@most/core": 173 | "1.6", 174 | 175 | "@most/scheduler": 176 | "1.3", 177 | 178 | "@most/adapter": 179 | "1" 180 | }, 181 | 182 | "pnpm": 183 | { 184 | "overrides": 185 | { 186 | "@definitelytyped/utils": "0.1.4", 187 | "@sinonjs/fake-timers": "7.1.2" 188 | } 189 | } 190 | } -------------------------------------------------------------------------------- /perf/.eslintrc.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = 3 | { 4 | rules: 5 | { 6 | 'node/no-unsupported-features/es-syntax': 0, 7 | }, 8 | } 9 | -------------------------------------------------------------------------------- /perf/fluh.perf.js: -------------------------------------------------------------------------------- 1 | 2 | import Bud from '../lib/Bud' 3 | import Nothing from '../lib/Nothing' 4 | import Many from '../lib/Many' 5 | 6 | import join from '../lib/join' 7 | import merge from '../lib/merge' 8 | 9 | 10 | export default 11 | { 12 | diamond () 13 | { 14 | var n = 1 15 | 16 | var A = Bud() 17 | var b = join(A, a => a + 1) 18 | var c = join(b, b => b + 1) 19 | var d = join(A, c, (a, c) => a + c + 1) 20 | 21 | d.on(() => n++) 22 | 23 | return () => 24 | { 25 | A.emit(17) 26 | } 27 | }, 28 | 29 | merge () 30 | { 31 | var n = 1 32 | 33 | var a = Bud() 34 | var b = Bud() 35 | var c = merge(a, b) 36 | 37 | c.on(() => n++) 38 | 39 | return () => 40 | { 41 | b.emit(17) 42 | a.emit(-1) 43 | } 44 | }, 45 | 46 | deep_linear () 47 | { 48 | var n = 1 49 | 50 | var a = Bud() 51 | 52 | var b = a 53 | for (var n = 0; n < 100; n++) 54 | { 55 | b = join(b, b => b + 1) 56 | } 57 | 58 | b.on(() => n++) 59 | 60 | return () => 61 | { 62 | a.emit(17) 63 | } 64 | }, 65 | 66 | deep_linear_many () 67 | { 68 | var n = 1 69 | 70 | var a = Bud() 71 | 72 | var b = a 73 | for (var n = 0; n < 100; n++) 74 | { 75 | b = join(b, b => b + 1) 76 | } 77 | 78 | b.on(() => n++) 79 | 80 | return () => 81 | { 82 | a.emit(Many(17, 1917)) 83 | } 84 | }, 85 | 86 | triangle_triangle () 87 | { 88 | var n = 1 89 | 90 | var a = Bud() 91 | 92 | var b1 = join(a, a => a + 1) 93 | var c1 = join(a, b1, (a, b) => a + b + 1) 94 | 95 | var b2 = join(b1, b => b + 1) 96 | var c2 = join(b2, c1, (b, c) => b + c + 1) 97 | 98 | c2.on(() => n++) 99 | 100 | return () => 101 | { 102 | a.emit(17) 103 | } 104 | }, 105 | 106 | shortcut () 107 | { 108 | var n = 1 109 | 110 | var a = Bud() 111 | 112 | var b = a 113 | for (var n = 0; n < 100; n++) 114 | { 115 | if (n === 10) 116 | { 117 | b = join(b, b => ((b % 2) && (b + 1) || Nothing)) 118 | } 119 | else 120 | { 121 | b = join(b, b => b + 1) 122 | } 123 | } 124 | 125 | b.on(() => n++) 126 | 127 | return () => 128 | { 129 | a.emit(18) 130 | a.emit(17) 131 | } 132 | }, 133 | } 134 | -------------------------------------------------------------------------------- /perf/flyd.perf.js: -------------------------------------------------------------------------------- 1 | 2 | import { stream } from 'flyd' 3 | import { combine } from 'flyd' 4 | import { merge } from 'flyd' 5 | import { on } from 'flyd' 6 | 7 | import filter from 'flyd/module/filter' 8 | 9 | 10 | export default 11 | { 12 | diamond () 13 | { 14 | var n = 1 15 | 16 | var Af = stream() 17 | var b = combine(a => a + 1, [ Af ]) 18 | var c = combine(b => b + 1, [ b ]) 19 | var d = combine((a, c) => a + c + 1, [ Af, c ]) 20 | 21 | on(() => n++, d) 22 | 23 | return () => 24 | { 25 | Af(17) 26 | } 27 | }, 28 | 29 | merge () 30 | { 31 | var n = 1 32 | 33 | var a = stream() 34 | var b = stream() 35 | var c = merge(a, b) 36 | 37 | on(() => n++, c) 38 | 39 | return () => 40 | { 41 | b(17) 42 | a(-1) 43 | } 44 | }, 45 | 46 | deep_linear () 47 | { 48 | var n = 1 49 | 50 | var a = stream() 51 | 52 | var b = a 53 | for (var n = 0; n < 100; n++) 54 | { 55 | b = combine(b => b + 1, [ b ]) 56 | } 57 | 58 | on(() => n++, b) 59 | 60 | return () => 61 | { 62 | a(17) 63 | } 64 | }, 65 | 66 | triangle_triangle () 67 | { 68 | var n = 1 69 | 70 | var a = stream() 71 | 72 | var b1 = combine(a => a + 1, [ a ]) 73 | var c1 = combine((a, b) => a + b + 1, [ a, b1 ]) 74 | 75 | var b2 = combine(b => b + 1, [ b1 ]) 76 | var c2 = combine((b, c) => b + c + 1, [ b2, c1 ]) 77 | 78 | on(() => n++, c2) 79 | 80 | return () => 81 | { 82 | a(17) 83 | } 84 | }, 85 | 86 | shortcut () 87 | { 88 | var n = 1 89 | 90 | var a = stream() 91 | 92 | var b = a 93 | for (var n = 0; n < 100; n++) 94 | { 95 | if (n === 10) 96 | { 97 | b = filter(b => Boolean(b % 2), b) 98 | } 99 | else 100 | { 101 | b = combine(b => b + 1, [ b ]) 102 | } 103 | } 104 | 105 | on(() => n++, b) 106 | 107 | return () => 108 | { 109 | a(18) 110 | a(17) 111 | } 112 | }, 113 | } 114 | -------------------------------------------------------------------------------- /perf/leak.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | /* global gc */ 3 | 4 | collect() 5 | 6 | var { Bud } = require('../release/npm') 7 | var { join } = require('../release/npm') 8 | var { merge } = require('../release/npm') 9 | var { End } = require('../release/npm') 10 | 11 | var { when_data } = require('../release/npm/map/when') 12 | var { when_data_all } = require('../release/npm/map/when') 13 | 14 | /* var domain = require('../release/npm/lib/domain') */ 15 | 16 | var plus = when_data_all((x, y) => (x + (y || 0) + 1)) 17 | 18 | var leak_holder = [] 19 | 20 | var total = 0 21 | 22 | var iters = (100e3) 23 | var brk = true 24 | 25 | for (let n = 1; (n <= iters); n++) 26 | { 27 | let a = Bud() 28 | a.id = `#a_${ n }` 29 | 30 | let b1 = join(a, plus) 31 | b1.id = `#b1_${ n }` 32 | 33 | let c1 = join(a, b1, plus) 34 | c1.id = `#c1_${ n }` 35 | 36 | let b2 = join(b1, plus) 37 | b2.id = `#b2_${ n }` 38 | 39 | let c2 = join(b2, c1, plus) 40 | c2.id = `#c2_${ n }` 41 | 42 | let ds = c2.on(when_data((x) => { total += x })) 43 | 44 | a.emit(1) 45 | // a.emit(End) 46 | // ds() 47 | // leak_holder.push(ds) 48 | } 49 | 50 | console.log('total #1', total) 51 | 52 | collect() 53 | 54 | var total = 0 55 | 56 | for (let n = 1; (n <= iters); n++) 57 | { 58 | let a = Bud() 59 | let b = a 60 | 61 | for (let n = 1; (n <= 10); n++) 62 | { 63 | b = b.map(plus) 64 | } 65 | 66 | let c = b 67 | 68 | let ds = c.on(when_data((x) => { total += x })) 69 | 70 | a.emit(1) 71 | // a.emit(End) 72 | // ds() 73 | // leak_holder.push(ds) 74 | } 75 | 76 | console.log('total #2', total) 77 | 78 | collect() 79 | 80 | var total = 0 81 | 82 | for (let n = 1; (n <= iters); n++) 83 | { 84 | let inputs = [] 85 | 86 | for (let n = 1; (n <= 15); n++) 87 | { 88 | inputs.push(Bud()) 89 | } 90 | 91 | let c = merge(...inputs) 92 | 93 | let ds = c.on(when_data((x) => { total += x })) 94 | 95 | for (let a of inputs) 96 | { 97 | a.emit(1) 98 | } 99 | // inputs[0].emit(End) 100 | // ds() 101 | // leak_holder.push(ds) 102 | } 103 | 104 | console.log('total #3', total) 105 | 106 | collect() 107 | 108 | leak_holder = null 109 | 110 | collect() 111 | 112 | // END 113 | 114 | function collect () 115 | { 116 | gc() 117 | wait() 118 | stat() 119 | // debugger 120 | } 121 | 122 | function wait () 123 | { 124 | var now = process.hrtime()[0] 125 | while (process.hrtime()[0] - now < 2) {} 126 | } 127 | 128 | function stat () 129 | { 130 | /* var heap = v8.getHeapStatistics().used_heap_size */ 131 | var heap = process.memoryUsage().heapUsed 132 | var heap_mb = (heap / 1000 / 1000) 133 | 134 | console.log('memory', heap_mb.toFixed(2)) 135 | 136 | if (heap_mb > 20) 137 | { 138 | throw new Error('leak_detected') 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /perf/most.perf.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-statements-per-line */ 2 | 3 | import { map } from '@most/core' 4 | import { filter } from '@most/core' 5 | import { combine } from '@most/core' 6 | import { merge } from '@most/core' 7 | import { tap } from '@most/core' 8 | 9 | import { runEffects } from '@most/core' 10 | import { newDefaultScheduler } from '@most/scheduler' 11 | 12 | import { createAdapter } from '@most/adapter' 13 | 14 | function Handle () 15 | { 16 | var [ emit, source ] = createAdapter() 17 | var handle = 18 | { 19 | rs: null, 20 | source, 21 | next, 22 | } 23 | 24 | function next (value) 25 | { 26 | var p = new Promise(rs => { handle.rs = rs }) 27 | emit(value) 28 | return p 29 | } 30 | 31 | return handle 32 | } 33 | 34 | 35 | export default 36 | { 37 | diamond () 38 | { 39 | var n = 1 40 | 41 | var handle = Handle() 42 | var A = handle.source 43 | 44 | var b = map(a => a + 1, A) 45 | var c = map(b => b + 1, b) 46 | var d = combine((a, c) => a + c + 1, A, c) 47 | 48 | var run = tap(() => { n = (n + 1); handle.rs() }, d) 49 | runEffects(run, newDefaultScheduler()) 50 | 51 | return () => 52 | { 53 | return handle.next(17) 54 | } 55 | }, 56 | 57 | merge () 58 | { 59 | var n = 1 60 | 61 | var h1 = Handle() 62 | var a = h1.source 63 | 64 | var h2 = Handle() 65 | var b = h2.source 66 | 67 | var c = merge(a, b) 68 | 69 | var run = tap(x => { n = (n + 1); if (x % 2) h1.rs(); else h2.rs() }, c) 70 | runEffects(run, newDefaultScheduler()) 71 | 72 | return async () => 73 | { 74 | await h1.next(17) 75 | await h2.next(18) 76 | } 77 | }, 78 | 79 | deep_linear () 80 | { 81 | var n = 1 82 | 83 | var handle = Handle() 84 | var a = handle.source 85 | 86 | var b = a 87 | for (var N = 0; N < 100; N++) 88 | { 89 | b = map(b => b + 1, b) 90 | } 91 | 92 | var run = tap(() => { n = (n + 1); handle.rs() }, b) 93 | runEffects(run, newDefaultScheduler()) 94 | 95 | return () => 96 | { 97 | return handle.next(17) 98 | } 99 | }, 100 | 101 | triangle_triangle () 102 | { 103 | var n = 1 104 | 105 | var handle = Handle() 106 | var a = handle.source 107 | 108 | var b1 = map(a => a + 1, a) 109 | var c1 = combine((a, b) => a + b + 1, a, b1) 110 | 111 | var b2 = map(b => b + 1, b1) 112 | var c2 = combine((b, c) => b + c + 1, b2, c1) 113 | 114 | var run = tap(() => { n = (n + 1); handle.rs() }, c2) 115 | runEffects(run, newDefaultScheduler()) 116 | 117 | return () => 118 | { 119 | return handle.next(17) 120 | } 121 | }, 122 | 123 | shortcut () 124 | { 125 | var n = 1 126 | 127 | var handle = Handle() 128 | 129 | var b = handle.source 130 | for (var N = 0; N < 100; N++) 131 | { 132 | if (N === 10) 133 | { 134 | var run1 = tap(b => ((b % 2) || handle.rs()), b) 135 | b = filter(b => Boolean(b % 2), b) 136 | } 137 | else 138 | { 139 | b = map(b => b + 1, b) 140 | } 141 | } 142 | 143 | var run2 = tap(() => { n = (n + 1); handle.rs() }, b) 144 | 145 | runEffects(run1, newDefaultScheduler()) 146 | runEffects(run2, newDefaultScheduler()) 147 | 148 | return async () => 149 | { 150 | await handle.next(18) 151 | await handle.next(17) 152 | } 153 | }, 154 | } 155 | -------------------------------------------------------------------------------- /perf/perf.js: -------------------------------------------------------------------------------- 1 | // https://github.com/maverick-js/observables TODO 2 | 3 | import { add } from 'benny' 4 | import { suite } from 'benny' 5 | import { cycle, complete } from 'benny' 6 | 7 | import fluh from './fluh.perf' 8 | import flyd from './flyd.perf' 9 | import rxjs from './rxjs.perf' 10 | import most from './most.perf' 11 | 12 | 13 | xSuite() 14 | 15 | async function benchmark () 16 | { 17 | 18 | await Suite('zero', 19 | [ 20 | add('zero', () => 21 | { 22 | var n = 1 23 | var emit = (m) => { n = (n * m) } 24 | return () => 25 | { 26 | emit(-1) 27 | } 28 | }), 29 | ]) 30 | 31 | await xSuite('special', 32 | [ 33 | add('deep linear', fluh.deep_linear), 34 | add('deep linear many', fluh.deep_linear_many), 35 | ]) 36 | 37 | await Suite('diamond', 38 | [ 39 | add('diamond (FLUH)', fluh.diamond), 40 | add('diamond (flyd)', flyd.diamond), 41 | add('diamond (most)', most.diamond), 42 | add('diamond (rxjs)', rxjs.diamond), 43 | ]) 44 | 45 | await Suite('merge', 46 | [ 47 | add('merge (FLUH)', fluh.merge), 48 | add('merge (flyd)', flyd.merge), 49 | add('merge (most)', most.merge), 50 | add('merge (rxjs)', rxjs.merge), 51 | ]) 52 | 53 | await Suite('deep_linear', 54 | [ 55 | add('deep linear (FLUH)', fluh.deep_linear), 56 | add('deep linear (flyd)', flyd.deep_linear), 57 | add('deep linear (most)', most.deep_linear), 58 | add('deep linear (rxjs)', rxjs.deep_linear), 59 | ]) 60 | 61 | await Suite('triangle_triangle', 62 | [ 63 | add('triangle in triangle (FLUH)', fluh.triangle_triangle), 64 | add('triangle in triangle (flyd)', flyd.triangle_triangle), 65 | add('triangle in triangle (most)', most.triangle_triangle), 66 | add('triangle in triangle (rxjs)', rxjs.triangle_triangle), 67 | ]) 68 | 69 | await Suite('shortcut', 70 | [ 71 | add('shortcut (FLUH)', fluh.shortcut), 72 | add('shortcut (flyd)', flyd.shortcut), 73 | add('shortcut (most)', most.shortcut), 74 | add('shortcut (rxjs)', rxjs.shortcut), 75 | ]) 76 | 77 | } 78 | 79 | 80 | function xSuite () {} 81 | 82 | function Suite (name, cases) 83 | { 84 | return suite( 85 | name, 86 | ...cases, 87 | 88 | cycle(), 89 | complete(), 90 | /*save( 91 | { 92 | folder: 'perf/chart', 93 | file: name, 94 | format: 'chart.html', 95 | }),*/ 96 | ) 97 | } 98 | 99 | 100 | benchmark() 101 | -------------------------------------------------------------------------------- /perf/rxjs.perf.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-statements-per-line */ 2 | 3 | import { Observable } from 'rxjs' 4 | import { combineLatest } from 'rxjs' 5 | import { map } from 'rxjs/operators' 6 | import { filter } from 'rxjs/operators' 7 | import { merge } from 'rxjs/operators' 8 | 9 | function Handle () 10 | { 11 | var emits = new Set 12 | var source = new Observable(sub => 13 | { 14 | var emit = (v) => sub.next(v) 15 | emits.add(emit) 16 | return () => emits.delete(emit) 17 | }) 18 | 19 | var handle = 20 | { 21 | rs: null, 22 | source, 23 | next, 24 | } 25 | 26 | function next (value) 27 | { 28 | var p = new Promise(rs => { handle.rs = rs }) 29 | emits.forEach(emit => emit(value)) 30 | return p 31 | } 32 | 33 | return handle 34 | } 35 | 36 | 37 | export default 38 | { 39 | diamond () 40 | { 41 | var n = 1 42 | 43 | var handle = Handle() 44 | var A = handle.source 45 | 46 | var b = A.pipe(map(a => a + 1)) 47 | var c = b.pipe(map(b => b + 1)) 48 | var d = combineLatest(A, c).pipe(map(([A, c]) => A + c + 1)) 49 | 50 | d.subscribe(() => { n = (n + 1); handle.rs() }) 51 | 52 | return () => 53 | { 54 | return handle.next(17) 55 | } 56 | }, 57 | 58 | merge () 59 | { 60 | var n = 1 61 | 62 | var h1 = Handle() 63 | var a = h1.source 64 | 65 | var h2 = Handle() 66 | var b = h2.source 67 | 68 | var c = a.pipe(merge(b)) 69 | 70 | c.subscribe(x => { n = (n + 1); if (x % 2) h1.rs(); else h2.rs() }) 71 | 72 | return async () => 73 | { 74 | await h1.next(17) 75 | await h2.next(18) 76 | } 77 | }, 78 | 79 | deep_linear () 80 | { 81 | var n = 1 82 | 83 | var handle = Handle() 84 | 85 | var b = handle.source 86 | for (var n = 0; n < 100; n++) 87 | { 88 | b = b.pipe(map(b => b + 1)) 89 | } 90 | 91 | b.subscribe(() => { n = (n + 1); handle.rs() }) 92 | 93 | return () => 94 | { 95 | return handle.next(17) 96 | } 97 | }, 98 | 99 | triangle_triangle () 100 | { 101 | var n = 1 102 | 103 | var handle = Handle() 104 | var a = handle.source 105 | 106 | var b1 = a.pipe(map(a => a + 1)) 107 | var c1 = combineLatest(a, b1).pipe(map(([a, b]) => a + b + 1)) 108 | 109 | var b2 = b1.pipe(map(b => b + 1)) 110 | var c2 = combineLatest(b2, c1).pipe(map(([b, c]) => b + c + 1)) 111 | 112 | c2.subscribe(() => { n = (n + 1); handle.rs() }) 113 | 114 | return () => 115 | { 116 | return handle.next(17) 117 | } 118 | }, 119 | 120 | shortcut () 121 | { 122 | var n = 1 123 | 124 | var handle = Handle() 125 | 126 | var b = handle.source 127 | for (var N = 0; N < 100; N++) 128 | { 129 | if (N === 10) 130 | { 131 | b.subscribe(b => ((b % 2) || handle.rs())) 132 | b = b.pipe(filter(b => Boolean(b % 2))) 133 | } 134 | else 135 | { 136 | b = b.pipe(map(b => b + 1)) 137 | } 138 | } 139 | 140 | b.subscribe(() => { n = (n + 1); handle.rs() }) 141 | 142 | return async () => 143 | { 144 | await handle.next(18) 145 | await handle.next(17) 146 | } 147 | }, 148 | } 149 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # fluh 2 | 3 | [![npm|fluh](http://img.shields.io/badge/npm-fluh-CB3837.svg?style=flat-square)](https://www.npmjs.org/package/fluh) 4 | [![typescript](http://img.shields.io/badge/type-script-0074C1.svg?style=flat-square)](#typescript) 5 | 6 | > Functional reactive library with atomic push strategy 7 | 8 | This library is designed to be easy to learn and simple to use. It may be meant as an answer to more complex systems which demand a lot from you even while solving simplest tasks. 9 | 10 | fluh is sync-by-default event graph library with only push strategy. This particular design is easy to reason about. Most of the time it will work just as you expect it to work: reactive nodes pass values to dependents and fires effects after that. The order of firing is deterministic and straighforward. 11 | 12 | The API is designed in a composable way, which means that it is really easy to create new transformations and utilities without tapping into the core. The API borrows heavily from functional paradigm, but without becoming it a burden. You still can use impure and stateful code, if you need. You can write code from left to right: there is chainable API and you don't need to use data-last functions if you don't want to. 13 | 14 | The package is now mature enough for others to use it. You're [welcome to try it](#to-users-and-contributors). 15 | 16 | 17 | 18 | - [The idea](#the-idea) 19 | - [API](#api) 20 | - [Bud](#bud) 21 | - [derivatives](#derivatives) 22 | - [merging](#merging) 23 | - [high-order](#high-order) 24 | - [effects](#effects) 25 | - [resources](#resources) 26 | - [Interesting reading](#interesting-reading) 27 | - [atomic updates](#atomic-updates) 28 | - [`map` with Nothing and Many](#map-with-nothing-and-many) 29 | - [high-order transformations](#high-order-transformations) 30 | - [handling errors](#handling-errors) 31 | - [handling promises](#handling-promises) 32 | - [TypeScript](#typescript) 33 | - [Examples](#examples) 34 | - [To Users and Contributors](#to-users-and-contributors) 35 | - [License](#license) 36 | 37 | 38 | ## The idea 39 | 40 | When thinking of reactive stuff there're multiple spaces of decisions in which you have to make a choice. 41 | 42 | * Is it push or pull? 43 | * Is it unicast or multicast? 44 | * Is it always live or does it depend on subscribers' presense? 45 | * Does stream have value at any time? 46 | * Is it sync or async? 47 | * How errors should be handled? 48 | * Does stream end? 49 | * Is data graph static or dynamic? 50 | 51 | Choosing some decisions will lead to a specific reactive system. 52 | Watch [this speech](https://www.youtube.com/watch?v=Agu6jipKfYw) about this vast decision space. 53 | 54 | **fluh** is inspired by [flyd](https://github.com/paldepind/flyd) in general. I want to use the reactivity for request-response systems, command-line applications and (especially) UIs. For that scope push strategy is good. 55 | 56 | The main unit is called `Bud`. 57 | * Bud is a push-strategy FRP unit for creating data (event) streams. 58 | * Bud is always active — it just emits values and pushes them to the dependents, no [backpressure](https://nodejs.org/en/docs/guides/backpressuring-in-streams/), no [cold streams](https://medium.com/@benlesh/hot-vs-cold-observables-f8094ed53339) or pulling-on-demand. 59 | * Bud is a Stream, but contain single (current) value, similar to Behavior. It's not an actual Behavior, because it cannot alterate continuously, like real one. 60 | This design is a simplification for my practical needs described above. 61 | See the [explanation concerning Behaviors and Streams](https://github.com/funkia/hareactive/tree/e40dddd1b6d55e59a9d9bf319b7d426566cbfd8d#conceptual-overview). 62 | Stream is a push, Behavior is a pull, there's a distinction. 63 | * Bud is initialized with special `Nothing` value, means no value. It is an explicit value, so you are free to emit `null` or `undefined` in your streams if you need to. 64 | * When Bud acquire value it propagates it to *effects* and *dependents*. If Bud already has value, newly created effects and dependents will be notified immediately with that value. 65 | * Effects run when all dependents had been updated, so, from effects' perspective the graph's all current values change atomically and are always in a consistent state. 66 | * Data graph is sync by default, but you are free to use async operators (defer, delay, raf) or create your own (like throttle, debounce, nextTick…). 67 | * Data graph is multicast — multiple dependencies does not create copies of the source and are just subscribed to that single source. It also solves [diamond problem](#atomic-updates) for you. 68 | * Data graph is optimized for static usage, however, you can create new streams dynamically. Streams' disposal triggered by emitting `End` on a stream. If `End` is received, the graph would be cleaned from terminated stream as well as certain streams' cross-references, which opens a way for a garbage collection. Streams that are both terminated and non-referenced by user become able to be garbage collected. In static graph memory consumption would remain on a stable level. 69 | * Can be pure or impure, depending on the usage. You can create stateful elements to solve complex tasks, like running totals or rate limiting. 70 | 71 | If you think some of that decisions are not good, there's a great family of different opinions. Check out at least theese great ones: 72 | [most.js](https://github.com/cujojs/most), 73 | [bacon.js](https://github.com/baconjs/bacon.js), 74 | [flyd](https://github.com/paldepind/flyd), 75 | [hareactive](https://github.com/funkia/hareactive), 76 | [xstream](https://github.com/staltz/xstream), 77 | [graflow](https://github.com/pmros/graflow), 78 | [S.js](https://github.com/adamhaile/S), 79 | [RX](https://github.com/ReactiveX/rxjs), 80 | [pull-stream](https://github.com/pull-stream/pull-stream), 81 | [Highland](https://github.com/caolan/highland), 82 | [MobX](https://mobx.js.org/). 83 | 84 | ## API 85 | ### Bud 86 | 87 | Bud is a main FRP unit. 88 | 89 | ```js 90 | /* empty Bud (Nothing) */ 91 | const bud = Bud() 92 | 93 | /* Bud with value */ 94 | const bud = Bud('value') 95 | 96 | /* emit new values */ 97 | bud.emit('value 1').emit('value 2') 98 | ``` 99 | 100 | ### derivatives 101 | 102 | * Create derived streams by using `const new_bud = join(...buds, (...bud_values) => new_value)`. 103 | * `emit`'s are propagated through the dependents. 104 | * By using `join` we guarantee that every dependency changes at most once for single `emit`. See [atomic updates](#atomic-updates). 105 | * `join(bud, fn)` is a `map`. 106 | * You can skip values, returning `Nothing`, works like `filter`. Further dependencies will 107 | not be touched. 108 | * You can pass more than one value using `Many(...values)`. Additional values will be handled one after another atomically (for the whole dependent subtree) and synchronously. 109 | * It is better to not performing side-effects inside `join`'s transformer. It is possible but 110 | it's better to do it as an effect (`on`). 111 | * All effects run after all propagations. From effects' perspective graph is always in a consistent state, corresponding to the most recently propagated value. Graph always changes atomically. 112 | * `bud.map(fn)` is a shortcut for `join` deriving from a single Bud. 113 | 114 | ```js 115 | const a = Bud() 116 | 117 | /* derive from `a` (map) */ 118 | const b = join(a, (a) => a + 'b') 119 | 120 | /* derive from both `a` and `b` */ 121 | const c = join(a, b, (a, b) => a + b + 'c') 122 | 123 | /* skip (filter out, reject) values */ 124 | const n = join(a, (a) => Nothing) 125 | 126 | /* return two values for each input (like transducer) */ 127 | const n = join(a, (a) => Many(a, a + 'x')) 128 | 129 | /* derive from single Bud `a` */ 130 | const b = a.map((a) => a + 'b') 131 | ``` 132 | 133 | ### merging 134 | 135 | * As an alternative to `join` where you join several streams by function of corresponding arity it is possible to just merge values of several streams into a single stream. 136 | * This is another way to create derivatives and it shares all the basic properties: sync, atomic, side effects run after all propagations. 137 | * The difference is that you can't use a joiner function and you must take care of `End` by your own, since any `End` will end the merged stream. 138 | * In case of multiple simultaneous inputs all of them would be passed down merged stream in the order of inputs from left to right. The resulting value would be the most right one. 139 | 140 | ```js 141 | const a = Bud() 142 | const b = Bud() 143 | 144 | /* merge all from `a` and `b` */ 145 | const c = merge(a, b) 146 | 147 | /* diamond is also possible */ 148 | const a = Bud() 149 | const b = join(a, (a) => a + 'b') 150 | const c = join(a, (a) => a + 'c') 151 | const d = merge(b, c) 152 | ``` 153 | 154 | ### high-order 155 | 156 | * Use `thru` for functions which accept Bud and return new Bud. 157 | * high-order `thru` transformers good when you can't express transformation in terms of `map`. 158 | 159 | ```js 160 | const a = Bud() 161 | 162 | /* delay must return function from Bud to Bud */ 163 | const b = a.thru(delay(50)) 164 | ``` 165 | 166 | ### effects 167 | 168 | * Attach effects to Bud by using `bud.on(fn)`. 169 | * Effects are fired in straight-by-attach order. 170 | * Effects on certain Bud run after propagating event to all dependents and before effects on that dependents. 171 | * In effect you can re-`emit` values on another Bud (or even that one), but taking care of order of emits and infinite loops are on your own. 172 | * Re-emitting will happen only after current emit is done for the whole dependent subtree. 173 | * `bud.on(fn)` returns disposer function. 174 | 175 | ```js 176 | const a = Bud() 177 | 178 | /* subscribe to changes */ 179 | const disposer = a.on((value) => console.log('a:', value)) 180 | 181 | /* disposing of the effect */ 182 | disposer() 183 | ``` 184 | 185 | ### resources 186 | 187 | * If you want to create a Bud binded to some disposable event source, use `resource`. 188 | * Such Bud will dispose underlying resource when it receives `End`. 189 | * Provide to `resource` a function which initiates resource and returns disposer function. Function itself will be provided with emit function (as a first argument) and a newly created Bud (as a second). 190 | 191 | ```js 192 | /* create Bud from DOM Event */ 193 | function dom_event (element, eventname) { 194 | return resource((emit) => { 195 | element.addEventListener(eventname, emit) 196 | 197 | return function disposer () { 198 | if (! element) return 199 | 200 | element.removeEventListener(eventname, emit) 201 | 202 | /* allow gc to release Bud and target element earlier */ 203 | element = null 204 | emit = null 205 | } 206 | }) 207 | } 208 | 209 | /* create Bud from interval timer */ 210 | function interval (ms) { 211 | return resource((emit) => { 212 | let t = setInterval(emit, ms) 213 | 214 | return function disposer () { 215 | if (! t) return 216 | 217 | clearInterval(t) 218 | 219 | t = null 220 | emit = null 221 | } 222 | }) 223 | } 224 | ``` 225 | 226 | ## Interesting reading 227 | ### atomic updates 228 | fluh just like flyd solves atomic updates problem. This means that in case of graph `A → B, A → C, B & C → D` stream `D` indirectly depends twice on `A`, via `B` and `C`. fluh guarantees that in case of single emission on `A` dependent `D` would recieve update only once, with two updated values from `B` and `C`. 229 | 230 | To do this, fluh recursively collects all dependencies of any `A` and orders them topologically. That `order` is lazily cached and is in use until graph changes. This gives excellent results for static graphs and optimal reordering when graph changes rarely. 231 | 232 | `order` is used as a basis for cycle, so all dependencies will be updated in single pass, without using recursion. 233 | 234 | See also [flyd's atomic updates](https://github.com/paldepind/flyd/tree/180e8f5b859ac9ae388a193d334e94f03e02feef#atomic-updates). 235 | 236 | ### `map` with Nothing and Many 237 | fluh's `bud.map(fn)` is very similar to [functor protocol](https://github.com/fantasyland/fantasy-land#functor), however, with additional features. The thing with usual `map` is that it always returns single value, mapping functor from one value to another. If you need to skip values or add another values you need to use something like `filter` or `flatMap`. In some cases this is not enough and you need to address more complex tasks with the help of `reduce` or [transducers](https://github.com/cognitect-labs/transducers-js). 238 | 239 | fluh's `map` works in three ways: 240 | 1. Ordinary map (return any values). 241 | 2. Skip values. Return special symbol `Nothing` and current value will be skipped. This means no updates on dependencies, value would be just eliminated from flow. 242 | 3. Return multiple values with `Many(...values)`. `Many`, just like `Nothing` is a special type, so no collisions with arrays or other objects. If instance of `Many` is returned from `map` it will propagate further first value from it and, after graph is updated atomically, emit following values as additional usual emits. 243 | 244 | So `map` covers all cases for `map`, `filter` and `flatMap` in a common manner. 245 | 246 | ### high-order transformations 247 | In practice, `map` covers most of the cases, but there're may be advanced tasks when you need to take a Bud, transform it (for instance, involving state) and return modified Bud: `const new_bud = transform(bud)`. 248 | 249 | In order to do this, fluh has `bud.thru(transform)` which accepts function from one Bud to another and returns result of invocation that function on this particular Bud. 250 | 251 | Here's the example of how it can be used to make Bud async by default (by creating new dependent Bud which receives updates asynchronously): 252 | 253 | ```js 254 | function defer (bud) { 255 | const deferred = bud.constructor() 256 | 257 | bud.on((value) => { 258 | setTimeout(() => { 259 | deferred.emit(value) 260 | } 261 | , 0) 262 | }) 263 | 264 | return deferred 265 | } 266 | ``` 267 | 268 | Then use it via `thru`: 269 | ```js 270 | const a = Bud(1) 271 | const b = a.thru(defer) 272 | a.emit(2) 273 | ``` 274 | 275 | fluh exposes special helper for easier implementation of high-order transformations, called `lib/trasfer`. In terms of `transfer` previous `defer` example may be implemented in such manner: 276 | ```js 277 | function defer (bud) { 278 | return transfer(bud, (value, emit) => { 279 | setTimeout(() => emit(value), 0) 280 | }) 281 | } 282 | ``` 283 | 284 | ### handling errors 285 | fluh does not capture throws by default, but you can make any function to do that, by decorating it with `capture`: 286 | ```js 287 | const a = Bud() 288 | 289 | import { capture } from 'fluh' 290 | 291 | const b = a.map(capture((x) => { 292 | /* throws in this function will be captured: */ 293 | /* … throw e … */ 294 | 295 | return x + 1 296 | })) 297 | ``` 298 | From now, any throws will return raised error as normal returns instead. 299 | 300 | Note that such function will return mixed data/error content. There's no special values aside from `Nothing`, `Many` and `End`. fluh treats `Error` objects as normal data, so you'll need additional steps to handle them. 301 | 302 | ```js 303 | import { when_data } from './map/when' 304 | 305 | /* `when_data` allows to work with data in pure manner, */ 306 | /* passing past any `Error` instances and `End` */ 307 | /* only real data passed to target function */ 308 | const c = b.map(when_data((b) => b + 1)) 309 | ``` 310 | 311 | There's no special error channel, use mixed content in combine with helper above if you need to handle errors. If you want a more pure approach, bring your own `Either`/`Result` container. 312 | 313 | ### handling promises 314 | fluh is sync by default. This decision makes whole graph predictable, allows to atomically update and opens a way for performance optimizations. Promise is just a regular value for fluh, so, in order to extract value from it, special high-order transformation is required. Such transformation will always resolve asynchronously, even if promise is already resolved. 315 | 316 | fluh supports three strategies for resolving promises: 317 | * `every` — every promise value passed to this transformation will instantly passthrough. In that case, no events lost and no additional memory is used, however, the order of resolved values may not be identical to the order of corresponding promises due to the race condition. 318 | * `last` — only last recieved promise value is passed through, if previous promise was not resolved, its value would be ignored. In that case, the resolution order is preserved, no additional memory is used, however, some promise values may be lost. 319 | * `buffered(N)` — store `N` recent promises and resolve them in order. If some promises was not resolved and they exceed `N` when new promises received, the older ones will be ignored. In that case, the resolution order is preserved, and up to `N` simultaneous racing promises are guaranteed to be passed through, however, if more simultaneous promises received, some of them still be lost. 320 | 321 | fluh promise transformations treats promise rejections as data values. So, the transformations will emit mixed data/error content. You'll need `when_data` to handle them. 322 | 323 | ## TypeScript 324 | This package has TypeScript definitions built in. The code is still vanilla JavaScript for the sake of speed and control. 325 | 326 | ## Examples 327 | Learn by [examples](examples/). You can run `examples/set-interval.ts` example via `npm run example set-interval`. Run all examples by `npm run examples`. 328 | 329 | ## To Users and Contributors 330 | This is a project of mine to prove that simplified approach to FRP is viable. I develop and use this in private projects for a while (in fact, there're at least two years of various development activities behind). It works quite fine, it's very well tested and there're no strange and unexpected behaviors I've struggled with. The performance is good as well. 331 | 332 | However, it lacks testing in battle conditions. There may be absence of operators you may deem to be must-have. Type definitions most likely to be not precise enough or even be incorrect. If you want to try this project you're welcome to try. Creating new transformations is easy in terms of `map` and `thru`. I would accept fixes, of course (for instance, better typings). 333 | 334 | I also would accept new operators in most of the cases. The rule of thumb is that they must be generic enough (anything underscore-like or rxjs-like) and they must not have dependencies on their own. It is OK to have them in the base package, because it is not a burden for a consumer in server-side (HDD) nor in client-side (tree-shaking). If the new operator does depend on something (like `throttle`) it is better to create standalone package (like `fluh-throttle` or `fluh-ratelimit` for `throttle`/`debounce`/etc… in one package). Such package should have direct dependency on `throttle`/etc… and peer dependency on `fluh`. 335 | 336 | If you'd have any problems while setting up a development copy of this package, I would easily help with this as well. 337 | 338 | ## License 339 | ISC, © Strider, 2022. 340 | -------------------------------------------------------------------------------- /test/.eslintrc.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = 3 | { 4 | env: 5 | { 6 | mocha: true, 7 | }, 8 | 9 | globals: 10 | { 11 | expect: true, 12 | spy: true, 13 | log: true, 14 | }, 15 | 16 | rules: 17 | { 18 | 'no-unused-expressions': 0, 19 | 'max-statements': [ 1, 35 ], 20 | 'node/no-missing-import': 0, 21 | 'node/no-unsupported-features/es-syntax': 0, 22 | }, 23 | } 24 | -------------------------------------------------------------------------------- /test/Bud.test.js: -------------------------------------------------------------------------------- 1 | 2 | import Bud from 'fluh/lib/Bud' 3 | 4 | import Nothing from 'fluh/lib/Nothing' 5 | import Many from 'fluh/lib/Many' 6 | import End from 'fluh/lib/End' 7 | 8 | import domain from 'fluh/lib/_/domain' 9 | 10 | 11 | describe('Bud', () => 12 | { 13 | it('Bud.is', () => 14 | { 15 | expect(Bud.is(Bud())).true 16 | expect(Bud.is(Bud(false))).true 17 | 18 | expect(Bud.is(17)).false 19 | expect(Bud.is({})).false 20 | expect(Bud.is({ x: 1 })).false 21 | expect(Bud.is({ constructor: Bud })).false 22 | expect(Bud.is(true)).false 23 | expect(Bud.is([ 1, 2, 3 ])).false 24 | }) 25 | 26 | it('Bud()', () => 27 | { 28 | var bud = Bud() 29 | 30 | state(bud) 31 | }) 32 | 33 | it('Bud(Nothing)', () => 34 | { 35 | var bud = Bud(Nothing) 36 | 37 | state(bud) 38 | }) 39 | 40 | it('Bud(value)', () => 41 | { 42 | var bud = Bud(1917) 43 | 44 | state(bud, { value: 1917 }) 45 | }) 46 | 47 | it('Bud(undefined)', () => 48 | { 49 | var bud = Bud(void 0) 50 | 51 | /* state(bud, { value: void 0 }) */ 52 | expect(bud.value).eq(void 0) 53 | }) 54 | 55 | it('Bud(Many)', () => 56 | { 57 | var bud = Bud(Many(17, 1917)) 58 | state(bud, { value: 1917 }) 59 | }) 60 | 61 | it('Bud(Many with Nothing)', () => 62 | { 63 | var bud = Bud(Many(Nothing, Nothing, Nothing)) 64 | state(bud) 65 | 66 | var bud = Bud(Many(17, Nothing, 1917)) 67 | state(bud, { value: 1917 }) 68 | 69 | var bud = Bud(Many(Nothing, 17, 1917)) 70 | state(bud, { value: 1917 }) 71 | 72 | var bud = Bud(Many(17, 1917, Nothing)) 73 | state(bud, { value: 1917 }) 74 | }) 75 | 76 | it('Bud(Many with End)', () => 77 | { 78 | var bud = Bud(Many(End, End, End)) 79 | state(bud, { value: End }) 80 | 81 | var bud = Bud(Many(17, End, 1917)) 82 | state(bud, { value: End }) 83 | 84 | var bud = Bud(Many(End, 17, 1917)) 85 | state(bud, { value: End }) 86 | 87 | var bud = Bud(Many(17, 1917, End)) 88 | state(bud, { value: End }) 89 | }) 90 | 91 | it('emit', () => 92 | { 93 | var bud = Bud() 94 | 95 | var r = bud.emit(1917) 96 | 97 | expect_bud(bud) 98 | expect_bud(r) 99 | 100 | expect(r).eq(bud) 101 | 102 | state(bud, { value: 1917 }) 103 | }) 104 | 105 | it('emit multiple', () => 106 | { 107 | var bud = Bud() 108 | 109 | var s = spy() 110 | bud.on(s) 111 | 112 | bud.emit(17).emit(1917) 113 | 114 | state(bud, { value: 1917 }) 115 | expect(s.callCount).eq(2) 116 | }) 117 | 118 | it('emit Nothing', () => 119 | { 120 | var bud = Bud() 121 | 122 | var s = spy() 123 | bud.on(s) 124 | 125 | bud.emit(1917) 126 | state(bud, { value: 1917 }) 127 | 128 | bud.emit(Nothing) 129 | /* value remains the same as before */ 130 | state(bud, { value: 1917 }) 131 | 132 | expect(s.callCount).eq(1) 133 | 134 | bud.emit() /* Nothing */ 135 | state(bud, { value: 1917 }) 136 | 137 | expect(s.callCount).eq(1) 138 | }) 139 | 140 | it('emit undefined', () => 141 | { 142 | var bud = Bud() 143 | 144 | var s = spy() 145 | bud.on(s) 146 | 147 | bud.emit(void 0) 148 | /* state(bud, { value: void 0 }) */ 149 | expect(bud.value).eq(void 0) 150 | 151 | bud.emit(void 0) 152 | /* state(bud, { value: void 0 }) */ 153 | expect(bud.value).eq(void 0) 154 | 155 | expect(s.callCount).eq(2) 156 | }) 157 | 158 | it('emit Many', () => 159 | { 160 | var bud = Bud() 161 | 162 | var s = spy() 163 | bud.on(s) 164 | 165 | bud.emit(Many(17, 1917)) 166 | 167 | state(bud, { value: 1917 }) 168 | expect(s.callCount).eq(2) 169 | }) 170 | 171 | it('emit Many with Nothing', () => 172 | { 173 | var bud = Bud() 174 | 175 | var s = spy() 176 | bud.on(s) 177 | 178 | bud.emit(Many(Nothing, Nothing, Nothing)) 179 | 180 | state(bud) 181 | expect(s.callCount).eq(0) 182 | }) 183 | 184 | it('emit Many with leading Nothing', () => 185 | { 186 | var bud = Bud() 187 | 188 | var s = spy() 189 | bud.on(s) 190 | 191 | bud.emit(Many(Nothing, 17, 1917)) 192 | 193 | state(bud, { value: 1917 }) 194 | expect(s.callCount).eq(2) 195 | }) 196 | }) 197 | 198 | 199 | export function expect_bud (bud) 200 | { 201 | expect(bud).an('object') 202 | 203 | expect(bud.constructor).eq(Bud) 204 | 205 | expect(bud).property('id') 206 | expect(bud.id).a('string') 207 | expect(bud.id).match(/^#\d+$/) 208 | 209 | expect(bud).property('value') 210 | 211 | expect(bud.sample).a('function') 212 | expect(bud.emit).a('function') 213 | expect(bud.on).a('function') 214 | expect(bud.map).a('function') 215 | expect(bud.thru).a('function') 216 | } 217 | 218 | export function state (bud, descr = {}) 219 | { 220 | expect_bud(bud) 221 | 222 | var { value = Nothing } = descr 223 | 224 | var { deps = [] } = descr 225 | var { rev = void 0 } = descr 226 | var { order = [] } = descr 227 | 228 | expect(bud.value).deep.eq(value) 229 | expect(bud.sample()).deep.eq(value) 230 | 231 | var st = domain.state(bud) 232 | 233 | expect(st.direct).deep.eq(deps) 234 | expect(st.reverse).deep.eq(rev) 235 | expect(st.order).deep.eq(order) 236 | } 237 | -------------------------------------------------------------------------------- /test/End.test.js: -------------------------------------------------------------------------------- 1 | 2 | import Bud from 'fluh/lib/Bud' 3 | import Many from 'fluh/lib/Many' 4 | import End from 'fluh/lib/End' 5 | import { when_data } from 'fluh/map/when' 6 | 7 | import { state } from './Bud.test' 8 | 9 | 10 | describe('End', () => 11 | { 12 | it('ends and ignores other', () => 13 | { 14 | var a = Bud() 15 | 16 | var b = a.map(v => v) 17 | 18 | var rs = [] 19 | b.on(value => rs.push(value)) 20 | 21 | var s = spy() 22 | b.on(s) 23 | 24 | state(a, 25 | { 26 | deps: [ b ], 27 | order: [ b ], 28 | }) 29 | state(b, 30 | { 31 | rev: [ a ] 32 | }) 33 | 34 | a.emit(1) 35 | a.emit(2) 36 | a.emit(3) 37 | 38 | state(a, 39 | { 40 | value: 3, 41 | deps: [ b ], 42 | order: [ b ], 43 | }) 44 | state(b, 45 | { 46 | value: 3, 47 | rev: [ a ] 48 | }) 49 | 50 | a.emit(End) 51 | a.emit(4) 52 | a.emit(End) 53 | a.emit(5) 54 | a.emit(End) 55 | 56 | state(a, { value: End }) 57 | state(b, { value: End }) 58 | 59 | expect(rs).deep.eq([ 1, 2, 3, End ]) 60 | expect(s.callCount).eq(4) 61 | }) 62 | 63 | it('End in map', () => 64 | { 65 | var a = Bud() 66 | var b = a.map(v => (v > 3) && End || v) 67 | 68 | var rs = [] 69 | b.on(value => rs.push(value)) 70 | 71 | state(a, 72 | { 73 | deps: [ b ], 74 | order: [ b ], 75 | }) 76 | state(b, 77 | { 78 | rev: [ a ] 79 | }) 80 | 81 | a.emit(1) 82 | a.emit(2) 83 | a.emit(3) 84 | 85 | state(a, 86 | { 87 | value: 3, 88 | deps: [ b ], 89 | order: [ b ], 90 | }) 91 | state(b, 92 | { 93 | value: 3, 94 | rev: [ a ] 95 | }) 96 | 97 | a.emit(4) 98 | 99 | state(a, 100 | { 101 | value: 4, 102 | deps: [], 103 | order: [], 104 | }) 105 | state(b, { value: End }) 106 | 107 | a.emit(5) 108 | 109 | state(a, 110 | { 111 | value: 5, 112 | deps: [], 113 | order: [], 114 | }) 115 | state(b, { value: End }) 116 | }) 117 | 118 | it('End work in the middle of Many', () => 119 | { 120 | var a = Bud() 121 | 122 | var b = a.map(v => v) 123 | 124 | var rs = [] 125 | b.on(value => rs.push(value)) 126 | 127 | var s = spy() 128 | b.on(s) 129 | 130 | a.emit(1) 131 | a.emit(Many(2, 3, End, 4)) 132 | a.emit(5) 133 | a.emit(Many(6, 7)) 134 | 135 | state(a, { value: End }) 136 | state(b, { value: End }) 137 | 138 | expect(rs).deep.eq([ 1, 2, 3, End ]) 139 | expect(s.callCount).eq(4) 140 | }) 141 | 142 | it('initially ended', () => 143 | { 144 | var a = Bud(End) 145 | 146 | var b = a.map(v => v) 147 | 148 | var rs = [] 149 | b.on(value => rs.push(value)) 150 | 151 | var s = spy() 152 | b.on(s) 153 | 154 | state(a, { value: End }) 155 | state(b, { value: End }) 156 | 157 | a.emit(1) 158 | a.emit(2) 159 | a.emit(3) 160 | a.emit(4) 161 | a.emit(End) 162 | a.emit(5) 163 | a.emit(End) 164 | 165 | state(a, { value: End }) 166 | state(b, { value: End }) 167 | 168 | expect(rs).deep.eq([ End ]) 169 | expect(s.callCount).eq(1) 170 | }) 171 | 172 | it('finalizes correctly if cache is flushed', () => 173 | { 174 | var a1 = Bud() 175 | var b1 = a1.map(when_data(x => x + 1)) 176 | 177 | var a2 = Bud() 178 | var b2 = a2.map(when_data(x => x + 1)) 179 | 180 | a1.emit(1) 181 | a2.emit(1) 182 | 183 | a1.emit(End) 184 | a2.emit(End) 185 | 186 | state(a1, { value: End }) 187 | state(b1, { value: End }) 188 | state(a2, { value: End }) 189 | state(b2, { value: End }) 190 | }) 191 | }) 192 | -------------------------------------------------------------------------------- /test/Many.test.js: -------------------------------------------------------------------------------- 1 | 2 | import Many from 'fluh/lib/Many' 3 | 4 | 5 | describe('Many', () => 6 | { 7 | it('Many()', () => 8 | { 9 | var many = Many(1, 2, 3) 10 | 11 | expect(many).an('array') 12 | expect(many).deep.eq([ 1, 2, 3 ]) 13 | 14 | expect(Many.is(many)).true 15 | expect(Array.isArray(many)).true 16 | }) 17 | 18 | it('Array is not Many', () => 19 | { 20 | var fake = [ 1, 2, 3 ] 21 | 22 | expect(fake).an('array') 23 | expect(fake).deep.eq([ 1, 2, 3 ]) 24 | 25 | expect(Many.is(fake)).false 26 | expect(Array.isArray(fake)).true 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /test/cache.test.js: -------------------------------------------------------------------------------- 1 | 2 | import Bud from 'fluh/lib/Bud' 3 | 4 | import End from 'fluh/lib/End' 5 | 6 | import join from 'fluh/lib/join' 7 | 8 | import domain from 'fluh/lib/_/domain' 9 | 10 | 11 | describe('cache', () => 12 | { 13 | it('works', () => 14 | { 15 | var a = Bud() 16 | 17 | expect(domain.comp(a).order).deep.eq([]) 18 | 19 | var b = join(a, a => a) 20 | 21 | expect(domain.comp(a).order).deep.eq([ b ]) 22 | expect(domain.comp(b).order).deep.eq([]) 23 | 24 | var bs = spy() 25 | b.on(bs) 26 | 27 | a.emit(1) 28 | expect(bs.callCount).eq(1) 29 | 30 | var c = join(b, b => b) 31 | 32 | expect(domain.comp(a).order).deep.eq([ b, c ]) 33 | expect(domain.comp(b).order).deep.eq([ c ]) 34 | expect(domain.comp(c).order).deep.eq([]) 35 | 36 | var cs = spy() 37 | c.on(cs) 38 | 39 | a.emit(1) 40 | 41 | expect(bs.callCount).eq(2) 42 | expect(cs.callCount).eq(2) 43 | 44 | var d = join(a, c, (a, c) => a + c) 45 | 46 | expect(domain.comp(a).order).deep.eq([ b, c, d ]) 47 | expect(domain.comp(b).order).deep.eq([ c, d ]) 48 | expect(domain.comp(c).order).deep.eq([ d ]) 49 | expect(domain.comp(d).order).deep.eq([]) 50 | 51 | var ds = spy() 52 | d.on(ds) 53 | 54 | a.emit(1) 55 | 56 | expect(bs.callCount).eq(3) 57 | expect(cs.callCount).eq(3) 58 | expect(ds.callCount).eq(2) 59 | }) 60 | 61 | it('behavior on End', () => 62 | { 63 | var a = Bud() 64 | 65 | expect(domain.comp(a).order).deep.eq([]) 66 | 67 | var b = join(a, a => a) 68 | 69 | expect(domain.comp(a).order).deep.eq([ b ]) 70 | expect(domain.comp(b).order).deep.eq([]) 71 | 72 | var bs = spy() 73 | b.on(bs) 74 | 75 | a.emit(1) 76 | expect(bs.callCount).eq(1) 77 | 78 | var c = join(b, b => b) 79 | 80 | expect(domain.comp(a).order).deep.eq([ b, c ]) 81 | expect(domain.comp(b).order).deep.eq([ c ]) 82 | expect(domain.comp(c).order).deep.eq([]) 83 | 84 | var cs = spy() 85 | c.on(cs) 86 | 87 | a.emit(1) 88 | 89 | expect(bs.callCount).eq(2) 90 | expect(cs.callCount).eq(2) 91 | 92 | b.emit(End) 93 | 94 | expect(bs.callCount).eq(3) 95 | expect(cs.callCount).eq(3) 96 | 97 | expect(domain.comp(a).order).deep.eq([]) 98 | expect(domain.comp(b).order).deep.eq([]) 99 | expect(domain.comp(c).order).deep.eq([]) 100 | }) 101 | }) 102 | -------------------------------------------------------------------------------- /test/capture.test.js: -------------------------------------------------------------------------------- 1 | 2 | import Bud from 'fluh/lib/Bud' 3 | 4 | import End from 'fluh/lib/End' 5 | import concat from 'fluh/lib/concat' 6 | import asap from 'fluh/lib/asap' 7 | 8 | import capture from 'fluh/lib/capture' 9 | 10 | 11 | describe('capture', () => 12 | { 13 | it('capture', async () => 14 | { 15 | var a = Bud() 16 | var e = new Error('x') 17 | 18 | var b = a.map(capture(x => 19 | { 20 | if (x === 3) { throw e } 21 | 22 | return x 23 | })) 24 | 25 | asap(() => 26 | { 27 | a 28 | .emit(1) 29 | .emit(2) 30 | .emit(3) 31 | .emit(4) 32 | .emit(End) 33 | }) 34 | 35 | expect(await concat(b)).deep.eq( 36 | [ 37 | 1, 38 | 2, 39 | e, 40 | 4, 41 | End, 42 | ]) 43 | }) 44 | }) 45 | -------------------------------------------------------------------------------- /test/debug.test.js: -------------------------------------------------------------------------------- 1 | 2 | import { inspect } from 'util' 3 | 4 | import Bud from 'fluh/lib/Bud' 5 | 6 | 7 | function as_inspect (value, options) 8 | { 9 | return inspect(value, { colors: true, depth: 2, ...options }) 10 | } 11 | 12 | 13 | describe('Log', () => 14 | { 15 | var log 16 | var calls = [] 17 | var last = null 18 | 19 | var object1 = 20 | { 21 | x: 22 | { 23 | y: [ 1, 2, 3 ], 24 | }, 25 | } 26 | var object2 = 27 | { 28 | x: 29 | { 30 | y: 31 | { 32 | z: [ 1, 2, 3 ], 33 | } 34 | } 35 | } 36 | 37 | beforeEach(() => 38 | { 39 | log = console.log 40 | console.log = (...args) => 41 | { 42 | calls.push(args) 43 | last = args 44 | } 45 | }) 46 | 47 | afterEach(() => 48 | { 49 | console.log = log 50 | calls = [] 51 | last = null 52 | }) 53 | 54 | it('works', () => 55 | { 56 | var a = Bud() 57 | var ds = a.debug() 58 | 59 | expect(calls.length).eq(0) 60 | 61 | a.emit(object1) 62 | expect(last).deep.eq([ a.id, as_inspect(object1) ]) 63 | expect(calls.length).eq(1) 64 | 65 | a.emit(object2) 66 | expect(last).deep.eq([ a.id, as_inspect(object2) ]) 67 | expect(calls.length).eq(2) 68 | 69 | ds() 70 | 71 | a.emit(object2) 72 | expect(last).deep.eq([ a.id, as_inspect(object2) ]) 73 | expect(calls.length).eq(2) 74 | }) 75 | 76 | it('label', () => 77 | { 78 | var a = Bud() 79 | var ds = a.debug('label') 80 | 81 | expect(calls.length).eq(0) 82 | 83 | a.emit(object1) 84 | expect(last).deep.eq([ 'label', as_inspect(object1) ]) 85 | expect(calls.length).eq(1) 86 | 87 | a.emit(object2) 88 | expect(last).deep.eq([ 'label', as_inspect(object2) ]) 89 | expect(calls.length).eq(2) 90 | 91 | ds() 92 | 93 | a.emit(object2) 94 | expect(last).deep.eq([ 'label', as_inspect(object2) ]) 95 | expect(calls.length).eq(2) 96 | }) 97 | 98 | it('options', () => 99 | { 100 | var a = Bud() 101 | var ds = a.debug({ label: 'label', depth: 1 }) 102 | 103 | expect(calls.length).eq(0) 104 | 105 | a.emit(object1) 106 | expect(last).deep.eq([ 'label', as_inspect(object1, { depth: 1 }) ]) 107 | expect(calls.length).eq(1) 108 | 109 | a.emit(object2) 110 | expect(last).deep.eq([ 'label', as_inspect(object2, { depth: 1 }) ]) 111 | expect(calls.length).eq(2) 112 | 113 | ds() 114 | 115 | a.emit(object2) 116 | expect(last).deep.eq([ 'label', as_inspect(object2, { depth: 1 }) ]) 117 | expect(calls.length).eq(2) 118 | }) 119 | 120 | it('options', () => 121 | { 122 | var a = Bud() 123 | var ds = a.debug({ depth: 1 }) 124 | 125 | expect(calls.length).eq(0) 126 | 127 | a.emit(object1) 128 | expect(last).deep.eq([ a.id, as_inspect(object1, { depth: 1 }) ]) 129 | expect(calls.length).eq(1) 130 | 131 | a.emit(object2) 132 | expect(last).deep.eq([ a.id, as_inspect(object2, { depth: 1 }) ]) 133 | expect(calls.length).eq(2) 134 | 135 | ds() 136 | 137 | a.emit(object2) 138 | expect(last).deep.eq([ a.id, as_inspect(object2, { depth: 1 }) ]) 139 | expect(calls.length).eq(2) 140 | }) 141 | 142 | it('options', () => 143 | { 144 | var a = Bud() 145 | var ds = a.debug('label', { depth: 3 }) 146 | 147 | expect(calls.length).eq(0) 148 | 149 | a.emit(object1) 150 | expect(last).deep.eq([ 'label', as_inspect(object1, { depth: 3 }) ]) 151 | expect(calls.length).eq(1) 152 | 153 | a.emit(object2) 154 | expect(last).deep.eq([ 'label', as_inspect(object2, { depth: 3 }) ]) 155 | expect(calls.length).eq(2) 156 | 157 | ds() 158 | 159 | a.emit(object2) 160 | expect(last).deep.eq([ 'label', as_inspect(object2, { depth: 3 }) ]) 161 | expect(calls.length).eq(2) 162 | }) 163 | }) 164 | -------------------------------------------------------------------------------- /test/delay-defer-raf.test.js: -------------------------------------------------------------------------------- 1 | 2 | import Bud from 'fluh/lib/Bud' 3 | 4 | import delay from 'fluh/thru/delay' 5 | import defer from 'fluh/thru/defer' 6 | import raf from 'fluh/thru/raf' 7 | 8 | import End from 'fluh/lib/End' 9 | import concat from 'fluh/lib/concat' 10 | 11 | 12 | describe('delay / defer / raf', () => 13 | { 14 | it('delay', async () => 15 | { 16 | var a = Bud() 17 | var b = a.thru(delay(10)) 18 | 19 | var rs = [] 20 | b.on(value => rs.push(value)) 21 | 22 | a 23 | .emit(1) 24 | .emit(2) 25 | .emit(3) 26 | .emit(End) 27 | 28 | expect(await concat(b)).deep.eq([ 1, 2, 3, End ]) 29 | }) 30 | 31 | it('delay with default', async () => 32 | { 33 | var a = Bud() 34 | var b = a.thru(delay()) 35 | 36 | var rs = [] 37 | b.on(value => rs.push(value)) 38 | 39 | a 40 | .emit(1) 41 | .emit(2) 42 | .emit(3) 43 | .emit(End) 44 | 45 | expect(await concat(b)).deep.eq([ 1, 2, 3, End ]) 46 | }) 47 | 48 | it('defer', async () => 49 | { 50 | var a = Bud() 51 | var b = a.thru(defer) 52 | 53 | var rs = [] 54 | b.on(value => rs.push(value)) 55 | 56 | a 57 | .emit(1) 58 | .emit(2) 59 | .emit(3) 60 | .emit(End) 61 | 62 | expect(await concat(b)).deep.eq([ 1, 2, 3, End ]) 63 | }) 64 | 65 | it('raf', async () => 66 | { 67 | var a = Bud() 68 | var b = a.thru(raf) 69 | 70 | var rs = [] 71 | b.on(value => rs.push(value)) 72 | 73 | a 74 | .emit(1) 75 | .emit(2) 76 | .emit(3) 77 | .emit(End) 78 | 79 | expect(await concat(b)).deep.eq([ 1, 2, 3, End ]) 80 | }) 81 | }) 82 | -------------------------------------------------------------------------------- /test/delta.test.js: -------------------------------------------------------------------------------- 1 | 2 | import Bud from 'fluh/lib/Bud' 3 | 4 | import Nothing from 'fluh/lib/Nothing' 5 | import End from 'fluh/lib/End' 6 | 7 | import delta from 'fluh/thru/delta' 8 | 9 | 10 | describe('delta', () => 11 | { 12 | it('as prev', () => 13 | { 14 | var a = Bud() 15 | var b = a.thru(delta()) 16 | 17 | expect(b.value).eq(Nothing) 18 | 19 | a.emit(1) 20 | expect(b.value).eq(Nothing) 21 | 22 | a.emit(2) 23 | expect(b.value).eq(1) 24 | 25 | a.emit(3) 26 | expect(b.value).eq(2) 27 | 28 | a.emit(End) 29 | expect(b.value).eq(3) 30 | 31 | a.emit(End) 32 | expect(b.value).eq(3) 33 | }) 34 | 35 | it('with delta function', () => 36 | { 37 | var a = Bud(0) 38 | var b = a.thru(delta(Math.max)) 39 | 40 | a.emit(-1) 41 | expect(b.value).eq(0) 42 | 43 | a.emit(1) 44 | expect(b.value).eq(1) 45 | 46 | a.emit(0) 47 | expect(b.value).eq(1) 48 | 49 | a.emit(5) 50 | expect(b.value).eq(5) 51 | 52 | a.emit(3) 53 | expect(b.value).eq(5) 54 | 55 | a.emit(-3) 56 | expect(b.value).eq(3) 57 | }) 58 | }) 59 | -------------------------------------------------------------------------------- /test/effects.test.js: -------------------------------------------------------------------------------- 1 | 2 | import Bud from 'fluh/lib/Bud' 3 | 4 | import Nothing from 'fluh/lib/Nothing' 5 | import End from 'fluh/lib/End' 6 | 7 | import join from 'fluh/lib/join' 8 | 9 | 10 | describe('effects', () => 11 | { 12 | it('Effects', () => 13 | { 14 | var bud = Bud() 15 | 16 | expect(bud.on).a('function') 17 | expect(bud.on.emit).a('function') 18 | }) 19 | 20 | it('disposer', () => 21 | { 22 | var bud = Bud() 23 | 24 | var ds = bud.on(() => {}) 25 | 26 | expect(typeof ds).eq('function') 27 | expect(ds.name).eq('ds') 28 | }) 29 | 30 | it('on Bud', () => 31 | { 32 | var bud = Bud() 33 | 34 | var ok = false 35 | 36 | bud.on((value) => { ok = (value === 17) }) 37 | 38 | var s = spy() 39 | bud.on(s) 40 | 41 | bud.emit(17) 42 | 43 | expect(ok).true 44 | expect(s.callCount).eq(1) 45 | }) 46 | 47 | it('on Bud instant', () => 48 | { 49 | var bud = Bud(17) 50 | 51 | var ok = false 52 | 53 | bud.on((value) => { ok = (value === 17) }) 54 | 55 | var s = spy() 56 | bud.on(s) 57 | 58 | expect(ok).true 59 | expect(s.callCount).eq(1) 60 | }) 61 | 62 | it('on Bud ended', () => 63 | { 64 | var bud = Bud(End) 65 | 66 | var ok = false 67 | 68 | bud.on((value) => { ok = (value === End) }) 69 | 70 | var s = spy() 71 | bud.on(s) 72 | 73 | expect(ok).true 74 | expect(s.callCount).eq(1) 75 | }) 76 | 77 | it('multiple', () => 78 | { 79 | var bud = Bud() 80 | 81 | var rs = [] 82 | 83 | bud.on((value) => rs.push([ 1, value ])) 84 | bud.on((value) => rs.push([ 2, value ])) 85 | 86 | bud.emit(17) 87 | bud.emit(Nothing) 88 | bud.emit(1917) 89 | bud.emit() 90 | 91 | expect(rs).deep.eq( 92 | [ 93 | [ 1, 17 ], 94 | [ 2, 17 ], 95 | [ 1, 1917 ], 96 | [ 2, 1917 ], 97 | ]) 98 | }) 99 | 100 | it('on pull', () => 101 | { 102 | var b1 = Bud() 103 | var b2 = join(b1, v => v) 104 | 105 | var rs = [] 106 | 107 | b2.on((value) => rs.push([ 1, value ])) 108 | b2.on((value) => rs.push([ 2, value ])) 109 | 110 | b1.emit(17) 111 | b1.emit(Nothing) 112 | b1.emit(1917) 113 | b1.emit() 114 | 115 | expect(rs).deep.eq( 116 | [ 117 | [ 1, 17 ], 118 | [ 2, 17 ], 119 | [ 1, 1917 ], 120 | [ 2, 1917 ], 121 | ]) 122 | }) 123 | 124 | it('after disposal', () => 125 | { 126 | var bud = Bud() 127 | 128 | var rs = [] 129 | var ds1 = bud.on((value) => rs.push(value)) 130 | 131 | var s = spy() 132 | var ds2 = bud.on(s) 133 | 134 | bud.emit(17) 135 | bud.emit(18) 136 | 137 | ds1() 138 | ds2() 139 | 140 | bud.emit(19) 141 | bud.emit(20) 142 | 143 | expect(rs).deep.eq([ 17, 18 ]) 144 | expect(s.callCount).eq(2) 145 | }) 146 | 147 | it('allows dispose during emit', () => 148 | { 149 | var bud = Bud() 150 | 151 | var r1 = 0 152 | var r2 = 0 153 | var r3 = 0 154 | 155 | var ds1 = bud.on((x) => 156 | { 157 | r1 = (r1 + x) 158 | ds1() 159 | ds3() 160 | }) 161 | bud.on((x) => { r2 = (r2 + x) }) 162 | var ds3 = bud.on((x) => { r3 = (r3 + x) }) 163 | 164 | expect(r1).eq(0) 165 | expect(r2).eq(0) 166 | expect(r3).eq(0) 167 | 168 | bud.emit(1) 169 | 170 | expect(r1).eq(1) 171 | expect(r2).eq(1) 172 | expect(r3).eq(1) 173 | 174 | bud.emit(10) 175 | 176 | expect(r1).eq(1) 177 | expect(r2).eq(11) 178 | expect(r3).eq(1) 179 | }) 180 | 181 | it('once', () => 182 | { 183 | var bud = Bud() 184 | 185 | var rs1 = [] 186 | var ds1 = bud.on((value) => 187 | { 188 | ds1() 189 | rs1.push(value) 190 | }) 191 | 192 | var rs2 = [] 193 | var ds2 = bud.on((value) => 194 | { 195 | rs2.push(value) 196 | }) 197 | 198 | bud.emit(17) 199 | bud.emit(18) 200 | 201 | expect(rs1).deep.eq([ 17 ]) 202 | expect(rs2).deep.eq([ 17, 18 ]) 203 | 204 | ds1() 205 | ds2() 206 | 207 | ds1() 208 | ds2() 209 | }) 210 | 211 | it('disposes single fn', () => 212 | { 213 | var bud = Bud() 214 | 215 | var r = 0 216 | var f = (x) => { r = (r + x) } 217 | 218 | var ds1 = bud.on(f) 219 | var ds2 = bud.on(f) 220 | 221 | bud.emit(2) 222 | expect(r).eq(4) 223 | 224 | ds1() 225 | 226 | bud.emit(2) 227 | expect(r).eq(6) 228 | 229 | ds2() 230 | 231 | bud.emit(2) 232 | expect(r).eq(6) 233 | }) 234 | 235 | it('order', () => 236 | { 237 | var rs = [] 238 | 239 | var a = Bud() 240 | var b = join(a, track('join-b')) 241 | var c = join(b, track('join-c')) 242 | 243 | a.on(track('a-1')) 244 | a.on(track('a-2')) 245 | b.on(track('b-1')) 246 | b.on(track('b-2')) 247 | c.on(track('c-1')) 248 | c.on(track('c-2')) 249 | 250 | function track (name) 251 | { 252 | return (value) => 253 | { 254 | rs.push([ name, value ]) 255 | 256 | return value 257 | } 258 | } 259 | 260 | a.emit(17) 261 | 262 | expect(rs).deep.eq( 263 | [ 264 | [ 'join-b', 17 ], 265 | [ 'join-c', 17 ], 266 | [ 'a-1', 17 ], 267 | [ 'a-2', 17 ], 268 | [ 'b-1', 17 ], 269 | [ 'b-2', 17 ], 270 | [ 'c-1', 17 ], 271 | [ 'c-2', 17 ], 272 | ]) 273 | }) 274 | 275 | it('order when additional emits', () => 276 | { 277 | var rs = [] 278 | 279 | var a = Bud() 280 | 281 | var b = join(a, track('join-b')) 282 | var c = join(b, track('join-c')) 283 | 284 | a.on(track('a-1')) 285 | a.on(track('a-2')) 286 | b.on(track('b-1')) 287 | b.on(track('b-2')) 288 | 289 | b.on((value) => 290 | { 291 | if (value === 17) 292 | { 293 | b.emit(1917) 294 | } 295 | }) 296 | 297 | c.on(track('c-1')) 298 | c.on(track('c-2')) 299 | 300 | function track (name) 301 | { 302 | return (value) => 303 | { 304 | rs.push([ name, value ]) 305 | 306 | return value 307 | } 308 | } 309 | 310 | a.emit(17) 311 | 312 | expect(rs).deep.eq( 313 | [ 314 | [ 'join-b', 17 ], 315 | [ 'join-c', 17 ], 316 | [ 'a-1', 17 ], 317 | [ 'a-2', 17 ], 318 | [ 'b-1', 17 ], 319 | [ 'b-2', 17 ], 320 | [ 'c-1', 17 ], 321 | [ 'c-2', 17 ], 322 | [ 'join-c', 1917 ], 323 | [ 'b-1', 1917 ], 324 | [ 'b-2', 1917 ], 325 | [ 'c-1', 1917 ], 326 | [ 'c-2', 1917 ], 327 | ]) 328 | }) 329 | 330 | it('re-emit on self', () => 331 | { 332 | var rs = [] 333 | 334 | var a = Bud() 335 | 336 | a.on((value) => 337 | { 338 | if (value < 5) 339 | { 340 | a.emit(value + 1) 341 | } 342 | }) 343 | 344 | 345 | var b = join(a, v => v) 346 | 347 | b.on((value) => rs.push(value)) 348 | 349 | a.emit(1) 350 | 351 | expect(rs).deep.eq([ 1, 2, 3, 4, 5 ]) 352 | }) 353 | 354 | it('circular re-emit', () => 355 | { 356 | var rs = [] 357 | 358 | var a = Bud() 359 | var b = join(a, v => v) 360 | 361 | b.on((value) => 362 | { 363 | if (value < 5) 364 | { 365 | a.emit(value + 1) 366 | } 367 | 368 | rs.push(value) 369 | }) 370 | 371 | a.emit(1) 372 | 373 | expect(rs).deep.eq([ 1, 2, 3, 4, 5 ]) 374 | }) 375 | 376 | it('double in join (caution)', () => 377 | { 378 | var order = [] 379 | var rs = 380 | { 381 | a: [], 382 | b: [], 383 | c: [], 384 | } 385 | 386 | var a = Bud() 387 | var b = join(a, (value) => 388 | { 389 | value = value * 2 390 | b.emit(value + 1) 391 | return value 392 | }) 393 | var c = join(b, v => v) 394 | 395 | a.on(track('a')) 396 | b.on(track('b')) 397 | c.on(track('c')) 398 | 399 | a.emit(1) 400 | a.emit(2) 401 | a.emit(3) 402 | 403 | expect(rs.a).deep.eq([ 1, 2, 3 ]) 404 | expect(rs.b).deep.eq([ 2, 3, 4, 5, 6, 7 ]) 405 | expect(rs.c).deep.eq([ 2, 3, 4, 5, 6, 7 ]) 406 | 407 | expect(order).deep.eq( 408 | [ 409 | [ 'a', 1 ], 410 | [ 'b', 2 ], 411 | [ 'c', 2 ], 412 | 413 | [ 'b', 3 ], 414 | [ 'c', 3 ], 415 | 416 | [ 'a', 2 ], 417 | [ 'b', 4 ], 418 | [ 'c', 4 ], 419 | 420 | [ 'b', 5 ], 421 | [ 'c', 5 ], 422 | 423 | [ 'a', 3 ], 424 | [ 'b', 6 ], 425 | [ 'c', 6 ], 426 | 427 | [ 'b', 7 ], 428 | [ 'c', 7 ], 429 | ]) 430 | 431 | function track (name) 432 | { 433 | return (value) => 434 | { 435 | rs[name].push(value) 436 | order.push([ name, value ]) 437 | } 438 | } 439 | }) 440 | }) 441 | -------------------------------------------------------------------------------- /test/filter.test.js: -------------------------------------------------------------------------------- 1 | 2 | import Bud from 'fluh/lib/Bud' 3 | import End from 'fluh/lib/End' 4 | 5 | import { when_data } from 'fluh/map/when' 6 | 7 | import filter from 'fluh/map/filter' 8 | import filter_by from 'fluh/map/filter-by' 9 | 10 | 11 | describe('filter', () => 12 | { 13 | it('filters', () => 14 | { 15 | var rsb = [] 16 | var rsc = [] 17 | 18 | var a = Bud() 19 | var b = a.map(filter(x => ((x < 3) || (x > 4)))) 20 | var c = b.map(filter(x => x % 2)) 21 | 22 | b.on(v => rsb.push(v)) 23 | c.on(v => rsc.push(v)) 24 | 25 | a 26 | .emit(1) 27 | .emit(2) 28 | .emit(3) 29 | .emit(4) 30 | .emit(5) 31 | 32 | expect(rsb).deep.eq([ 1, 2, 5 ]) 33 | expect(rsc).deep.eq([ 1, 5 ]) 34 | }) 35 | 36 | it('no end', () => 37 | { 38 | var rs1 = [] 39 | var rs2 = [] 40 | 41 | var a = Bud() 42 | 43 | var b1 = a.map(filter(x => typeof x === 'number' && x > 1)) 44 | var b2 = a.map(when_data(filter(x => (x > 1)))) 45 | 46 | b1.on(v => rs1.push(v)) 47 | b2.on(v => rs2.push(v)) 48 | 49 | a 50 | .emit(1) 51 | .emit(2) 52 | .emit(3) 53 | .emit(End) 54 | 55 | expect(rs1).deep.eq([ 2, 3 ]) 56 | expect(rs2).deep.eq([ 2, 3, End ]) 57 | }) 58 | 59 | it('filter_by', () => 60 | { 61 | var a = Bud() 62 | var signal = a.map(v => (v > 2)) 63 | var b = a.map(filter_by(signal)) 64 | 65 | var rs = [] 66 | b.on(v => rs.push(v)) 67 | 68 | a 69 | .emit(1) 70 | .emit(2) 71 | .emit(3) 72 | .emit(4) 73 | .emit(5) 74 | 75 | expect(rs).deep.eq([ 3, 4, 5 ]) 76 | }) 77 | }) 78 | -------------------------------------------------------------------------------- /test/globals.js: -------------------------------------------------------------------------------- 1 | 2 | import { expect } from 'chai' 3 | import { spy } from 'sinon' 4 | global.expect = expect 5 | global.spy = spy 6 | 7 | import { polyfill as raf } from 'raf' 8 | raf(global) 9 | -------------------------------------------------------------------------------- /test/index.test.js: -------------------------------------------------------------------------------- 1 | 2 | import * as index from '../' 3 | 4 | 5 | describe('index', () => 6 | { 7 | it('exports', () => 8 | { 9 | expect(Object.keys(index).length).eq(17) 10 | 11 | expect(index.Bud).a('function') 12 | 13 | expect(index.Nothing).a('symbol') 14 | expect(index.Many).a('function') 15 | expect(index.End).a('symbol') 16 | 17 | expect(index.Noop).a('function') 18 | expect(index.Same).a('function') 19 | expect(index.Fin).a('function') 20 | 21 | expect(index.join).a('function') 22 | expect(index.merge).a('function') 23 | expect(index.transfer).a('function') 24 | expect(index.turnoff).a('function') 25 | expect(index.resource).a('function') 26 | 27 | expect(index.tap).a('function') 28 | expect(index.asap).a('function') 29 | expect(index.capture).a('function') 30 | expect(index.concat).a('function') 31 | expect(index.drain).a('function') 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /test/join.test.js: -------------------------------------------------------------------------------- 1 | 2 | import Bud from 'fluh/lib/Bud' 3 | import join from 'fluh/lib/join' 4 | 5 | import { state } from './Bud.test' 6 | 7 | 8 | describe('join', () => 9 | { 10 | it('A,B → C', () => 11 | { 12 | var a = Bud() 13 | var b = Bud() 14 | 15 | var c = join(a, b, (a, b) => a + b) 16 | 17 | state(a, 18 | { 19 | deps: [ c ], 20 | order: [ c ], 21 | }) 22 | state(b, 23 | { 24 | deps: [ c ], 25 | order: [ c ], 26 | }) 27 | state(c, 28 | { 29 | rev: [ a, b ], 30 | }) 31 | 32 | a.emit(1) 33 | state(a, 34 | { 35 | value: 1, 36 | deps: [ c ], 37 | order: [ c ], 38 | }) 39 | state(b, 40 | { 41 | deps: [ c ], 42 | order: [ c ], 43 | }) 44 | state(c, 45 | { 46 | rev: [ a, b ], 47 | }) 48 | 49 | b.emit(10) 50 | state(a, 51 | { 52 | value: 1, 53 | deps: [ c ], 54 | order: [ c ], 55 | }) 56 | state(b, 57 | { 58 | value: 10, 59 | deps: [ c ], 60 | order: [ c ], 61 | }) 62 | state(c, 63 | { 64 | value: 11, 65 | rev: [ a, b ], 66 | }) 67 | }) 68 | 69 | it('fn first A,B → C', () => 70 | { 71 | var a = Bud() 72 | var b = Bud() 73 | 74 | var c = join((a, b) => a + b, a, b) 75 | 76 | state(a, 77 | { 78 | deps: [ c ], 79 | order: [ c ], 80 | }) 81 | state(b, 82 | { 83 | deps: [ c ], 84 | order: [ c ], 85 | }) 86 | state(c, 87 | { 88 | rev: [ a, b ], 89 | }) 90 | 91 | a.emit(1) 92 | state(a, 93 | { 94 | value: 1, 95 | deps: [ c ], 96 | order: [ c ], 97 | }) 98 | state(b, 99 | { 100 | deps: [ c ], 101 | order: [ c ], 102 | }) 103 | state(c, 104 | { 105 | rev: [ a, b ], 106 | }) 107 | 108 | b.emit(10) 109 | state(a, 110 | { 111 | value: 1, 112 | deps: [ c ], 113 | order: [ c ], 114 | }) 115 | state(b, 116 | { 117 | value: 10, 118 | deps: [ c ], 119 | order: [ c ], 120 | }) 121 | state(c, 122 | { 123 | value: 11, 124 | rev: [ a, b ], 125 | }) 126 | }) 127 | }) 128 | -------------------------------------------------------------------------------- /test/linear.test.js: -------------------------------------------------------------------------------- 1 | 2 | import Bud from 'fluh/lib/Bud' 3 | 4 | import Nothing from 'fluh/lib/Nothing' 5 | import Many from 'fluh/lib/Many' 6 | import End from 'fluh/lib/End' 7 | 8 | import join from 'fluh/lib/join' 9 | 10 | import { state } from './Bud.test' 11 | 12 | 13 | describe('linear', () => 14 | { 15 | it('A → B', () => 16 | { 17 | var a = Bud() 18 | 19 | state(a) 20 | 21 | var b = join(a, a => a + 1) 22 | 23 | state(a, 24 | { 25 | deps: [ b ], 26 | order: [ b ], 27 | }) 28 | 29 | state(b, 30 | { 31 | rev: [ a ], 32 | }) 33 | 34 | var as1 = spy() 35 | a.on(as1) 36 | 37 | var bs1 = spy() 38 | a.on(bs1) 39 | 40 | expect(as1.called).false 41 | expect(bs1.called).false 42 | 43 | a.emit(1) 44 | 45 | var as2 = spy() 46 | var bs2 = spy() 47 | a.on(as2) 48 | a.on(bs2) 49 | 50 | state(a, 51 | { 52 | value: 1, 53 | deps: [ b ], 54 | order: [ b ], 55 | }) 56 | 57 | state(b, 58 | { 59 | value: 2, 60 | rev: [ a ], 61 | }) 62 | 63 | expect(as2.callCount).eq(1) 64 | expect(bs2.callCount).eq(1) 65 | }) 66 | 67 | it('A(v) → B', () => 68 | { 69 | var a = Bud(1) 70 | 71 | state(a, 72 | { 73 | value: 1, 74 | }) 75 | 76 | var b = join(a, a => a + 1) 77 | 78 | state(a, 79 | { 80 | value: 1, 81 | deps: [ b ], 82 | order: [ b ], 83 | }) 84 | 85 | state(b, 86 | { 87 | value: 2, 88 | rev: [ a ], 89 | }) 90 | 91 | var as1 = spy() 92 | a.on(as1) 93 | 94 | var bs1 = spy() 95 | a.on(bs1) 96 | 97 | expect(as1.called).true 98 | expect(bs1.called).true 99 | 100 | a.emit(2) 101 | 102 | state(a, 103 | { 104 | value: 2, 105 | deps: [ b ], 106 | order: [ b ], 107 | }) 108 | 109 | state(b, 110 | { 111 | value: 3, 112 | rev: [ a ], 113 | }) 114 | 115 | expect(as1.callCount).eq(2) 116 | expect(bs1.callCount).eq(2) 117 | }) 118 | 119 | it('A(emit v) → B', () => 120 | { 121 | var a = Bud().emit(1) 122 | 123 | state(a, 124 | { 125 | value: 1, 126 | }) 127 | 128 | var b = join(a, a => a + 1) 129 | 130 | state(a, 131 | { 132 | value: 1, 133 | deps: [ b ], 134 | order: [ b ], 135 | }) 136 | 137 | state(b, 138 | { 139 | value: 2, 140 | rev: [ a ], 141 | }) 142 | 143 | var as1 = spy() 144 | a.on(as1) 145 | 146 | var bs1 = spy() 147 | a.on(bs1) 148 | 149 | expect(as1.called).true 150 | expect(bs1.called).true 151 | 152 | a.emit(2) 153 | 154 | state(a, 155 | { 156 | value: 2, 157 | deps: [ b ], 158 | order: [ b ], 159 | }) 160 | 161 | state(b, 162 | { 163 | value: 3, 164 | rev: [ a ], 165 | }) 166 | 167 | expect(as1.callCount).eq(2) 168 | expect(bs1.callCount).eq(2) 169 | }) 170 | 171 | it('A(End) → B', () => 172 | { 173 | var a = Bud(End) 174 | 175 | state(a, 176 | { 177 | value: End, 178 | }) 179 | 180 | var b = join(a, a => a) 181 | 182 | state(a, 183 | { 184 | value: End, 185 | }) 186 | 187 | state(b, 188 | { 189 | value: End, 190 | }) 191 | 192 | var as1 = spy() 193 | a.on(as1) 194 | 195 | var bs1 = spy() 196 | b.on(bs1) 197 | 198 | expect(as1.called).true 199 | expect(bs1.called).true 200 | 201 | /* - */ 202 | a.emit(1) 203 | 204 | state(a, 205 | { 206 | value: End, 207 | }) 208 | 209 | state(b, 210 | { 211 | value: End, 212 | }) 213 | 214 | expect(as1.callCount).eq(1) 215 | expect(bs1.callCount).eq(1) 216 | 217 | /* - */ 218 | a.emit(End) 219 | 220 | state(a, 221 | { 222 | value: End, 223 | }) 224 | 225 | state(b, 226 | { 227 | value: End, 228 | }) 229 | 230 | expect(as1.callCount).eq(1) 231 | expect(bs1.callCount).eq(1) 232 | }) 233 | 234 | it('A(Many) → B', () => 235 | { 236 | var a = Bud(Many(17, 1917)) 237 | 238 | state(a, 239 | { 240 | value: 1917, 241 | }) 242 | 243 | var b = join(a, a => a) 244 | 245 | state(a, 246 | { 247 | value: 1917, 248 | deps: [ b ], 249 | order: [ b ], 250 | }) 251 | 252 | state(b, 253 | { 254 | value: 1917, 255 | rev: [ a ], 256 | }) 257 | }) 258 | 259 | it('A(End) → B(Live)', () => 260 | { 261 | var a = Bud() 262 | 263 | state(a) 264 | 265 | var b = join(a, a => (a, 0)) 266 | 267 | var as1 = spy() 268 | a.on(as1) 269 | 270 | var bs1 = spy() 271 | b.on(bs1) 272 | 273 | state(a, 274 | { 275 | deps: [ b ], 276 | order: [ b ], 277 | }) 278 | 279 | state(b, 280 | { 281 | rev: [ a ], 282 | }) 283 | 284 | expect(as1.called).false 285 | expect(bs1.called).false 286 | 287 | /* - */ 288 | a.emit(1) 289 | 290 | state(a, 291 | { 292 | value: 1, 293 | deps: [ b ], 294 | order: [ b ], 295 | }) 296 | 297 | state(b, 298 | { 299 | value: 0, 300 | rev: [ a ], 301 | }) 302 | 303 | expect(as1.callCount).eq(1) 304 | expect(bs1.callCount).eq(1) 305 | 306 | /* - */ 307 | a.emit(End) 308 | 309 | state(a, 310 | { 311 | value: End, 312 | }) 313 | 314 | state(b, 315 | { 316 | value: 0, 317 | rev: [ a ], 318 | }) 319 | 320 | expect(as1.callCount).eq(2) 321 | expect(bs1.callCount).eq(2) 322 | 323 | /* - */ 324 | a.emit(End) 325 | 326 | state(a, 327 | { 328 | value: End, 329 | }) 330 | 331 | state(b, 332 | { 333 | value: 0, 334 | rev: [ a ], 335 | }) 336 | 337 | expect(as1.callCount).eq(2) 338 | expect(bs1.callCount).eq(2) 339 | }) 340 | 341 | it('A → B(End)', () => 342 | { 343 | var a = Bud(1) 344 | 345 | state(a, { value: 1 }) 346 | 347 | var b = join(a, a => (a, End)) 348 | 349 | var as1 = spy() 350 | a.on(as1) 351 | 352 | var bs1 = spy() 353 | b.on(bs1) 354 | 355 | state(a, 356 | { 357 | value: 1, 358 | }) 359 | state(b, 360 | { 361 | value: End, 362 | }) 363 | 364 | expect(as1.called).true 365 | expect(bs1.called).true 366 | 367 | /* - */ 368 | a.emit(2) 369 | 370 | state(a, 371 | { 372 | value: 2, 373 | }) 374 | state(b, 375 | { 376 | value: End, 377 | }) 378 | 379 | expect(as1.callCount).eq(2) 380 | expect(bs1.callCount).eq(1) 381 | 382 | /* - */ 383 | a.emit(End) 384 | 385 | state(a, 386 | { 387 | value: End, 388 | }) 389 | state(b, 390 | { 391 | value: End, 392 | }) 393 | 394 | expect(as1.callCount).eq(3) 395 | expect(bs1.callCount).eq(1) 396 | }) 397 | 398 | it('A → B → C', () => 399 | { 400 | var a = Bud() 401 | 402 | var b = join(a, a => a + 1) 403 | var c = join(b, b => b * 100) 404 | 405 | state(a, 406 | { 407 | deps: [ b ], 408 | order: [ b, c ], 409 | }) 410 | 411 | state(b, 412 | { 413 | deps: [ c ], 414 | order: [ c ], 415 | rev: [ a ], 416 | }) 417 | 418 | state(c, 419 | { 420 | rev: [ b ], 421 | }) 422 | 423 | a.emit(1) 424 | 425 | state(a, 426 | { 427 | value: 1, 428 | deps: [ b ], 429 | order: [ b, c ], 430 | }) 431 | 432 | state(b, 433 | { 434 | value: 2, 435 | deps: [ c ], 436 | order: [ c ], 437 | rev: [ a ], 438 | }) 439 | 440 | state(c, 441 | { 442 | value: 200, 443 | rev: [ b ], 444 | }) 445 | }) 446 | 447 | it('A(v) → B → C', () => 448 | { 449 | var a = Bud(1) 450 | 451 | var b = join(a, a => a + 1) 452 | var c = join(b, b => b * 100) 453 | 454 | state(a, 455 | { 456 | value: 1, 457 | deps: [ b ], 458 | order: [ b, c ], 459 | }) 460 | 461 | state(b, 462 | { 463 | value: 2, 464 | deps: [ c ], 465 | order: [ c ], 466 | rev: [ a ], 467 | }) 468 | 469 | state(c, 470 | { 471 | value: 200, 472 | rev: [ b ], 473 | }) 474 | }) 475 | 476 | it('A(emit v) → B → C', () => 477 | { 478 | var a = Bud().emit(1) 479 | 480 | var b = join(a, a => a + 1) 481 | var c = join(b, b => b * 100) 482 | 483 | state(a, 484 | { 485 | value: 1, 486 | deps: [ b ], 487 | order: [ b, c ], 488 | }) 489 | 490 | state(b, 491 | { 492 | value: 2, 493 | deps: [ c ], 494 | order: [ c ], 495 | rev: [ a ], 496 | }) 497 | 498 | state(c, 499 | { 500 | value: 200, 501 | rev: [ b ], 502 | }) 503 | }) 504 | 505 | it('A → B(Nothing) → C', () => 506 | { 507 | var a = Bud() 508 | 509 | var b = join(a, () => Nothing) 510 | var c = join(b, b => b * 100) 511 | 512 | var bs = spy() 513 | b.on(bs) 514 | 515 | var cs = spy() 516 | c.on(cs) 517 | 518 | state(a, 519 | { 520 | value: Nothing, 521 | deps: [ b ], 522 | order: [ b, c ], 523 | }) 524 | 525 | state(b, 526 | { 527 | value: Nothing, 528 | deps: [ c ], 529 | order: [ c ], 530 | rev: [ a ], 531 | }) 532 | 533 | state(c, 534 | { 535 | value: Nothing, 536 | rev: [ b ], 537 | }) 538 | 539 | a.emit(1) 540 | 541 | state(a, 542 | { 543 | value: 1, 544 | deps: [ b ], 545 | order: [ b, c ], 546 | }) 547 | 548 | state(b, 549 | { 550 | value: Nothing, 551 | deps: [ c ], 552 | order: [ c ], 553 | rev: [ a ], 554 | }) 555 | 556 | state(c, 557 | { 558 | value: Nothing, 559 | rev: [ b ], 560 | }) 561 | 562 | a.emit(2) 563 | 564 | state(a, 565 | { 566 | value: 2, 567 | deps: [ b ], 568 | order: [ b, c ], 569 | }) 570 | 571 | state(b, 572 | { 573 | value: Nothing, 574 | deps: [ c ], 575 | order: [ c ], 576 | rev: [ a ], 577 | }) 578 | 579 | state(c, 580 | { 581 | value: Nothing, 582 | rev: [ b ], 583 | }) 584 | 585 | expect(bs.called).false 586 | expect(cs.called).false 587 | }) 588 | 589 | it('A → B → C multiple emit', () => 590 | { 591 | var a = Bud() 592 | 593 | var b = join(a, a => a + 1) 594 | var c = join(b, b => b * 100) 595 | 596 | var cs = spy() 597 | c.on(cs) 598 | 599 | state(a, 600 | { 601 | value: Nothing, 602 | deps: [ b ], 603 | order: [ b, c ], 604 | }) 605 | 606 | state(b, 607 | { 608 | value: Nothing, 609 | deps: [ c ], 610 | order: [ c ], 611 | rev: [ a ], 612 | }) 613 | 614 | state(c, 615 | { 616 | value: Nothing, 617 | rev: [ b ], 618 | }) 619 | 620 | expect(cs.callCount).eq(0) 621 | 622 | a.emit(1) 623 | a.emit(2) 624 | a.emit(3) 625 | 626 | state(a, 627 | { 628 | value: 3, 629 | deps: [ b ], 630 | order: [ b, c ], 631 | }) 632 | 633 | state(b, 634 | { 635 | value: 4, 636 | deps: [ c ], 637 | order: [ c ], 638 | rev: [ a ], 639 | }) 640 | 641 | state(c, 642 | { 643 | value: 400, 644 | rev: [ b ], 645 | }) 646 | 647 | expect(cs.callCount).eq(3) 648 | }) 649 | 650 | it('A → B(Many) → C', () => 651 | { 652 | var a = Bud() 653 | 654 | var b = join(a, a => Many(a, a * 10)) 655 | var c = join(b, b => b * 100) 656 | 657 | var order = [] 658 | 659 | var as = [] 660 | a.on(track('a')) 661 | a.on(value => as.push(value)) 662 | 663 | var bs = [] 664 | b.on(track('b')) 665 | b.on(value => bs.push(value)) 666 | 667 | var cs = [] 668 | c.on(track('c')) 669 | c.on(value => cs.push(value)) 670 | 671 | state(a, 672 | { 673 | value: Nothing, 674 | deps: [ b ], 675 | order: [ b, c ], 676 | }) 677 | 678 | state(b, 679 | { 680 | value: Nothing, 681 | deps: [ c ], 682 | order: [ c ], 683 | rev: [ a ], 684 | }) 685 | 686 | state(c, 687 | { 688 | value: Nothing, 689 | rev: [ b ], 690 | }) 691 | 692 | a.emit(5) 693 | a.emit(6) 694 | a.emit(7) 695 | 696 | state(a, 697 | { 698 | value: 7, 699 | deps: [ b ], 700 | order: [ b, c ], 701 | }) 702 | 703 | state(b, 704 | { 705 | value: 70, 706 | deps: [ c ], 707 | order: [ c ], 708 | rev: [ a ], 709 | }) 710 | 711 | state(c, 712 | { 713 | value: 7000, 714 | rev: [ b ], 715 | }) 716 | 717 | expect(as).deep.eq([ 5, 6, 7 ]) 718 | expect(bs).deep.eq([ 5, 50, 6, 60, 7, 70 ]) 719 | expect(cs).deep.eq([ 500, 5000, 600, 6000, 700, 7000 ]) 720 | expect(order).deep.eq( 721 | [ 722 | [ 'a', 5 ], 723 | [ 'b', 5 ], 724 | [ 'c', 500 ], 725 | [ 'b', 50 ], 726 | [ 'c', 5000 ], 727 | [ 'a', 6 ], 728 | [ 'b', 6 ], 729 | [ 'c', 600 ], 730 | [ 'b', 60 ], 731 | [ 'c', 6000 ], 732 | [ 'a', 7 ], 733 | [ 'b', 7 ], 734 | [ 'c', 700 ], 735 | [ 'b', 70 ], 736 | [ 'c', 7000 ], 737 | ]) 738 | 739 | function track (name) 740 | { 741 | return (value) => 742 | { 743 | order.push([ name, value ]) 744 | 745 | return value 746 | } 747 | } 748 | }) 749 | 750 | it('A → B(Many) → C(Many)', () => 751 | { 752 | var a = Bud() 753 | 754 | var b = join(a, a => Many(a, (a + 1))) 755 | var c = join(b, b => Many(b, (b + 2))) 756 | 757 | var order = [] 758 | 759 | var as = [] 760 | a.on(track('a')) 761 | a.on(value => as.push(value)) 762 | 763 | var bs = [] 764 | b.on(track('b')) 765 | b.on(value => bs.push(value)) 766 | 767 | var cs = [] 768 | c.on(track('c')) 769 | c.on(value => cs.push(value)) 770 | 771 | expect(a.value).eq(Nothing) 772 | expect(b.value).eq(Nothing) 773 | expect(c.value).eq(Nothing) 774 | 775 | a.emit(1) 776 | a.emit(5) 777 | a.emit(9) 778 | 779 | expect(a.value).eq(9) 780 | expect(b.value).eq(10) 781 | expect(c.value).eq(12) 782 | 783 | expect(as).deep.eq([ 1, 5, 9 ]) 784 | expect(bs).deep.eq([ 1, 2, 5, 6, 9, 10 ]) 785 | expect(cs).deep.eq([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ]) 786 | 787 | expect(order).deep.eq( 788 | [ 789 | [ 'a', 1 ], 790 | [ 'b', 1 ], 791 | [ 'c', 1 ], 792 | [ 'b', 2 ], 793 | [ 'c', 2 ], 794 | [ 'c', 3 ], 795 | [ 'c', 4 ], 796 | 797 | [ 'a', 5 ], 798 | [ 'b', 5 ], 799 | [ 'c', 5 ], 800 | [ 'b', 6 ], 801 | [ 'c', 6 ], 802 | [ 'c', 7 ], 803 | [ 'c', 8 ], 804 | 805 | [ 'a', 9 ], 806 | [ 'b', 9 ], 807 | [ 'c', 9 ], 808 | [ 'b', 10 ], 809 | [ 'c', 10 ], 810 | [ 'c', 11 ], 811 | [ 'c', 12 ], 812 | ]) 813 | 814 | function track (name) 815 | { 816 | return (value) => 817 | { 818 | order.push([ name, value ]) 819 | 820 | return value 821 | } 822 | } 823 | }) 824 | 825 | it('A → B(Nothing) → C', () => 826 | { 827 | var a = Bud() 828 | 829 | var b = join(a, () => Nothing) 830 | var c = join(b, b => b * 10) 831 | 832 | var bs = [] 833 | b.on(value => bs.push(value)) 834 | 835 | var cs = [] 836 | c.on(value => cs.push(value)) 837 | 838 | a.emit(1).emit(2).emit(3) 839 | 840 | expect(bs).deep.eq([]) 841 | expect(cs).deep.eq([]) 842 | }) 843 | 844 | it('A → B(maybe Nothing) → C', () => 845 | { 846 | var a = Bud() 847 | 848 | var b = join(a, a => 849 | { 850 | if (a % 2) { return a } 851 | return Nothing 852 | }) 853 | var c = join(b, b => b * 10) 854 | 855 | var bs = [] 856 | b.on(value => bs.push(value)) 857 | 858 | var cs = [] 859 | c.on(value => cs.push(value)) 860 | 861 | a 862 | .emit(0) 863 | .emit(1) 864 | .emit(2) 865 | .emit(3) 866 | .emit(4) 867 | 868 | expect(bs).deep.eq([ 1, 3 ]) 869 | expect(cs).deep.eq([ 10, 30 ]) 870 | }) 871 | 872 | it('A(End) → B → C', () => 873 | { 874 | var a = Bud() 875 | var b = join(a, a => a) 876 | var c = join(b, b => b) 877 | 878 | var as1 = spy() 879 | a.on(as1) 880 | 881 | var bs1 = spy() 882 | b.on(bs1) 883 | 884 | var cs1 = spy() 885 | c.on(cs1) 886 | 887 | state(a, 888 | { 889 | value: Nothing, 890 | deps: [ b ], 891 | order: [ b, c ], 892 | }) 893 | 894 | state(b, 895 | { 896 | value: Nothing, 897 | deps: [ c ], 898 | order: [ c ], 899 | rev: [ a ], 900 | }) 901 | 902 | state(c, 903 | { 904 | value: Nothing, 905 | rev: [ b ], 906 | }) 907 | 908 | expect(as1.called).false 909 | expect(bs1.called).false 910 | expect(cs1.called).false 911 | 912 | /* - */ 913 | a.emit(End) 914 | 915 | state(a, 916 | { 917 | value: End, 918 | }) 919 | 920 | state(b, 921 | { 922 | value: End, 923 | }) 924 | 925 | state(c, 926 | { 927 | value: End, 928 | }) 929 | 930 | expect(as1.callCount).eq(1) 931 | expect(bs1.callCount).eq(1) 932 | expect(cs1.callCount).eq(1) 933 | }) 934 | 935 | it('Zero, A → B', () => 936 | { 937 | var a = Bud() 938 | a.emit(1) 939 | 940 | state(a, { value: 1 }) 941 | 942 | var Z = Bud() 943 | Z.emit(1) 944 | 945 | state(Z, { value: 1 }) 946 | 947 | var b = a.map(x => x + 1) 948 | 949 | state(b, 950 | { 951 | value: 2, 952 | rev: [ a ], 953 | }) 954 | }) 955 | 956 | it('pass undefined', () => 957 | { 958 | var a = Bud() 959 | var b = join(a, a => void a) 960 | 961 | a.emit(1) 962 | 963 | expect(b.value).eq(void 0) 964 | }) 965 | }) 966 | -------------------------------------------------------------------------------- /test/map.test.js: -------------------------------------------------------------------------------- 1 | 2 | import Bud from 'fluh/lib/Bud' 3 | import Nothing from 'fluh/lib/Nothing' 4 | import Many from 'fluh/lib/Many' 5 | 6 | 7 | describe('map', () => 8 | { 9 | it('like map', () => 10 | { 11 | var rs1 = [] 12 | var rs2 = [] 13 | 14 | var a = Bud() 15 | var b = a.map(x => x * 2) 16 | var c = b.map(x => x + 1) 17 | 18 | b.on(v => rs1.push(v)) 19 | c.on(v => rs2.push(v)) 20 | 21 | a.emit(1).emit(2).emit(3) 22 | 23 | expect(rs1).deep.eq([ 2, 4, 6 ]) 24 | expect(rs2).deep.eq([ 3, 5, 7 ]) 25 | }) 26 | 27 | it('like filter', () => 28 | { 29 | var rs1 = [] 30 | var rs2 = [] 31 | 32 | var a = Bud() 33 | var b = a.map(filter(x => (x <= 3))) 34 | var c = b.map(x => x + 1) 35 | 36 | b.on(v => rs1.push(v)) 37 | c.on(v => rs2.push(v)) 38 | 39 | a 40 | .emit(1) 41 | .emit(2) 42 | .emit(3) 43 | .emit(4) 44 | .emit(5) 45 | 46 | expect(rs1).deep.eq([ 1, 2, 3 ]) 47 | expect(rs2).deep.eq([ 2, 3, 4 ]) 48 | 49 | function filter (fn) 50 | { 51 | return (value) => 52 | { 53 | if (fn(value)) 54 | { 55 | return value 56 | } 57 | else 58 | { 59 | return Nothing 60 | } 61 | } 62 | } 63 | }) 64 | 65 | it('produce Many', () => 66 | { 67 | var rs1 = [] 68 | var rs2 = [] 69 | 70 | var a = Bud() 71 | var b = a.map(a => Many(a + 'b1', a + 'b2')) 72 | var c = b.map(b => Many(b + 'c1', b + 'c2')) 73 | 74 | b.on(v => rs1.push(v)) 75 | c.on(v => rs2.push(v)) 76 | 77 | a 78 | .emit(1) 79 | .emit(2) 80 | .emit(3) 81 | 82 | expect(rs1).deep.eq([ '1b1', '1b2', '2b1', '2b2', '3b1', '3b2' ]) 83 | expect(rs2).deep.eq( 84 | [ 85 | '1b1c1', '1b2c1', '1b1c2', '1b2c2', 86 | '2b1c1', '2b2c1', '2b1c2', '2b2c2', 87 | '3b1c1', '3b2c1', '3b1c2', '3b2c2', 88 | ]) 89 | }) 90 | }) 91 | -------------------------------------------------------------------------------- /test/merge.test.js: -------------------------------------------------------------------------------- 1 | 2 | import Bud from 'fluh/lib/Bud' 3 | 4 | // import Nothing from 'fluh/lib/Nothing' 5 | import Many from 'fluh/lib/Many' 6 | import End from 'fluh/lib/End' 7 | 8 | import join from 'fluh/lib/join' 9 | import merge from 'fluh/lib/merge' 10 | 11 | import { state } from './Bud.test' 12 | 13 | 14 | describe('merge', () => 15 | { 16 | it('A ↣ B', () => 17 | { 18 | var a = Bud() 19 | 20 | state(a) 21 | 22 | var b = merge(a) 23 | 24 | state(a, 25 | { 26 | deps: [ b ], 27 | order: [ b ], 28 | }) 29 | 30 | state(b, 31 | { 32 | rev: [ a ], 33 | }) 34 | 35 | var as1 = spy() 36 | a.on(as1) 37 | 38 | var bs1 = spy() 39 | a.on(bs1) 40 | 41 | expect(as1.called).false 42 | expect(bs1.called).false 43 | 44 | a.emit(1) 45 | 46 | var as2 = spy() 47 | var bs2 = spy() 48 | a.on(as2) 49 | a.on(bs2) 50 | 51 | state(a, 52 | { 53 | value: 1, 54 | deps: [ b ], 55 | order: [ b ], 56 | }) 57 | 58 | state(b, 59 | { 60 | value: 1, 61 | rev: [ a ], 62 | }) 63 | 64 | expect(as2.callCount).eq(1) 65 | expect(bs2.callCount).eq(1) 66 | 67 | a.emit(2) 68 | 69 | state(b, 70 | { 71 | value: 2, 72 | rev: [ a ], 73 | }) 74 | 75 | expect(as2.callCount).eq(2) 76 | expect(bs2.callCount).eq(2) 77 | 78 | a.emit(End) 79 | 80 | state(a, { value: End }) 81 | state(b, { value: End }) 82 | 83 | expect(as2.callCount).eq(3) 84 | expect(bs2.callCount).eq(3) 85 | 86 | a.emit(End) 87 | 88 | state(a, { value: End }) 89 | state(b, { value: End }) 90 | 91 | expect(as2.callCount).eq(3) 92 | expect(bs2.callCount).eq(3) 93 | }) 94 | 95 | it('A(v) ↣ B', () => 96 | { 97 | var a = Bud(1) 98 | 99 | state(a, 100 | { 101 | value: 1, 102 | }) 103 | 104 | var b = merge(a) 105 | 106 | state(a, 107 | { 108 | value: 1, 109 | deps: [ b ], 110 | order: [ b ], 111 | }) 112 | 113 | state(b, 114 | { 115 | value: 1, 116 | rev: [ a ], 117 | }) 118 | 119 | a.emit(2) 120 | 121 | state(b, 122 | { 123 | value: 2, 124 | rev: [ a ], 125 | }) 126 | }) 127 | 128 | it('A(emit v) ↣ B', () => 129 | { 130 | var a = Bud() 131 | 132 | a.emit(1) 133 | 134 | var b = merge(a) 135 | 136 | state(b, 137 | { 138 | value: 1, 139 | rev: [ a ], 140 | }) 141 | }) 142 | 143 | it('A(End) ↣ B', () => 144 | { 145 | var a = Bud(End) 146 | var b = merge(a) 147 | 148 | state(b, { value: End }) 149 | }) 150 | 151 | it('A ↣ B(End)', () => 152 | { 153 | var a = Bud() 154 | var b = merge(a) 155 | 156 | var as1 = spy() 157 | a.on(as1) 158 | 159 | var bs1 = spy() 160 | b.on(bs1) 161 | 162 | b.emit(End) 163 | a.emit(1) 164 | 165 | state(a, { value: 1 }) 166 | state(b, { value: End }) 167 | 168 | expect(as1.callCount).eq(1) 169 | expect(bs1.callCount).eq(1) 170 | 171 | a.emit(2) 172 | 173 | expect(as1.callCount).eq(2) 174 | expect(bs1.callCount).eq(1) 175 | 176 | a.emit(End) 177 | 178 | expect(as1.callCount).eq(3) 179 | expect(bs1.callCount).eq(1) 180 | 181 | state(a, { value: End }) 182 | state(b, { value: End }) 183 | }) 184 | 185 | it('A(Many) ↣ B', () => 186 | { 187 | var a = Bud(Many(1, 2)) 188 | var b = merge(a) 189 | 190 | var as1 = spy() 191 | a.on(as1) 192 | 193 | var bs1 = spy() 194 | b.on(bs1) 195 | 196 | expect(as1.callCount).eq(1) 197 | expect(bs1.callCount).eq(1) 198 | 199 | a.emit(Many(3, 4)) 200 | 201 | expect(as1.callCount).eq(3) 202 | expect(bs1.callCount).eq(3) 203 | 204 | state(a, 205 | { 206 | value: 4, 207 | deps: [ b ], 208 | order: [ b ], 209 | }) 210 | 211 | state(b, 212 | { 213 | value: 4, 214 | rev: [ a ], 215 | }) 216 | }) 217 | 218 | // 219 | 220 | it('A,B ↣ C', () => 221 | { 222 | var a = Bud() 223 | var b = Bud() 224 | 225 | var c = merge(a, b) 226 | 227 | state(a, 228 | { 229 | deps: [ c ], 230 | order: [ c ], 231 | }) 232 | state(b, 233 | { 234 | deps: [ c ], 235 | order: [ c ], 236 | }) 237 | state(c, 238 | { 239 | rev: [ a, b ], 240 | }) 241 | 242 | a.emit(1) 243 | 244 | state(a, 245 | { 246 | value: 1, 247 | deps: [ c ], 248 | order: [ c ], 249 | }) 250 | state(b, 251 | { 252 | deps: [ c ], 253 | order: [ c ], 254 | }) 255 | state(c, 256 | { 257 | value: 1, 258 | rev: [ a, b ], 259 | }) 260 | 261 | a.emit(End) 262 | 263 | state(a, { value: End }) 264 | state(b) 265 | state(c, { value: End }) 266 | }) 267 | 268 | it('A,B,C ↣ D', () => 269 | { 270 | var a = Bud() 271 | var b = Bud() 272 | var c = Bud() 273 | 274 | var d = merge(a, b, c) 275 | 276 | state(a, 277 | { 278 | deps: [ d ], 279 | order: [ d ], 280 | }) 281 | state(b, 282 | { 283 | deps: [ d ], 284 | order: [ d ], 285 | }) 286 | state(c, 287 | { 288 | deps: [ d ], 289 | order: [ d ], 290 | }) 291 | state(d, 292 | { 293 | rev: [ a, b, c ], 294 | }) 295 | 296 | b.emit(1) 297 | 298 | state(a, 299 | { 300 | deps: [ d ], 301 | order: [ d ], 302 | }) 303 | state(b, 304 | { 305 | value: 1, 306 | deps: [ d ], 307 | order: [ d ], 308 | }) 309 | state(c, 310 | { 311 | deps: [ d ], 312 | order: [ d ], 313 | }) 314 | state(d, 315 | { 316 | value: 1, 317 | rev: [ a, b, c ], 318 | }) 319 | 320 | a.emit(2) 321 | 322 | state(a, 323 | { 324 | value: 2, 325 | deps: [ d ], 326 | order: [ d ], 327 | }) 328 | state(b, 329 | { 330 | value: 1, 331 | deps: [ d ], 332 | order: [ d ], 333 | }) 334 | state(c, 335 | { 336 | deps: [ d ], 337 | order: [ d ], 338 | }) 339 | state(d, 340 | { 341 | value: 2, 342 | rev: [ a, b, c ], 343 | }) 344 | }) 345 | 346 | it('A(v),B ↣ C', () => 347 | { 348 | var a = Bud(1) 349 | var b = Bud() 350 | 351 | var c = merge(a, b) 352 | 353 | state(a, 354 | { 355 | value: 1, 356 | deps: [ c ], 357 | order: [ c ], 358 | }) 359 | state(b, 360 | { 361 | deps: [ c ], 362 | order: [ c ], 363 | }) 364 | state(c, 365 | { 366 | value: 1, 367 | rev: [ a, b ], 368 | }) 369 | 370 | b.emit(2) 371 | 372 | state(a, 373 | { 374 | value: 1, 375 | deps: [ c ], 376 | order: [ c ], 377 | }) 378 | state(b, 379 | { 380 | value: 2, 381 | deps: [ c ], 382 | order: [ c ], 383 | }) 384 | state(c, 385 | { 386 | value: 2, 387 | rev: [ a, b ], 388 | }) 389 | }) 390 | 391 | it('A(v),B(v) ↣ C', () => 392 | { 393 | var a = Bud(1) 394 | var b = Bud(2) 395 | 396 | var c = merge(a, b) 397 | 398 | var rs = [] 399 | c.on(value => rs.push(value)) 400 | 401 | state(a, 402 | { 403 | value: 1, 404 | deps: [ c ], 405 | order: [ c ], 406 | }) 407 | state(b, 408 | { 409 | value: 2, 410 | deps: [ c ], 411 | order: [ c ], 412 | }) 413 | state(c, 414 | { 415 | value: 2, 416 | rev: [ a, b ], 417 | }) 418 | 419 | expect(rs).deep.eq([ 2 ]) 420 | 421 | b.emit(3) 422 | a.emit(4) 423 | 424 | state(a, 425 | { 426 | value: 4, 427 | deps: [ c ], 428 | order: [ c ], 429 | }) 430 | state(b, 431 | { 432 | value: 3, 433 | deps: [ c ], 434 | order: [ c ], 435 | }) 436 | state(c, 437 | { 438 | value: 4, 439 | rev: [ a, b ], 440 | }) 441 | 442 | expect(rs).deep.eq([ 2, 3, 4 ]) 443 | }) 444 | 445 | it('A(End),B ↣ C', () => 446 | { 447 | var a = Bud(End) 448 | var b = Bud() 449 | 450 | var c = merge(a, b) 451 | 452 | state(a, { value: End }) 453 | state(b) 454 | state(c, { value: End }) 455 | 456 | b.emit(1) 457 | 458 | state(a, { value: End }) 459 | state(b, { value: 1 }) 460 | state(c, { value: End }) 461 | }) 462 | 463 | // 464 | 465 | it('A → B,C ↣ D', () => 466 | { 467 | var a = Bud() 468 | 469 | var b = join(a, a => a * 10) 470 | var c = join(a, a => a * 100) 471 | 472 | var d = merge(b, c) 473 | 474 | var as = spy() 475 | a.on(as) 476 | var ds = spy() 477 | d.on(ds) 478 | 479 | var rs = [] 480 | d.on(value => rs.push(value)) 481 | 482 | expect(as.callCount).eq(0) 483 | expect(ds.callCount).eq(0) 484 | expect(rs).deep.eq([]) 485 | 486 | state(a, 487 | { 488 | deps: [ b, c ], 489 | order: [ b, c, d ], 490 | }) 491 | 492 | state(b, 493 | { 494 | rev: [ a ], 495 | deps: [ d ], 496 | order: [ d ], 497 | }) 498 | 499 | state(c, 500 | { 501 | rev: [ a ], 502 | deps: [ d ], 503 | order: [ d ], 504 | }) 505 | 506 | state(d, 507 | { 508 | rev: [ b, c ], 509 | }) 510 | 511 | a.emit(1) 512 | 513 | expect(as.callCount).eq(1) 514 | expect(ds.callCount).eq(2) 515 | expect(rs).deep.eq([ 10, 100 ]) 516 | 517 | state(a, 518 | { 519 | value: 1, 520 | deps: [ b, c ], 521 | order: [ b, c, d ], 522 | }) 523 | 524 | state(b, 525 | { 526 | value: 10, 527 | rev: [ a ], 528 | deps: [ d ], 529 | order: [ d ], 530 | }) 531 | 532 | state(c, 533 | { 534 | value: 100, 535 | rev: [ a ], 536 | deps: [ d ], 537 | order: [ d ], 538 | }) 539 | 540 | state(d, 541 | { 542 | value: 100, 543 | rev: [ b, c ], 544 | }) 545 | 546 | a.emit(2) 547 | 548 | expect(as.callCount).eq(2) 549 | expect(ds.callCount).eq(4) 550 | expect(rs).deep.eq([ 10, 100, 20, 200 ]) 551 | 552 | state(a, 553 | { 554 | value: 2, 555 | deps: [ b, c ], 556 | order: [ b, c, d ], 557 | }) 558 | 559 | state(b, 560 | { 561 | value: 20, 562 | rev: [ a ], 563 | deps: [ d ], 564 | order: [ d ], 565 | }) 566 | 567 | state(c, 568 | { 569 | value: 200, 570 | rev: [ a ], 571 | deps: [ d ], 572 | order: [ d ], 573 | }) 574 | 575 | state(d, 576 | { 577 | value: 200, 578 | rev: [ b, c ], 579 | }) 580 | }) 581 | }) 582 | -------------------------------------------------------------------------------- /test/parallel.test.js: -------------------------------------------------------------------------------- 1 | 2 | import Bud from 'fluh/lib/Bud' 3 | 4 | import Nothing from 'fluh/lib/Nothing' 5 | import Many from 'fluh/lib/Many' 6 | import End from 'fluh/lib/End' 7 | 8 | import join from 'fluh/lib/join' 9 | 10 | import { state } from './Bud.test' 11 | 12 | 13 | describe('parallel', () => 14 | { 15 | it('A → B, A → C', () => 16 | { 17 | var a = Bud().emit(1) 18 | 19 | var b = join(a, a => a * 10) 20 | var c = join(a, a => a * 100) 21 | 22 | var as = spy() 23 | a.on(as) 24 | 25 | state(a, 26 | { 27 | value: 1, 28 | deps: [ b, c ], 29 | order: [ b, c ], 30 | }) 31 | 32 | state(b, 33 | { 34 | value: 10, 35 | rev: [ a ], 36 | }) 37 | 38 | state(c, 39 | { 40 | value: 100, 41 | rev: [ a ], 42 | }) 43 | 44 | expect(as.callCount).eq(1) 45 | }) 46 | 47 | it('A → B,C → D', () => 48 | { 49 | var a = Bud().emit(1) 50 | 51 | var b = join(a, a => a * 10) 52 | var c = join(a, a => a * 100) 53 | 54 | var d = join(b, c, (b, c) => b + c) 55 | 56 | var as = spy() 57 | a.on(as) 58 | var ds = spy() 59 | d.on(ds) 60 | 61 | state(a, 62 | { 63 | value: 1, 64 | deps: [ b, c ], 65 | order: [ b, c, d ], 66 | }) 67 | 68 | state(b, 69 | { 70 | value: 10, 71 | rev: [ a ], 72 | deps: [ d ], 73 | order: [ d ], 74 | }) 75 | 76 | state(c, 77 | { 78 | value: 100, 79 | rev: [ a ], 80 | deps: [ d ], 81 | order: [ d ], 82 | }) 83 | 84 | state(d, 85 | { 86 | value: 110, 87 | rev: [ b, c ], 88 | }) 89 | 90 | expect(as.callCount).eq(1) 91 | expect(ds.callCount).eq(1) 92 | 93 | a.emit(2) 94 | 95 | expect(as.callCount).eq(2) 96 | expect(ds.callCount).eq(2) 97 | }) 98 | 99 | it('A → x3 → B', () => 100 | { 101 | var a = Bud().emit(1) 102 | 103 | var x = [0, 1, 2, 3].map(pow) 104 | var b = join(...x, sum) 105 | 106 | expect(b.value).eq(1111) 107 | 108 | var as = spy() 109 | a.on(as) 110 | var bs = spy() 111 | b.on(bs) 112 | 113 | expect(as.callCount).eq(1) 114 | expect(bs.callCount).eq(1) 115 | 116 | a.emit(2) 117 | 118 | expect(as.callCount).eq(2) 119 | expect(bs.callCount).eq(2) 120 | 121 | function pow (n) 122 | { 123 | return join(a, a => a * 10 ** n) 124 | } 125 | function sum (...args) 126 | { 127 | return args.reduce((x, y) => x + y, 0) 128 | } 129 | }) 130 | 131 | it('A → B,C → D with Nothing', () => 132 | { 133 | var a = Bud() 134 | 135 | var b = join(a, a => (a % 2) && Nothing || (a / 2)) 136 | var c = join(a, a => a * 100) 137 | 138 | var d = join(b, c, (b, c) => b + c) 139 | 140 | var as = spy() 141 | a.on(as) 142 | var ds = spy() 143 | d.on(ds) 144 | 145 | a.emit(1) 146 | expect(d.value).eq(Nothing) 147 | 148 | expect(as.callCount).eq(1) 149 | expect(ds.callCount).eq(0) 150 | 151 | a.emit(2) 152 | expect(d.value).eq(201) 153 | 154 | expect(as.callCount).eq(2) 155 | expect(ds.callCount).eq(1) 156 | }) 157 | 158 | it('A → B,C → D Many', () => 159 | { 160 | var a = Bud() 161 | 162 | var b = join(a, a => a) 163 | var c = join(a, a => a * 100) 164 | 165 | var d = join(b, c, (b, c) => b + c) 166 | 167 | var as = spy() 168 | a.on(as) 169 | var ds = spy() 170 | d.on(ds) 171 | 172 | a.emit(Many(1, 2)) 173 | 174 | expect(as.callCount).eq(2) 175 | expect(ds.callCount).eq(2) 176 | 177 | a.emit(Many(3, 4, 5)) 178 | expect(d.value).eq(505) 179 | 180 | expect(as.callCount).eq(5) 181 | expect(ds.callCount).eq(5) 182 | }) 183 | 184 | it('A → B(End),C → D', () => 185 | { 186 | var a = Bud() 187 | 188 | var b = join(a, a => 189 | { 190 | if (a === 3) 191 | { 192 | return End 193 | } 194 | 195 | return (a * 10) 196 | }) 197 | var c = join(a, a => a * 100) 198 | 199 | var d = join(b, c, (b, c) => (b, c)) 200 | 201 | var as = spy() 202 | a.on(as) 203 | var ds = spy() 204 | d.on(ds) 205 | 206 | a.emit(1) 207 | 208 | state(a, 209 | { 210 | value: 1, 211 | deps: [ b, c ], 212 | order: [ b, c, d ], 213 | }) 214 | 215 | state(b, 216 | { 217 | value: 10, 218 | rev: [ a ], 219 | deps: [ d ], 220 | order: [ d ], 221 | }) 222 | 223 | state(c, 224 | { 225 | value: 100, 226 | rev: [ a ], 227 | deps: [ d ], 228 | order: [ d ], 229 | }) 230 | 231 | state(d, 232 | { 233 | value: 100, 234 | rev: [ b, c ], 235 | }) 236 | 237 | expect(as.callCount).eq(1) 238 | expect(ds.callCount).eq(1) 239 | 240 | a.emit(2) 241 | 242 | state(a, 243 | { 244 | value: 2, 245 | deps: [ b, c ], 246 | order: [ b, c, d ], 247 | }) 248 | 249 | state(b, 250 | { 251 | value: 20, 252 | rev: [ a ], 253 | deps: [ d ], 254 | order: [ d ], 255 | }) 256 | 257 | state(c, 258 | { 259 | value: 200, 260 | rev: [ a ], 261 | deps: [ d ], 262 | order: [ d ], 263 | }) 264 | 265 | state(d, 266 | { 267 | value: 200, 268 | rev: [ b, c ], 269 | }) 270 | 271 | expect(as.callCount).eq(2) 272 | expect(ds.callCount).eq(2) 273 | 274 | a.emit(3) 275 | 276 | state(a, 277 | { 278 | value: 3, 279 | deps: [ c ], 280 | order: [ c, d ], 281 | }) 282 | 283 | state(b, 284 | { 285 | value: End, 286 | }) 287 | 288 | state(c, 289 | { 290 | value: 300, 291 | rev: [ a ], 292 | deps: [ d ], 293 | order: [ d ], 294 | }) 295 | 296 | state(d, 297 | { 298 | value: 300, 299 | rev: [ b, c ], 300 | }) 301 | 302 | expect(as.callCount).eq(3) 303 | expect(ds.callCount).eq(3) 304 | }) 305 | }) 306 | -------------------------------------------------------------------------------- /test/play.js: -------------------------------------------------------------------------------- 1 | 2 | import { Bud } from '..' 3 | // import { when_data } from '../map/when' 4 | // import { End } from '..' 5 | import { merge } from '..' 6 | 7 | import domain from '../lib/_/domain' 8 | 9 | function state (bud) 10 | { 11 | console.log(bud.id, bud.value, domain.state(bud)) 12 | } 13 | 14 | var a1 = Bud() 15 | var a2 = Bud() 16 | 17 | var b = merge(a1, a2) 18 | 19 | console.log('b', b.value) 20 | 21 | b.on(x => console.log('x', x)) 22 | 23 | a1.emit(1) 24 | a2.emit(2) 25 | a1.emit(3) 26 | 27 | state(a1) 28 | state(a2) 29 | state(b) 30 | -------------------------------------------------------------------------------- /test/promise.test.js: -------------------------------------------------------------------------------- 1 | 2 | import Bud from 'fluh/lib/Bud' 3 | 4 | import * as promise from 'fluh/thru/promise' 5 | 6 | import End from 'fluh/lib/End' 7 | import concat from 'fluh/lib/concat' 8 | 9 | 10 | describe('promise', () => 11 | { 12 | it('every', async () => 13 | { 14 | var a = Bud() 15 | var b = a.thru(promise.every) 16 | 17 | var rs = [] 18 | b.on(value => rs.push(value)) 19 | 20 | a 21 | .emit(delay.resolve(2, 'c')) 22 | .emit(delay.resolve(0, 'a')) 23 | .emit(delay.resolve(1, 'b')) 24 | .emit(delay.resolve(3, End)) 25 | 26 | expect(await concat(b)).deep.eq([ 'a', 'b', 'c', End ]) 27 | }) 28 | 29 | it('every catch', async () => 30 | { 31 | var a = Bud() 32 | var b = a.thru(promise.every) 33 | 34 | var e = new Error('x') 35 | 36 | var rs = [] 37 | b.on(value => rs.push(value)) 38 | 39 | a 40 | .emit(delay.resolve(2, 'c')) 41 | .emit(delay.resolve(0, 'a')) 42 | .emit(delay.reject(1, e)) 43 | .emit(delay.resolve(3, End)) 44 | 45 | expect(await concat(b)).deep.eq([ 'a', e, 'c', End ]) 46 | }) 47 | 48 | it('every heterohenous', async () => 49 | { 50 | var a = Bud() 51 | var b = a.thru(promise.every) 52 | 53 | var rs = [] 54 | b.on(value => rs.push(value)) 55 | 56 | a 57 | .emit(delay.resolve(2, 'c')) 58 | .emit('a') 59 | .emit(delay.resolve(1, 'b')) 60 | .emit(delay.resolve(3, End)) 61 | 62 | expect(await concat(b)).deep.eq([ 'a', 'b', 'c', End ]) 63 | }) 64 | 65 | it('last', async () => 66 | { 67 | var a = Bud() 68 | var b = a.thru(promise.last) 69 | 70 | var rs = [] 71 | b.on(value => rs.push(value)) 72 | 73 | a 74 | .emit(delay.resolve(2, 'c')) 75 | .emit(delay.resolve(0, 'a')) 76 | .emit(delay.resolve(1, 'b')) 77 | 78 | delay.resolve(3).then(() => a.emit(Promise.resolve(End))) 79 | 80 | expect(await concat(b)).deep.eq([ 'b', End ]) 81 | }) 82 | 83 | it('last catch', async () => 84 | { 85 | var a = Bud() 86 | var b = a.thru(promise.last) 87 | 88 | var e1 = new Error('x') 89 | var e2 = new Error('y') 90 | 91 | var rs = [] 92 | b.on(value => rs.push(value)) 93 | 94 | a 95 | .emit(delay.reject(2, e2)) 96 | .emit(delay.resolve(0, 'a')) 97 | .emit(delay.reject(1, e1)) 98 | 99 | delay.resolve(3).then(() => a.emit(Promise.resolve(End))) 100 | 101 | expect(await concat(b)).deep.eq([ e1, End ]) 102 | }) 103 | 104 | it('last heterohenous', async () => 105 | { 106 | var a = Bud() 107 | var b = a.thru(promise.last) 108 | 109 | var rs = [] 110 | b.on(value => rs.push(value)) 111 | 112 | a 113 | .emit(delay.resolve(2, 'c')) 114 | .emit('a') 115 | .emit(delay.resolve(1, 'b')) 116 | 117 | delay.resolve(3).then(() => a.emit(Promise.resolve(End))) 118 | 119 | expect(await concat(b)).deep.eq([ 'b', End ]) 120 | }) 121 | 122 | it('buffered', async () => 123 | { 124 | var a = Bud() 125 | var b = a.thru(promise.buffered(3)) 126 | 127 | var rs = [] 128 | b.on(value => rs.push(value)) 129 | 130 | a 131 | .emit(delay.resolve(2, 'a')) 132 | .emit(delay.resolve(0, 'b')) 133 | .emit(delay.resolve(1, 'c')) 134 | .emit(delay.resolve(4, 'd')) 135 | .emit(delay.resolve(3, 'e')) 136 | 137 | delay.resolve(5).then(() => a.emit(Promise.resolve(End))) 138 | 139 | expect(await concat(b)).deep.eq([ 'c', 'd', 'e', End ]) 140 | }) 141 | 142 | it('buffered(1)', async () => 143 | { 144 | var a = Bud() 145 | var b = a.thru(promise.buffered(1)) 146 | 147 | var rs = [] 148 | b.on(value => rs.push(value)) 149 | 150 | a 151 | .emit(delay.resolve(2, 'c')) 152 | .emit(delay.resolve(0, 'a')) 153 | .emit(delay.resolve(1, 'b')) 154 | 155 | delay.resolve(3).then(() => a.emit(Promise.resolve(End))) 156 | 157 | expect(await concat(b)).deep.eq([ 'b', End ]) 158 | }) 159 | 160 | it('buffered catch', async () => 161 | { 162 | var a = Bud() 163 | var b = a.thru(promise.buffered(3)) 164 | 165 | var e = new Error('x') 166 | 167 | var rs = [] 168 | b.on(value => rs.push(value)) 169 | 170 | a 171 | .emit(delay.resolve(2, 'a')) 172 | .emit(delay.resolve(0, 'b')) 173 | .emit(delay.resolve(1, 'c')) 174 | .emit(delay.reject(4, e)) 175 | .emit(delay.resolve(3, 'e')) 176 | 177 | delay.resolve(5).then(() => a.emit(Promise.resolve(End))) 178 | 179 | expect(await concat(b)).deep.eq([ 'c', e, 'e', End ]) 180 | }) 181 | 182 | it('buffered heterohenous', async () => 183 | { 184 | var a = Bud() 185 | var b = a.thru(promise.buffered(3)) 186 | 187 | var rs = [] 188 | b.on(value => rs.push(value)) 189 | 190 | a 191 | .emit(delay.resolve(2, 'a')) 192 | .emit(delay.resolve(0, 'b')) 193 | .emit(delay.resolve(1, 'c')) 194 | .emit('d') 195 | .emit(delay.resolve(3, 'e')) 196 | 197 | delay.resolve(5).then(() => a.emit(Promise.resolve(End))) 198 | 199 | expect(await concat(b)).deep.eq([ 'c', 'd', 'e', End ]) 200 | }) 201 | 202 | it('buffered smooth', async () => 203 | { 204 | var a = Bud() 205 | var b = a.thru(promise.buffered(3)) 206 | 207 | var rs = [] 208 | b.on(value => rs.push(value)) 209 | 210 | a 211 | .emit(delay.resolve(0, 'a')) 212 | .emit(delay.resolve(1, 'b')) 213 | .emit(delay.resolve(2, 'c')) 214 | 215 | delay.resolve(3).then(() => 216 | { 217 | a 218 | .emit(delay.resolve(0, 'd')) 219 | .emit(delay.resolve(1, 'e')) 220 | .emit(delay.resolve(2, End)) 221 | }) 222 | 223 | expect(await concat(b)).deep.eq([ 'a', 'b', 'c', 'd', 'e', End ]) 224 | }) 225 | }) 226 | 227 | var delay = 228 | { 229 | resolve (n, value) 230 | { 231 | return new Promise(rs => 232 | { 233 | setTimeout(() => rs(value), (20 * n)) 234 | }) 235 | }, 236 | reject (n, value) 237 | { 238 | return delay 239 | .resolve(n) 240 | .then(() => { throw value }) 241 | }, 242 | } 243 | -------------------------------------------------------------------------------- /test/reduce.test.js: -------------------------------------------------------------------------------- 1 | 2 | import Bud from 'fluh/lib/Bud' 3 | import End from 'fluh/lib/End' 4 | 5 | import { when_data } from 'fluh/map/when' 6 | 7 | import reduce from 'fluh/map/reduce' 8 | 9 | 10 | describe('reduce', () => 11 | { 12 | function sum (a, b) 13 | { 14 | return (a + b) 15 | } 16 | 17 | it('sum', () => 18 | { 19 | var a = Bud() 20 | var b = a.map(reduce(100, sum)) 21 | 22 | var rsb = [] 23 | b.on(v => rsb.push(v)) 24 | 25 | a 26 | .emit(1) 27 | .emit(2) 28 | .emit(3) 29 | .emit(4) 30 | 31 | expect(b.value).eq(110) 32 | expect(rsb).deep.eq([ 101, 103, 106, 110 ]) 33 | }) 34 | 35 | it('sum ends', () => 36 | { 37 | var a = Bud() 38 | var b = a.map(when_data(reduce(100, sum))) 39 | 40 | var rsb = [] 41 | b.on(v => rsb.push(v)) 42 | 43 | a 44 | .emit(1) 45 | .emit(2) 46 | .emit(3) 47 | .emit(4) 48 | .emit(End) 49 | 50 | expect(b.value).eq(End) 51 | expect(rsb).deep.eq([ 101, 103, 106, 110, End ]) 52 | }) 53 | 54 | it('concat', () => 55 | { 56 | function concat () 57 | { 58 | return reduce([], (memo, next) => 59 | { 60 | return [ ...memo, next ] 61 | }) 62 | } 63 | 64 | var a = Bud() 65 | var b = a.map(when_data(concat())) 66 | 67 | var rsb = [] 68 | b.on(v => rsb.push(v)) 69 | 70 | a 71 | .emit(1) 72 | .emit(2) 73 | .emit(3) 74 | .emit(4) 75 | .emit(End) 76 | 77 | expect(b.value).eq(End) 78 | expect(rsb).deep.eq( 79 | [ 80 | [ 1 ], 81 | [ 1, 2 ], 82 | [ 1, 2, 3 ], 83 | [ 1, 2, 3, 4 ], 84 | End, 85 | ]) 86 | }) 87 | }) 88 | -------------------------------------------------------------------------------- /test/resource.test.js: -------------------------------------------------------------------------------- 1 | 2 | var noop = () => void 0 3 | 4 | import resource from 'fluh/lib/resource' 5 | 6 | import End from 'fluh/lib/End' 7 | import concat from 'fluh/lib/concat' 8 | import { when_data } from 'fluh/map/when' 9 | import turnoff from 'fluh/lib/turnoff' 10 | 11 | import { expect_bud } from './Bud.test' 12 | 13 | 14 | describe('resource', () => 15 | { 16 | function Timer (interval) 17 | { 18 | return resource(emit => 19 | { 20 | var n = 0 21 | var t = setInterval(() => 22 | { 23 | emit(n = (n + 1)) 24 | } 25 | , interval) 26 | 27 | return () => 28 | { 29 | if (! t) { return } 30 | 31 | clearInterval(t) 32 | t = null 33 | } 34 | }) 35 | } 36 | 37 | 38 | it('works', async () => 39 | { 40 | var t = Timer(50) 41 | 42 | t.on(v => (v === 5) && t.emit(End)) 43 | 44 | expect(await concat(t)).deep.eq([ 1, 2, 3, 4, 5, End ]) 45 | }) 46 | 47 | it('passes args', () => 48 | { 49 | resource((emit, bud) => 50 | { 51 | expect(emit).a('function') 52 | expect_bud(bud) 53 | 54 | return noop 55 | }) 56 | .emit(End) 57 | }) 58 | 59 | it('works with turnoff', async () => 60 | { 61 | var t = Timer(50) 62 | 63 | var t2 = t.map(when_data(v => (v * 2))) 64 | 65 | t2.on(v => (v === 10) && t.emit(End)) 66 | 67 | turnoff(t2, t) 68 | 69 | var [ rs1, rs2 ] = await Promise.all([ concat(t), concat(t2) ]) 70 | 71 | expect(rs1).deep.eq([ 1, 2, 3, 4, 5, End ]) 72 | expect(rs2).deep.eq([ 2, 4, 6, 8, 10, End ]) 73 | }) 74 | }) 75 | -------------------------------------------------------------------------------- /test/tap.test.js: -------------------------------------------------------------------------------- 1 | 2 | import tap from 'fluh/lib/tap' 3 | 4 | 5 | describe('tap', () => 6 | { 7 | it('not mutate', () => 8 | { 9 | var mut = 0 10 | 11 | function mutate (value) 12 | { 13 | mut = ('m' + value) 14 | 15 | return 'd' 16 | } 17 | 18 | var T = tap(mutate) 19 | 20 | expect(mut).eq(0) 21 | 22 | var R = T('a', 'b', 'c') 23 | 24 | expect(R).eq('a') 25 | expect(mut).eq('ma') 26 | }) 27 | 28 | it('receives all arguments', () => 29 | { 30 | function receiver (...args) 31 | { 32 | expect(args).deep.eq([ 'a', 'b', 'c' ]) 33 | } 34 | 35 | var T = tap(receiver) 36 | 37 | T('a', 'b', 'c') 38 | }) 39 | }) 40 | -------------------------------------------------------------------------------- /test/thru.test.js: -------------------------------------------------------------------------------- 1 | 2 | import Bud from 'fluh/lib/Bud' 3 | 4 | 5 | describe('thru', () => 6 | { 7 | it('works', () => 8 | { 9 | var mono = Bud() 10 | 11 | var a = Bud() 12 | var b = a.thru(bud => 13 | { 14 | expect(bud).eq(a) 15 | 16 | return mono 17 | }) 18 | 19 | expect(b).eq(mono) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /test/triangle.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable template-curly-spacing */ 2 | 3 | import Bud from 'fluh/lib/Bud' 4 | 5 | import Nothing from 'fluh/lib/Nothing' 6 | import Many from 'fluh/lib/Many' 7 | import End from 'fluh/lib/End' 8 | 9 | import join from 'fluh/lib/join' 10 | 11 | import { state } from './Bud.test' 12 | 13 | 14 | describe('triangle', () => 15 | { 16 | it('A,B,C; B → C', () => 17 | { 18 | var a = Bud().emit(1) 19 | 20 | var b = join(a, a => a * 10) 21 | var c = join(a, b, (a, b) => a * 100 + b) 22 | 23 | var s = spy() 24 | c.on(s) 25 | 26 | state(a, 27 | { 28 | value: 1, 29 | deps: [ b, c ], 30 | order: [ b, c ], 31 | }) 32 | 33 | state(b, 34 | { 35 | value: 10, 36 | rev: [ a ], 37 | deps: [ c ], 38 | order: [ c ], 39 | }) 40 | 41 | state(c, 42 | { 43 | value: 110, 44 | rev: [ a, b ], 45 | }) 46 | 47 | expect(s.callCount).eq(1) 48 | 49 | a.emit(2) 50 | 51 | expect(s.callCount).eq(2) 52 | }) 53 | 54 | it('A,B,C; B → C with Many', () => 55 | { 56 | var a = Bud().emit(1) 57 | 58 | var b = join(a, a => a * 10) 59 | var c = join(a, b, (a, b) => a * 100 + b) 60 | 61 | var s = spy() 62 | c.on(s) 63 | 64 | expect(b.value).eq(10) 65 | expect(c.value).eq(110) 66 | 67 | expect(s.callCount).eq(1) 68 | 69 | a.emit(Many(Nothing, 2)) 70 | 71 | expect(s.callCount).eq(2) 72 | }) 73 | 74 | it('triangle in triangle', () => 75 | { 76 | var a = Bud().emit('A') 77 | 78 | var b1 = join(a, a => a + '.B1') 79 | var c1 = join(a, b1, (a, b) => `${a}/${b}.C1`) 80 | 81 | var b2 = join(b1, b => b + '.B2') 82 | var c2 = join(b2, c1, (b, c) => `${b}/${c}.C2`) 83 | 84 | var s = spy() 85 | c2.on(s) 86 | 87 | state(a, 88 | { 89 | value: 'A', 90 | deps: [ b1, c1 ], 91 | order: [ b1, b2, c1, c2 ], 92 | }) 93 | 94 | state(b1, 95 | { 96 | value: 'A.B1', 97 | rev: [ a ], 98 | deps: [ c1, b2 ], 99 | order: [ c1, b2, c2 ], 100 | }) 101 | 102 | state(c1, 103 | { 104 | value: 'A/A.B1.C1', 105 | rev: [ a, b1 ], 106 | deps: [ c2 ], 107 | order: [ c2 ], 108 | }) 109 | 110 | state(b2, 111 | { 112 | value: 'A.B1.B2', 113 | rev: [ b1 ], 114 | deps: [ c2 ], 115 | order: [ c2 ], 116 | }) 117 | 118 | state(c2, 119 | { 120 | value: 'A.B1.B2/A/A.B1.C1.C2', 121 | rev: [ b2, c1 ], 122 | }) 123 | 124 | expect(s.callCount).eq(1) 125 | 126 | a.emit('B') 127 | 128 | expect(s.callCount).eq(2) 129 | }) 130 | 131 | it('A,B(End),C; B → C', () => 132 | { 133 | var a = Bud(1) 134 | 135 | var b = join(a, a => 136 | { 137 | if (a === 3) 138 | { 139 | return End 140 | } 141 | 142 | return a 143 | }) 144 | 145 | var c = join(a, b, (a, b) => (b, a)) 146 | 147 | var s = spy() 148 | c.on(s) 149 | 150 | state(a, 151 | { 152 | value: 1, 153 | deps: [ b, c ], 154 | order: [ b, c ], 155 | }) 156 | 157 | state(b, 158 | { 159 | value: 1, 160 | rev: [ a ], 161 | deps: [ c ], 162 | order: [ c ], 163 | }) 164 | 165 | state(c, 166 | { 167 | value: 1, 168 | rev: [ a, b ], 169 | }) 170 | 171 | expect(s.callCount).eq(1) 172 | 173 | /* - */ 174 | a.emit(2) 175 | 176 | state(a, 177 | { 178 | value: 2, 179 | deps: [ b, c ], 180 | order: [ b, c ], 181 | }) 182 | 183 | state(b, 184 | { 185 | value: 2, 186 | rev: [ a ], 187 | deps: [ c ], 188 | order: [ c ], 189 | }) 190 | 191 | state(c, 192 | { 193 | value: 2, 194 | rev: [ a, b ], 195 | }) 196 | 197 | expect(s.callCount).eq(2) 198 | 199 | /* - */ 200 | a.emit(3) 201 | 202 | state(a, 203 | { 204 | value: 3, 205 | deps: [ c ], 206 | order: [ c ], 207 | }) 208 | 209 | state(b, 210 | { 211 | value: End, 212 | }) 213 | 214 | state(c, 215 | { 216 | value: 3, 217 | rev: [ a, b ], 218 | }) 219 | 220 | expect(s.callCount).eq(3) 221 | 222 | /* - */ 223 | a.emit(4) 224 | 225 | state(a, 226 | { 227 | value: 4, 228 | deps: [ c ], 229 | order: [ c ], 230 | }) 231 | 232 | state(b, 233 | { 234 | value: End, 235 | }) 236 | 237 | state(c, 238 | { 239 | value: 4, 240 | rev: [ a, b ], 241 | }) 242 | 243 | expect(s.callCount).eq(4) 244 | }) 245 | }) 246 | -------------------------------------------------------------------------------- /test/turnoff.test.js: -------------------------------------------------------------------------------- 1 | 2 | import End from 'fluh/lib/End' 3 | 4 | import Bud from 'fluh/lib/Bud' 5 | import turnoff from 'fluh/lib/turnoff' 6 | 7 | import { when_data } from 'fluh/map/when' 8 | 9 | import { state } from './Bud.test' 10 | 11 | 12 | describe('turnoff', () => 13 | { 14 | it('works', () => 15 | { 16 | var a = Bud() 17 | var b = a.map(when_data(v => (v * 2))) 18 | 19 | turnoff(b, a) 20 | 21 | var rs = [] 22 | b.on(v => rs.push(v)) 23 | 24 | b 25 | .emit(100) 26 | .emit(End) 27 | 28 | a 29 | .emit(1) 30 | .emit(2) 31 | .emit(3) 32 | 33 | expect(rs).deep.eq([ 100, End ]) 34 | 35 | state(a, { value: End }) 36 | state(b, { value: End }) 37 | }) 38 | }) 39 | -------------------------------------------------------------------------------- /test/type/filter.test.ts: -------------------------------------------------------------------------------- 1 | 2 | import Bud from 'fluh/lib/Bud' 3 | import End from 'fluh/lib/End' 4 | 5 | import filter from 'fluh/map/filter' 6 | import filter_by from 'fluh/map/filter-by' 7 | 8 | import { when_data } from 'fluh/map/when' 9 | 10 | function isNumber (x: any): x is number 11 | { 12 | return (typeof x === 'number') 13 | } 14 | 15 | function isString (x: any): x is string 16 | { 17 | return (typeof x === 'string') 18 | } 19 | 20 | function Filter () 21 | { 22 | const input = Bud() 23 | const nums = input.map(filter(isNumber)) 24 | const strs = input.map(filter(isString)) 25 | const othr = input.map(filter(x => Boolean(String(x).match(/^\d+$/)))) 26 | 27 | nums // $ExpectType Bud 28 | strs // $ExpectType Bud 29 | othr // $ExpectType Bud 30 | 31 | nums.on(x => console.log(x + 1)) 32 | strs.on(x => x.toUpperCase()) 33 | othr.on(x => console.log(x)) 34 | 35 | input 36 | .emit(1) 37 | .emit('b') 38 | .emit(2) 39 | .emit('c') 40 | .emit(3) 41 | } 42 | 43 | function FilterBy () 44 | { 45 | const input = Bud() 46 | const signal = input.map(v => (v > 2)) 47 | const output = input.map(filter_by(signal)) 48 | 49 | signal // $ExpectType Bud 50 | output // $ExpectType Bud 51 | 52 | output.on(x => console.log(x + 1)) 53 | 54 | input.emit(1).emit(2).emit(3) 55 | } 56 | 57 | function WhenData () 58 | { 59 | const input = Bud() 60 | 61 | input.map(x => x * 2) // $ExpectError 62 | const output = input.map(when_data(x => x * 2)) 63 | 64 | output // $ExpectType Bud 65 | 66 | output.on(x => console.log(x + 1)) // $ExpectError 67 | output.on(when_data(x => console.log(x + 1))) 68 | 69 | input.emit(1).emit(2).emit(3).emit(End) 70 | } 71 | 72 | function WhenDataRedundant () 73 | { 74 | const input = Bud() 75 | const output = input.map(when_data(x => x * 2)) 76 | 77 | output // $ExpectType Bud 78 | 79 | output.on(x => console.log(x + 1)) 80 | output.on(when_data(x => console.log(x + 1))) 81 | 82 | input.emit(1).emit(2).emit(3) 83 | } 84 | 85 | function WhenDataError () 86 | { 87 | const input = Bud() 88 | 89 | input.map(x => x * 2) // $ExpectError 90 | const output = input.map(when_data(x => x * 2)) 91 | 92 | output // $ExpectType Bud 93 | 94 | output.on(x => console.log(x + 1)) // $ExpectError 95 | output.on(when_data(x => console.log(x + 1))) 96 | 97 | input.emit(1).emit(2).emit(3).emit(new Error).emit(End) 98 | } 99 | -------------------------------------------------------------------------------- /test/type/index.d.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StreetStrider/fluh/356787dc00a78606cbb5bf11094200abcf041613/test/type/index.d.ts -------------------------------------------------------------------------------- /test/type/join.test.ts: -------------------------------------------------------------------------------- 1 | 2 | import Bud from 'fluh/lib/Bud' 3 | import join from 'fluh/lib/join' 4 | 5 | function Join1 () 6 | { 7 | const a = Bud(1) 8 | const b = join(a, x => 9 | { 10 | x // $ExpectType number 11 | return (x + 1) 12 | }) 13 | const c = join(a, x => String(x)) 14 | 15 | a // $ExpectType Bud 16 | b // $ExpectType Bud 17 | c // $ExpectType Bud 18 | 19 | a.on(x => 20 | { 21 | x // $ExpectType number 22 | }) 23 | b.on(x => 24 | { 25 | x // $ExpectType number 26 | }) 27 | c.on(x => 28 | { 29 | x // $ExpectType string 30 | }) 31 | } 32 | 33 | function Join1F () 34 | { 35 | const a = Bud(1) 36 | const b = join(x => 37 | { 38 | x // $ExpectType number 39 | return (x + 1) 40 | } 41 | , a) 42 | const c = join(x => String(x), a) 43 | 44 | a // $ExpectType Bud 45 | b // $ExpectType Bud 46 | c // $ExpectType Bud 47 | 48 | b.on(x => 49 | { 50 | x // $ExpectType number 51 | }) 52 | c.on(x => 53 | { 54 | x // $ExpectType string 55 | }) 56 | } 57 | 58 | function Join2 () 59 | { 60 | const a = Bud(1) 61 | const b = Bud('X') 62 | const c = join(a, b, (a, b) => 63 | { 64 | a // $ExpectType number 65 | b // $ExpectType string 66 | return (String(a) === b) 67 | }) 68 | 69 | a // $ExpectType Bud 70 | b // $ExpectType Bud 71 | c // $ExpectType Bud 72 | 73 | c.on(x => 74 | { 75 | x // $ExpectType boolean 76 | }) 77 | } 78 | 79 | function Join2F () 80 | { 81 | const a = Bud(1) 82 | const b = Bud('X') 83 | const c = join((a, b) => 84 | { 85 | a // $ExpectType number 86 | b // $ExpectType string 87 | return (String(a) === b) 88 | } 89 | , a, b) 90 | 91 | a // $ExpectType Bud 92 | b // $ExpectType Bud 93 | c // $ExpectType Bud 94 | 95 | c.on(x => 96 | { 97 | x // $ExpectType boolean 98 | }) 99 | } 100 | 101 | function Join6 () 102 | { 103 | const b1 = Bud(1) 104 | const b2 = Bud('X') 105 | const c = join((b1, b2, b3, b4, b5, b6) => 106 | { 107 | b1 // $ExpectType number 108 | b2 // $ExpectType string 109 | b3 // $ExpectType number 110 | b4 // $ExpectType number 111 | b5 // $ExpectType string 112 | b6 // $ExpectType number 113 | return `${ b1 }:${ b2 }:${ b3 }:${ b4 }:${ b5 }:${ b6 }` 114 | } 115 | , b1, b2, Bud(3), Bud(4), Bud('Y'), Bud(6)) 116 | 117 | c // $ExpectType Bud 118 | 119 | c.on(x => 120 | { 121 | x // $ExpectType string 122 | }) 123 | } 124 | 125 | /* 126 | function Join6Tuple () 127 | { 128 | const b1 = Bud(1) 129 | const b2 = Bud('X') 130 | const c = join((b1, b2, b3, b4, b5, b6) => 131 | { 132 | b1 // $-ExpectType number 133 | b2 // $-ExpectType string 134 | b3 // $-ExpectType number 135 | b4 // $-ExpectType number 136 | b5 // $-ExpectType string 137 | b6 // $-ExpectType number 138 | return ([ b1, b2, b3, b4, b5, b6 ] as const) 139 | } 140 | , b1, b2, Bud(3), Bud(4), Bud('Y'), Bud(6)) 141 | 142 | c // $-ExpectType Bud 143 | 144 | c.on(x => 145 | { 146 | x // $-ExpectType readonly [number, string, number, number, string, number] 147 | }) 148 | } 149 | */ 150 | 151 | function Join6Spread () 152 | { 153 | const c = join((b1, b2, b3, b4, b5, b6) => 154 | { 155 | b1 // $ExpectType number 156 | b2 // $ExpectType string 157 | b3 // $ExpectType number 158 | b4 // $ExpectType number 159 | b5 // $ExpectType string 160 | b6 // $ExpectType number 161 | return `${ b1 }:${ b2 }:${ b3 }:${ b4 }:${ b5 }:${ b6 }` 162 | } 163 | , ...[ Bud(1), Bud('X'), Bud(3), Bud(4), Bud('Y'), Bud(6) ] as const) 164 | 165 | c // $ExpectType Bud 166 | 167 | c.on(x => 168 | { 169 | x // $ExpectType string 170 | }) 171 | } 172 | -------------------------------------------------------------------------------- /test/type/map.test.ts: -------------------------------------------------------------------------------- 1 | 2 | import Bud from 'fluh/lib/Bud' 3 | import Nothing from 'fluh/lib/Nothing' 4 | import Many from 'fluh/lib/Many' 5 | import End from 'fluh/lib/End' 6 | 7 | function Map () 8 | { 9 | const a = Bud(1) 10 | const b = a.map(x => 11 | { 12 | x // $ExpectType number 13 | return (x + 1) 14 | }) 15 | const c = a.map(x => String(x)) 16 | 17 | a // $ExpectType Bud 18 | b // $ExpectType Bud 19 | c // $ExpectType Bud 20 | 21 | a.on(x => 22 | { 23 | x // $ExpectType number 24 | }) 25 | b.on(x => 26 | { 27 | x // $ExpectType number 28 | }) 29 | c.on(x => 30 | { 31 | x // $ExpectType string 32 | }) 33 | } 34 | 35 | function MapNothing () 36 | { 37 | const a = Bud() 38 | const b = a.map(x => 39 | { 40 | x // $ExpectType number 41 | return (x + 1) 42 | }) 43 | const c = a.map(x => String(x)) 44 | 45 | a // $ExpectType Bud 46 | b // $ExpectType Bud 47 | c // $ExpectType Bud 48 | 49 | a.on(x => 50 | { 51 | x // $ExpectType number 52 | }) 53 | b.on(x => 54 | { 55 | x // $ExpectType number 56 | }) 57 | c.on(x => 58 | { 59 | x // $ExpectType string 60 | }) 61 | } 62 | 63 | function MapSpecial () 64 | { 65 | const a = Bud(1) 66 | a // $ExpectType Bud 67 | 68 | const b = a.map(x => Nothing) 69 | const c = a.map(x => Many(1, 2)) 70 | const d = a.map(x => End) 71 | 72 | b // $ExpectType Bud 73 | c // $ExpectType Bud 74 | d // $ExpectType Bud 75 | 76 | /* TODO: 77 | b.on(x => 78 | { 79 | x // $---ExpectType never 80 | }) 81 | */ 82 | } 83 | -------------------------------------------------------------------------------- /test/type/merge.test.ts: -------------------------------------------------------------------------------- 1 | 2 | import Bud from 'fluh/lib/Bud' 3 | import merge from 'fluh/lib/merge' 4 | 5 | function Merge () 6 | { 7 | const a = Bud(1) 8 | const b = Bud('X') 9 | 10 | const c = merge(a, b) 11 | 12 | c // $ExpectType Bud 13 | 14 | c.on(x => 15 | { 16 | x // $ExpectType string | number 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /test/type/reduce.test.ts: -------------------------------------------------------------------------------- 1 | import Bud from 'fluh/lib/Bud' 2 | import End from 'fluh/lib/End' 3 | 4 | import reduce from 'fluh/map/reduce' 5 | 6 | import { when_data } from 'fluh/map/when' 7 | 8 | function sum (a: number, b: number) 9 | { 10 | return (a + b) 11 | } 12 | 13 | function concat () 14 | { 15 | return reduce([] as T[], (memo: T[], next: T) => 16 | { 17 | return [ ...memo, next ] 18 | }) 19 | } 20 | 21 | function Sum () 22 | { 23 | const a = Bud() 24 | const b = a.map(reduce(100, sum)) 25 | 26 | b // $ExpectType Bud 27 | 28 | const rsb = [] 29 | b.on(v => rsb.push(v)) 30 | 31 | a 32 | .emit(1) 33 | .emit(2) 34 | .emit(4) 35 | } 36 | 37 | function SumRedundant () 38 | { 39 | const a = Bud() 40 | const b = a.map(when_data(reduce(100, sum))) 41 | 42 | b // $ExpectType Bud 43 | 44 | const rsb = [] 45 | b.on(v => rsb.push(v)) 46 | 47 | a 48 | .emit(1) 49 | .emit(2) 50 | .emit(3) 51 | } 52 | 53 | function SumEnd () 54 | { 55 | const a = Bud() 56 | const b = a.map(when_data(reduce(100, sum))) 57 | 58 | b // $ExpectType Bud 59 | 60 | const rsb = [] 61 | b.on(v => rsb.push(v)) 62 | 63 | a 64 | .emit(1) 65 | .emit(2) 66 | .emit(3) 67 | .emit(End) 68 | } 69 | 70 | function Concat () 71 | { 72 | const a = Bud() 73 | const b = a.map(when_data(concat())) 74 | // const b = a.map(when_data(concat())) 75 | // const b = a.map(when_data(concat())) 76 | 77 | b // $ExpectType Bud 78 | 79 | const rsb = [] 80 | b.on(v => rsb.push(v)) 81 | 82 | a 83 | .emit(1) 84 | .emit(2) 85 | .emit(3) 86 | .emit(4) 87 | .emit(End) 88 | } 89 | -------------------------------------------------------------------------------- /test/type/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": 3 | [ 4 | "**/*.test.ts" 5 | ], 6 | "compilerOptions": 7 | { 8 | "noEmit": true, 9 | "pretty": true, 10 | 11 | "strict": true, 12 | "target": "ES6", 13 | "lib": [ "ES6" ], 14 | 15 | "module": "nodenext" 16 | } 17 | } -------------------------------------------------------------------------------- /test/uniq.test.js: -------------------------------------------------------------------------------- 1 | 2 | import Bud from 'fluh/lib/Bud' 3 | import End from 'fluh/lib/End' 4 | 5 | import concat from 'fluh/lib/concat' 6 | 7 | import uniq from 'fluh/map/uniq' 8 | 9 | 10 | describe('uniq', () => 11 | { 12 | it('uniq', async () => 13 | { 14 | var a = Bud() 15 | var b = a.map(uniq()) 16 | 17 | var rs = concat(b) 18 | 19 | a 20 | .emit(1) 21 | .emit(1) 22 | .emit(2) 23 | .emit(2) 24 | .emit(1) 25 | .emit(2) 26 | .emit(3) 27 | .emit(End) 28 | 29 | expect(await rs).deep.eq([ 1, 2, 1, 2, 3, End ]) 30 | }) 31 | 32 | it('uniq (value)', async () => 33 | { 34 | var a = Bud(1) 35 | var b = a.map(uniq()) 36 | 37 | var rs = concat(b) 38 | 39 | a 40 | .emit(1) 41 | .emit(1) 42 | .emit(2) 43 | .emit(2) 44 | .emit(1) 45 | .emit(2) 46 | .emit(3) 47 | .emit(End) 48 | 49 | expect(await rs).deep.eq([ 1, 2, 1, 2, 3, End ]) 50 | }) 51 | 52 | it('uniq (value + emit)', async () => 53 | { 54 | var a = Bud(0).emit(1) 55 | var b = a.map(uniq()) 56 | 57 | var rs = concat(b) 58 | 59 | a 60 | .emit(1) 61 | .emit(1) 62 | .emit(2) 63 | .emit(2) 64 | .emit(1) 65 | .emit(2) 66 | .emit(3) 67 | .emit(End) 68 | 69 | expect(await rs).deep.eq([ 1, 2, 1, 2, 3, End ]) 70 | }) 71 | }) 72 | -------------------------------------------------------------------------------- /test/when.test.js: -------------------------------------------------------------------------------- 1 | 2 | import Nothing from 'fluh/lib/Nothing' 3 | import End from 'fluh/lib/End' 4 | 5 | import Noop from 'fluh/lib/Noop' 6 | import Fin from 'fluh/lib/Fin' 7 | 8 | import { When } from 'fluh/map/when' 9 | import { when_data } from 'fluh/map/when' 10 | import { when_end } from 'fluh/map/when' 11 | import { when_error } from 'fluh/map/when' 12 | import { when_data_all } from 'fluh/map/when' 13 | 14 | 15 | describe('when', () => 16 | { 17 | it('when(_)(_, _)', () => 18 | { 19 | var W = When(x => (x <= 1)) 20 | 21 | expect(W).a('function') 22 | expect(W.length).eq(1) 23 | 24 | var w = W(x => (x * 10 + 1), x => (x * 100 + 1)) 25 | 26 | expect(w).a('function') 27 | expect(w.length).eq(0) 28 | 29 | expect(w(0)).eq(1) 30 | expect(w(1)).eq(11) 31 | expect(w(2)).eq(201) 32 | expect(w(3)).eq(301) 33 | }) 34 | 35 | it('when(_)(_)', () => 36 | { 37 | var W = When(x => (x <= 1)) 38 | 39 | expect(W).a('function') 40 | expect(W.length).eq(1) 41 | 42 | var w = W(x => (x * 10 + 1)) 43 | 44 | expect(w).a('function') 45 | expect(w.length).eq(0) 46 | 47 | expect(w(0)).eq(1) 48 | expect(w(1)).eq(11) 49 | expect(w(2)).eq(2) 50 | expect(w(3)).eq(3) 51 | }) 52 | 53 | it('when_data', () => 54 | { 55 | var e = new Error('x') 56 | 57 | var w = when_data(x => (x * 10 + 1)) 58 | 59 | expect(w(0)).eq(1) 60 | expect(w(1)).eq(11) 61 | expect(w(End)).eq(End) 62 | expect(w(e)).eq(e) 63 | 64 | var w = when_data(x => x, Noop) 65 | 66 | expect(w(0)).eq(0) 67 | expect(w(1)).eq(1) 68 | expect(w(End)).eq(Nothing) 69 | expect(w(e)).eq(Nothing) 70 | 71 | var w = when_data(x => x, Noop) 72 | 73 | expect(w(0)).eq(0) 74 | expect(w(1)).eq(1) 75 | expect(w(End)).eq(Nothing) 76 | expect(w(e)).eq(Nothing) 77 | }) 78 | 79 | it('when_end', () => 80 | { 81 | var e = new Error('x') 82 | 83 | var w = when_end(() => 'yes') 84 | 85 | expect(w(0)).eq(0) 86 | expect(w(1)).eq(1) 87 | expect(w(End)).eq('yes') 88 | expect(w(e)).eq(e) 89 | 90 | var w = when_end(x => x, Noop) 91 | 92 | expect(w(0)).eq(Nothing) 93 | expect(w(1)).eq(Nothing) 94 | expect(w(End)).eq(End) 95 | expect(w(e)).eq(Nothing) 96 | }) 97 | 98 | it('when_error', () => 99 | { 100 | var e = new Error('x') 101 | 102 | var w = when_error(() => 'yes') 103 | 104 | expect(w(0)).eq(0) 105 | expect(w(1)).eq(1) 106 | expect(w(End)).eq(End) 107 | expect(w(e)).eq('yes') 108 | 109 | var w = when_error(Fin) 110 | 111 | expect(w(0)).eq(0) 112 | expect(w(1)).eq(1) 113 | expect(w(End)).eq(End) 114 | expect(w(e)).eq(End) 115 | }) 116 | 117 | it('when_data_all', () => 118 | { 119 | var w = when_data_all((...v) => v.join('/')) 120 | 121 | expect(w(0, 1, 2)).eq('0/1/2') 122 | expect(w(0, 1)).eq('0/1') 123 | expect(w(0, End, 2)).eq(End) 124 | 125 | var w = when_data_all((...v) => v.join('/')) 126 | 127 | var E1 = new Error('E1') 128 | var E2 = new Error('E2') 129 | 130 | expect(w(0, 1, 2)).eq('0/1/2') 131 | expect(w(0, E1)).eq(E1) 132 | 133 | expect(w(0, End, E1)).eq(End) 134 | expect(w(0, E1, End)).eq(End) 135 | 136 | expect(w(0, E1, E2)).eq(E1) 137 | expect(w(End, E1, E2)).eq(End) 138 | }) 139 | }) 140 | -------------------------------------------------------------------------------- /thru/defer.d.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Bud } from '../lib/Bud' 3 | 4 | export default function (bud: Bud): Bud 5 | -------------------------------------------------------------------------------- /thru/defer.js: -------------------------------------------------------------------------------- 1 | 2 | import transfer from '../lib/transfer' 3 | import asap from '../lib/asap' 4 | 5 | 6 | export default function defer (bud_source) 7 | { 8 | return transfer(bud_source, (value, emit) => 9 | { 10 | asap(() => emit(value)) 11 | }) 12 | } 13 | -------------------------------------------------------------------------------- /thru/delay.d.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Transformer } from '../lib/Bud' 3 | 4 | export default function (ms?: number): Transformer 5 | -------------------------------------------------------------------------------- /thru/delay.js: -------------------------------------------------------------------------------- 1 | 2 | import transfer from '../lib/transfer' 3 | 4 | 5 | export default function delay (ms = 0) 6 | { 7 | return (bud_source) => 8 | { 9 | return transfer(bud_source, (value, emit) => 10 | { 11 | setTimeout(() => 12 | { 13 | emit(value) 14 | } 15 | , ms) 16 | }) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /thru/delta.d.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Transformer } from '../lib/Bud' 3 | 4 | type ProducerDelta = (next: T, prev: T) => R 5 | 6 | export default function (fn?: ProducerDelta): Transformer 7 | -------------------------------------------------------------------------------- /thru/delta.js: -------------------------------------------------------------------------------- 1 | 2 | import Same from '../lib/Same' 3 | 4 | import transfer from '../lib/transfer' 5 | 6 | 7 | export default function delta (fn = Same) 8 | { 9 | return (bud_source) => 10 | { 11 | var prev = bud_source.value 12 | 13 | return transfer(bud_source, (next, emit) => 14 | { 15 | try 16 | { 17 | emit(fn(prev, next)) 18 | } 19 | finally 20 | { 21 | prev = next 22 | } 23 | }) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /thru/promise.d.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Bud } from '../lib/Bud' 3 | import { Transformer } from '../lib/Bud' 4 | 5 | export function every (bud: Bud): Bud 6 | 7 | export function last (bud: Bud): Bud 8 | 9 | export function buffered (depth: number): Transformer 10 | -------------------------------------------------------------------------------- /thru/promise.js: -------------------------------------------------------------------------------- 1 | 2 | import transfer from '../lib/transfer' 3 | import Counter from '../lib/_/Counter' 4 | 5 | 6 | export function every (bud_source) 7 | { 8 | return transfer(bud_source, (promise, emit) => 9 | { 10 | Promise.resolve(promise).then(emit, emit) 11 | }) 12 | } 13 | 14 | export function last (bud_source) 15 | { 16 | var Op = Counter() 17 | 18 | return transfer(bud_source, (promise, emit) => 19 | { 20 | var next = Next() 21 | 22 | Promise.resolve(promise).then(next, next) 23 | 24 | function Next () 25 | { 26 | var op = (Op.next(), Op.current()) 27 | 28 | return (value) => 29 | { 30 | if (op !== Op.current()) { return } 31 | 32 | emit(value) 33 | } 34 | } 35 | }) 36 | } 37 | 38 | export function buffered (depth) 39 | { 40 | return (bud_source) => 41 | { 42 | var buffer = [] 43 | var results = {} 44 | 45 | var Op = Counter() 46 | 47 | return transfer(bud_source, (promise, emit) => 48 | { 49 | var next = Next() 50 | 51 | Promise.resolve(promise).then(next, next) 52 | 53 | function Next () 54 | { 55 | var op = (Op.next(), Op.current()) 56 | 57 | update(op) 58 | 59 | return (value) => 60 | { 61 | var index = buffer.indexOf(op) 62 | if (index === -1) { return } 63 | 64 | results[op] = value 65 | 66 | if (index === 0) { flush(emit) } 67 | } 68 | } 69 | }) 70 | 71 | function update (op) 72 | { 73 | buffer.push(op) 74 | 75 | if (buffer.length > depth) 76 | { 77 | var op_stale = buffer.shift() 78 | delete results[op_stale] 79 | } 80 | } 81 | 82 | function flush (emit) 83 | { 84 | var op = buffer.shift() 85 | while (true) 86 | { 87 | emit(results[op]) 88 | delete results[op] 89 | 90 | if (! buffer.length) { break } 91 | 92 | op = buffer[0] 93 | if (! (op in results)) { break } 94 | 95 | buffer.shift() 96 | } 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /thru/raf.d.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Bud } from '../lib/Bud' 3 | 4 | export default function (bud: Bud): Bud 5 | -------------------------------------------------------------------------------- /thru/raf.js: -------------------------------------------------------------------------------- 1 | /* global requestAnimationFrame */ 2 | 3 | var raf = requestAnimationFrame 4 | 5 | import transfer from '../lib/transfer' 6 | 7 | 8 | export default function defer (bud_source) 9 | { 10 | return transfer(bud_source, (value, emit) => 11 | { 12 | raf(() => emit(value)) 13 | }) 14 | } 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": 3 | [ 4 | "**/*.ts" 5 | ], 6 | "exclude": 7 | [ 8 | "release/", 9 | "test/", 10 | "examples" 11 | ], 12 | "compilerOptions": 13 | { 14 | "noEmit": true, 15 | "pretty": true, 16 | 17 | "strict": true, 18 | "target": "ES6", 19 | "lib": [ "ES6" ], 20 | 21 | "moduleResolution": "node" 22 | } 23 | } --------------------------------------------------------------------------------