├── .flowconfig ├── .github └── workflows │ └── node.js.yml ├── .gitignore ├── .mocharc.json ├── .npmignore ├── .prettierrc ├── Kefir-with-bg.svg ├── Kefir.svg ├── LICENSE.txt ├── README.md ├── bacon-vs-kefir-api.md ├── bower.json ├── changelog.md ├── configs ├── docs.js ├── rollup.dev.js ├── rollup.esm.js └── rollup.prod.js ├── deprecated-api-docs.md ├── docs-src ├── descriptions │ ├── about-observables.pug │ ├── active-state.pug │ ├── convert.pug │ ├── create.pug │ ├── current-in-streams.pug │ ├── emitter-object.pug │ ├── errors.pug │ ├── examples.pug │ ├── interop.pug │ ├── intro.pug │ ├── main-methods.pug │ ├── multiple-sources.pug │ ├── one-source.pug │ └── two-sources.pug ├── images │ └── stream-and-property.png ├── includes │ ├── mixins.pug │ ├── side-menu.pug │ └── stylesheet.css └── index.pug ├── kefir.js.flow ├── package-lock.json ├── package.json ├── release.js ├── src ├── constants.js ├── dispatcher.js ├── emitter.js ├── index.js ├── interop │ ├── from-es-observable.js │ ├── from-promise.js │ ├── static-land.js │ ├── symbol.js │ ├── to-es-observable.js │ └── to-promise.js ├── many-sources │ ├── abstract-pool.js │ ├── combine.js │ ├── concat.js │ ├── flat-map-errors.js │ ├── flat-map.js │ ├── merge.js │ ├── pool.js │ ├── repeat.js │ └── zip.js ├── observable.js ├── one-source │ ├── before-end.js │ ├── buffer-while.js │ ├── buffer-with-count.js │ ├── buffer-with-time-or-count.js │ ├── changes.js │ ├── debounce.js │ ├── delay.js │ ├── diff.js │ ├── end-on-error.js │ ├── errors-to-values.js │ ├── filter-errors.js │ ├── filter.js │ ├── flatten.js │ ├── ignore-end.js │ ├── ignore-errors.js │ ├── ignore-values.js │ ├── last.js │ ├── map-errors.js │ ├── map.js │ ├── scan.js │ ├── skip-duplicates.js │ ├── skip-end.js │ ├── skip-while.js │ ├── skip.js │ ├── sliding-window.js │ ├── take-errors.js │ ├── take-while.js │ ├── take.js │ ├── throttle.js │ ├── to-property.js │ ├── transduce.js │ ├── values-to-errors.js │ └── with-handler.js ├── patterns │ ├── one-source.js │ ├── time-based.js │ └── two-sources.js ├── primary │ ├── constant-error.js │ ├── constant.js │ ├── from-callback.js │ ├── from-events.js │ ├── from-node-callback.js │ ├── from-sub-unsub.js │ ├── never.js │ └── stream.js ├── property.js ├── stream.js ├── time-based │ ├── from-poll.js │ ├── interval.js │ ├── later.js │ ├── sequentially.js │ └── with-interval.js ├── two-sources │ ├── awaiting.js │ ├── buffer-by.js │ ├── buffer-while-by.js │ ├── filter-by.js │ ├── sampled-by.js │ ├── skip-until-by.js │ └── take-until-by.js └── utils │ ├── collections.js │ ├── functions.js │ ├── now.js │ └── objects.js └── test ├── flow ├── combine.js ├── diff.js ├── error.js ├── explicitType.js ├── filter.js ├── flatten.js ├── interop.js ├── merge.js ├── observe.js ├── pool.js ├── propType.js ├── sampledBy.js ├── scan.js ├── setName.js ├── thru.js ├── toProperty.js ├── transforms.js ├── vacuousObservables.js └── zip.js ├── specs ├── before-end.js ├── buffer-by.js ├── buffer-while-by.js ├── buffer-while.js ├── buffer-with-count.js ├── buffer-with-time-or-count.js ├── changes.js ├── combine.js ├── concat.js ├── constant-error.js ├── constant.js ├── debounce.js ├── delay.js ├── diff.js ├── end-on-error.js ├── errors-to-values.js ├── es-observable.js ├── filter-by.js ├── filter-errors.js ├── filter.js ├── flat-map-concat.js ├── flat-map-errors.js ├── flat-map-first.js ├── flat-map-latest.js ├── flat-map-with-concurrency-limit.js ├── flat-map.js ├── flatten.js ├── from-callback.js ├── from-es-observable.js ├── from-event.js ├── from-node-callback.js ├── from-poll.js ├── from-promise.js ├── ignore-end.js ├── ignore-errors.js ├── ignore-values.js ├── interval.js ├── kefir-observable.js ├── kefir-stream.js ├── last.js ├── later.js ├── log.js ├── map-errors.js ├── map.js ├── merge.js ├── never.js ├── pool.js ├── property.js ├── repeat.js ├── sampled-by.js ├── scan.js ├── sequentially.js ├── skip-duplicates.js ├── skip-until-by.js ├── skip-while.js ├── skip.js ├── sliding-window.js ├── spy.js ├── static-land.js ├── stream.js ├── sugar.js ├── take-errors.js ├── take-until-by.js ├── take-while.js ├── take.js ├── throttle.js ├── thru.js ├── to-promise.js ├── to-property.js ├── transduce.js ├── values-to-errors.js ├── with-handler.js ├── with-interval.js └── zip.js └── test-helpers.js /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | .*/node_modules/.*/test/.* 3 | 4 | [include] 5 | 6 | [libs] 7 | 8 | [options] 9 | suppress_comment=\\(.\\|\n\\)*\\$ExpectError 10 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: ['master'] 6 | pull_request: 7 | branches: ['master'] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | node-version: [14.x, 16.x, 18.x] 16 | 17 | steps: 18 | - name: Chekout 19 | uses: actions/checkout@v3 20 | 21 | - name: Setup Node ${{ matrix.node-version }} 22 | uses: actions/setup-node@v3 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | cache: 'npm' 26 | 27 | - name: Install dependencies 28 | run: npm ci 29 | 30 | - name: Build 31 | run: npm run build 32 | 33 | - name: Test 34 | run: npm test 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | tmp 3 | bower-packages 4 | dist 5 | test/in-browser/spec/KefirSpecs.js 6 | index.html 7 | test.html 8 | test.js 9 | -------------------------------------------------------------------------------- /.mocharc.json: -------------------------------------------------------------------------------- 1 | { 2 | "reporter": "spec", 3 | "spec": "test/specs/*.js", 4 | "ui": "bdd" 5 | } 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | Gruntfile.coffee 2 | Kefir-with-bg.svg 3 | Kefir.svg 4 | bower-packages 5 | bower.json 6 | demos 7 | docs-src 8 | grunt-tasks 9 | index.html 10 | test 11 | .npmignore 12 | .travis.yml 13 | .jshintrc 14 | changelog.md 15 | tmp 16 | .babelrc 17 | .prettierrc 18 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "bracketSpacing": false, 4 | "printWidth": 120, 5 | "singleQuote": true, 6 | "trailingComma": "es5" 7 | } 8 | -------------------------------------------------------------------------------- /Kefir-with-bg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Kefir 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Kefir.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Kefir 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2015 Roman Pominov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kefir 2 | 3 | Kefir — is an Reactive Programming library for JavaScript 4 | inspired by [Bacon.js](https://github.com/baconjs/bacon.js) 5 | and [RxJS](https://github.com/Reactive-Extensions/RxJS) 6 | with focus on high performance and low memory usage. 7 | 8 | For docs visit [kefirjs.github.io/kefir](http://kefirjs.github.io/kefir). 9 | See also [Deprecated API docs](https://github.com/kefirjs/kefir/blob/master/deprecated-api-docs.md). 10 | 11 | [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/kefirjs/kefir/blob/master/LICENSE.txt) 12 | [![npm version](https://img.shields.io/npm/v/kefir.svg?style=flat)](https://www.npmjs.com/package/kefir) 13 | [![Build](https://github.com/kefirjs/kefir/actions/workflows/node.js.yml/badge.svg)](https://github.com/kefirjs/kefir/actions/workflows/node.js.yml) 14 | [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/pozadi/kefir?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) 15 | 16 | # Installation 17 | 18 | Kefir available as NPM and Bower packages, as well as simple files download. 19 | 20 | ### NPM 21 | 22 | ```sh 23 | npm install kefir 24 | ``` 25 | 26 | ### Bower 27 | 28 | ```sh 29 | bower install kefir 30 | ``` 31 | 32 | ### Download 33 | 34 | See [downloads](https://kefirjs.github.io/kefir/#downloads) section in the docs. 35 | 36 | Also available on [jsDelivr](http://www.jsdelivr.com/#!kefir). 37 | 38 | # Browsers support 39 | 40 | We don't support IE8 and below, aside from that Kefir should work in any browser. 41 | 42 | ## [Flow](https://flowtype.org/) 43 | 44 | The NPM package ships with Flow definitions. So you can do something like this if you use Flow: 45 | 46 | ```js 47 | // @flow 48 | 49 | import Kefir from 'kefir' 50 | 51 | function foo(numberStream: Kefir.Observable) { 52 | numberStream.onValue(x => { 53 | // Flow knows x is a number here 54 | }) 55 | } 56 | 57 | const s = Kefir.constant(5) 58 | // Flow can automatically infer the type of values in the stream and determine 59 | // that `s` is of type Kefir.Observable here. 60 | foo(s) 61 | ``` 62 | 63 | # Development 64 | 65 | ```sh 66 | npm run prettify # makes source code pretty (you must run it before a PR could be merged) 67 | npm run build-js # builds js bundlers 68 | npm run test # runs all the checks 69 | npm run test-only # runs only unit tests without other checks 70 | npm run test-debug # runs tests with a chrome inspector connected to the node process 71 | npm run build-docs # builds the documentation html file 72 | ``` 73 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kefir", 3 | "version": "3.8.8", 4 | "homepage": "https://github.com/kefirjs/kefir", 5 | "authors": [ 6 | "Roman Pominov " 7 | ], 8 | "description": "Reactive Programming library for JavaScript inspired by Bacon.js and RxJS with focus on high performance and low memory usage", 9 | "main": "dist/kefir.js", 10 | "moduleType": [ 11 | "amd", 12 | "globals", 13 | "node" 14 | ], 15 | "keywords": [ 16 | "frp", 17 | "bacon", 18 | "bacon.js", 19 | "kefir", 20 | "kefir.js", 21 | "functional", 22 | "reactive", 23 | "stream", 24 | "streams", 25 | "EventStream", 26 | "Rx", 27 | "RxJs", 28 | "Observable" 29 | ], 30 | "license": "MIT", 31 | "ignore": [ 32 | "**", 33 | "!dist/**", 34 | "!LICENSE.txt" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /configs/docs.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const pug = require('pug') 3 | const pkg = require('../package.json') 4 | 5 | function escapehtml(block) { 6 | return block 7 | .replace(/&/g, '&') 8 | .replace(//g, '>') 10 | .replace(/"/g, '"') 11 | .replace(/'/g, ''') 12 | } 13 | 14 | const compiled = pug.compileFile('./docs-src/index.pug', {filters: {escapehtml}}) 15 | fs.writeFileSync('./index.html', compiled({pkg})) 16 | console.log('Documentation rendered!') 17 | -------------------------------------------------------------------------------- /configs/rollup.dev.js: -------------------------------------------------------------------------------- 1 | import babel from 'rollup-plugin-babel' 2 | import nodeResolve from 'rollup-plugin-node-resolve' 3 | import commonjs from 'rollup-plugin-commonjs' 4 | 5 | const pkg = require('../package.json') 6 | 7 | const banner = `/*! Kefir.js v${pkg.version} 8 | * ${pkg.homepage} 9 | */ 10 | ` 11 | 12 | export default { 13 | moduleName: 'Kefir', 14 | entry: 'src/index.js', 15 | format: 'umd', 16 | banner: banner, 17 | plugins: [babel({presets: ['es2015-loose-rollup']}), nodeResolve({main: true}), commonjs()], 18 | dest: 'dist/kefir.js', 19 | } 20 | -------------------------------------------------------------------------------- /configs/rollup.esm.js: -------------------------------------------------------------------------------- 1 | import base from './rollup.dev.js' 2 | 3 | export default Object.assign({}, base, { 4 | format: 'es', 5 | dest: 'dist/kefir.esm.js', 6 | }) 7 | -------------------------------------------------------------------------------- /configs/rollup.prod.js: -------------------------------------------------------------------------------- 1 | import base from './rollup.dev.js' 2 | import uglify from 'rollup-plugin-uglify' 3 | 4 | export default Object.assign({}, base, { 5 | plugins: base.plugins.concat([uglify({output: {comments: /\!\s\w/}})]), 6 | dest: 'dist/kefir.min.js', 7 | }) 8 | -------------------------------------------------------------------------------- /docs-src/descriptions/about-observables.pug: -------------------------------------------------------------------------------- 1 | h2#about-observables Intro to Streams and Properties 2 | 3 | p. 4 | Kefir supports two types of observables — streams and properties. 5 | Streams represent sequences of events made available over time. And properties represent 6 | values that change over time. The value of a property changes in response to events, 7 | which means that any stream may be easily converted to a property. 8 | 9 | figure 10 | img(src='docs-src/images/stream-and-property.png') 11 | 12 | p. 13 | In practice, the only difference between the two types of observables 14 | is that properties may have a current value. The process of subscribing 15 | to both types of observables is the same: you call the #[a(href='#on-value') onValue] 16 | method, passing a callback function to it. But when you subscribe 17 | to a property which has a current value, the callback is called 18 | immediately (synchronously) with the current value of the property. 19 | -------------------------------------------------------------------------------- /docs-src/descriptions/active-state.pug: -------------------------------------------------------------------------------- 1 | h2#active-state Activation and deactivation of observables 2 | 3 | p. 4 | At the moment one create an observable it's not yet subscribed to its source. 5 | Observables subscribe to their sources only when they themselves get a first subscriber. 6 | In this docs this process is called #[b activation] of an observable. 7 | Also when the last subscriber is removed from an observable, 8 | the observable #[b deactivates] and unsubscribes from its source. 9 | Later it can be #[b activated] again, and so on. 10 | 11 | p. 12 | The #[i source] to which observable subscribe on #[b activation] may 13 | be an another observable (for example in #[tt .map]), 14 | several other observables (#[tt .combine]), 15 | or some external source (#[tt .fromEvents]). 16 | 17 | p. 18 | For example #[tt stream = Kefir.fromEvents(el, 'click')] won't 19 | immediately subscribe to the #[tt 'click'] event on #[tt el], 20 | it will subscribe only when the first listener will be added to the 21 | #[tt stream]. And it will automatically unsubscribe when the last listener 22 | will be removed from the #[tt stream]. 23 | 24 | pre.javascript 25 | :escapehtml 26 | var stream = Kefir.fromEvents(el, 'click'); 27 | // at this moment event listener to _el_ not added 28 | 29 | stream.onValue(someFn); 30 | // now 'click' listener is added to _el_ 31 | 32 | stream.offValue(someFn); 33 | // and now it is removed again 34 | 35 | p. 36 | As you might already guess #[b activation] and #[b deactivation] propagates up the observables chain. 37 | For instance if one create a long chain like #[tt Kefir.fromEvents(...).map(...).filter(...).take(...)], 38 | the whole chain will be #[b inactive] until first subscriber is added, 39 | and then it will #[b activate] up to #[tt .fromEvents]. Same for #[b deactivation]. 40 | -------------------------------------------------------------------------------- /docs-src/descriptions/convert.pug: -------------------------------------------------------------------------------- 1 | h2#convert Convert observables 2 | 3 | 4 | +descr-method('to-property', 'toProperty', 'stream.toProperty([getCurrent])'). 5 | Converts a stream to a property. 6 | Accepts an optional #[b getCurrent] callback, which will be called on 7 | each #[a(href='#active-state') activation] to get the current value at that moment. 8 | 9 | pre.javascript(title='example') 10 | :escapehtml 11 | var source = Kefir.sequentially(100, [1, 2, 3]); 12 | var result = source.toProperty(() => 0); 13 | result.log(); 14 | 15 | pre(title='console output') 16 | :escapehtml 17 | > [sequentially.toProperty] 0 18 | > [sequentially.toProperty] 1 19 | > [sequentially.toProperty] 2 20 | > [sequentially.toProperty] 3 21 | > [sequentially.toProperty] 22 | 23 | pre(title='events in time'). 24 | source: ----1----2----3X 25 | result: 0----1----2----3X 26 | div 27 | 28 | 29 | 30 | +descr-method('changes', 'changes', 'property.changes()'). 31 | Converts a property to a stream. 32 | If the property has a current value (or error), it will be ignored 33 | (subscribers of the stream won't get it). 34 | 35 | p. 36 | If you call #[b changes] on a stream, it will return a new stream with 37 | #[a(href='#current-in-streams') current values/errors] removed. 38 | 39 | pre.javascript(title='example') 40 | :escapehtml 41 | var source = Kefir.sequentially(100, [1, 2, 3]); 42 | var property = source.toProperty(() => 0); 43 | var result = property.changes(); 44 | result.log(); 45 | 46 | pre(title='console output') 47 | :escapehtml 48 | > [sequentially.toProperty.changes] 1 49 | > [sequentially.toProperty.changes] 2 50 | > [sequentially.toProperty.changes] 3 51 | > [sequentially.toProperty.changes] 52 | 53 | pre(title='events in time'). 54 | property: 0----1----2----3X 55 | result: ----1----2----3X 56 | div 57 | -------------------------------------------------------------------------------- /docs-src/descriptions/emitter-object.pug: -------------------------------------------------------------------------------- 1 | h2#emitter-object Emitter 2 | 3 | p. 4 | Emitter is an object that has four methods for emitting events. 5 | It is used in several places in Kefir as a proxy to emit events from some observable. 6 | 7 | ul 8 | li #[tt emitter.value(value)] emits a value in the connected observable 9 | li #[tt emitter.error(error)] emits a error in the connected observable 10 | li #[tt emitter.end()] ends the connected observable 11 | li #[tt emitter.event(event)] emits an event (object with same format as in #[a(href='#on-any') onAny] method) in the connected observable 12 | 13 | pre.javascript(title='example') 14 | :escapehtml 15 | emitter.value(123); 16 | emitter.error('Oh, snap!'); 17 | emitter.end(); 18 | div 19 | 20 | p. 21 | All #[b emitter] methods are bound to their context, 22 | and can be passed as callbacks safely without binding: 23 | 24 | pre.javascript 25 | :escapehtml 26 | // instead of this 27 | el.addEventListener('click', emitter.value.bind(emitter)); 28 | 29 | // you can do just this 30 | el.addEventListener('click', emitter.value); 31 | div 32 | 33 | p. 34 | There also exist legacy aliases to #[b emitter] methods: 35 | 36 | ul 37 | li #[tt emitter.emit === emitter.value] 38 | li #[tt emitter.emitEvent === emitter.event] 39 | 40 | -------------------------------------------------------------------------------- /docs-src/descriptions/errors.pug: -------------------------------------------------------------------------------- 1 | h2#about-errors Errors 2 | 3 | p. 4 | Kefir supports an additional channel to pass data through observables — errors. 5 | Unlike values, errors normally just flow through the observable chain 6 | without any transformation. Consider this example: 7 | 8 | pre.javascript(title='example') 9 | :escapehtml 10 | var foo = Kefir.stream(emitter => { 11 | emitter.emit(0); 12 | emitter.emit(2); 13 | emitter.error(-1); 14 | emitter.emit(3); 15 | emitter.end(); 16 | }); 17 | 18 | var bar = foo.map(x => x + 2).filter(x => x > 3); 19 | bar.log(); 20 | 21 | 22 | pre(title='console output') 23 | :escapehtml 24 | > [stream.map.filter] 4 25 | > [stream.map.filter] -1 26 | > [stream.map.filter] 5 27 | > [stream.map.filter] 28 | 29 | pre(title='events in time') 30 | :escapehtml 31 | foo: ---0---2---e---3---X 32 | -1 33 | 34 | bar: -------4---e---5---X 35 | -1 36 | div 37 | 38 | p. 39 | As you can see values are being mapped and filtered, 40 | but errors just flow unchanged. 41 | Also notice that observable doesn't end on an error by default, 42 | but you can use the #[a(href='#take-errors') takeErrors] method to make it happen. 43 | Consider a slight change to the above example: 44 | 45 | pre.javascript(title='example') 46 | :escapehtml 47 | var foo = Kefir.stream(emitter => { 48 | emitter.emit(0); 49 | emitter.emit(2); 50 | emitter.error(-1); 51 | emitter.emit(3); 52 | emitter.end(); 53 | }); 54 | 55 | var bar = foo.map(x => x + 2).filter(x => x > 3); 56 | bar.takeErrors(1).log(); 57 | 58 | pre(title='console output') 59 | :escapehtml 60 | > [stream.map.filter.takeErrors] 4 61 | > [stream.map.filter.takeErrors] -1 62 | > [stream.map.filter.takeErrors] 63 | 64 | pre(title='events in time') 65 | :escapehtml 66 | foo: ---0---2---e---3---X 67 | -1 68 | 69 | bar: -------4---eX 70 | -1 71 | div 72 | -------------------------------------------------------------------------------- /docs-src/descriptions/intro.pug: -------------------------------------------------------------------------------- 1 | h1 2 | | Kefir.js 3 | = " " 4 | sup #{pkg.version} (changelog) 5 | 6 | p. 7 | Kefir — is a Reactive Programming 8 | library for JavaScript inspired by 9 | Bacon.js 10 | and 11 | RxJS, 12 | with focus on high performance and low memory usage. 13 | 14 | 15 | p. 16 | Kefir has a 17 | 18 | GitHub repository, where you can 19 | send pull requests, 20 | report bugs, 21 | and have fun reading 22 | source code. 23 | 24 | p. 25 | See also 26 | #[a(href='https://github.com/kefirjs/kefir/blob/master/deprecated-api-docs.md') Deprecated API docs]. 27 | 28 | 29 | h2#installation Installation 30 | 31 | p. 32 | Kefir is available as an NPM and a Bower package, as well as a simple file download. 33 | 34 | h3#npm NPM 35 | 36 | pre. 37 | npm install kefir 38 | 39 | h3#bower Bower 40 | 41 | pre. 42 | bower install kefir 43 | 44 | h3#downloads Downloads (#{pkg.version}) 45 | 46 | table 47 | tr 48 | th(rowspan="1" valign="top") For Development 49 | td: a(href='dist/kefir.js') kefir.js 50 | td: i ~ 100 KB 51 | tr 52 | th(rowspan="1" valign="top") For Production 53 | td: a(href='dist/kefir.min.js') kefir.min.js 54 | td: i ~ 10 KB (when gzipped) 55 | 56 | tr 57 | td.rule(colspan="3") 58 | tr 59 | th All files 60 | td: a(href='https://github.com/kefirjs/kefir/archive/' + pkg.version + '.zip') kefir-#{pkg.version}.zip 61 | td ... including documentation, tests, source maps, etc. 62 | 63 | p. 64 | Kefir also available on #[a(href='http://www.jsdelivr.com/#!kefir') jsDelivr]. 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /docs-src/images/stream-and-property.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kefirjs/kefir/d1c51fd56bb1997f66faed98c58f1701791ed1ed/docs-src/images/stream-and-property.png -------------------------------------------------------------------------------- /docs-src/includes/mixins.pug: -------------------------------------------------------------------------------- 1 | mixin method(anchor, name) 2 | li 3 | a(href=anchor)= name 4 | 5 | 6 | mixin descr-method(anchor, name, code, alias, shorthand) 7 | p(id=anchor) 8 | a.header(href='#' + anchor) #{name} 9 | code #{code} 10 | if alias 11 | code.alias #{alias} 12 | if shorthand 13 | code.shorthand #{shorthand} 14 | if !alias && !shorthand 15 | br 16 | block 17 | -------------------------------------------------------------------------------- /docs-src/index.pug: -------------------------------------------------------------------------------- 1 | include includes/mixins 2 | 3 | doctype html 4 | html(lang="en") 5 | head 6 | meta(charset='utf-8') 7 | meta(http-equiv='X-UA-Compatible', content='IE=edge') 8 | title Kefir.js — fast and light Reactive Programming library for JavaScript inspired by Bacon.js and RxJS 9 | meta(name='viewport', content='width=device-width, initial-scale=1') 10 | style(type='text/css') 11 | include includes/stylesheet.css 12 | 13 | script(src='https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js') 14 | script. 15 | window.module = {} 16 | script(src='https://unpkg.com/transducers-js@0.4.174') 17 | script. 18 | window.transducers = window.module.exports 19 | delete window.module 20 | script(src='dist/kefir.js') 21 | 22 | link(rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.6/styles/tomorrow.min.css") 23 | link(rel="stylesheet" href="https://code.cdn.mozilla.net/fonts/fira.css") 24 | style(type='text/css'). 25 | .hljs { 26 | background: transparent; 27 | padding: 2px 2px 2px 12px; 28 | } 29 | script(src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.6/highlight.min.js") 30 | 31 | 32 | body 33 | 34 | .sidebar 35 | include includes/side-menu 36 | 37 | .container 38 | include descriptions/intro 39 | include descriptions/examples 40 | include descriptions/about-observables 41 | include descriptions/create 42 | include descriptions/convert 43 | include descriptions/main-methods 44 | include descriptions/one-source 45 | include descriptions/multiple-sources 46 | include descriptions/two-sources 47 | include descriptions/interop 48 | include descriptions/active-state 49 | include descriptions/emitter-object 50 | include descriptions/errors 51 | include descriptions/current-in-streams 52 | 53 | script. 54 | $.getJSON('https://api.github.com/emojis', function(emojis){ 55 | $('[data-emoji]').each(function(){ 56 | var name = $(this).data('emoji'); 57 | $(this).attr({ 58 | src: emojis[name], 59 | title: ':' + name + ':', 60 | alt: ':' + name + ':' 61 | }); 62 | }); 63 | }); 64 | 65 | var $window = $(window); 66 | var $document = $(document); 67 | 68 | function getScrollLeft() { 69 | return $window.scrollLeft(); 70 | } 71 | 72 | function getWinWidth() { 73 | return $window.width(); 74 | } 75 | 76 | function getDocWidth() { 77 | return $document.width(); 78 | } 79 | 80 | var scrolls = Kefir.fromEvents($window, 'scroll'); 81 | var resizes = Kefir.fromEvents($window, 'resize'); 82 | 83 | var scrollLeft = scrolls.map(getScrollLeft).toProperty(getScrollLeft).skipDuplicates(); 84 | var winWidth = resizes.map(getWinWidth).toProperty(getWinWidth).skipDuplicates(); 85 | var docWidth = resizes.map(getDocWidth).toProperty(getDocWidth).skipDuplicates(); 86 | 87 | Kefir.combine([scrollLeft, winWidth, docWidth], function(scrollLeft, winWidth, docWidth) { 88 | return -Math.min(docWidth - winWidth, Math.max(0, scrollLeft)); 89 | }).skipDuplicates().onValue(function(x) {$('.sidebar').css('left', x)}); 90 | 91 | $('pre.javascript').each(function(_, block) { 92 | hljs.highlightBlock(block); 93 | }); 94 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kefir", 3 | "version": "3.8.8", 4 | "description": "Reactive Programming library for JavaScript inspired by Bacon.js and RxJS with focus on high performance and low memory usage", 5 | "main": "dist/kefir.js", 6 | "module": "dist/kefir.esm.js", 7 | "scripts": { 8 | "prettify": "prettier --write '{src,configs,test}/**/*.js' '*.js'", 9 | "prettier-check": "prettier --list-different '{src,configs,test}/**/*.js' '*.js'", 10 | "build-js": "rollup -c ./configs/rollup.dev.js && rollup -c ./configs/rollup.esm.js && rollup -c ./configs/rollup.prod.js && cp kefir.js.flow dist/", 11 | "build-docs": "node configs/docs.js", 12 | "deploy-docs": "git checkout gh-pages && git merge master && npm run build && git add . && git commit -m 'build all' && git push && git checkout master", 13 | "clean": "rm -r dist index.html || true", 14 | "build": "npm run clean && npm run build-js && npm run build-docs", 15 | "test": "npm run prettier-check && rollup -c ./configs/rollup.dev.js && mocha && flow check", 16 | "test-only": "rollup -c ./configs/rollup.dev.js && mocha", 17 | "test-debug": "rollup -c ./configs/rollup.dev.js && mocha --inspect-brk" 18 | }, 19 | "keywords": [ 20 | "frp", 21 | "bacon", 22 | "bacon.js", 23 | "kefir", 24 | "kefir.js", 25 | "functional", 26 | "reactive", 27 | "stream", 28 | "streams", 29 | "EventStream", 30 | "Rx", 31 | "RxJs", 32 | "Observable" 33 | ], 34 | "author": "Roman Pominov ", 35 | "homepage": "https://github.com/kefirjs/kefir", 36 | "repository": { 37 | "type": "git", 38 | "url": "http://github.com/kefirjs/kefir.git" 39 | }, 40 | "license": "MIT", 41 | "devDependencies": { 42 | "babel-preset-es2015-loose-rollup": "^7.0.0", 43 | "chai": "^4.2.0", 44 | "chai-kefir": "^2.0.5", 45 | "flow-bin": "^0.100.0", 46 | "inquirer": "^6.3.1", 47 | "mocha": "^10.0.0", 48 | "prettier": "^1.18.2", 49 | "pug": "^3.0.2", 50 | "rollup": "^0.41.6", 51 | "rollup-plugin-babel": "^2.7.1", 52 | "rollup-plugin-commonjs": "^8.4.1", 53 | "rollup-plugin-node-resolve": "^3.4.0", 54 | "rollup-plugin-uglify": "^1.0.2", 55 | "rxjs": "^6.5.2", 56 | "semver": "^6.1.1", 57 | "sinon": "^7.3.2", 58 | "sinon-chai": "^3.3.0", 59 | "symbol-observable": "^1.2.0", 60 | "transducers-js": "^0.4.174", 61 | "transducers.js": "^0.3.2", 62 | "zen-observable": "^0.8.14" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /release.js: -------------------------------------------------------------------------------- 1 | var child_process = require('child_process') 2 | var inquirer = require('inquirer') 3 | var semver = require('semver') 4 | var fs = require('fs') 5 | 6 | var pkg = require('./package.json') 7 | var bower = require('./bower.json') 8 | 9 | console.log('') 10 | console.log('Wellcome to Kefir release utility!') 11 | console.log('----------------------------------------------------------------') 12 | console.log('') 13 | 14 | var questions = [ 15 | { 16 | type: 'list', 17 | name: 'version', 18 | message: 'Which version will it be? (current is ' + pkg.version + ')', 19 | choices: [ 20 | semver.inc(pkg.version, 'patch'), 21 | semver.inc(pkg.version, 'minor'), 22 | semver.inc(pkg.version, 'major'), 23 | semver.inc(pkg.version, 'premajor', 'rc'), 24 | ], 25 | }, 26 | { 27 | type: 'list', 28 | name: 'dryRun', 29 | message: 'Do you want to release, or just see what would happen if you do?', 30 | choices: ['Just see', 'Release!'], 31 | }, 32 | ] 33 | 34 | inquirer.prompt(questions).then(function(answers) { 35 | var newVerison = answers.version 36 | var dryRun = answers.dryRun === 'Just see' 37 | 38 | bower.version = pkg.version = newVerison 39 | 40 | console.log('') 41 | if (dryRun) { 42 | console.log('Ok, here is what would happen:') 43 | } else { 44 | console.log('Doing actual release:') 45 | } 46 | console.log('') 47 | 48 | run('npm test', dryRun) && 49 | bumpVersion('package.json', pkg, dryRun) && 50 | bumpVersion('bower.json', bower, dryRun) && 51 | run('npm run build', dryRun) && 52 | run('git add .', dryRun) && 53 | run('git add -f dist', dryRun) && 54 | run('git add -f index.html', dryRun) && 55 | run('git commit -m "' + newVerison + '"', dryRun) && 56 | run('git push', dryRun) && 57 | run('git tag -a ' + newVerison + ' -m "v' + newVerison + '"', dryRun) && 58 | run('git push origin --tags', dryRun) && 59 | run('npm publish', dryRun) && 60 | run('git rm -r dist', dryRun) && 61 | run('git rm index.html', dryRun) && 62 | run('git commit -m "cleanup repository after release"', dryRun) && 63 | run('git push', dryRun) 64 | }) 65 | 66 | function bumpVersion(fileName, obj, dry) { 67 | console.log('Bumping version in `' + fileName + '` to ' + obj.version) 68 | if (!dry) { 69 | try { 70 | fs.writeFileSync(fileName, JSON.stringify(obj, null, ' ') + '\n') 71 | console.log('... ok') 72 | } catch (e) { 73 | console.error(e) 74 | return false 75 | } 76 | } 77 | return true 78 | } 79 | 80 | function run(cmd, dry) { 81 | console.log('Running `' + cmd + '`') 82 | if (!dry) { 83 | var proc = child_process.spawnSync(cmd, { 84 | stdio: 'inherit', 85 | shell: true, 86 | }) 87 | if (proc.status === 0) { 88 | console.log('... ok') 89 | } else { 90 | console.error('... fail!') 91 | return false 92 | } 93 | } 94 | return true 95 | } 96 | -------------------------------------------------------------------------------- /src/constants.js: -------------------------------------------------------------------------------- 1 | export const NOTHING = [''] 2 | export const END = 'end' 3 | export const VALUE = 'value' 4 | export const ERROR = 'error' 5 | export const ANY = 'any' 6 | -------------------------------------------------------------------------------- /src/dispatcher.js: -------------------------------------------------------------------------------- 1 | import {extend} from './utils/objects' 2 | import {VALUE, ERROR, ANY} from './constants' 3 | import {concat, findByPred, remove, contains} from './utils/collections' 4 | 5 | function callSubscriber(type, fn, event) { 6 | if (type === ANY) { 7 | fn(event) 8 | } else if (type === event.type) { 9 | if (type === VALUE || type === ERROR) { 10 | fn(event.value) 11 | } else { 12 | fn() 13 | } 14 | } 15 | } 16 | 17 | function Dispatcher() { 18 | this._items = [] 19 | this._spies = [] 20 | this._inLoop = 0 21 | this._removedItems = null 22 | } 23 | 24 | extend(Dispatcher.prototype, { 25 | add(type, fn) { 26 | this._items = concat(this._items, [{type, fn}]) 27 | return this._items.length 28 | }, 29 | 30 | remove(type, fn) { 31 | const index = findByPred(this._items, x => x.type === type && x.fn === fn) 32 | 33 | // if we're currently in a notification loop, 34 | // remember this subscriber was removed 35 | if (this._inLoop !== 0 && index !== -1) { 36 | if (this._removedItems === null) { 37 | this._removedItems = [] 38 | } 39 | this._removedItems.push(this._items[index]) 40 | } 41 | 42 | this._items = remove(this._items, index) 43 | return this._items.length 44 | }, 45 | 46 | addSpy(fn) { 47 | this._spies = concat(this._spies, [fn]) 48 | return this._spies.length 49 | }, 50 | 51 | // Because spies are only ever a function that perform logging as 52 | // their only side effect, we don't need the same complicated 53 | // removal logic like in remove() 54 | removeSpy(fn) { 55 | this._spies = remove(this._spies, this._spies.indexOf(fn)) 56 | return this._spies.length 57 | }, 58 | 59 | dispatch(event) { 60 | this._inLoop++ 61 | for (let i = 0, spies = this._spies; this._spies !== null && i < spies.length; i++) { 62 | spies[i](event) 63 | } 64 | 65 | for (let i = 0, items = this._items; i < items.length; i++) { 66 | // cleanup was called 67 | if (this._items === null) { 68 | break 69 | } 70 | 71 | // this subscriber was removed 72 | if (this._removedItems !== null && contains(this._removedItems, items[i])) { 73 | continue 74 | } 75 | 76 | callSubscriber(items[i].type, items[i].fn, event) 77 | } 78 | this._inLoop-- 79 | if (this._inLoop === 0) { 80 | this._removedItems = null 81 | } 82 | }, 83 | 84 | cleanup() { 85 | this._items = null 86 | this._spies = null 87 | }, 88 | }) 89 | 90 | export {callSubscriber, Dispatcher} 91 | -------------------------------------------------------------------------------- /src/emitter.js: -------------------------------------------------------------------------------- 1 | export default function emitter(obs) { 2 | function value(x) { 3 | obs._emitValue(x) 4 | return obs._active 5 | } 6 | 7 | function error(x) { 8 | obs._emitError(x) 9 | return obs._active 10 | } 11 | 12 | function end() { 13 | obs._emitEnd() 14 | return obs._active 15 | } 16 | 17 | function event(e) { 18 | obs._emit(e.type, e.value) 19 | return obs._active 20 | } 21 | 22 | return { 23 | value, 24 | error, 25 | end, 26 | event, 27 | 28 | // legacy 29 | emit: value, 30 | emitEvent: event, 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/interop/from-es-observable.js: -------------------------------------------------------------------------------- 1 | import stream from '../primary/stream' 2 | import $$observable from './symbol' 3 | 4 | export default function fromESObservable(_observable) { 5 | const observable = _observable[$$observable] ? _observable[$$observable]() : _observable 6 | return stream(function(emitter) { 7 | const unsub = observable.subscribe({ 8 | error(error) { 9 | emitter.error(error) 10 | emitter.end() 11 | }, 12 | next(value) { 13 | emitter.emit(value) 14 | }, 15 | complete() { 16 | emitter.end() 17 | }, 18 | }) 19 | 20 | if (unsub.unsubscribe) { 21 | return function() { 22 | unsub.unsubscribe() 23 | } 24 | } else { 25 | return unsub 26 | } 27 | }).setName('fromESObservable') 28 | } 29 | -------------------------------------------------------------------------------- /src/interop/from-promise.js: -------------------------------------------------------------------------------- 1 | import stream from '../primary/stream' 2 | import toProperty from '../one-source/to-property' 3 | 4 | export default function fromPromise(promise) { 5 | let called = false 6 | 7 | let result = stream(function(emitter) { 8 | if (!called) { 9 | let onValue = function(x) { 10 | emitter.emit(x) 11 | emitter.end() 12 | } 13 | let onError = function(x) { 14 | emitter.error(x) 15 | emitter.end() 16 | } 17 | let _promise = promise.then(onValue, onError) 18 | 19 | // prevent libraries like 'Q' or 'when' from swallowing exceptions 20 | if (_promise && typeof _promise.done === 'function') { 21 | _promise.done() 22 | } 23 | 24 | called = true 25 | } 26 | }) 27 | 28 | return toProperty(result, null).setName('fromPromise') 29 | } 30 | -------------------------------------------------------------------------------- /src/interop/static-land.js: -------------------------------------------------------------------------------- 1 | import constant from '../primary/constant' 2 | import never from '../primary/never' 3 | import combine from '../many-sources/combine' 4 | 5 | const Observable = { 6 | empty() { 7 | return never() 8 | }, 9 | 10 | // Monoid based on merge() seems more useful than one based on concat(). 11 | concat(a, b) { 12 | return a.merge(b) 13 | }, 14 | 15 | of(x) { 16 | return constant(x) 17 | }, 18 | 19 | map(fn, obs) { 20 | return obs.map(fn) 21 | }, 22 | 23 | bimap(fnErr, fnVal, obs) { 24 | return obs.mapErrors(fnErr).map(fnVal) 25 | }, 26 | 27 | // This ap strictly speaking incompatible with chain. If we derive ap from chain we get 28 | // different (not very useful) behavior. But spec requires that if method can be derived 29 | // it must have the same behavior as hand-written method. We intentionally violate the spec 30 | // in hope that it won't cause many troubles in practice. And in return we have more useful type. 31 | ap(obsFn, obsVal) { 32 | return combine([obsFn, obsVal], (fn, val) => fn(val)) 33 | }, 34 | 35 | chain(fn, obs) { 36 | return obs.flatMap(fn) 37 | }, 38 | } 39 | 40 | export {Observable} 41 | -------------------------------------------------------------------------------- /src/interop/symbol.js: -------------------------------------------------------------------------------- 1 | // this file contains some hot JS modules systems stuff 2 | 3 | import symbol from 'symbol-observable' 4 | export default symbol.default ? symbol.default : symbol 5 | -------------------------------------------------------------------------------- /src/interop/to-es-observable.js: -------------------------------------------------------------------------------- 1 | import {extend} from '../utils/objects' 2 | import {VALUE, ERROR, END} from '../constants' 3 | import $$observable from './symbol' 4 | 5 | function ESObservable(observable) { 6 | this._observable = observable.takeErrors(1) 7 | } 8 | 9 | extend(ESObservable.prototype, { 10 | subscribe(observerOrOnNext, onError, onComplete) { 11 | const observer = 12 | typeof observerOrOnNext === 'function' 13 | ? {next: observerOrOnNext, error: onError, complete: onComplete} 14 | : observerOrOnNext 15 | 16 | const fn = event => { 17 | if (event.type === END) { 18 | closed = true 19 | } 20 | 21 | if (event.type === VALUE && observer.next) { 22 | observer.next(event.value) 23 | } else if (event.type === ERROR && observer.error) { 24 | observer.error(event.value) 25 | } else if (event.type === END && observer.complete) { 26 | observer.complete(event.value) 27 | } 28 | } 29 | 30 | this._observable.onAny(fn) 31 | let closed = false 32 | 33 | const subscription = { 34 | unsubscribe: () => { 35 | closed = true 36 | this._observable.offAny(fn) 37 | }, 38 | get closed() { 39 | return closed 40 | }, 41 | } 42 | return subscription 43 | }, 44 | }) 45 | 46 | // Need to assign directly b/c Symbols aren't enumerable. 47 | ESObservable.prototype[$$observable] = function() { 48 | return this 49 | } 50 | 51 | export default function toESObservable() { 52 | return new ESObservable(this) 53 | } 54 | -------------------------------------------------------------------------------- /src/interop/to-promise.js: -------------------------------------------------------------------------------- 1 | import {VALUE, END} from '../constants' 2 | 3 | function getGlodalPromise() { 4 | if (typeof Promise === 'function') { 5 | return Promise 6 | } else { 7 | throw new Error("There isn't default Promise, use shim or parameter") 8 | } 9 | } 10 | 11 | export default function(obs, Promise = getGlodalPromise()) { 12 | let last = null 13 | return new Promise((resolve, reject) => { 14 | obs.onAny(event => { 15 | if (event.type === END && last !== null) { 16 | ;(last.type === VALUE ? resolve : reject)(last.value) 17 | last = null 18 | } else { 19 | last = event 20 | } 21 | }) 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /src/many-sources/concat.js: -------------------------------------------------------------------------------- 1 | import repeat from './repeat' 2 | 3 | export default function concat(observables) { 4 | return repeat(function(index) { 5 | return observables.length > index ? observables[index] : false 6 | }).setName('concat') 7 | } 8 | -------------------------------------------------------------------------------- /src/many-sources/flat-map-errors.js: -------------------------------------------------------------------------------- 1 | import {VALUE, ERROR, END} from '../constants' 2 | import {inherit} from '../utils/objects' 3 | import FlatMap from './flat-map' 4 | 5 | function FlatMapErrors(source, fn) { 6 | FlatMap.call(this, source, fn) 7 | } 8 | 9 | inherit(FlatMapErrors, FlatMap, { 10 | // Same as in FlatMap, only VALUE/ERROR flipped 11 | _handleMain(event) { 12 | if (event.type === ERROR) { 13 | let sameCurr = this._activating && this._hadNoEvSinceDeact && this._lastCurrent === event.value 14 | if (!sameCurr) { 15 | this._add(event.value, this._fn) 16 | } 17 | this._lastCurrent = event.value 18 | this._hadNoEvSinceDeact = false 19 | } 20 | 21 | if (event.type === VALUE) { 22 | this._emitValue(event.value) 23 | } 24 | 25 | if (event.type === END) { 26 | if (this._isEmpty()) { 27 | this._emitEnd() 28 | } else { 29 | this._mainEnded = true 30 | } 31 | } 32 | }, 33 | }) 34 | 35 | export default FlatMapErrors 36 | -------------------------------------------------------------------------------- /src/many-sources/flat-map.js: -------------------------------------------------------------------------------- 1 | import {VALUE, ERROR, END} from '../constants' 2 | import {inherit} from '../utils/objects' 3 | import AbstractPool from './abstract-pool' 4 | 5 | function FlatMap(source, fn, options) { 6 | AbstractPool.call(this, options) 7 | this._source = source 8 | this._fn = fn 9 | this._mainEnded = false 10 | this._lastCurrent = null 11 | this._$handleMain = event => this._handleMain(event) 12 | } 13 | 14 | inherit(FlatMap, AbstractPool, { 15 | _onActivation() { 16 | AbstractPool.prototype._onActivation.call(this) 17 | if (this._active) { 18 | this._source.onAny(this._$handleMain) 19 | } 20 | }, 21 | 22 | _onDeactivation() { 23 | AbstractPool.prototype._onDeactivation.call(this) 24 | this._source.offAny(this._$handleMain) 25 | this._hadNoEvSinceDeact = true 26 | }, 27 | 28 | _handleMain(event) { 29 | if (event.type === VALUE) { 30 | // Is latest value before deactivation survived, and now is 'current' on this activation? 31 | // We don't want to handle such values, to prevent to constantly add 32 | // same observale on each activation/deactivation when our main source 33 | // is a `Kefir.conatant()` for example. 34 | let sameCurr = this._activating && this._hadNoEvSinceDeact && this._lastCurrent === event.value 35 | if (!sameCurr) { 36 | this._add(event.value, this._fn) 37 | } 38 | this._lastCurrent = event.value 39 | this._hadNoEvSinceDeact = false 40 | } 41 | 42 | if (event.type === ERROR) { 43 | this._emitError(event.value) 44 | } 45 | 46 | if (event.type === END) { 47 | if (this._isEmpty()) { 48 | this._emitEnd() 49 | } else { 50 | this._mainEnded = true 51 | } 52 | } 53 | }, 54 | 55 | _onEmpty() { 56 | if (this._mainEnded) { 57 | this._emitEnd() 58 | } 59 | }, 60 | 61 | _clear() { 62 | AbstractPool.prototype._clear.call(this) 63 | this._source = null 64 | this._lastCurrent = null 65 | this._$handleMain = null 66 | }, 67 | }) 68 | 69 | export default FlatMap 70 | -------------------------------------------------------------------------------- /src/many-sources/merge.js: -------------------------------------------------------------------------------- 1 | import {inherit} from '../utils/objects' 2 | import AbstractPool from './abstract-pool' 3 | import never from '../primary/never' 4 | 5 | function Merge(sources) { 6 | AbstractPool.call(this) 7 | this._addAll(sources) 8 | this._initialised = true 9 | } 10 | 11 | inherit(Merge, AbstractPool, { 12 | _name: 'merge', 13 | 14 | _onEmpty() { 15 | if (this._initialised) { 16 | this._emitEnd() 17 | } 18 | }, 19 | }) 20 | 21 | export default function merge(observables) { 22 | return observables.length === 0 ? never() : new Merge(observables) 23 | } 24 | -------------------------------------------------------------------------------- /src/many-sources/pool.js: -------------------------------------------------------------------------------- 1 | import {inherit} from '../utils/objects' 2 | import AbstractPool from './abstract-pool' 3 | 4 | function Pool() { 5 | AbstractPool.call(this) 6 | } 7 | 8 | inherit(Pool, AbstractPool, { 9 | _name: 'pool', 10 | 11 | plug(obs) { 12 | this._add(obs) 13 | return this 14 | }, 15 | 16 | unplug(obs) { 17 | this._remove(obs) 18 | return this 19 | }, 20 | }) 21 | 22 | export default Pool 23 | -------------------------------------------------------------------------------- /src/many-sources/repeat.js: -------------------------------------------------------------------------------- 1 | import {inherit} from '../utils/objects' 2 | import Stream from '../stream' 3 | import {END} from '../constants' 4 | 5 | function S(generator) { 6 | Stream.call(this) 7 | this._generator = generator 8 | this._source = null 9 | this._inLoop = false 10 | this._iteration = 0 11 | this._$handleAny = event => this._handleAny(event) 12 | } 13 | 14 | inherit(S, Stream, { 15 | _name: 'repeat', 16 | 17 | _handleAny(event) { 18 | if (event.type === END) { 19 | this._source = null 20 | this._getSource() 21 | } else { 22 | this._emit(event.type, event.value) 23 | } 24 | }, 25 | 26 | _getSource() { 27 | if (!this._inLoop) { 28 | this._inLoop = true 29 | const generator = this._generator 30 | while (this._source === null && this._alive && this._active) { 31 | this._source = generator(this._iteration++) 32 | if (this._source) { 33 | this._source.onAny(this._$handleAny) 34 | } else { 35 | this._emitEnd() 36 | } 37 | } 38 | this._inLoop = false 39 | } 40 | }, 41 | 42 | _onActivation() { 43 | if (this._source) { 44 | this._source.onAny(this._$handleAny) 45 | } else { 46 | this._getSource() 47 | } 48 | }, 49 | 50 | _onDeactivation() { 51 | if (this._source) { 52 | this._source.offAny(this._$handleAny) 53 | } 54 | }, 55 | 56 | _clear() { 57 | Stream.prototype._clear.call(this) 58 | this._generator = null 59 | this._source = null 60 | this._$handleAny = null 61 | }, 62 | }) 63 | 64 | export default function(generator) { 65 | return new S(generator) 66 | } 67 | -------------------------------------------------------------------------------- /src/many-sources/zip.js: -------------------------------------------------------------------------------- 1 | import Stream from '../stream' 2 | import {VALUE, ERROR, END} from '../constants' 3 | import {inherit} from '../utils/objects' 4 | import {map, cloneArray} from '../utils/collections' 5 | import {spread} from '../utils/functions' 6 | import never from '../primary/never' 7 | 8 | const isArray = 9 | Array.isArray || 10 | function(xs) { 11 | return Object.prototype.toString.call(xs) === '[object Array]' 12 | } 13 | 14 | function Zip(sources, combinator) { 15 | Stream.call(this) 16 | 17 | this._buffers = map(sources, source => (isArray(source) ? cloneArray(source) : [])) 18 | this._sources = map(sources, source => (isArray(source) ? never() : source)) 19 | 20 | this._combinator = combinator ? spread(combinator, this._sources.length) : x => x 21 | this._aliveCount = 0 22 | 23 | this._$handlers = [] 24 | for (let i = 0; i < this._sources.length; i++) { 25 | this._$handlers.push(event => this._handleAny(i, event)) 26 | } 27 | } 28 | 29 | inherit(Zip, Stream, { 30 | _name: 'zip', 31 | 32 | _onActivation() { 33 | // if all sources are arrays 34 | while (this._isFull()) { 35 | this._emit() 36 | } 37 | 38 | const length = this._sources.length 39 | this._aliveCount = length 40 | for (let i = 0; i < length && this._active; i++) { 41 | this._sources[i].onAny(this._$handlers[i]) 42 | } 43 | }, 44 | 45 | _onDeactivation() { 46 | for (let i = 0; i < this._sources.length; i++) { 47 | this._sources[i].offAny(this._$handlers[i]) 48 | } 49 | }, 50 | 51 | _emit() { 52 | let values = new Array(this._buffers.length) 53 | for (let i = 0; i < this._buffers.length; i++) { 54 | values[i] = this._buffers[i].shift() 55 | } 56 | const combinator = this._combinator 57 | this._emitValue(combinator(values)) 58 | }, 59 | 60 | _isFull() { 61 | for (let i = 0; i < this._buffers.length; i++) { 62 | if (this._buffers[i].length === 0) { 63 | return false 64 | } 65 | } 66 | return true 67 | }, 68 | 69 | _handleAny(i, event) { 70 | if (event.type === VALUE) { 71 | this._buffers[i].push(event.value) 72 | if (this._isFull()) { 73 | this._emit() 74 | } 75 | } 76 | if (event.type === ERROR) { 77 | this._emitError(event.value) 78 | } 79 | if (event.type === END) { 80 | this._aliveCount-- 81 | if (this._aliveCount === 0) { 82 | this._emitEnd() 83 | } 84 | } 85 | }, 86 | 87 | _clear() { 88 | Stream.prototype._clear.call(this) 89 | this._sources = null 90 | this._buffers = null 91 | this._combinator = null 92 | this._$handlers = null 93 | }, 94 | }) 95 | 96 | export default function zip(observables, combinator /* Function | falsey */) { 97 | return observables.length === 0 ? never() : new Zip(observables, combinator) 98 | } 99 | -------------------------------------------------------------------------------- /src/one-source/before-end.js: -------------------------------------------------------------------------------- 1 | import {createStream, createProperty} from '../patterns/one-source' 2 | 3 | const mixin = { 4 | _init({fn}) { 5 | this._fn = fn 6 | }, 7 | 8 | _free() { 9 | this._fn = null 10 | }, 11 | 12 | _handleEnd() { 13 | const fn = this._fn 14 | this._emitValue(fn()) 15 | this._emitEnd() 16 | }, 17 | } 18 | 19 | const S = createStream('beforeEnd', mixin) 20 | const P = createProperty('beforeEnd', mixin) 21 | 22 | export default function beforeEnd(obs, fn) { 23 | return new (obs._ofSameType(S, P))(obs, {fn}) 24 | } 25 | -------------------------------------------------------------------------------- /src/one-source/buffer-while.js: -------------------------------------------------------------------------------- 1 | import {createStream, createProperty} from '../patterns/one-source' 2 | 3 | const mixin = { 4 | _init({fn, flushOnEnd}) { 5 | this._fn = fn 6 | this._flushOnEnd = flushOnEnd 7 | this._buff = [] 8 | }, 9 | 10 | _free() { 11 | this._buff = null 12 | }, 13 | 14 | _flush() { 15 | if (this._buff !== null && this._buff.length !== 0) { 16 | this._emitValue(this._buff) 17 | this._buff = [] 18 | } 19 | }, 20 | 21 | _handleValue(x) { 22 | this._buff.push(x) 23 | const fn = this._fn 24 | if (!fn(x)) { 25 | this._flush() 26 | } 27 | }, 28 | 29 | _handleEnd() { 30 | if (this._flushOnEnd) { 31 | this._flush() 32 | } 33 | this._emitEnd() 34 | }, 35 | } 36 | 37 | const S = createStream('bufferWhile', mixin) 38 | const P = createProperty('bufferWhile', mixin) 39 | 40 | const id = x => x 41 | 42 | export default function bufferWhile(obs, fn, {flushOnEnd = true} = {}) { 43 | return new (obs._ofSameType(S, P))(obs, {fn: fn || id, flushOnEnd}) 44 | } 45 | -------------------------------------------------------------------------------- /src/one-source/buffer-with-count.js: -------------------------------------------------------------------------------- 1 | import {createStream, createProperty} from '../patterns/one-source' 2 | 3 | const mixin = { 4 | _init({count, flushOnEnd}) { 5 | this._count = count 6 | this._flushOnEnd = flushOnEnd 7 | this._buff = [] 8 | }, 9 | 10 | _free() { 11 | this._buff = null 12 | }, 13 | 14 | _flush() { 15 | if (this._buff !== null && this._buff.length !== 0) { 16 | this._emitValue(this._buff) 17 | this._buff = [] 18 | } 19 | }, 20 | 21 | _handleValue(x) { 22 | this._buff.push(x) 23 | if (this._buff.length >= this._count) { 24 | this._flush() 25 | } 26 | }, 27 | 28 | _handleEnd() { 29 | if (this._flushOnEnd) { 30 | this._flush() 31 | } 32 | this._emitEnd() 33 | }, 34 | } 35 | 36 | const S = createStream('bufferWithCount', mixin) 37 | const P = createProperty('bufferWithCount', mixin) 38 | 39 | export default function bufferWhile(obs, count, {flushOnEnd = true} = {}) { 40 | return new (obs._ofSameType(S, P))(obs, {count: count, flushOnEnd}) 41 | } 42 | -------------------------------------------------------------------------------- /src/one-source/buffer-with-time-or-count.js: -------------------------------------------------------------------------------- 1 | import {createStream, createProperty} from '../patterns/one-source' 2 | 3 | const mixin = { 4 | _init({wait, count, flushOnEnd}) { 5 | this._wait = wait 6 | this._count = count 7 | this._flushOnEnd = flushOnEnd 8 | this._intervalId = null 9 | this._$onTick = () => this._flush() 10 | this._buff = [] 11 | }, 12 | 13 | _free() { 14 | this._$onTick = null 15 | this._buff = null 16 | }, 17 | 18 | _flush() { 19 | if (this._buff !== null) { 20 | this._emitValue(this._buff) 21 | this._buff = [] 22 | } 23 | }, 24 | 25 | _handleValue(x) { 26 | this._buff.push(x) 27 | if (this._buff.length >= this._count) { 28 | clearInterval(this._intervalId) 29 | this._flush() 30 | this._intervalId = setInterval(this._$onTick, this._wait) 31 | } 32 | }, 33 | 34 | _handleEnd() { 35 | if (this._flushOnEnd && this._buff.length !== 0) { 36 | this._flush() 37 | } 38 | this._emitEnd() 39 | }, 40 | 41 | _onActivation() { 42 | this._intervalId = setInterval(this._$onTick, this._wait) 43 | this._source.onAny(this._$handleAny) // copied from patterns/one-source 44 | }, 45 | 46 | _onDeactivation() { 47 | if (this._intervalId !== null) { 48 | clearInterval(this._intervalId) 49 | this._intervalId = null 50 | } 51 | this._source.offAny(this._$handleAny) // copied from patterns/one-source 52 | }, 53 | } 54 | 55 | const S = createStream('bufferWithTimeOrCount', mixin) 56 | const P = createProperty('bufferWithTimeOrCount', mixin) 57 | 58 | export default function bufferWithTimeOrCount(obs, wait, count, {flushOnEnd = true} = {}) { 59 | return new (obs._ofSameType(S, P))(obs, {wait, count, flushOnEnd}) 60 | } 61 | -------------------------------------------------------------------------------- /src/one-source/changes.js: -------------------------------------------------------------------------------- 1 | import {createStream} from '../patterns/one-source' 2 | 3 | const S = createStream('changes', { 4 | _handleValue(x) { 5 | if (!this._activating) { 6 | this._emitValue(x) 7 | } 8 | }, 9 | 10 | _handleError(x) { 11 | if (!this._activating) { 12 | this._emitError(x) 13 | } 14 | }, 15 | }) 16 | 17 | export default function changes(obs) { 18 | return new S(obs) 19 | } 20 | -------------------------------------------------------------------------------- /src/one-source/debounce.js: -------------------------------------------------------------------------------- 1 | import {createStream, createProperty} from '../patterns/one-source' 2 | import now from '../utils/now' 3 | 4 | const mixin = { 5 | _init({wait, immediate}) { 6 | this._wait = Math.max(0, wait) 7 | this._immediate = immediate 8 | this._lastAttempt = 0 9 | this._timeoutId = null 10 | this._laterValue = null 11 | this._endLater = false 12 | this._$later = () => this._later() 13 | }, 14 | 15 | _free() { 16 | this._laterValue = null 17 | this._$later = null 18 | }, 19 | 20 | _handleValue(x) { 21 | if (this._activating) { 22 | this._emitValue(x) 23 | } else { 24 | this._lastAttempt = now() 25 | if (this._immediate && !this._timeoutId) { 26 | this._emitValue(x) 27 | } 28 | if (!this._timeoutId) { 29 | this._timeoutId = setTimeout(this._$later, this._wait) 30 | } 31 | if (!this._immediate) { 32 | this._laterValue = x 33 | } 34 | } 35 | }, 36 | 37 | _handleEnd() { 38 | if (this._activating) { 39 | this._emitEnd() 40 | } else { 41 | if (this._timeoutId && !this._immediate) { 42 | this._endLater = true 43 | } else { 44 | this._emitEnd() 45 | } 46 | } 47 | }, 48 | 49 | _later() { 50 | let last = now() - this._lastAttempt 51 | if (last < this._wait && last >= 0) { 52 | this._timeoutId = setTimeout(this._$later, this._wait - last) 53 | } else { 54 | this._timeoutId = null 55 | if (!this._immediate) { 56 | const _laterValue = this._laterValue 57 | this._laterValue = null 58 | this._emitValue(_laterValue) 59 | } 60 | if (this._endLater) { 61 | this._emitEnd() 62 | } 63 | } 64 | }, 65 | } 66 | 67 | const S = createStream('debounce', mixin) 68 | const P = createProperty('debounce', mixin) 69 | 70 | export default function debounce(obs, wait, {immediate = false} = {}) { 71 | return new (obs._ofSameType(S, P))(obs, {wait, immediate}) 72 | } 73 | -------------------------------------------------------------------------------- /src/one-source/delay.js: -------------------------------------------------------------------------------- 1 | import {createStream, createProperty} from '../patterns/one-source' 2 | 3 | const END_MARKER = {} 4 | 5 | const mixin = { 6 | _init({wait}) { 7 | this._wait = Math.max(0, wait) 8 | this._buff = [] 9 | this._$shiftBuff = () => { 10 | const value = this._buff.shift() 11 | if (value === END_MARKER) { 12 | this._emitEnd() 13 | } else { 14 | this._emitValue(value) 15 | } 16 | } 17 | }, 18 | 19 | _free() { 20 | this._buff = null 21 | this._$shiftBuff = null 22 | }, 23 | 24 | _handleValue(x) { 25 | if (this._activating) { 26 | this._emitValue(x) 27 | } else { 28 | this._buff.push(x) 29 | setTimeout(this._$shiftBuff, this._wait) 30 | } 31 | }, 32 | 33 | _handleEnd() { 34 | if (this._activating) { 35 | this._emitEnd() 36 | } else { 37 | this._buff.push(END_MARKER) 38 | setTimeout(this._$shiftBuff, this._wait) 39 | } 40 | }, 41 | } 42 | 43 | const S = createStream('delay', mixin) 44 | const P = createProperty('delay', mixin) 45 | 46 | export default function delay(obs, wait) { 47 | return new (obs._ofSameType(S, P))(obs, {wait}) 48 | } 49 | -------------------------------------------------------------------------------- /src/one-source/diff.js: -------------------------------------------------------------------------------- 1 | import {createStream, createProperty} from '../patterns/one-source' 2 | import {NOTHING} from '../constants' 3 | 4 | const mixin = { 5 | _init({fn, seed}) { 6 | this._fn = fn 7 | this._prev = seed 8 | }, 9 | 10 | _free() { 11 | this._prev = null 12 | this._fn = null 13 | }, 14 | 15 | _handleValue(x) { 16 | if (this._prev !== NOTHING) { 17 | const fn = this._fn 18 | this._emitValue(fn(this._prev, x)) 19 | } 20 | this._prev = x 21 | }, 22 | } 23 | 24 | const S = createStream('diff', mixin) 25 | const P = createProperty('diff', mixin) 26 | 27 | function defaultFn(a, b) { 28 | return [a, b] 29 | } 30 | 31 | export default function diff(obs, fn, seed = NOTHING) { 32 | return new (obs._ofSameType(S, P))(obs, {fn: fn || defaultFn, seed}) 33 | } 34 | -------------------------------------------------------------------------------- /src/one-source/end-on-error.js: -------------------------------------------------------------------------------- 1 | import {createStream, createProperty} from '../patterns/one-source' 2 | 3 | const mixin = { 4 | _handleError(x) { 5 | this._emitError(x) 6 | this._emitEnd() 7 | }, 8 | } 9 | 10 | const S = createStream('endOnError', mixin) 11 | const P = createProperty('endOnError', mixin) 12 | 13 | export default function endOnError(obs) { 14 | return new (obs._ofSameType(S, P))(obs) 15 | } 16 | -------------------------------------------------------------------------------- /src/one-source/errors-to-values.js: -------------------------------------------------------------------------------- 1 | import {createStream, createProperty} from '../patterns/one-source' 2 | 3 | const mixin = { 4 | _init({fn}) { 5 | this._fn = fn 6 | }, 7 | 8 | _free() { 9 | this._fn = null 10 | }, 11 | 12 | _handleError(x) { 13 | const fn = this._fn 14 | const result = fn(x) 15 | if (result.convert) { 16 | this._emitValue(result.value) 17 | } else { 18 | this._emitError(x) 19 | } 20 | }, 21 | } 22 | 23 | const S = createStream('errorsToValues', mixin) 24 | const P = createProperty('errorsToValues', mixin) 25 | 26 | const defFn = x => ({convert: true, value: x}) 27 | 28 | export default function errorsToValues(obs, fn = defFn) { 29 | return new (obs._ofSameType(S, P))(obs, {fn}) 30 | } 31 | -------------------------------------------------------------------------------- /src/one-source/filter-errors.js: -------------------------------------------------------------------------------- 1 | import {createStream, createProperty} from '../patterns/one-source' 2 | 3 | const mixin = { 4 | _init({fn}) { 5 | this._fn = fn 6 | }, 7 | 8 | _free() { 9 | this._fn = null 10 | }, 11 | 12 | _handleError(x) { 13 | const fn = this._fn 14 | if (fn(x)) { 15 | this._emitError(x) 16 | } 17 | }, 18 | } 19 | 20 | const S = createStream('filterErrors', mixin) 21 | const P = createProperty('filterErrors', mixin) 22 | 23 | const id = x => x 24 | 25 | export default function filterErrors(obs, fn = id) { 26 | return new (obs._ofSameType(S, P))(obs, {fn}) 27 | } 28 | -------------------------------------------------------------------------------- /src/one-source/filter.js: -------------------------------------------------------------------------------- 1 | import {createStream, createProperty} from '../patterns/one-source' 2 | 3 | const mixin = { 4 | _init({fn}) { 5 | this._fn = fn 6 | }, 7 | 8 | _free() { 9 | this._fn = null 10 | }, 11 | 12 | _handleValue(x) { 13 | const fn = this._fn 14 | if (fn(x)) { 15 | this._emitValue(x) 16 | } 17 | }, 18 | } 19 | 20 | const S = createStream('filter', mixin) 21 | const P = createProperty('filter', mixin) 22 | 23 | const id = x => x 24 | 25 | export default function filter(obs, fn = id) { 26 | return new (obs._ofSameType(S, P))(obs, {fn}) 27 | } 28 | -------------------------------------------------------------------------------- /src/one-source/flatten.js: -------------------------------------------------------------------------------- 1 | import {createStream} from '../patterns/one-source' 2 | 3 | const mixin = { 4 | _init({fn}) { 5 | this._fn = fn 6 | }, 7 | 8 | _free() { 9 | this._fn = null 10 | }, 11 | 12 | _handleValue(x) { 13 | const fn = this._fn 14 | const xs = fn(x) 15 | for (let i = 0; i < xs.length; i++) { 16 | this._emitValue(xs[i]) 17 | } 18 | }, 19 | } 20 | 21 | const S = createStream('flatten', mixin) 22 | 23 | const id = x => x 24 | 25 | export default function flatten(obs, fn = id) { 26 | return new S(obs, {fn}) 27 | } 28 | -------------------------------------------------------------------------------- /src/one-source/ignore-end.js: -------------------------------------------------------------------------------- 1 | import {createStream, createProperty} from '../patterns/one-source' 2 | 3 | const mixin = { 4 | _handleEnd() {}, 5 | } 6 | 7 | const S = createStream('ignoreEnd', mixin) 8 | const P = createProperty('ignoreEnd', mixin) 9 | 10 | export default function ignoreEnd(obs) { 11 | return new (obs._ofSameType(S, P))(obs) 12 | } 13 | -------------------------------------------------------------------------------- /src/one-source/ignore-errors.js: -------------------------------------------------------------------------------- 1 | import {createStream, createProperty} from '../patterns/one-source' 2 | 3 | const mixin = { 4 | _handleError() {}, 5 | } 6 | 7 | const S = createStream('ignoreErrors', mixin) 8 | const P = createProperty('ignoreErrors', mixin) 9 | 10 | export default function ignoreErrors(obs) { 11 | return new (obs._ofSameType(S, P))(obs) 12 | } 13 | -------------------------------------------------------------------------------- /src/one-source/ignore-values.js: -------------------------------------------------------------------------------- 1 | import {createStream, createProperty} from '../patterns/one-source' 2 | 3 | const mixin = { 4 | _handleValue() {}, 5 | } 6 | 7 | const S = createStream('ignoreValues', mixin) 8 | const P = createProperty('ignoreValues', mixin) 9 | 10 | export default function ignoreValues(obs) { 11 | return new (obs._ofSameType(S, P))(obs) 12 | } 13 | -------------------------------------------------------------------------------- /src/one-source/last.js: -------------------------------------------------------------------------------- 1 | import {createStream, createProperty} from '../patterns/one-source' 2 | import {NOTHING} from '../constants' 3 | 4 | const mixin = { 5 | _init() { 6 | this._lastValue = NOTHING 7 | }, 8 | 9 | _free() { 10 | this._lastValue = null 11 | }, 12 | 13 | _handleValue(x) { 14 | this._lastValue = x 15 | }, 16 | 17 | _handleEnd() { 18 | if (this._lastValue !== NOTHING) { 19 | this._emitValue(this._lastValue) 20 | } 21 | this._emitEnd() 22 | }, 23 | } 24 | 25 | const S = createStream('last', mixin) 26 | const P = createProperty('last', mixin) 27 | 28 | export default function last(obs) { 29 | return new (obs._ofSameType(S, P))(obs) 30 | } 31 | -------------------------------------------------------------------------------- /src/one-source/map-errors.js: -------------------------------------------------------------------------------- 1 | import {createStream, createProperty} from '../patterns/one-source' 2 | 3 | const mixin = { 4 | _init({fn}) { 5 | this._fn = fn 6 | }, 7 | 8 | _free() { 9 | this._fn = null 10 | }, 11 | 12 | _handleError(x) { 13 | const fn = this._fn 14 | this._emitError(fn(x)) 15 | }, 16 | } 17 | 18 | const S = createStream('mapErrors', mixin) 19 | const P = createProperty('mapErrors', mixin) 20 | 21 | const id = x => x 22 | 23 | export default function mapErrors(obs, fn = id) { 24 | return new (obs._ofSameType(S, P))(obs, {fn}) 25 | } 26 | -------------------------------------------------------------------------------- /src/one-source/map.js: -------------------------------------------------------------------------------- 1 | import {createStream, createProperty} from '../patterns/one-source' 2 | 3 | const mixin = { 4 | _init({fn}) { 5 | this._fn = fn 6 | }, 7 | 8 | _free() { 9 | this._fn = null 10 | }, 11 | 12 | _handleValue(x) { 13 | const fn = this._fn 14 | this._emitValue(fn(x)) 15 | }, 16 | } 17 | 18 | const S = createStream('map', mixin) 19 | const P = createProperty('map', mixin) 20 | 21 | const id = x => x 22 | 23 | export default function map(obs, fn = id) { 24 | return new (obs._ofSameType(S, P))(obs, {fn}) 25 | } 26 | -------------------------------------------------------------------------------- /src/one-source/scan.js: -------------------------------------------------------------------------------- 1 | import {createProperty} from '../patterns/one-source' 2 | import {ERROR, NOTHING} from '../constants' 3 | 4 | const P = createProperty('scan', { 5 | _init({fn, seed}) { 6 | this._fn = fn 7 | this._seed = seed 8 | if (seed !== NOTHING) { 9 | this._emitValue(seed) 10 | } 11 | }, 12 | 13 | _free() { 14 | this._fn = null 15 | this._seed = null 16 | }, 17 | 18 | _handleValue(x) { 19 | const fn = this._fn 20 | if (this._currentEvent === null || this._currentEvent.type === ERROR) { 21 | this._emitValue(this._seed === NOTHING ? x : fn(this._seed, x)) 22 | } else { 23 | this._emitValue(fn(this._currentEvent.value, x)) 24 | } 25 | }, 26 | }) 27 | 28 | export default function scan(obs, fn, seed = NOTHING) { 29 | return new P(obs, {fn, seed}) 30 | } 31 | -------------------------------------------------------------------------------- /src/one-source/skip-duplicates.js: -------------------------------------------------------------------------------- 1 | import {createStream, createProperty} from '../patterns/one-source' 2 | import {NOTHING} from '../constants' 3 | 4 | const mixin = { 5 | _init({fn}) { 6 | this._fn = fn 7 | this._prev = NOTHING 8 | }, 9 | 10 | _free() { 11 | this._fn = null 12 | this._prev = null 13 | }, 14 | 15 | _handleValue(x) { 16 | const fn = this._fn 17 | if (this._prev === NOTHING || !fn(this._prev, x)) { 18 | this._prev = x 19 | this._emitValue(x) 20 | } 21 | }, 22 | } 23 | 24 | const S = createStream('skipDuplicates', mixin) 25 | const P = createProperty('skipDuplicates', mixin) 26 | 27 | const eq = (a, b) => a === b 28 | 29 | export default function skipDuplicates(obs, fn = eq) { 30 | return new (obs._ofSameType(S, P))(obs, {fn}) 31 | } 32 | -------------------------------------------------------------------------------- /src/one-source/skip-end.js: -------------------------------------------------------------------------------- 1 | import {createStream, createProperty} from '../patterns/one-source' 2 | 3 | const mixin = { 4 | _handleEnd() {}, 5 | } 6 | 7 | const S = createStream('skipEnd', mixin) 8 | const P = createProperty('skipEnd', mixin) 9 | 10 | export default function skipEnd(obs) { 11 | return new (obs._ofSameType(S, P))(obs) 12 | } 13 | -------------------------------------------------------------------------------- /src/one-source/skip-while.js: -------------------------------------------------------------------------------- 1 | import {createStream, createProperty} from '../patterns/one-source' 2 | 3 | const mixin = { 4 | _init({fn}) { 5 | this._fn = fn 6 | }, 7 | 8 | _free() { 9 | this._fn = null 10 | }, 11 | 12 | _handleValue(x) { 13 | const fn = this._fn 14 | if (this._fn !== null && !fn(x)) { 15 | this._fn = null 16 | } 17 | if (this._fn === null) { 18 | this._emitValue(x) 19 | } 20 | }, 21 | } 22 | 23 | const S = createStream('skipWhile', mixin) 24 | const P = createProperty('skipWhile', mixin) 25 | 26 | const id = x => x 27 | 28 | export default function skipWhile(obs, fn = id) { 29 | return new (obs._ofSameType(S, P))(obs, {fn}) 30 | } 31 | -------------------------------------------------------------------------------- /src/one-source/skip.js: -------------------------------------------------------------------------------- 1 | import {createStream, createProperty} from '../patterns/one-source' 2 | 3 | const mixin = { 4 | _init({n}) { 5 | this._n = Math.max(0, n) 6 | }, 7 | 8 | _handleValue(x) { 9 | if (this._n === 0) { 10 | this._emitValue(x) 11 | } else { 12 | this._n-- 13 | } 14 | }, 15 | } 16 | 17 | const S = createStream('skip', mixin) 18 | const P = createProperty('skip', mixin) 19 | 20 | export default function skip(obs, n) { 21 | return new (obs._ofSameType(S, P))(obs, {n}) 22 | } 23 | -------------------------------------------------------------------------------- /src/one-source/sliding-window.js: -------------------------------------------------------------------------------- 1 | import {createStream, createProperty} from '../patterns/one-source' 2 | import {slide} from '../utils/collections' 3 | 4 | const mixin = { 5 | _init({min, max}) { 6 | this._max = max 7 | this._min = min 8 | this._buff = [] 9 | }, 10 | 11 | _free() { 12 | this._buff = null 13 | }, 14 | 15 | _handleValue(x) { 16 | this._buff = slide(this._buff, x, this._max) 17 | if (this._buff.length >= this._min) { 18 | this._emitValue(this._buff) 19 | } 20 | }, 21 | } 22 | 23 | const S = createStream('slidingWindow', mixin) 24 | const P = createProperty('slidingWindow', mixin) 25 | 26 | export default function slidingWindow(obs, max, min = 0) { 27 | return new (obs._ofSameType(S, P))(obs, {min, max}) 28 | } 29 | -------------------------------------------------------------------------------- /src/one-source/take-errors.js: -------------------------------------------------------------------------------- 1 | import {createStream, createProperty} from '../patterns/one-source' 2 | 3 | const mixin = { 4 | _init({n}) { 5 | this._n = n 6 | if (n <= 0) { 7 | this._emitEnd() 8 | } 9 | }, 10 | 11 | _handleError(x) { 12 | if (this._n === 0) { 13 | return 14 | } 15 | this._n-- 16 | this._emitError(x) 17 | if (this._n === 0) { 18 | this._emitEnd() 19 | } 20 | }, 21 | } 22 | 23 | const S = createStream('takeErrors', mixin) 24 | const P = createProperty('takeErrors', mixin) 25 | 26 | export default function takeErrors(obs, n) { 27 | return new (obs._ofSameType(S, P))(obs, {n}) 28 | } 29 | -------------------------------------------------------------------------------- /src/one-source/take-while.js: -------------------------------------------------------------------------------- 1 | import {createStream, createProperty} from '../patterns/one-source' 2 | 3 | const mixin = { 4 | _init({fn}) { 5 | this._fn = fn 6 | }, 7 | 8 | _free() { 9 | this._fn = null 10 | }, 11 | 12 | _handleValue(x) { 13 | const fn = this._fn 14 | if (fn(x)) { 15 | this._emitValue(x) 16 | } else { 17 | this._emitEnd() 18 | } 19 | }, 20 | } 21 | 22 | const S = createStream('takeWhile', mixin) 23 | const P = createProperty('takeWhile', mixin) 24 | 25 | const id = x => x 26 | 27 | export default function takeWhile(obs, fn = id) { 28 | return new (obs._ofSameType(S, P))(obs, {fn}) 29 | } 30 | -------------------------------------------------------------------------------- /src/one-source/take.js: -------------------------------------------------------------------------------- 1 | import {createStream, createProperty} from '../patterns/one-source' 2 | 3 | const mixin = { 4 | _init({n}) { 5 | this._n = n 6 | if (n <= 0) { 7 | this._emitEnd() 8 | } 9 | }, 10 | 11 | _handleValue(x) { 12 | if (this._n === 0) { 13 | return 14 | } 15 | this._n-- 16 | this._emitValue(x) 17 | if (this._n === 0) { 18 | this._emitEnd() 19 | } 20 | }, 21 | } 22 | 23 | const S = createStream('take', mixin) 24 | const P = createProperty('take', mixin) 25 | 26 | export default function take(obs, n) { 27 | return new (obs._ofSameType(S, P))(obs, {n}) 28 | } 29 | -------------------------------------------------------------------------------- /src/one-source/throttle.js: -------------------------------------------------------------------------------- 1 | import {createStream, createProperty} from '../patterns/one-source' 2 | import now from '../utils/now' 3 | 4 | const mixin = { 5 | _init({wait, leading, trailing}) { 6 | this._wait = Math.max(0, wait) 7 | this._leading = leading 8 | this._trailing = trailing 9 | this._trailingValue = null 10 | this._timeoutId = null 11 | this._endLater = false 12 | this._lastCallTime = 0 13 | this._$trailingCall = () => this._trailingCall() 14 | }, 15 | 16 | _free() { 17 | this._trailingValue = null 18 | this._$trailingCall = null 19 | }, 20 | 21 | _handleValue(x) { 22 | if (this._activating) { 23 | this._emitValue(x) 24 | } else { 25 | let curTime = now() 26 | if (this._lastCallTime === 0 && !this._leading) { 27 | this._lastCallTime = curTime 28 | } 29 | let remaining = this._wait - (curTime - this._lastCallTime) 30 | if (remaining <= 0) { 31 | this._cancelTrailing() 32 | this._lastCallTime = curTime 33 | this._emitValue(x) 34 | } else if (this._trailing) { 35 | this._cancelTrailing() 36 | this._trailingValue = x 37 | this._timeoutId = setTimeout(this._$trailingCall, remaining) 38 | } 39 | } 40 | }, 41 | 42 | _handleEnd() { 43 | if (this._activating) { 44 | this._emitEnd() 45 | } else { 46 | if (this._timeoutId) { 47 | this._endLater = true 48 | } else { 49 | this._emitEnd() 50 | } 51 | } 52 | }, 53 | 54 | _cancelTrailing() { 55 | if (this._timeoutId !== null) { 56 | clearTimeout(this._timeoutId) 57 | this._timeoutId = null 58 | } 59 | }, 60 | 61 | _trailingCall() { 62 | this._emitValue(this._trailingValue) 63 | this._timeoutId = null 64 | this._trailingValue = null 65 | this._lastCallTime = !this._leading ? 0 : now() 66 | if (this._endLater) { 67 | this._emitEnd() 68 | } 69 | }, 70 | } 71 | 72 | const S = createStream('throttle', mixin) 73 | const P = createProperty('throttle', mixin) 74 | 75 | export default function throttle(obs, wait, {leading = true, trailing = true} = {}) { 76 | return new (obs._ofSameType(S, P))(obs, {wait, leading, trailing}) 77 | } 78 | -------------------------------------------------------------------------------- /src/one-source/to-property.js: -------------------------------------------------------------------------------- 1 | import {createProperty} from '../patterns/one-source' 2 | 3 | const P = createProperty('toProperty', { 4 | _init({fn}) { 5 | this._getInitialCurrent = fn 6 | }, 7 | 8 | _onActivation() { 9 | if (this._getInitialCurrent !== null) { 10 | const getInitial = this._getInitialCurrent 11 | this._emitValue(getInitial()) 12 | } 13 | this._source.onAny(this._$handleAny) // copied from patterns/one-source 14 | }, 15 | }) 16 | 17 | export default function toProperty(obs, fn = null) { 18 | if (fn !== null && typeof fn !== 'function') { 19 | throw new Error('You should call toProperty() with a function or no arguments.') 20 | } 21 | return new P(obs, {fn}) 22 | } 23 | -------------------------------------------------------------------------------- /src/one-source/transduce.js: -------------------------------------------------------------------------------- 1 | import {createStream, createProperty} from '../patterns/one-source' 2 | 3 | function xformForObs(obs) { 4 | return { 5 | '@@transducer/step'(res, input) { 6 | obs._emitValue(input) 7 | return null 8 | }, 9 | 10 | '@@transducer/result'() { 11 | obs._emitEnd() 12 | return null 13 | }, 14 | } 15 | } 16 | 17 | const mixin = { 18 | _init({transducer}) { 19 | this._xform = transducer(xformForObs(this)) 20 | }, 21 | 22 | _free() { 23 | this._xform = null 24 | }, 25 | 26 | _handleValue(x) { 27 | if (this._xform['@@transducer/step'](null, x) !== null) { 28 | this._xform['@@transducer/result'](null) 29 | } 30 | }, 31 | 32 | _handleEnd() { 33 | this._xform['@@transducer/result'](null) 34 | }, 35 | } 36 | 37 | const S = createStream('transduce', mixin) 38 | const P = createProperty('transduce', mixin) 39 | 40 | export default function transduce(obs, transducer) { 41 | return new (obs._ofSameType(S, P))(obs, {transducer}) 42 | } 43 | -------------------------------------------------------------------------------- /src/one-source/values-to-errors.js: -------------------------------------------------------------------------------- 1 | import {createStream, createProperty} from '../patterns/one-source' 2 | 3 | const mixin = { 4 | _init({fn}) { 5 | this._fn = fn 6 | }, 7 | 8 | _free() { 9 | this._fn = null 10 | }, 11 | 12 | _handleValue(x) { 13 | const fn = this._fn 14 | let result = fn(x) 15 | if (result.convert) { 16 | this._emitError(result.error) 17 | } else { 18 | this._emitValue(x) 19 | } 20 | }, 21 | } 22 | 23 | const S = createStream('valuesToErrors', mixin) 24 | const P = createProperty('valuesToErrors', mixin) 25 | 26 | const defFn = x => ({convert: true, error: x}) 27 | 28 | export default function valuesToErrors(obs, fn = defFn) { 29 | return new (obs._ofSameType(S, P))(obs, {fn}) 30 | } 31 | -------------------------------------------------------------------------------- /src/one-source/with-handler.js: -------------------------------------------------------------------------------- 1 | import {createStream, createProperty} from '../patterns/one-source' 2 | import emitter from '../emitter' 3 | 4 | const mixin = { 5 | _init({fn}) { 6 | this._handler = fn 7 | this._emitter = emitter(this) 8 | }, 9 | 10 | _free() { 11 | this._handler = null 12 | this._emitter = null 13 | }, 14 | 15 | _handleAny(event) { 16 | this._handler(this._emitter, event) 17 | }, 18 | } 19 | 20 | const S = createStream('withHandler', mixin) 21 | const P = createProperty('withHandler', mixin) 22 | 23 | export default function withHandler(obs, fn) { 24 | return new (obs._ofSameType(S, P))(obs, {fn}) 25 | } 26 | -------------------------------------------------------------------------------- /src/patterns/one-source.js: -------------------------------------------------------------------------------- 1 | import Stream from '../stream' 2 | import Property from '../property' 3 | import {inherit} from '../utils/objects' 4 | import {VALUE, ERROR, END} from '../constants' 5 | 6 | function createConstructor(BaseClass, name) { 7 | return function AnonymousObservable(source, options) { 8 | BaseClass.call(this) 9 | this._source = source 10 | this._name = `${source._name}.${name}` 11 | this._init(options) 12 | this._$handleAny = event => this._handleAny(event) 13 | } 14 | } 15 | 16 | function createClassMethods(BaseClass) { 17 | return { 18 | _init() {}, 19 | _free() {}, 20 | 21 | _handleValue(x) { 22 | this._emitValue(x) 23 | }, 24 | _handleError(x) { 25 | this._emitError(x) 26 | }, 27 | _handleEnd() { 28 | this._emitEnd() 29 | }, 30 | 31 | _handleAny(event) { 32 | switch (event.type) { 33 | case VALUE: 34 | return this._handleValue(event.value) 35 | case ERROR: 36 | return this._handleError(event.value) 37 | case END: 38 | return this._handleEnd() 39 | } 40 | }, 41 | 42 | _onActivation() { 43 | this._source.onAny(this._$handleAny) 44 | }, 45 | _onDeactivation() { 46 | this._source.offAny(this._$handleAny) 47 | }, 48 | 49 | _clear() { 50 | BaseClass.prototype._clear.call(this) 51 | this._source = null 52 | this._$handleAny = null 53 | this._free() 54 | }, 55 | } 56 | } 57 | 58 | function createStream(name, mixin) { 59 | const S = createConstructor(Stream, name) 60 | inherit(S, Stream, createClassMethods(Stream), mixin) 61 | return S 62 | } 63 | 64 | function createProperty(name, mixin) { 65 | const P = createConstructor(Property, name) 66 | inherit(P, Property, createClassMethods(Property), mixin) 67 | return P 68 | } 69 | 70 | export {createStream, createProperty} 71 | -------------------------------------------------------------------------------- /src/patterns/time-based.js: -------------------------------------------------------------------------------- 1 | import {inherit} from '../utils/objects' 2 | import Stream from '../stream' 3 | 4 | export default function timeBased(mixin) { 5 | function AnonymousStream(wait, options) { 6 | Stream.call(this) 7 | this._wait = wait 8 | this._intervalId = null 9 | this._$onTick = () => this._onTick() 10 | this._init(options) 11 | } 12 | 13 | inherit( 14 | AnonymousStream, 15 | Stream, 16 | { 17 | _init() {}, 18 | _free() {}, 19 | 20 | _onTick() {}, 21 | 22 | _onActivation() { 23 | this._intervalId = setInterval(this._$onTick, this._wait) 24 | }, 25 | 26 | _onDeactivation() { 27 | if (this._intervalId !== null) { 28 | clearInterval(this._intervalId) 29 | this._intervalId = null 30 | } 31 | }, 32 | 33 | _clear() { 34 | Stream.prototype._clear.call(this) 35 | this._$onTick = null 36 | this._free() 37 | }, 38 | }, 39 | mixin 40 | ) 41 | 42 | return AnonymousStream 43 | } 44 | -------------------------------------------------------------------------------- /src/patterns/two-sources.js: -------------------------------------------------------------------------------- 1 | import Stream from '../stream' 2 | import Property from '../property' 3 | import {inherit} from '../utils/objects' 4 | import {VALUE, ERROR, END, NOTHING} from '../constants' 5 | 6 | function createConstructor(BaseClass, name) { 7 | return function AnonymousObservable(primary, secondary, options) { 8 | BaseClass.call(this) 9 | this._primary = primary 10 | this._secondary = secondary 11 | this._name = `${primary._name}.${name}` 12 | this._lastSecondary = NOTHING 13 | this._$handleSecondaryAny = event => this._handleSecondaryAny(event) 14 | this._$handlePrimaryAny = event => this._handlePrimaryAny(event) 15 | this._init(options) 16 | } 17 | } 18 | 19 | function createClassMethods(BaseClass) { 20 | return { 21 | _init() {}, 22 | _free() {}, 23 | 24 | _handlePrimaryValue(x) { 25 | this._emitValue(x) 26 | }, 27 | _handlePrimaryError(x) { 28 | this._emitError(x) 29 | }, 30 | _handlePrimaryEnd() { 31 | this._emitEnd() 32 | }, 33 | 34 | _handleSecondaryValue(x) { 35 | this._lastSecondary = x 36 | }, 37 | _handleSecondaryError(x) { 38 | this._emitError(x) 39 | }, 40 | _handleSecondaryEnd() {}, 41 | 42 | _handlePrimaryAny(event) { 43 | switch (event.type) { 44 | case VALUE: 45 | return this._handlePrimaryValue(event.value) 46 | case ERROR: 47 | return this._handlePrimaryError(event.value) 48 | case END: 49 | return this._handlePrimaryEnd(event.value) 50 | } 51 | }, 52 | _handleSecondaryAny(event) { 53 | switch (event.type) { 54 | case VALUE: 55 | return this._handleSecondaryValue(event.value) 56 | case ERROR: 57 | return this._handleSecondaryError(event.value) 58 | case END: 59 | this._handleSecondaryEnd(event.value) 60 | this._removeSecondary() 61 | } 62 | }, 63 | 64 | _removeSecondary() { 65 | if (this._secondary !== null) { 66 | this._secondary.offAny(this._$handleSecondaryAny) 67 | this._$handleSecondaryAny = null 68 | this._secondary = null 69 | } 70 | }, 71 | 72 | _onActivation() { 73 | if (this._secondary !== null) { 74 | this._secondary.onAny(this._$handleSecondaryAny) 75 | } 76 | if (this._active) { 77 | this._primary.onAny(this._$handlePrimaryAny) 78 | } 79 | }, 80 | _onDeactivation() { 81 | if (this._secondary !== null) { 82 | this._secondary.offAny(this._$handleSecondaryAny) 83 | } 84 | this._primary.offAny(this._$handlePrimaryAny) 85 | }, 86 | 87 | _clear() { 88 | BaseClass.prototype._clear.call(this) 89 | this._primary = null 90 | this._secondary = null 91 | this._lastSecondary = null 92 | this._$handleSecondaryAny = null 93 | this._$handlePrimaryAny = null 94 | this._free() 95 | }, 96 | } 97 | } 98 | 99 | function createStream(name, mixin) { 100 | const S = createConstructor(Stream, name) 101 | inherit(S, Stream, createClassMethods(Stream), mixin) 102 | return S 103 | } 104 | 105 | function createProperty(name, mixin) { 106 | const P = createConstructor(Property, name) 107 | inherit(P, Property, createClassMethods(Property), mixin) 108 | return P 109 | } 110 | 111 | export {createStream, createProperty} 112 | -------------------------------------------------------------------------------- /src/primary/constant-error.js: -------------------------------------------------------------------------------- 1 | import {inherit} from '../utils/objects' 2 | import Property from '../property' 3 | 4 | // HACK: 5 | // We don't call parent Class constructor, but instead putting all necessary 6 | // properties into prototype to simulate ended Property 7 | // (see Propperty and Observable classes). 8 | 9 | function P(value) { 10 | this._currentEvent = {type: 'error', value, current: true} 11 | } 12 | 13 | inherit(P, Property, { 14 | _name: 'constantError', 15 | _active: false, 16 | _activating: false, 17 | _alive: false, 18 | _dispatcher: null, 19 | _logHandlers: null, 20 | }) 21 | 22 | export default function constantError(x) { 23 | return new P(x) 24 | } 25 | -------------------------------------------------------------------------------- /src/primary/constant.js: -------------------------------------------------------------------------------- 1 | import {inherit} from '../utils/objects' 2 | import Property from '../property' 3 | 4 | // HACK: 5 | // We don't call parent Class constructor, but instead putting all necessary 6 | // properties into prototype to simulate ended Property 7 | // (see Propperty and Observable classes). 8 | 9 | function P(value) { 10 | this._currentEvent = {type: 'value', value, current: true} 11 | } 12 | 13 | inherit(P, Property, { 14 | _name: 'constant', 15 | _active: false, 16 | _activating: false, 17 | _alive: false, 18 | _dispatcher: null, 19 | _logHandlers: null, 20 | }) 21 | 22 | export default function constant(x) { 23 | return new P(x) 24 | } 25 | -------------------------------------------------------------------------------- /src/primary/from-callback.js: -------------------------------------------------------------------------------- 1 | import stream from './stream' 2 | 3 | export default function fromCallback(callbackConsumer) { 4 | let called = false 5 | 6 | return stream(function(emitter) { 7 | if (!called) { 8 | callbackConsumer(function(x) { 9 | emitter.emit(x) 10 | emitter.end() 11 | }) 12 | called = true 13 | } 14 | }).setName('fromCallback') 15 | } 16 | -------------------------------------------------------------------------------- /src/primary/from-events.js: -------------------------------------------------------------------------------- 1 | import fromSubUnsub from './from-sub-unsub' 2 | 3 | const pairs = [ 4 | ['addEventListener', 'removeEventListener'], 5 | ['addListener', 'removeListener'], 6 | ['on', 'off'], 7 | ] 8 | 9 | export default function fromEvents(target, eventName, transformer) { 10 | let sub, unsub 11 | 12 | for (let i = 0; i < pairs.length; i++) { 13 | if (typeof target[pairs[i][0]] === 'function' && typeof target[pairs[i][1]] === 'function') { 14 | sub = pairs[i][0] 15 | unsub = pairs[i][1] 16 | break 17 | } 18 | } 19 | 20 | if (sub === undefined) { 21 | throw new Error( 22 | "target don't support any of " + 23 | 'addEventListener/removeEventListener, addListener/removeListener, on/off method pair' 24 | ) 25 | } 26 | 27 | return fromSubUnsub( 28 | handler => target[sub](eventName, handler), 29 | handler => target[unsub](eventName, handler), 30 | transformer 31 | ).setName('fromEvents') 32 | } 33 | -------------------------------------------------------------------------------- /src/primary/from-node-callback.js: -------------------------------------------------------------------------------- 1 | import stream from './stream' 2 | 3 | export default function fromNodeCallback(callbackConsumer) { 4 | let called = false 5 | 6 | return stream(function(emitter) { 7 | if (!called) { 8 | callbackConsumer(function(error, x) { 9 | if (error) { 10 | emitter.error(error) 11 | } else { 12 | emitter.emit(x) 13 | } 14 | emitter.end() 15 | }) 16 | called = true 17 | } 18 | }).setName('fromNodeCallback') 19 | } 20 | -------------------------------------------------------------------------------- /src/primary/from-sub-unsub.js: -------------------------------------------------------------------------------- 1 | import stream from './stream' 2 | import {apply} from '../utils/functions' 3 | 4 | export default function fromSubUnsub(sub, unsub, transformer /* Function | falsey */) { 5 | return stream(function(emitter) { 6 | let handler = transformer 7 | ? function() { 8 | emitter.emit(apply(transformer, this, arguments)) 9 | } 10 | : x => { 11 | emitter.emit(x) 12 | } 13 | 14 | sub(handler) 15 | return () => unsub(handler) 16 | }).setName('fromSubUnsub') 17 | } 18 | -------------------------------------------------------------------------------- /src/primary/never.js: -------------------------------------------------------------------------------- 1 | import Stream from '../stream' 2 | 3 | const neverS = new Stream() 4 | neverS._emitEnd() 5 | neverS._name = 'never' 6 | 7 | export default function never() { 8 | return neverS 9 | } 10 | -------------------------------------------------------------------------------- /src/primary/stream.js: -------------------------------------------------------------------------------- 1 | import {inherit} from '../utils/objects' 2 | import Stream from '../stream' 3 | import emitter from '../emitter' 4 | 5 | function S(fn) { 6 | Stream.call(this) 7 | this._fn = fn 8 | this._unsubscribe = null 9 | } 10 | 11 | inherit(S, Stream, { 12 | _name: 'stream', 13 | 14 | _onActivation() { 15 | const fn = this._fn 16 | const unsubscribe = fn(emitter(this)) 17 | this._unsubscribe = typeof unsubscribe === 'function' ? unsubscribe : null 18 | 19 | // fix https://github.com/kefirjs/kefir/issues/35 20 | if (!this._active) { 21 | this._callUnsubscribe() 22 | } 23 | }, 24 | 25 | _callUnsubscribe() { 26 | if (this._unsubscribe !== null) { 27 | this._unsubscribe() 28 | this._unsubscribe = null 29 | } 30 | }, 31 | 32 | _onDeactivation() { 33 | this._callUnsubscribe() 34 | }, 35 | 36 | _clear() { 37 | Stream.prototype._clear.call(this) 38 | this._fn = null 39 | }, 40 | }) 41 | 42 | export default function stream(fn) { 43 | return new S(fn) 44 | } 45 | -------------------------------------------------------------------------------- /src/property.js: -------------------------------------------------------------------------------- 1 | import {inherit} from './utils/objects' 2 | import {VALUE, ERROR, END} from './constants' 3 | import {callSubscriber} from './dispatcher' 4 | import Observable from './observable' 5 | 6 | function Property() { 7 | Observable.call(this) 8 | this._currentEvent = null 9 | } 10 | 11 | inherit(Property, Observable, { 12 | _name: 'property', 13 | 14 | _emitValue(value) { 15 | if (this._alive) { 16 | this._currentEvent = {type: VALUE, value} 17 | if (!this._activating) { 18 | this._dispatcher.dispatch({type: VALUE, value}) 19 | } 20 | } 21 | }, 22 | 23 | _emitError(value) { 24 | if (this._alive) { 25 | this._currentEvent = {type: ERROR, value} 26 | if (!this._activating) { 27 | this._dispatcher.dispatch({type: ERROR, value}) 28 | } 29 | } 30 | }, 31 | 32 | _emitEnd() { 33 | if (this._alive) { 34 | this._alive = false 35 | if (!this._activating) { 36 | this._dispatcher.dispatch({type: END}) 37 | } 38 | this._clear() 39 | } 40 | }, 41 | 42 | _on(type, fn) { 43 | if (this._alive) { 44 | this._dispatcher.add(type, fn) 45 | this._setActive(true) 46 | } 47 | if (this._currentEvent !== null) { 48 | callSubscriber(type, fn, this._currentEvent) 49 | } 50 | if (!this._alive) { 51 | callSubscriber(type, fn, {type: END}) 52 | } 53 | return this 54 | }, 55 | 56 | getType() { 57 | return 'property' 58 | }, 59 | }) 60 | 61 | export default Property 62 | -------------------------------------------------------------------------------- /src/stream.js: -------------------------------------------------------------------------------- 1 | import {inherit} from './utils/objects' 2 | import Observable from './observable' 3 | 4 | function Stream() { 5 | Observable.call(this) 6 | } 7 | 8 | inherit(Stream, Observable, { 9 | _name: 'stream', 10 | 11 | getType() { 12 | return 'stream' 13 | }, 14 | }) 15 | 16 | export default Stream 17 | -------------------------------------------------------------------------------- /src/time-based/from-poll.js: -------------------------------------------------------------------------------- 1 | import timeBased from '../patterns/time-based' 2 | 3 | const S = timeBased({ 4 | _name: 'fromPoll', 5 | 6 | _init({fn}) { 7 | this._fn = fn 8 | }, 9 | 10 | _free() { 11 | this._fn = null 12 | }, 13 | 14 | _onTick() { 15 | const fn = this._fn 16 | this._emitValue(fn()) 17 | }, 18 | }) 19 | 20 | export default function fromPoll(wait, fn) { 21 | return new S(wait, {fn}) 22 | } 23 | -------------------------------------------------------------------------------- /src/time-based/interval.js: -------------------------------------------------------------------------------- 1 | import timeBased from '../patterns/time-based' 2 | 3 | const S = timeBased({ 4 | _name: 'interval', 5 | 6 | _init({x}) { 7 | this._x = x 8 | }, 9 | 10 | _free() { 11 | this._x = null 12 | }, 13 | 14 | _onTick() { 15 | this._emitValue(this._x) 16 | }, 17 | }) 18 | 19 | export default function interval(wait, x) { 20 | return new S(wait, {x}) 21 | } 22 | -------------------------------------------------------------------------------- /src/time-based/later.js: -------------------------------------------------------------------------------- 1 | import timeBased from '../patterns/time-based' 2 | 3 | const S = timeBased({ 4 | _name: 'later', 5 | 6 | _init({x}) { 7 | this._x = x 8 | }, 9 | 10 | _free() { 11 | this._x = null 12 | }, 13 | 14 | _onTick() { 15 | this._emitValue(this._x) 16 | this._emitEnd() 17 | }, 18 | }) 19 | 20 | export default function later(wait, x) { 21 | return new S(wait, {x}) 22 | } 23 | -------------------------------------------------------------------------------- /src/time-based/sequentially.js: -------------------------------------------------------------------------------- 1 | import timeBased from '../patterns/time-based' 2 | import {cloneArray} from '../utils/collections' 3 | import never from '../primary/never' 4 | 5 | const S = timeBased({ 6 | _name: 'sequentially', 7 | 8 | _init({xs}) { 9 | this._xs = cloneArray(xs) 10 | }, 11 | 12 | _free() { 13 | this._xs = null 14 | }, 15 | 16 | _onTick() { 17 | if (this._xs.length === 1) { 18 | this._emitValue(this._xs[0]) 19 | this._emitEnd() 20 | } else { 21 | this._emitValue(this._xs.shift()) 22 | } 23 | }, 24 | }) 25 | 26 | export default function sequentially(wait, xs) { 27 | return xs.length === 0 ? never() : new S(wait, {xs}) 28 | } 29 | -------------------------------------------------------------------------------- /src/time-based/with-interval.js: -------------------------------------------------------------------------------- 1 | import timeBased from '../patterns/time-based' 2 | import emitter from '../emitter' 3 | 4 | const S = timeBased({ 5 | _name: 'withInterval', 6 | 7 | _init({fn}) { 8 | this._fn = fn 9 | this._emitter = emitter(this) 10 | }, 11 | 12 | _free() { 13 | this._fn = null 14 | this._emitter = null 15 | }, 16 | 17 | _onTick() { 18 | const fn = this._fn 19 | fn(this._emitter) 20 | }, 21 | }) 22 | 23 | export default function withInterval(wait, fn) { 24 | return new S(wait, {fn}) 25 | } 26 | -------------------------------------------------------------------------------- /src/two-sources/awaiting.js: -------------------------------------------------------------------------------- 1 | import merge from '../many-sources/merge' 2 | import map from '../one-source/map' 3 | import skipDuplicates from '../one-source/skip-duplicates' 4 | import toProperty from '../one-source/to-property' 5 | 6 | const f = () => false 7 | const t = () => true 8 | 9 | export default function awaiting(a, b) { 10 | let result = merge([map(a, t), map(b, f)]) 11 | result = skipDuplicates(result) 12 | result = toProperty(result, f) 13 | return result.setName(a, 'awaiting') 14 | } 15 | -------------------------------------------------------------------------------- /src/two-sources/buffer-by.js: -------------------------------------------------------------------------------- 1 | import {createStream, createProperty} from '../patterns/two-sources' 2 | 3 | const mixin = { 4 | _init({flushOnEnd = true} = {}) { 5 | this._buff = [] 6 | this._flushOnEnd = flushOnEnd 7 | }, 8 | 9 | _free() { 10 | this._buff = null 11 | }, 12 | 13 | _flush() { 14 | if (this._buff !== null) { 15 | this._emitValue(this._buff) 16 | this._buff = [] 17 | } 18 | }, 19 | 20 | _handlePrimaryEnd() { 21 | if (this._flushOnEnd) { 22 | this._flush() 23 | } 24 | this._emitEnd() 25 | }, 26 | 27 | _onActivation() { 28 | this._primary.onAny(this._$handlePrimaryAny) 29 | if (this._alive && this._secondary !== null) { 30 | this._secondary.onAny(this._$handleSecondaryAny) 31 | } 32 | }, 33 | 34 | _handlePrimaryValue(x) { 35 | this._buff.push(x) 36 | }, 37 | 38 | _handleSecondaryValue() { 39 | this._flush() 40 | }, 41 | 42 | _handleSecondaryEnd() { 43 | if (!this._flushOnEnd) { 44 | this._emitEnd() 45 | } 46 | }, 47 | } 48 | 49 | const S = createStream('bufferBy', mixin) 50 | const P = createProperty('bufferBy', mixin) 51 | 52 | export default function bufferBy(primary, secondary, options /* optional */) { 53 | return new (primary._ofSameType(S, P))(primary, secondary, options) 54 | } 55 | -------------------------------------------------------------------------------- /src/two-sources/buffer-while-by.js: -------------------------------------------------------------------------------- 1 | import {createStream, createProperty} from '../patterns/two-sources' 2 | import {NOTHING} from '../constants' 3 | 4 | const mixin = { 5 | _init({flushOnEnd = true, flushOnChange = false} = {}) { 6 | this._buff = [] 7 | this._flushOnEnd = flushOnEnd 8 | this._flushOnChange = flushOnChange 9 | }, 10 | 11 | _free() { 12 | this._buff = null 13 | }, 14 | 15 | _flush() { 16 | if (this._buff !== null) { 17 | this._emitValue(this._buff) 18 | this._buff = [] 19 | } 20 | }, 21 | 22 | _handlePrimaryEnd() { 23 | if (this._flushOnEnd) { 24 | this._flush() 25 | } 26 | this._emitEnd() 27 | }, 28 | 29 | _handlePrimaryValue(x) { 30 | this._buff.push(x) 31 | if (this._lastSecondary !== NOTHING && !this._lastSecondary) { 32 | this._flush() 33 | } 34 | }, 35 | 36 | _handleSecondaryEnd() { 37 | if (!this._flushOnEnd && (this._lastSecondary === NOTHING || this._lastSecondary)) { 38 | this._emitEnd() 39 | } 40 | }, 41 | 42 | _handleSecondaryValue(x) { 43 | if (this._flushOnChange && !x) { 44 | this._flush() 45 | } 46 | 47 | // from default _handleSecondaryValue 48 | this._lastSecondary = x 49 | }, 50 | } 51 | 52 | const S = createStream('bufferWhileBy', mixin) 53 | const P = createProperty('bufferWhileBy', mixin) 54 | 55 | export default function bufferWhileBy(primary, secondary, options /* optional */) { 56 | return new (primary._ofSameType(S, P))(primary, secondary, options) 57 | } 58 | -------------------------------------------------------------------------------- /src/two-sources/filter-by.js: -------------------------------------------------------------------------------- 1 | import {createStream, createProperty} from '../patterns/two-sources' 2 | import {NOTHING} from '../constants' 3 | 4 | const mixin = { 5 | _handlePrimaryValue(x) { 6 | if (this._lastSecondary !== NOTHING && this._lastSecondary) { 7 | this._emitValue(x) 8 | } 9 | }, 10 | 11 | _handleSecondaryEnd() { 12 | if (this._lastSecondary === NOTHING || !this._lastSecondary) { 13 | this._emitEnd() 14 | } 15 | }, 16 | } 17 | 18 | const S = createStream('filterBy', mixin) 19 | const P = createProperty('filterBy', mixin) 20 | 21 | export default function filterBy(primary, secondary) { 22 | return new (primary._ofSameType(S, P))(primary, secondary) 23 | } 24 | -------------------------------------------------------------------------------- /src/two-sources/sampled-by.js: -------------------------------------------------------------------------------- 1 | import combine from '../many-sources/combine' 2 | 3 | const id2 = (_, x) => x 4 | 5 | export default function sampledBy(passive, active, combinator) { 6 | let _combinator = combinator ? (a, b) => combinator(b, a) : id2 7 | return combine([active], [passive], _combinator).setName(passive, 'sampledBy') 8 | } 9 | -------------------------------------------------------------------------------- /src/two-sources/skip-until-by.js: -------------------------------------------------------------------------------- 1 | import {createStream, createProperty} from '../patterns/two-sources' 2 | import {NOTHING} from '../constants' 3 | 4 | const mixin = { 5 | _handlePrimaryValue(x) { 6 | if (this._lastSecondary !== NOTHING) { 7 | this._emitValue(x) 8 | } 9 | }, 10 | 11 | _handleSecondaryEnd() { 12 | if (this._lastSecondary === NOTHING) { 13 | this._emitEnd() 14 | } 15 | }, 16 | } 17 | 18 | const S = createStream('skipUntilBy', mixin) 19 | const P = createProperty('skipUntilBy', mixin) 20 | 21 | export default function skipUntilBy(primary, secondary) { 22 | return new (primary._ofSameType(S, P))(primary, secondary) 23 | } 24 | -------------------------------------------------------------------------------- /src/two-sources/take-until-by.js: -------------------------------------------------------------------------------- 1 | import {createStream, createProperty} from '../patterns/two-sources' 2 | 3 | const mixin = { 4 | _handleSecondaryValue() { 5 | this._emitEnd() 6 | }, 7 | } 8 | 9 | const S = createStream('takeUntilBy', mixin) 10 | const P = createProperty('takeUntilBy', mixin) 11 | 12 | export default function takeUntilBy(primary, secondary) { 13 | return new (primary._ofSameType(S, P))(primary, secondary) 14 | } 15 | -------------------------------------------------------------------------------- /src/utils/collections.js: -------------------------------------------------------------------------------- 1 | function concat(a, b) { 2 | let result, length, i, j 3 | if (a.length === 0) { 4 | return b 5 | } 6 | if (b.length === 0) { 7 | return a 8 | } 9 | j = 0 10 | result = new Array(a.length + b.length) 11 | length = a.length 12 | for (i = 0; i < length; i++, j++) { 13 | result[j] = a[i] 14 | } 15 | length = b.length 16 | for (i = 0; i < length; i++, j++) { 17 | result[j] = b[i] 18 | } 19 | return result 20 | } 21 | 22 | function circleShift(arr, distance) { 23 | let length = arr.length, 24 | result = new Array(length), 25 | i 26 | for (i = 0; i < length; i++) { 27 | result[(i + distance) % length] = arr[i] 28 | } 29 | return result 30 | } 31 | 32 | function find(arr, value) { 33 | let length = arr.length, 34 | i 35 | for (i = 0; i < length; i++) { 36 | if (arr[i] === value) { 37 | return i 38 | } 39 | } 40 | return -1 41 | } 42 | 43 | function findByPred(arr, pred) { 44 | let length = arr.length, 45 | i 46 | for (i = 0; i < length; i++) { 47 | if (pred(arr[i])) { 48 | return i 49 | } 50 | } 51 | return -1 52 | } 53 | 54 | function cloneArray(input) { 55 | let length = input.length, 56 | result = new Array(length), 57 | i 58 | for (i = 0; i < length; i++) { 59 | result[i] = input[i] 60 | } 61 | return result 62 | } 63 | 64 | function remove(input, index) { 65 | let length = input.length, 66 | result, 67 | i, 68 | j 69 | if (index >= 0 && index < length) { 70 | if (length === 1) { 71 | return [] 72 | } else { 73 | result = new Array(length - 1) 74 | for (i = 0, j = 0; i < length; i++) { 75 | if (i !== index) { 76 | result[j] = input[i] 77 | j++ 78 | } 79 | } 80 | return result 81 | } 82 | } else { 83 | return input 84 | } 85 | } 86 | 87 | function removeByPred(input, pred) { 88 | return remove(input, findByPred(input, pred)) 89 | } 90 | 91 | function map(input, fn) { 92 | let length = input.length, 93 | result = new Array(length), 94 | i 95 | for (i = 0; i < length; i++) { 96 | result[i] = fn(input[i]) 97 | } 98 | return result 99 | } 100 | 101 | function forEach(arr, fn) { 102 | let length = arr.length, 103 | i 104 | for (i = 0; i < length; i++) { 105 | fn(arr[i]) 106 | } 107 | } 108 | 109 | function fillArray(arr, value) { 110 | let length = arr.length, 111 | i 112 | for (i = 0; i < length; i++) { 113 | arr[i] = value 114 | } 115 | } 116 | 117 | function contains(arr, value) { 118 | return find(arr, value) !== -1 119 | } 120 | 121 | function slide(cur, next, max) { 122 | let length = Math.min(max, cur.length + 1), 123 | offset = cur.length - length + 1, 124 | result = new Array(length), 125 | i 126 | for (i = offset; i < length; i++) { 127 | result[i - offset] = cur[i] 128 | } 129 | result[length - 1] = next 130 | return result 131 | } 132 | 133 | export { 134 | concat, 135 | circleShift, 136 | find, 137 | findByPred, 138 | cloneArray, 139 | remove, 140 | removeByPred, 141 | map, 142 | forEach, 143 | fillArray, 144 | contains, 145 | slide, 146 | } 147 | -------------------------------------------------------------------------------- /src/utils/functions.js: -------------------------------------------------------------------------------- 1 | function spread(fn, length) { 2 | switch (length) { 3 | case 0: 4 | return function() { 5 | return fn() 6 | } 7 | case 1: 8 | return function(a) { 9 | return fn(a[0]) 10 | } 11 | case 2: 12 | return function(a) { 13 | return fn(a[0], a[1]) 14 | } 15 | case 3: 16 | return function(a) { 17 | return fn(a[0], a[1], a[2]) 18 | } 19 | case 4: 20 | return function(a) { 21 | return fn(a[0], a[1], a[2], a[3]) 22 | } 23 | default: 24 | return function(a) { 25 | return fn.apply(null, a) 26 | } 27 | } 28 | } 29 | 30 | function apply(fn, c, a) { 31 | let aLength = a ? a.length : 0 32 | if (c == null) { 33 | switch (aLength) { 34 | case 0: 35 | return fn() 36 | case 1: 37 | return fn(a[0]) 38 | case 2: 39 | return fn(a[0], a[1]) 40 | case 3: 41 | return fn(a[0], a[1], a[2]) 42 | case 4: 43 | return fn(a[0], a[1], a[2], a[3]) 44 | default: 45 | return fn.apply(null, a) 46 | } 47 | } else { 48 | switch (aLength) { 49 | case 0: 50 | return fn.call(c) 51 | default: 52 | return fn.apply(c, a) 53 | } 54 | } 55 | } 56 | 57 | export {spread, apply} 58 | -------------------------------------------------------------------------------- /src/utils/now.js: -------------------------------------------------------------------------------- 1 | export default Date.now ? () => Date.now() : () => new Date().getTime() 2 | -------------------------------------------------------------------------------- /src/utils/objects.js: -------------------------------------------------------------------------------- 1 | function createObj(proto) { 2 | let F = function() {} 3 | F.prototype = proto 4 | return new F() 5 | } 6 | 7 | function extend(target /*, mixin1, mixin2...*/) { 8 | let length = arguments.length, 9 | i, 10 | prop 11 | for (i = 1; i < length; i++) { 12 | for (prop in arguments[i]) { 13 | target[prop] = arguments[i][prop] 14 | } 15 | } 16 | return target 17 | } 18 | 19 | function inherit(Child, Parent /*, mixin1, mixin2...*/) { 20 | let length = arguments.length, 21 | i 22 | Child.prototype = createObj(Parent.prototype) 23 | Child.prototype.constructor = Child 24 | for (i = 2; i < length; i++) { 25 | extend(Child.prototype, arguments[i]) 26 | } 27 | return Child 28 | } 29 | 30 | export {extend, inherit} 31 | -------------------------------------------------------------------------------- /test/flow/diff.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import Kefir from '../../kefir' 4 | 5 | const source = Kefir.sequentially(10, [1, 2, 3, 4]) 6 | 7 | function test_diff_noArgs() { 8 | const xs = source.diff() 9 | 10 | xs.onValue((x: [number, number]) => {}) 11 | 12 | // $ExpectError 13 | xs.onValue((x: number) => {}) 14 | } 15 | 16 | function test_diff_fn() { 17 | const xs = source.diff((x, y) => x - y) 18 | 19 | xs.onValue((x: number) => {}) 20 | 21 | // $ExpectError 22 | xs.onValue((x: [number, number]) => {}) 23 | } 24 | 25 | function test_diff_fnAndSeed() { 26 | const xs = source.diff((x, y) => x - y, 0) 27 | 28 | // $ExpectError 29 | source.diff((x, y) => x - y, 'zero') 30 | 31 | xs.onValue((x: number) => {}) 32 | 33 | // $ExpectError 34 | xs.onValue((x: [number, number]) => {}) 35 | } 36 | 37 | function test_diff_seed() { 38 | const xs = source.diff(null, 'zero') 39 | 40 | xs.onValue((x: [number | string, number]) => {}) 41 | 42 | // $ExpectError 43 | xs.onValue((x: [number, number]) => {}) 44 | 45 | // $ExpectError 46 | xs.onValue((x: number) => {}) 47 | } 48 | -------------------------------------------------------------------------------- /test/flow/error.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import Kefir from '../../kefir' 4 | 5 | // error type should be persisted through and unaffected by map call. 6 | const prop = Kefir.constantError(1) 7 | .merge(Kefir.constant(2)) 8 | .map(x => String(x)) 9 | .mapErrors(err => ({a: err})) 10 | 11 | prop 12 | .onValue(x => { 13 | const good: string = x 14 | // $ExpectError 15 | const bad: number = x 16 | }) 17 | .onError(x => { 18 | const good: {a: number} = x 19 | // $ExpectError 20 | const bad: string = x 21 | }) 22 | -------------------------------------------------------------------------------- /test/flow/explicitType.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import Kefir from '../../kefir' 4 | import type {Observable} from '../../kefir' 5 | 6 | const p1: Observable<{a: number, b: string}> = Kefir.constant({a: 1, b: 'b'}) 7 | // Test that covariant casts are allowed. 8 | const p1c: Observable<{a: number}> = p1 9 | // $ExpectError 10 | const f1c: Observable = p1 11 | 12 | p1.onValue(v => { 13 | const good: {a: number, b: string} = v 14 | // $ExpectError 15 | const bad: number = v 16 | }) 17 | 18 | // Check that "Kefir.Observable" refers to the type too. 19 | const p2: Kefir.Observable<{a: number, b: string}> = Kefir.constant({a: 2, b: 'b'}) 20 | // $ExpectError 21 | const f2c: Observable = p1 22 | 23 | p2.onValue(v => { 24 | const good: {a: number, b: string} = v 25 | // $ExpectError 26 | const bad: number = v 27 | }) 28 | 29 | // Check that the error type parameter works too. 30 | 31 | const p3: Observable = Kefir.constantError('foo') 32 | p3.observe( 33 | v => { 34 | const good: number = v 35 | // $ExpectError 36 | const bad: string = v 37 | }, 38 | err => { 39 | const good: string = err 40 | // $ExpectError 41 | const bad: number = err 42 | } 43 | ) 44 | 45 | // $ExpectError 46 | const bad: Observable = Kefir.constantError('foo') 47 | -------------------------------------------------------------------------------- /test/flow/filter.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import Kefir from '../../kefir' 4 | 5 | Kefir.sequentially(10, [5, null]) 6 | .onValue(x => { 7 | const good: ?number = x 8 | // $ExpectError 9 | const bad1: number = x 10 | // $ExpectError 11 | const bad2: null = x 12 | }) 13 | .filter() 14 | .onValue(x => { 15 | const good: number = x 16 | // $ExpectError 17 | const bad: null = x 18 | }) 19 | 20 | Kefir.sequentially(10, [5, null]) 21 | .onValue(x => { 22 | const good: ?number = x 23 | // $ExpectError 24 | const bad1: number = x 25 | // $ExpectError 26 | const bad2: null = x 27 | }) 28 | .filter(Boolean) 29 | .onValue(x => { 30 | const good: number = x 31 | // $ExpectError 32 | const bad: null = x 33 | }) 34 | 35 | Kefir.sequentially(10, [5, null]) 36 | .onValue(x => { 37 | const good: ?number = x 38 | // $ExpectError 39 | const bad1: number = x 40 | // $ExpectError 41 | const bad2: null = x 42 | }) 43 | .filter(x => true) 44 | .onValue(x => { 45 | const good: ?number = x 46 | // $ExpectError 47 | const bad: number = x 48 | }) 49 | -------------------------------------------------------------------------------- /test/flow/flatten.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import Kefir from '../../kefir' 4 | 5 | function test_flatten() { 6 | const source = Kefir.sequentially(0, [[1], [2, 2], [3, 3, 3]]) 7 | const xs = source.flatten() 8 | 9 | xs.onValue((x: number) => {}) 10 | } 11 | 12 | function test_flatten_withTransformer() { 13 | const source = Kefir.sequentially(0, [1, 2, 3, 4]) 14 | const xs = source.flatten(x => ['foo', 'bar']) 15 | 16 | xs.onValue((x: string) => {}) 17 | 18 | // $ExpectError 19 | xs.onValue((x: number) => {}) 20 | } 21 | -------------------------------------------------------------------------------- /test/flow/interop.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import Kefir from '../../kefir' 4 | 5 | import EventEmitter from 'events' 6 | 7 | class MyEmitter extends EventEmitter {} 8 | 9 | function test_fromEvents() { 10 | const ee = new MyEmitter() 11 | const xs = Kefir.fromEvents(ee, 'somethingHappened', (x: string, y: number) => 'foo') 12 | 13 | xs.onValue((x: string) => {}) 14 | 15 | // $ExpectError 16 | xs.onValue((x: number) => {}) 17 | } 18 | 19 | function test_toPromise() { 20 | const xs = Kefir.constant('foo') 21 | const p = xs.toPromise() 22 | 23 | p.then((x: string) => {}) 24 | 25 | // $ExpectError 26 | p.then((x: number) => {}) 27 | } 28 | 29 | function test_fromPromise() { 30 | const p = Promise.resolve('foo') 31 | const xs = Kefir.fromPromise(p) 32 | 33 | xs.onValue((x: string) => {}) 34 | 35 | // $ExpectError 36 | xs.onValue((x: number) => {}) 37 | } 38 | 39 | function test_fromESObservable() { 40 | const myObservable = { 41 | subscribe({next, error, complete}) { 42 | next && next('foo') 43 | error && error(new Error()) 44 | complete && complete() 45 | return { 46 | unsubscribe() {}, 47 | } 48 | }, 49 | } 50 | 51 | const xs = Kefir.fromESObservable(myObservable) 52 | 53 | xs.onValue((x: string) => {}) 54 | 55 | // $ExpectError 56 | xs.onValue((x: number) => {}) 57 | 58 | xs.onError((e: Error) => {}) 59 | 60 | // $ExpectError 61 | xs.onError((e: number) => {}) 62 | } 63 | 64 | function test_toESObservable() { 65 | const xs: Kefir.Observable = Kefir.constant('foo') 66 | const obs = xs.toESObservable() 67 | 68 | obs.subscribe({ 69 | next: (x: string) => {}, 70 | error: (e: Error) => {}, 71 | complete: () => {}, 72 | }) 73 | 74 | obs.subscribe({ 75 | // $ExpectError 76 | next: (x: number) => {}, 77 | // $ExpectError 78 | error: (e: number) => {}, 79 | complete: () => {}, 80 | }) 81 | } 82 | -------------------------------------------------------------------------------- /test/flow/merge.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import Kefir from '../../kefir' 4 | 5 | const s1: Kefir.Observable = Kefir.constant(1) 6 | const s2: Kefir.Observable = Kefir.constant('two') 7 | const s3 = Kefir.constantError({foo: 1}) 8 | 9 | const merged = Kefir.merge([s1, s2, s3]) 10 | 11 | merged.observe( 12 | x => { 13 | const good: number | string = x 14 | // $ExpectError 15 | const bad1: number = x 16 | // $ExpectError 17 | const bad2: string = x 18 | }, 19 | err => { 20 | const good: {foo: number} = err 21 | // $ExpectError 22 | const bad: number = err 23 | } 24 | ) 25 | -------------------------------------------------------------------------------- /test/flow/observe.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import Kefir from '../../kefir' 4 | 5 | const s1: Kefir.Observable = Kefir.constant(1) 6 | 7 | s1.observe({ 8 | value(x) { 9 | const good: number = x 10 | // $ExpectError 11 | const bad: string = x 12 | }, 13 | error: e => {}, 14 | }) 15 | 16 | class MyObserver { 17 | value(x) { 18 | const good: number = x 19 | // $ExpectError 20 | const bad: string = x 21 | } 22 | error(e) {} 23 | } 24 | 25 | s1.observe(new MyObserver()) 26 | 27 | s1.observe( 28 | value => {}, 29 | error => {}, 30 | () => {} 31 | ) 32 | 33 | s1.observe(value => {}) 34 | s1.observe(null, error => {}) 35 | -------------------------------------------------------------------------------- /test/flow/pool.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import Kefir from '../../kefir' 4 | 5 | const pool: Kefir.Pool = Kefir.pool() 6 | pool.plug(Kefir.constant(1)) 7 | // $ExpectError 8 | pool.plug(Kefir.constant('a')) 9 | 10 | pool.onValue(x => { 11 | const n: number = x 12 | // $ExpectError 13 | const s: string = x 14 | }) 15 | -------------------------------------------------------------------------------- /test/flow/propType.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import Kefir from '../../kefir' 4 | 5 | const prop = Kefir.constant(1) 6 | 7 | prop.onValue(x => { 8 | const n: number = x 9 | // $ExpectError 10 | const s: string = x 11 | }) 12 | -------------------------------------------------------------------------------- /test/flow/sampledBy.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import Kefir from '../../kefir' 4 | 5 | const source = Kefir.sequentially(0, [1, 2, 3, 4]) 6 | const samples = Kefir.sequentially(0, ['one', 'one', 'one', 'one']) 7 | 8 | function test_sampledBy_noCombinator() { 9 | const xs = source.sampledBy(samples) 10 | 11 | xs.onValue((x: number) => {}) 12 | } 13 | 14 | function test_sampledBy_withCombinator() { 15 | const xs = source.sampledBy(samples, (n: number, s: string) => s) 16 | 17 | // $ExpectError 18 | source.sampledBy(samples, (s: string, n: number) => s) 19 | 20 | xs.onValue((x: string) => {}) 21 | 22 | // $ExpectError 23 | xs.onValue((x: number) => {}) 24 | } 25 | -------------------------------------------------------------------------------- /test/flow/scan.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import Kefir from '../../kefir' 4 | 5 | const source = Kefir.sequentially(10, [1, 2, 3, 4]) 6 | 7 | function test_scan_noSeed() { 8 | const xs = source.scan((x, y) => x - y) 9 | 10 | xs.onValue((x: number) => {}) 11 | } 12 | 13 | function test_scan_withSeed() { 14 | // $ExpectError 15 | source.scan((x, y) => x - y, 'foo') 16 | 17 | const xs = source.scan((x, y) => x + String(y), 'foo') 18 | 19 | xs.onValue((x: string) => {}) 20 | 21 | // $ExpectError 22 | xs.onValue((x: number) => {}) 23 | } 24 | -------------------------------------------------------------------------------- /test/flow/setName.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import Kefir from '../../kefir' 4 | import type {Observable} from '../../kefir' 5 | 6 | const s1: Observable = Kefir.constant(1) 7 | 8 | s1.setName('s1') 9 | // $ExpectError 10 | s1.setName() 11 | 12 | const s2: Observable = Kefir.constant('one') 13 | s1.setName(s2, 's1') 14 | // $ExpectError 15 | s1.setName(s2) 16 | -------------------------------------------------------------------------------- /test/flow/thru.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import Kefir from '../../kefir' 4 | import type {Observable} from '../../kefir' 5 | 6 | const obs: Observable = Kefir.constant('hello') 7 | 8 | const good: boolean = obs.thru((x: Observable): boolean => true) 9 | 10 | // $ExpectError 11 | const bad1: boolean = obs.thru((x: Observable): boolean => true) 12 | 13 | // $ExpectError 14 | const bad2: string = obs.thru((x: Observable): boolean => true) 15 | -------------------------------------------------------------------------------- /test/flow/toProperty.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import Kefir from '../../kefir' 4 | 5 | function test_toProperty() { 6 | const xs = Kefir.sequentially(0, [1, 2, 3, 4]) 7 | const p = xs.toProperty() 8 | 9 | p.onValue((x: number) => {}) 10 | 11 | // $ExpectError 12 | p.onValue((x: string) => {}) 13 | } 14 | 15 | function test_toProperty_getCurrent() { 16 | const xs = Kefir.sequentially(0, [1, 2, 3, 4]) 17 | const p = xs.toProperty(() => 'foo') 18 | 19 | p.onValue((x: number | string) => {}) 20 | 21 | // $ExpectError 22 | p.onValue((x: number) => {}) 23 | } 24 | -------------------------------------------------------------------------------- /test/flow/transforms.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import Kefir from '../../kefir' 4 | 5 | const prop = Kefir.constant(1) 6 | .map(x => { 7 | const n: number = x 8 | // $ExpectError 9 | const s: string = x 10 | return String(x) 11 | }) 12 | .flatMap(x => { 13 | const s: string = x 14 | // $ExpectError 15 | const n: number = x 16 | return Kefir.later(10, parseInt(x)) 17 | }) 18 | .filter(x => { 19 | const n: number = x 20 | // $ExpectError 21 | const s: string = x 22 | return true 23 | }) 24 | .flatMap(x => { 25 | if (Math.random() == 0) { 26 | return Kefir.never() 27 | } else { 28 | return Kefir.constant(String(x)) 29 | } 30 | }) 31 | 32 | prop.onValue(x => { 33 | const s: string = x 34 | // $ExpectError 35 | const n: number = x 36 | }) 37 | -------------------------------------------------------------------------------- /test/flow/vacuousObservables.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Some observables are guaranteed not to emit values or errors. For example, 3 | * `Kefir.constant(x)` produces an observable that never emits errors. In those 4 | * cases Kefir's type definitions use `empty` as a type parameter. 5 | * 6 | * @flow 7 | */ 8 | 9 | import Kefir from '../../kefir' 10 | 11 | function test_Constant() { 12 | const c = Kefir.constant('foo') 13 | 14 | c.onValue((x: string) => {}) 15 | 16 | // $ExpectError 17 | c.onValue((x: number) => {}) 18 | 19 | // Error callbacks accept any argument type 20 | c.onError((e: Error) => {}) 21 | } 22 | 23 | function testConstantError() { 24 | const c = Kefir.constantError(new Error('foo')) 25 | 26 | // Value callbacks accept any argument type 27 | c.onValue((x: string) => {}) 28 | c.onValue((x: number) => {}) 29 | 30 | c.onError((e: Error) => {}) 31 | 32 | // $ExpectError 33 | c.onError((e: string) => {}) 34 | } 35 | 36 | function testNever() { 37 | const never = Kefir.never() 38 | 39 | // Value and error callbacks accept any argument types 40 | never.onValue((x: string) => {}) 41 | never.onValue((x: number) => {}) 42 | never.onError((e: Error) => {}) 43 | 44 | const merged = never.merge(Kefir.constant('foo')) 45 | 46 | merged.onValue((x: string) => {}) 47 | 48 | // $ExpectError - the merged observable has `string` values, value callbacks must accept `string` arguments 49 | merged.onValue((x: number) => {}) 50 | 51 | merged.onError((e: Error) => {}) 52 | } 53 | -------------------------------------------------------------------------------- /test/flow/zip.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import Kefir from '../../kefir' 4 | 5 | const as = Kefir.sequentially(0, [1, 2, 3, 4]) 6 | const bs = Kefir.sequentially(0, ['foo', 'bar', 'baz']) 7 | const cs = Kefir.sequentially(0, [true, false, true]) 8 | 9 | function test_zip_method_noCombinator() { 10 | const xs = as.zip(bs) 11 | 12 | xs.onValue((x: [number, string]) => {}) 13 | 14 | // $ExpectError 15 | xs.onValue((x: Array<*>) => {}) 16 | } 17 | 18 | function test_zip_method_withCombinator() { 19 | const xs = as.zip(bs, (n, s) => `${n}: ${s}`) 20 | 21 | xs.onValue((x: string) => {}) 22 | 23 | // $ExpectError 24 | xs.onValue((x: [number, string]) => {}) 25 | } 26 | 27 | function test_zip_noCombinator() { 28 | const xs = Kefir.zip([as, bs, cs]) 29 | 30 | xs.onValue((x: [number, string, boolean]) => {}) 31 | 32 | // $ExpectError 33 | xs.onValue((x: [boolean, string, number]) => {}) 34 | 35 | xs.onValue((x: Array) => {}) 36 | 37 | // $ExpectError 38 | xs.onValue((x: Array) => {}) 39 | } 40 | 41 | function test_zip_withCombinator() { 42 | const xs = Kefir.zip([as, bs], (n, s) => `${n}: ${s}`) 43 | 44 | xs.onValue((x: string) => {}) 45 | } 46 | -------------------------------------------------------------------------------- /test/specs/before-end.js: -------------------------------------------------------------------------------- 1 | const {stream, prop, send, value, end, expect} = require('../test-helpers') 2 | 3 | describe('beforeEnd', () => { 4 | describe('stream', () => { 5 | it('should return stream', () => { 6 | expect(stream().beforeEnd(() => {})).to.be.observable.stream() 7 | }) 8 | 9 | it('should activate/deactivate source', () => { 10 | const a = stream() 11 | expect(a.beforeEnd(() => {})).to.activate(a) 12 | }) 13 | 14 | it('should be ended if source was ended', () => { 15 | expect(send(stream(), [end()]).beforeEnd(() => 42)).to.emit([value(42, {current: true}), end({current: true})]) 16 | }) 17 | 18 | it('should handle events', () => { 19 | const a = stream() 20 | expect(a.beforeEnd(() => 42)).to.emit([value(1), value(2), value(42), end()], () => 21 | send(a, [value(1), value(2), end()]) 22 | ) 23 | }) 24 | 25 | it('errors should flow', () => { 26 | const a = stream() 27 | expect(a.beforeEnd(() => {})).to.flowErrors(a) 28 | }) 29 | }) 30 | 31 | describe('property', () => { 32 | it('should return property', () => { 33 | expect(prop().beforeEnd(() => {})).to.be.observable.property() 34 | }) 35 | 36 | it('should activate/deactivate source', () => { 37 | const a = prop() 38 | expect(a.beforeEnd(() => {})).to.activate(a) 39 | }) 40 | 41 | it('should be ended if source was ended', () => { 42 | expect(send(prop(), [end()]).beforeEnd(() => 42)).to.emit([value(42, {current: true}), end({current: true})]) 43 | expect(send(prop(), [value(1), end()]).beforeEnd(() => 42)).to.emit([ 44 | value(42, {current: true}), 45 | end({current: true}), 46 | ]) 47 | }) 48 | 49 | it('should handle events and current', () => { 50 | const a = send(prop(), [value(1)]) 51 | expect(a.beforeEnd(() => 42)).to.emit([value(1, {current: true}), value(2), value(3), value(42), end()], () => 52 | send(a, [value(2), value(3), end()]) 53 | ) 54 | }) 55 | 56 | it('errors should flow', () => { 57 | const a = prop() 58 | expect(a.beforeEnd(() => {})).to.flowErrors(a) 59 | }) 60 | }) 61 | }) 62 | -------------------------------------------------------------------------------- /test/specs/buffer-while.js: -------------------------------------------------------------------------------- 1 | const {stream, prop, send, value, end, expect} = require('../test-helpers') 2 | 3 | const not3 = x => x !== 3 4 | 5 | describe('bufferWhile', () => { 6 | describe('stream', () => { 7 | it('should stream', () => { 8 | expect(stream().bufferWhile(not3)).to.be.observable.stream() 9 | }) 10 | 11 | it('should activate/deactivate source', () => { 12 | const a = stream() 13 | expect(a.bufferWhile(not3)).to.activate(a) 14 | }) 15 | 16 | it('should be ended if source was ended', () => 17 | expect(send(stream(), [end()]).bufferWhile(not3)).to.emit([end({current: true})])) 18 | 19 | it('should work correctly', () => { 20 | const a = stream() 21 | expect(a.bufferWhile(not3)).to.emit( 22 | [value([3]), value([1, 2, 3]), value([4, 3]), value([3]), value([5, 6]), end()], 23 | () => send(a, [value(3), value(1), value(2), value(3), value(4), value(3), value(3), value(5), value(6), end()]) 24 | ) 25 | }) 26 | 27 | it('should not flush buffer on end if {flushOnEnd: false}', () => { 28 | const a = stream() 29 | expect(a.bufferWhile(not3, {flushOnEnd: false})).to.emit( 30 | [value([3]), value([1, 2, 3]), value([4, 3]), value([3]), end()], 31 | () => send(a, [value(3), value(1), value(2), value(3), value(4), value(3), value(3), value(5), value(6), end()]) 32 | ) 33 | }) 34 | 35 | it('errors should flow', () => { 36 | const a = stream() 37 | expect(a.bufferWhile(not3)).to.flowErrors(a) 38 | }) 39 | }) 40 | 41 | describe('property', () => { 42 | it('should property', () => { 43 | expect(prop().bufferWhile(not3)).to.be.observable.property() 44 | }) 45 | 46 | it('should activate/deactivate source', () => { 47 | const a = prop() 48 | expect(a.bufferWhile(not3)).to.activate(a) 49 | }) 50 | 51 | it('should be ended if source was ended', () => { 52 | expect(send(prop(), [end()]).bufferWhile(not3)).to.emit([end({current: true})]) 53 | expect(send(prop(), [value(3), end()]).bufferWhile(not3)).to.emit([ 54 | value([3], {current: true}), 55 | end({current: true}), 56 | ]) 57 | expect(send(prop(), [value(2), end()]).bufferWhile(not3)).to.emit([ 58 | value([2], {current: true}), 59 | end({current: true}), 60 | ]) 61 | expect(send(prop(), [value(3), end()]).bufferWhile(not3, {flushOnEnd: false})).to.emit([ 62 | value([3], {current: true}), 63 | end({current: true}), 64 | ]) 65 | expect(send(prop(), [value(2), end()]).bufferWhile(not3, {flushOnEnd: false})).to.emit([end({current: true})]) 66 | }) 67 | 68 | it('should work correctly', () => { 69 | let a = send(prop(), [value(3)]) 70 | expect(a.bufferWhile(not3)).to.emit( 71 | [value([3], {current: true}), value([1, 2, 3]), value([4, 3]), value([3]), value([5, 6]), end()], 72 | () => send(a, [value(1), value(2), value(3), value(4), value(3), value(3), value(5), value(6), end()]) 73 | ) 74 | a = send(prop(), [value(1)]) 75 | expect(a.bufferWhile(not3)).to.emit([value([1, 2, 3]), value([5, 6]), end()], () => 76 | send(a, [value(2), value(3), value(5), value(6), end()]) 77 | ) 78 | }) 79 | 80 | it('errors should flow', () => { 81 | const a = prop() 82 | expect(a.bufferWhile(not3)).to.flowErrors(a) 83 | }) 84 | }) 85 | }) 86 | -------------------------------------------------------------------------------- /test/specs/changes.js: -------------------------------------------------------------------------------- 1 | const {stream, prop, send, value, error, end, Kefir, expect} = require('../test-helpers') 2 | 3 | const streamWithCurrent = event => Kefir.stream(emitter => emitter.emitEvent(event)) 4 | 5 | describe('changes', () => { 6 | describe('stream', () => { 7 | it('should stream', () => { 8 | expect(stream().changes()).to.be.observable.stream() 9 | }) 10 | 11 | it('should activate/deactivate source', () => { 12 | const a = stream() 13 | expect(a.changes()).to.activate(a) 14 | }) 15 | 16 | it('should be ended if source was ended', () => { 17 | expect(send(stream(), [end()]).changes()).to.emit([end({current: true})]) 18 | }) 19 | 20 | it('test `streamWithCurrent` helper', () => { 21 | expect(streamWithCurrent({type: 'value', value: 1})).to.emit([value(1, {current: true})]) 22 | expect(streamWithCurrent({type: 'error', value: 1})).to.emit([error(1, {current: true})]) 23 | }) 24 | 25 | it('should handle events and current', () => { 26 | let a = streamWithCurrent({type: 'value', value: 1}) 27 | expect(a.changes()).to.emit([value(value(2)), error(5), value(value(3)), end()], () => 28 | send(a, [value(value(2)), error(5), value(value(3)), end()]) 29 | ) 30 | a = streamWithCurrent({type: 'error', value: 1}) 31 | expect(a.changes()).to.emit([value(2), error(5), value(3), end()], () => 32 | send(a, [value(2), error(5), value(3), end()]) 33 | ) 34 | }) 35 | }) 36 | 37 | describe('property', () => { 38 | it('should stream', () => { 39 | expect(prop().changes()).to.be.observable.stream() 40 | }) 41 | 42 | it('should activate/deactivate source', () => { 43 | const a = prop() 44 | expect(a.changes()).to.activate(a) 45 | }) 46 | 47 | it('should be ended if source was ended', () => { 48 | expect(send(prop(), [end()]).changes()).to.emit([end({current: true})]) 49 | }) 50 | 51 | it('should handle events and current', () => { 52 | const a = send(prop(), [value(1), error(4)]) 53 | expect(a.changes()).to.emit([value(2), error(5), value(3), end()], () => 54 | send(a, [value(2), error(5), value(3), end()]) 55 | ) 56 | }) 57 | }) 58 | }) 59 | -------------------------------------------------------------------------------- /test/specs/constant-error.js: -------------------------------------------------------------------------------- 1 | const {Kefir, error, end, expect} = require('../test-helpers') 2 | 3 | describe('constantError', () => { 4 | it('should return property', () => { 5 | expect(Kefir.constantError(1)).to.be.observable.property() 6 | }) 7 | 8 | it('should be ended and has a current error', () => { 9 | expect(Kefir.constantError(1)).to.emit([error(1, {current: true}), end({current: true})]) 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /test/specs/constant.js: -------------------------------------------------------------------------------- 1 | const {Kefir, expect, value, end} = require('../test-helpers') 2 | 3 | describe('constant', () => { 4 | it('should return property', () => { 5 | expect(Kefir.constant(1)).to.be.observable.property() 6 | }) 7 | 8 | it('should be ended and has current', () => { 9 | expect(Kefir.constant(1)).to.emit([value(1, {current: true}), end({current: true})]) 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /test/specs/delay.js: -------------------------------------------------------------------------------- 1 | const {stream, prop, send, value, end, shakyTimeTest, expect} = require('../test-helpers') 2 | 3 | describe('delay', () => { 4 | describe('stream', () => { 5 | it('should return stream', () => { 6 | expect(stream().delay(100)).to.be.observable.stream() 7 | }) 8 | 9 | it('should activate/deactivate source', () => { 10 | const a = stream() 11 | expect(a.delay(100)).to.activate(a) 12 | }) 13 | 14 | it('should be ended if source was ended', () => { 15 | expect(send(stream(), [end()]).delay(100)).to.emit([end({current: true})]) 16 | }) 17 | 18 | it('should handle events', () => { 19 | const a = stream() 20 | expect(a.delay(100)).to.emitInTime( 21 | [ 22 | [100, value(1)], 23 | [150, value(2)], 24 | [250, end()], 25 | ], 26 | tick => { 27 | send(a, [value(1)]) 28 | tick(50) 29 | send(a, [value(2)]) 30 | tick(100) 31 | send(a, [end()]) 32 | } 33 | ) 34 | }) 35 | 36 | it('errors should flow', () => { 37 | const a = stream() 38 | expect(a.delay(100)).to.flowErrors(a) 39 | }) 40 | 41 | // see https://github.com/kefirjs/kefir/issues/134 42 | describe('works with undependable setTimeout', () => { 43 | shakyTimeTest(expectToEmitOverShakyTime => { 44 | const a = stream() 45 | expectToEmitOverShakyTime( 46 | a.delay(10), 47 | [ 48 | [10, value(1)], 49 | [15, value(4)], 50 | [15, end()], 51 | ], 52 | tick => { 53 | send(a, [value(1)]) 54 | tick(5) 55 | send(a, [value(4)]) 56 | send(a, [end()]) 57 | } 58 | ) 59 | }) 60 | }) 61 | }) 62 | 63 | describe('property', () => { 64 | it('should return property', () => { 65 | expect(prop().delay(100)).to.be.observable.property() 66 | }) 67 | 68 | it('should activate/deactivate source', () => { 69 | const a = prop() 70 | expect(a.delay(100)).to.activate(a) 71 | }) 72 | 73 | it('should be ended if source was ended', () => { 74 | expect(send(prop(), [end()]).delay(100)).to.emit([end({current: true})]) 75 | }) 76 | 77 | it('should handle events and current', () => { 78 | const a = send(prop(), [value(1)]) 79 | expect(a.delay(100)).to.emitInTime( 80 | [ 81 | [0, value(1, {current: true})], 82 | [100, value(2)], 83 | [150, value(3)], 84 | [250, end()], 85 | ], 86 | tick => { 87 | send(a, [value(2)]) 88 | tick(50) 89 | send(a, [value(3)]) 90 | tick(100) 91 | send(a, [end()]) 92 | } 93 | ) 94 | }) 95 | 96 | it('errors should flow', () => { 97 | const a = prop() 98 | expect(a.delay(100)).to.flowErrors(a) 99 | }) 100 | }) 101 | }) 102 | -------------------------------------------------------------------------------- /test/specs/diff.js: -------------------------------------------------------------------------------- 1 | const {stream, prop, send, value, end, expect} = require('../test-helpers') 2 | 3 | const noop = () => {} 4 | const minus = (prev, next) => prev - next 5 | 6 | describe('diff', () => { 7 | describe('stream', () => { 8 | it('should return stream', () => { 9 | expect(stream().diff(noop, 0)).to.be.observable.stream() 10 | }) 11 | 12 | it('should activate/deactivate source', () => { 13 | const a = stream() 14 | expect(a.diff(noop, 0)).to.activate(a) 15 | }) 16 | 17 | it('should be ended if source was ended', () => { 18 | expect(send(stream(), [end()]).diff(noop, 0)).to.emit([end({current: true})]) 19 | }) 20 | 21 | it('should handle events', () => { 22 | const a = stream() 23 | expect(a.diff(minus, 0)).to.emit([value(-1), value(-2), end()], () => send(a, [value(1), value(3), end()])) 24 | }) 25 | 26 | it('works without fn argument', () => { 27 | const a = stream() 28 | expect(a.diff(null, 0)).to.emit([value([0, 1]), value([1, 3]), end()], () => send(a, [value(1), value(3), end()])) 29 | }) 30 | 31 | it('if no seed provided uses first value as seed', () => { 32 | let a = stream() 33 | expect(a.diff(minus)).to.emit([value(-1), value(-2), end()], () => send(a, [value(0), value(1), value(3), end()])) 34 | a = stream() 35 | expect(a.diff()).to.emit([value([0, 1]), value([1, 3]), end()], () => 36 | send(a, [value(0), value(1), value(3), end()]) 37 | ) 38 | }) 39 | 40 | it('errors should flow', () => { 41 | const a = stream() 42 | expect(a.diff()).to.flowErrors(a) 43 | }) 44 | }) 45 | 46 | describe('property', () => { 47 | it('should return property', () => { 48 | expect(prop().diff(noop, 0)).to.be.observable.property() 49 | }) 50 | 51 | it('should activate/deactivate source', () => { 52 | const a = prop() 53 | expect(a.diff(noop, 0)).to.activate(a) 54 | }) 55 | 56 | it('should be ended if source was ended', () => { 57 | expect(send(prop(), [end()]).diff(noop, 0)).to.emit([end({current: true})]) 58 | }) 59 | 60 | it('should handle events and current', () => { 61 | const a = send(prop(), [value(1)]) 62 | expect(a.diff(minus, 0)).to.emit([value(-1, {current: true}), value(-2), value(-3), end()], () => 63 | send(a, [value(3), value(6), end()]) 64 | ) 65 | }) 66 | 67 | it('works without fn argument', () => { 68 | const a = send(prop(), [value(1)]) 69 | expect(a.diff(null, 0)).to.emit([value([0, 1], {current: true}), value([1, 3]), value([3, 6]), end()], () => 70 | send(a, [value(3), value(6), end()]) 71 | ) 72 | }) 73 | 74 | it('if no seed provided uses first value as seed', () => { 75 | let a = send(prop(), [value(0)]) 76 | expect(a.diff(minus)).to.emit([value(-1), value(-2), end()], () => send(a, [value(1), value(3), end()])) 77 | a = send(prop(), [value(0)]) 78 | expect(a.diff()).to.emit([value([0, 1]), value([1, 3]), end()], () => send(a, [value(1), value(3), end()])) 79 | }) 80 | 81 | it('errors should flow', () => { 82 | const a = prop() 83 | expect(a.diff()).to.flowErrors(a) 84 | }) 85 | }) 86 | }) 87 | -------------------------------------------------------------------------------- /test/specs/end-on-error.js: -------------------------------------------------------------------------------- 1 | const {stream, prop, send, value, error, end, expect} = require('../test-helpers') 2 | 3 | describe('endOnError', () => { 4 | describe('stream', () => { 5 | it('should return stream', () => { 6 | expect(stream().endOnError()).to.be.observable.stream() 7 | }) 8 | 9 | it('should activate/deactivate source', () => { 10 | const a = stream() 11 | expect(a.endOnError()).to.activate(a) 12 | }) 13 | 14 | it('should be ended if source was ended', () => { 15 | expect(send(stream(), [end()]).endOnError()).to.emit([end({current: true})]) 16 | }) 17 | 18 | it('should handle events', () => { 19 | let a = stream() 20 | expect(a.endOnError()).to.emit([value(1), error(5), end()], () => send(a, [value(1), error(5), value(2)])) 21 | a = stream() 22 | expect(a.endOnError()).to.emit([value(1), value(2), end()], () => send(a, [value(1), value(2), end()])) 23 | }) 24 | }) 25 | 26 | describe('property', () => { 27 | it('should return property', () => { 28 | expect(prop().endOnError()).to.be.observable.property() 29 | }) 30 | 31 | it('should activate/deactivate source', () => { 32 | const a = prop() 33 | expect(a.endOnError()).to.activate(a) 34 | }) 35 | 36 | it('should be ended if source was ended', () => { 37 | expect(send(prop(), [end()]).endOnError()).to.emit([end({current: true})]) 38 | }) 39 | 40 | it('should handle events and current', () => { 41 | let a = send(prop(), [value(1)]) 42 | expect(a.endOnError()).to.emit([value(1, {current: true}), error(5), end()], () => send(a, [error(5), value(2)])) 43 | a = send(prop(), [value(1)]) 44 | expect(a.endOnError()).to.emit([value(1, {current: true}), value(2), end()], () => send(a, [value(2), end()])) 45 | }) 46 | 47 | it('should handle currents', () => { 48 | let a = send(prop(), [error(-1, {current: true})]) 49 | expect(a.endOnError()).to.emit([error(-1, {current: true}), end({current: true})]) 50 | a = send(prop(), [error(-1, {current: true}), end()]) 51 | expect(a.endOnError()).to.emit([error(-1, {current: true}), end({current: true})]) 52 | a = send(prop(), [value(1)]) 53 | expect(a.endOnError()).to.emit([value(1, {current: true})]) 54 | a = send(prop(), [value(1), end()]) 55 | expect(a.endOnError()).to.emit([value(1, {current: true}), end({current: true})]) 56 | }) 57 | }) 58 | }) 59 | -------------------------------------------------------------------------------- /test/specs/errors-to-values.js: -------------------------------------------------------------------------------- 1 | const {stream, prop, send, value, error, end, expect} = require('../test-helpers') 2 | 3 | const handler = x => ({ 4 | convert: x >= 0, 5 | value: x * 3, 6 | }) 7 | 8 | describe('errorsToValues', () => { 9 | describe('stream', () => { 10 | it('should return stream', () => { 11 | expect(stream().errorsToValues(() => {})).to.be.observable.stream() 12 | }) 13 | 14 | it('should activate/deactivate source', () => { 15 | const a = stream() 16 | expect(a.errorsToValues(() => {})).to.activate(a) 17 | }) 18 | 19 | it('should be ended if source was ended', () => { 20 | expect(send(stream(), [end()]).errorsToValues(() => {})).to.emit([end({current: true})]) 21 | }) 22 | 23 | it('should handle events', () => { 24 | const a = stream() 25 | expect(a.errorsToValues(handler)).to.emit([value(1), value(6), error(-1), value(9), value(4), end()], () => 26 | send(a, [value(1), error(2), error(-1), error(3), value(4), end()]) 27 | ) 28 | }) 29 | 30 | it('default handler should convert all errors', () => { 31 | const a = stream() 32 | expect(a.errorsToValues()).to.emit([value(1), value(2), value(-1), value(3), value(4), end()], () => 33 | send(a, [value(1), error(2), error(-1), error(3), value(4), end()]) 34 | ) 35 | }) 36 | }) 37 | 38 | describe('property', () => { 39 | it('should return property', () => { 40 | expect(prop().errorsToValues(() => {})).to.be.observable.property() 41 | }) 42 | 43 | it('should activate/deactivate source', () => { 44 | const a = prop() 45 | expect(a.errorsToValues(() => {})).to.activate(a) 46 | }) 47 | 48 | it('should be ended if source was ended', () => { 49 | expect(send(prop(), [end()]).errorsToValues(() => {})).to.emit([end({current: true})]) 50 | }) 51 | 52 | it('should handle events', () => { 53 | const a = send(prop(), [value(1)]) 54 | expect(a.errorsToValues(handler)).to.emit( 55 | [value(1, {current: true}), value(6), error(-1), value(9), value(4), end()], 56 | () => send(a, [error(2), error(-1), error(3), value(4), end()]) 57 | ) 58 | }) 59 | 60 | it('should handle currents', () => { 61 | let a = send(prop(), [error(-2)]) 62 | expect(a.errorsToValues(handler)).to.emit([error(-2, {current: true})]) 63 | a = send(prop(), [error(2)]) 64 | expect(a.errorsToValues(handler)).to.emit([value(6, {current: true})]) 65 | a = send(prop(), [value(1)]) 66 | expect(a.errorsToValues(handler)).to.emit([value(1, {current: true})]) 67 | }) 68 | }) 69 | }) 70 | -------------------------------------------------------------------------------- /test/specs/es-observable.js: -------------------------------------------------------------------------------- 1 | const $$observable = require('symbol-observable').default 2 | const Observable = require('zen-observable') 3 | const {stream, send, value, error, end, expect} = require('../test-helpers') 4 | 5 | describe('[Symbol.observable]', () => { 6 | it('outputs a compatible Observable', done => { 7 | const a = stream() 8 | const values = [] 9 | const observable = Observable.from(a) 10 | observable.subscribe({ 11 | next(x) { 12 | values.push(x) 13 | }, 14 | complete() { 15 | expect(values).to.deep.equal([1, 2, 3]) 16 | done() 17 | }, 18 | }) 19 | send(a, [value(1), value(2), value(3), end()]) 20 | }) 21 | 22 | it('unsubscribes stream after an error', () => { 23 | const a = stream() 24 | const values = [] 25 | const observable = a[$$observable]() 26 | observable.subscribe({ 27 | next(x) { 28 | values.push(x) 29 | }, 30 | }) 31 | send(a, [value(1), error(2), value(3)]) 32 | expect(values).to.deep.equal([1]) 33 | }) 34 | 35 | it('subscribe() returns an subscribtion object with unsubscribe method', () => { 36 | const a = stream() 37 | const values = [] 38 | const observable = a[$$observable]() 39 | const subscribtion = observable.subscribe({ 40 | next(x) { 41 | values.push(x) 42 | }, 43 | }) 44 | send(a, [value(1)]) 45 | subscribtion.unsubscribe() 46 | send(a, [value(2)]) 47 | expect(values).to.deep.equal([1]) 48 | }) 49 | 50 | it('subscribtion object has `closed` property', () => { 51 | const a = stream() 52 | const observable = a[$$observable]() 53 | const subscribtion = observable.subscribe({next() {}}) 54 | expect(subscribtion.closed).to.deep.equal(false) 55 | subscribtion.unsubscribe() 56 | expect(subscribtion.closed).to.deep.equal(true) 57 | }) 58 | 59 | it('supports subscribe(onNext, onError, onCompete) format', () => { 60 | const a = stream() 61 | const values = [] 62 | const errors = [] 63 | const completes = [] 64 | const onValue = x => values.push(x) 65 | const onError = x => errors.push(x) 66 | const onComplete = x => completes.push(x) 67 | const observable = a[$$observable]() 68 | observable.subscribe(onValue, onError, onComplete) 69 | send(a, [value(1), error(2)]) 70 | expect(values).to.deep.equal([1]) 71 | expect(errors).to.deep.equal([2]) 72 | expect(completes).to.deep.equal([undefined]) 73 | }) 74 | 75 | it('closed=true after end', () => { 76 | const a = stream() 77 | const observable = a[$$observable]() 78 | const subscribtion = observable.subscribe(() => {}) 79 | expect(subscribtion.closed).to.deep.equal(false) 80 | send(a, [end()]) 81 | expect(subscribtion.closed).to.deep.equal(true) 82 | }) 83 | }) 84 | -------------------------------------------------------------------------------- /test/specs/filter-errors.js: -------------------------------------------------------------------------------- 1 | const {stream, prop, send, value, error, end, expect} = require('../test-helpers') 2 | 3 | describe('filterErrors', () => { 4 | describe('stream', () => { 5 | it('should return stream', () => { 6 | expect(stream().filterErrors(() => {})).to.be.observable.stream() 7 | }) 8 | 9 | it('should activate/deactivate source', () => { 10 | const a = stream() 11 | expect(a.filterErrors(() => {})).to.activate(a) 12 | }) 13 | 14 | it('should be ended if source was ended', () => 15 | expect(send(stream(), [end()]).filterErrors(() => {})).to.emit([end({current: true})])) 16 | 17 | it('should handle events', () => { 18 | const a = stream() 19 | expect(a.filterErrors(x => x > 3)).to.emit([value(-1), error(4), value(-2), error(5), error(6), end()], () => 20 | send(a, [value(-1), error(1), error(2), error(3), error(4), value(-2), error(5), error(0), error(6), end()]) 21 | ) 22 | }) 23 | 24 | it('shoud use id as default predicate', () => { 25 | const a = stream() 26 | expect(a.filterErrors()).to.emit([value(-1), error(4), value(-2), error(5), value(false), error(6), end()], () => 27 | send(a, [ 28 | value(-1), 29 | error(0), 30 | error(false), 31 | error(null), 32 | error(4), 33 | value(-2), 34 | error(5), 35 | error(''), 36 | value(false), 37 | error(6), 38 | end(), 39 | ]) 40 | ) 41 | }) 42 | }) 43 | 44 | describe('property', () => { 45 | it('should return property', () => { 46 | expect(prop().filterErrors(() => {})).to.be.observable.property() 47 | }) 48 | 49 | it('should activate/deactivate source', () => { 50 | const a = prop() 51 | expect(a.filterErrors(() => {})).to.activate(a) 52 | }) 53 | 54 | it('should be ended if source was ended', () => 55 | expect(send(prop(), [end()]).filterErrors(() => {})).to.emit([end({current: true})])) 56 | 57 | it('should handle events and current', () => { 58 | const a = send(prop(), [error(5)]) 59 | expect(a.filterErrors(x => x > 3)).to.emit( 60 | [error(5, {current: true}), error(4), value(-2), error(6), end()], 61 | () => send(a, [error(1), error(2), error(3), error(4), value(-2), error(0), error(6), end()]) 62 | ) 63 | }) 64 | 65 | it('should handle current (not pass)', () => { 66 | const a = send(prop(), [error(0)]) 67 | expect(a.filterErrors(x => x > 2)).to.emit([]) 68 | }) 69 | 70 | it('shoud use id as default predicate', () => { 71 | let a = send(prop(), [error(5)]) 72 | expect(a.filterErrors()).to.emit([error(5, {current: true}), error(4), value(-2), error(6), end()], () => 73 | send(a, [error(0), error(false), error(null), error(4), value(-2), error(undefined), error(6), end()]) 74 | ) 75 | a = send(prop(), [error(0)]) 76 | expect(a.filterErrors()).to.emit([]) 77 | }) 78 | }) 79 | }) 80 | -------------------------------------------------------------------------------- /test/specs/filter.js: -------------------------------------------------------------------------------- 1 | const {stream, prop, send, value, error, end, expect} = require('../test-helpers') 2 | 3 | describe('filter', () => { 4 | describe('stream', () => { 5 | it('should return stream', () => { 6 | expect(stream().filter(() => {})).to.be.observable.stream() 7 | }) 8 | 9 | it('should activate/deactivate source', () => { 10 | const a = stream() 11 | expect(a.filter(() => {})).to.activate(a) 12 | }) 13 | 14 | it('should be ended if source was ended', () => { 15 | expect(send(stream(), [end()]).filter(() => {})).to.emit([end({current: true})]) 16 | }) 17 | 18 | it('should handle events', () => { 19 | const a = stream() 20 | expect(a.filter(x => x > 3)).to.emit([value(4), value(5), error(7), value(6), end()], () => 21 | send(a, [value(1), value(2), value(4), value(5), value(0), error(7), value(6), end()]) 22 | ) 23 | }) 24 | 25 | it('shoud use id as default predicate', () => { 26 | const a = stream() 27 | expect(a.filter()).to.emit([value(4), value(5), error(7), value(6), end()], () => 28 | send(a, [value(0), value(0), value(4), value(5), value(0), error(7), value(6), end()]) 29 | ) 30 | }) 31 | }) 32 | 33 | describe('property', () => { 34 | it('should return property', () => { 35 | expect(prop().filter(() => {})).to.be.observable.property() 36 | }) 37 | 38 | it('should activate/deactivate source', () => { 39 | const a = prop() 40 | expect(a.filter(() => {})).to.activate(a) 41 | }) 42 | 43 | it('should be ended if source was ended', () => { 44 | expect(send(prop(), [end()]).filter(() => {})).to.emit([end({current: true})]) 45 | }) 46 | 47 | it('should handle events and current', () => { 48 | let a = send(prop(), [value(5)]) 49 | expect(a.filter(x => x > 2)).to.emit([value(5, {current: true}), value(4), error(7), value(3), end()], () => 50 | send(a, [value(4), error(7), value(3), value(2), value(1), end()]) 51 | ) 52 | a = send(prop(), [error(0)]) 53 | expect(a.filter(x => x > 2)).to.emit([error(0, {current: true}), value(4), error(7), value(3), end()], () => 54 | send(a, [value(4), error(7), value(3), value(2), value(1), end()]) 55 | ) 56 | }) 57 | 58 | it('should handle current (not pass)', () => { 59 | const a = send(prop(), [value(1), error(0)]) 60 | expect(a.filter(x => x > 2)).to.emit([error(0, {current: true})]) 61 | }) 62 | 63 | it('shoud use id as default predicate', () => { 64 | let a = send(prop(), [value(0)]) 65 | expect(a.filter()).to.emit([value(4), error(-value(2)), value(5), value(6), end()], () => 66 | send(a, [value(0), value(4), error(-value(2)), value(5), value(0), value(6), end()]) 67 | ) 68 | a = send(prop(), [value(1)]) 69 | expect(a.filter()).to.emit( 70 | [value(1, {current: true}), value(4), error(-value(2)), value(5), value(6), end()], 71 | () => send(a, [value(0), value(4), error(-value(2)), value(5), value(0), value(6), end()]) 72 | ) 73 | }) 74 | }) 75 | }) 76 | -------------------------------------------------------------------------------- /test/specs/flatten.js: -------------------------------------------------------------------------------- 1 | const {stream, prop, send, value, error, end, Kefir, expect} = require('../test-helpers') 2 | 3 | describe('flatten', () => { 4 | describe('stream', () => { 5 | it('should return stream', () => { 6 | expect(stream().flatten(() => {})).to.be.observable.stream() 7 | }) 8 | 9 | it('should activate/deactivate source', () => { 10 | const a = stream() 11 | expect(a.flatten(() => {})).to.activate(a) 12 | }) 13 | 14 | it('should be ended if source was ended', () => 15 | expect(send(stream(), [end()]).flatten(() => {})).to.emit([end({current: true})])) 16 | 17 | it('should handle events', () => { 18 | const a = stream() 19 | expect( 20 | a.flatten(x => { 21 | if (x > 1) { 22 | return __range__(1, x, true) 23 | } else { 24 | return [] 25 | } 26 | }) 27 | ).to.emit([value(1), value(2), error(4), value(1), value(2), value(3), end()], () => 28 | send(a, [value(1), value(2), error(4), value(3), end()]) 29 | ) 30 | }) 31 | 32 | it('if no `fn` provided should use the `id` function by default', () => { 33 | const a = stream() 34 | expect(a.flatten()).to.emit([value(1), value(2), value(3), end()], () => 35 | send(a, [value([1]), value([]), value([2, 3]), end()]) 36 | ) 37 | }) 38 | }) 39 | 40 | describe('property', () => { 41 | it('should return stream', () => { 42 | expect(prop().flatten(() => {})).to.be.observable.stream() 43 | }) 44 | 45 | it('should activate/deactivate source', () => { 46 | const a = prop() 47 | expect(a.flatten(() => {})).to.activate(a) 48 | }) 49 | 50 | it('should be ended if source was ended', () => 51 | expect(send(prop(), [end()]).flatten(() => {})).to.emit([end({current: true})])) 52 | 53 | it('should handle events (handler skips current)', () => { 54 | const a = send(prop(), [value(1)]) 55 | expect( 56 | a.flatten(x => { 57 | if (x > 1) { 58 | return __range__(1, x, true) 59 | } else { 60 | return [] 61 | } 62 | }) 63 | ).to.emit([value(1), value(2), error(4), value(1), value(2), value(3), end()], () => 64 | send(a, [value(2), error(4), value(3), end()]) 65 | ) 66 | }) 67 | 68 | it('should handle current correctly', () => { 69 | expect(send(prop(), [value(1)]).flatten(x => [x])).to.emit([value(1, {current: true})]) 70 | expect(send(prop(), [error(0)]).flatten(() => {})).to.emit([error(0, {current: true})]) 71 | }) 72 | 73 | it('should handle multiple currents correctly', () => 74 | expect(send(prop(), [value(2)]).flatten(x => __range__(1, x, true))).to.emit([ 75 | value(1, {current: true}), 76 | value(2, {current: true}), 77 | ])) 78 | }) 79 | }) 80 | 81 | function __range__(left, right, inclusive) { 82 | let range = [] 83 | let ascending = left < right 84 | let end = !inclusive ? right : ascending ? right + 1 : right - 1 85 | for (let i = left; ascending ? i < end : i > end; ascending ? i++ : i--) { 86 | range.push(i) 87 | } 88 | return range 89 | } 90 | -------------------------------------------------------------------------------- /test/specs/from-callback.js: -------------------------------------------------------------------------------- 1 | const {activate, deactivate, Kefir, value, end, expect} = require('../test-helpers') 2 | 3 | describe('fromCallback', () => { 4 | it('should return stream', () => { 5 | expect(Kefir.fromCallback(() => {})).to.be.observable.stream() 6 | }) 7 | 8 | it('should not be ended', () => { 9 | expect(Kefir.fromCallback(() => {})).to.emit([]) 10 | }) 11 | 12 | it('should call `callbackConsumer` on first activation, and only on first', () => { 13 | let count = 0 14 | const s = Kefir.fromCallback(() => count++) 15 | expect(count).to.equal(0) 16 | activate(s) 17 | expect(count).to.equal(1) 18 | deactivate(s) 19 | activate(s) 20 | deactivate(s) 21 | activate(s) 22 | expect(count).to.equal(1) 23 | }) 24 | 25 | it('should emit first result and end after that', () => { 26 | let cb = null 27 | expect(Kefir.fromCallback(_cb => (cb = _cb))).to.emit([value(1), end()], () => cb(1)) 28 | }) 29 | 30 | it('should work after deactivation/activate cicle', () => { 31 | let cb = null 32 | const s = Kefir.fromCallback(_cb => (cb = _cb)) 33 | activate(s) 34 | deactivate(s) 35 | activate(s) 36 | deactivate(s) 37 | expect(s).to.emit([value(1), end()], () => cb(1)) 38 | }) 39 | 40 | it('should emit a current, if `callback` is called immediately in `callbackConsumer`', () => { 41 | expect(Kefir.fromCallback(cb => cb(1))).to.emit([value(1, {current: true}), end({current: true})]) 42 | }) 43 | }) 44 | -------------------------------------------------------------------------------- /test/specs/from-es-observable.js: -------------------------------------------------------------------------------- 1 | const Observable = require('zen-observable') 2 | const {of: observableOf} = require('rxjs') 3 | const {activate, deactivate, Kefir, expect} = require('../test-helpers') 4 | 5 | describe('fromESObservable', () => { 6 | it('turns an ES7 observable into a stream', () => { 7 | expect(Kefir.fromESObservable(Observable.of(1, 2))).to.be.observable.stream() 8 | }) 9 | 10 | it('emits events from observable to stream', done => { 11 | const stream = Kefir.fromESObservable(Observable.of(1, 2)) 12 | const values = [] 13 | stream.onValue(value => values.push(value)) 14 | return stream.onEnd(() => { 15 | expect(values).to.deep.equal([1, 2]) 16 | return done() 17 | }) 18 | }) 19 | 20 | it('ends stream after an error', done => { 21 | const observable = new Observable(observer => { 22 | observer.next(1) 23 | return observer.error() 24 | }) 25 | return Kefir.fromESObservable(observable).onEnd(() => done()) 26 | }) 27 | 28 | it('turns an RxJS observable into a Kefir stream', done => { 29 | const stream = Kefir.fromESObservable(observableOf('hello world')) 30 | const values = [] 31 | stream.onValue(value => values.push(value)) 32 | return stream.onEnd(() => { 33 | expect(values).to.deep.equal(['hello world']) 34 | return done() 35 | }) 36 | }) 37 | }) 38 | -------------------------------------------------------------------------------- /test/specs/from-node-callback.js: -------------------------------------------------------------------------------- 1 | const {activate, deactivate, Kefir, value, error, end, expect} = require('../test-helpers') 2 | 3 | describe('fromNodeCallback', () => { 4 | it('should return stream', () => { 5 | expect(Kefir.fromNodeCallback(() => {})).to.be.observable.stream() 6 | }) 7 | 8 | it('should not be ended', () => { 9 | expect(Kefir.fromNodeCallback(() => {})).to.emit([]) 10 | }) 11 | 12 | it('should call `callbackConsumer` on first activation, and only on first', () => { 13 | let count = 0 14 | const s = Kefir.fromNodeCallback(() => count++) 15 | expect(count).to.equal(0) 16 | activate(s) 17 | expect(count).to.equal(1) 18 | deactivate(s) 19 | activate(s) 20 | deactivate(s) 21 | activate(s) 22 | expect(count).to.equal(1) 23 | }) 24 | 25 | it('should emit first result and end after that', () => { 26 | let cb = null 27 | expect(Kefir.fromNodeCallback(_cb => (cb = _cb))).to.emit([value(1), end()], () => cb(null, 1)) 28 | }) 29 | 30 | it('should emit first error and end after that', () => { 31 | let cb = null 32 | expect(Kefir.fromNodeCallback(_cb => (cb = _cb))).to.emit([error(-1), end()], () => cb(-1)) 33 | }) 34 | 35 | it('should work after deactivation/activate cicle', () => { 36 | let cb = null 37 | const s = Kefir.fromNodeCallback(_cb => (cb = _cb)) 38 | activate(s) 39 | deactivate(s) 40 | activate(s) 41 | deactivate(s) 42 | expect(s).to.emit([value(1), end()], () => cb(null, 1)) 43 | }) 44 | 45 | it('should emit a current, if `callback` is called immediately in `callbackConsumer`', () => { 46 | expect(Kefir.fromNodeCallback(cb => cb(null, 1))).to.emit([value(1, {current: true}), end({current: true})]) 47 | 48 | expect(Kefir.fromNodeCallback(cb => cb(-1))).to.emit([error(-1, {current: true}), end({current: true})]) 49 | }) 50 | }) 51 | -------------------------------------------------------------------------------- /test/specs/from-poll.js: -------------------------------------------------------------------------------- 1 | const {value, Kefir, expect} = require('../test-helpers') 2 | 3 | describe('fromPoll', () => { 4 | it('should return stream', () => { 5 | expect(Kefir.fromPoll(100, () => {})).to.be.observable.stream() 6 | }) 7 | 8 | it('should emit whatever fn returns at certain time', () => { 9 | let i = 0 10 | expect(Kefir.fromPoll(100, () => ++i)).to.emitInTime( 11 | [ 12 | [100, value(1)], 13 | [200, value(2)], 14 | [300, value(3)], 15 | ], 16 | undefined, 17 | {timeLimit: 350} 18 | ) 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /test/specs/from-promise.js: -------------------------------------------------------------------------------- 1 | const {activate, deactivate, value, error, end, Kefir, expect} = require('../test-helpers') 2 | 3 | describe('fromPromise', () => { 4 | const inProgress = { 5 | then() {}, 6 | } 7 | 8 | const fulfilledSync = { 9 | then(onSuccess) { 10 | return onSuccess(1) 11 | }, 12 | } 13 | 14 | const failedSync = { 15 | then(onSuccess, onError) { 16 | return onError(1) 17 | }, 18 | } 19 | 20 | const fulfilledAsync = { 21 | then(onSuccess) { 22 | const fulfill = () => onSuccess(1) 23 | return setTimeout(fulfill, 1000) 24 | }, 25 | } 26 | 27 | const failedAsync = { 28 | then(onSuccess, onError) { 29 | const fail = () => onError(1) 30 | return setTimeout(fail, 1000) 31 | }, 32 | } 33 | 34 | it('should return property', () => { 35 | expect(Kefir.fromPromise(inProgress)).to.be.observable.property() 36 | }) 37 | 38 | it('should call `property.then` on first activation, and only on first', () => { 39 | let count = 0 40 | const s = Kefir.fromPromise({ 41 | then() { 42 | return count++ 43 | }, 44 | }) 45 | expect(count).to.equal(0) 46 | activate(s) 47 | expect(count).to.equal(1) 48 | deactivate(s) 49 | activate(s) 50 | deactivate(s) 51 | activate(s) 52 | expect(count).to.equal(1) 53 | }) 54 | 55 | it('should call `property.done`', () => { 56 | let count = 0 57 | const s = Kefir.fromPromise({ 58 | then() { 59 | return this 60 | }, 61 | done() { 62 | return count++ 63 | }, 64 | }) 65 | expect(count).to.equal(0) 66 | activate(s) 67 | expect(count).to.equal(1) 68 | deactivate(s) 69 | activate(s) 70 | deactivate(s) 71 | activate(s) 72 | expect(count).to.equal(1) 73 | }) 74 | 75 | it('should work correctly with inProgress property', () => { 76 | expect(Kefir.fromPromise(inProgress)).to.emitInTime([]) 77 | }) 78 | 79 | it('... with fulfilledSync property', () => { 80 | expect(Kefir.fromPromise(fulfilledSync)).to.emit([value(1, {current: true}), end({current: true})]) 81 | }) 82 | 83 | it('... with failedSync property', () => 84 | expect(Kefir.fromPromise(failedSync)).to.emit([error(1, {current: true}), end({current: true})])) 85 | 86 | it('... with fulfilledAsync property', () => { 87 | const a = Kefir.fromPromise(fulfilledAsync) 88 | expect(a).to.emitInTime([ 89 | [1000, value(1)], 90 | [1000, end()], 91 | ]) 92 | expect(a).to.emit([value(1, {current: true}), end({current: true})]) 93 | }) 94 | 95 | it('... with failedAsync property', () => { 96 | const a = Kefir.fromPromise(failedAsync) 97 | expect(a).to.emitInTime([ 98 | [1000, error(1)], 99 | [1000, end()], 100 | ]) 101 | expect(a).to.emit([error(1, {current: true}), end({current: true})]) 102 | }) 103 | }) 104 | -------------------------------------------------------------------------------- /test/specs/ignore-end.js: -------------------------------------------------------------------------------- 1 | const {stream, prop, send, value, end, expect} = require('../test-helpers') 2 | 3 | describe('ignoreEnd', () => { 4 | describe('stream', () => { 5 | it('should return stream', () => { 6 | expect(stream().ignoreEnd()).to.be.observable.stream() 7 | }) 8 | 9 | it('should activate/deactivate source', () => { 10 | const a = stream() 11 | expect(a.ignoreEnd()).to.activate(a) 12 | }) 13 | 14 | it('should not be ended if source was ended', () => { 15 | expect(send(stream(), [end()]).ignoreEnd()).to.emit([]) 16 | }) 17 | 18 | it('should handle events', () => { 19 | const a = stream() 20 | expect(a.ignoreEnd()).to.emit([value(1), value(2)], () => send(a, [value(1), value(2), end()])) 21 | }) 22 | 23 | it('errors should flow', () => { 24 | const a = stream() 25 | expect(a.ignoreEnd()).to.flowErrors(a) 26 | }) 27 | }) 28 | 29 | describe('property', () => { 30 | it('should return property', () => { 31 | expect(prop().ignoreEnd()).to.be.observable.property() 32 | }) 33 | 34 | it('should activate/deactivate source', () => { 35 | const a = prop() 36 | expect(a.ignoreEnd()).to.activate(a) 37 | }) 38 | 39 | it('should not be ended if source was ended', () => { 40 | expect(send(prop(), [end()]).ignoreEnd()).to.emit([]) 41 | expect(send(prop(), [value(1), end()]).ignoreEnd()).to.emit([value(1, {current: true})]) 42 | }) 43 | 44 | it('should handle events and current', () => { 45 | const a = send(prop(), [value(1)]) 46 | expect(a.ignoreEnd()).to.emit([value(1, {current: true}), value(2), value(3)], () => 47 | send(a, [value(2), value(3), end()]) 48 | ) 49 | }) 50 | 51 | it('errors should flow', () => { 52 | const a = prop() 53 | expect(a.ignoreEnd()).to.flowErrors(a) 54 | }) 55 | }) 56 | }) 57 | -------------------------------------------------------------------------------- /test/specs/ignore-errors.js: -------------------------------------------------------------------------------- 1 | const {stream, prop, send, value, error, end, expect} = require('../test-helpers') 2 | 3 | describe('ignoreErrors', () => { 4 | describe('stream', () => { 5 | it('should return stream', () => { 6 | expect(stream().ignoreErrors()).to.be.observable.stream() 7 | }) 8 | 9 | it('should activate/deactivate source', () => { 10 | const a = stream() 11 | expect(a.ignoreErrors()).to.activate(a) 12 | }) 13 | 14 | it('should be ended if source was ended', () => 15 | expect(send(stream(), [end()]).ignoreErrors()).to.emit([end({current: true})])) 16 | 17 | it('should handle events', () => { 18 | const a = stream() 19 | expect(a.ignoreErrors()).to.emit([value(1), value(2), end()], () => 20 | send(a, [value(1), error(-1), value(2), error(-2), end()]) 21 | ) 22 | }) 23 | }) 24 | 25 | describe('property', () => { 26 | it('should return property', () => { 27 | expect(prop().ignoreErrors()).to.be.observable.property() 28 | }) 29 | 30 | it('should activate/deactivate source', () => { 31 | const a = prop() 32 | expect(a.ignoreErrors()).to.activate(a) 33 | }) 34 | 35 | it('should be ended if source was ended', () => 36 | expect(send(prop(), [end()]).ignoreErrors()).to.emit([end({current: true})])) 37 | 38 | it('should handle events and current', () => { 39 | let a = send(prop(), [error(-1)]) 40 | expect(a.ignoreErrors()).to.emit([value(2), value(3), end()], () => 41 | send(a, [value(2), error(-2), value(3), error(-3), end()]) 42 | ) 43 | a = send(prop(), [value(1)]) 44 | expect(a.ignoreErrors()).to.emit([value(1, {current: true}), value(2), value(3), end()], () => 45 | send(a, [value(2), error(-2), value(3), error(-3), end()]) 46 | ) 47 | }) 48 | }) 49 | }) 50 | -------------------------------------------------------------------------------- /test/specs/ignore-values.js: -------------------------------------------------------------------------------- 1 | const {stream, prop, send, value, error, end, Kefir, expect} = require('../test-helpers') 2 | 3 | describe('ignoreValues', () => { 4 | describe('stream', () => { 5 | it('should return stream', () => { 6 | expect(stream().ignoreValues()).to.be.observable.stream() 7 | }) 8 | 9 | it('should activate/deactivate source', () => { 10 | const a = stream() 11 | expect(a.ignoreValues()).to.activate(a) 12 | }) 13 | 14 | it('should be ended if source was ended', () => 15 | expect(send(stream(), [end()]).ignoreValues()).to.emit([end({current: true})])) 16 | 17 | it('should handle events', () => { 18 | const a = stream() 19 | expect(a.ignoreValues()).to.emit([error(-1), error(-2), end()], () => 20 | send(a, [value(1), error(-1), value(2), error(-2), end()]) 21 | ) 22 | }) 23 | }) 24 | 25 | describe('property', () => { 26 | it('should return property', () => { 27 | expect(prop().ignoreValues()).to.be.observable.property() 28 | }) 29 | 30 | it('should activate/deactivate source', () => { 31 | const a = prop() 32 | expect(a.ignoreValues()).to.activate(a) 33 | }) 34 | 35 | it('should be ended if source was ended', () => 36 | expect(send(prop(), [end()]).ignoreValues()).to.emit([end({current: true})])) 37 | 38 | it('should handle events and current', () => { 39 | const a = send(prop(), [value(1), error(-1)]) 40 | expect(a.ignoreValues()).to.emit([error(-1, {current: true}), error(-2), error(-3), end()], () => 41 | send(a, [value(2), error(-2), value(3), error(-3), end()]) 42 | ) 43 | }) 44 | }) 45 | }) 46 | -------------------------------------------------------------------------------- /test/specs/interval.js: -------------------------------------------------------------------------------- 1 | const {value, Kefir, expect} = require('../test-helpers') 2 | 3 | describe('interval', () => { 4 | it('should return stream', () => { 5 | expect(Kefir.interval(100, 1)).to.be.observable.stream() 6 | }) 7 | 8 | it('should repeat same value at certain time', () => { 9 | expect(Kefir.interval(100, 1)).to.emitInTime( 10 | [ 11 | [100, value(1)], 12 | [200, value(1)], 13 | [300, value(1)], 14 | ], 15 | undefined, 16 | { 17 | timeLimit: 350, 18 | } 19 | ) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /test/specs/kefir-observable.js: -------------------------------------------------------------------------------- 1 | let {Kefir, expect} = require('../test-helpers') 2 | 3 | describe('Kefir.Observable', () => { 4 | describe('observe', () => { 5 | let em, count, obs, sub 6 | 7 | beforeEach(() => { 8 | em = null 9 | count = 0 10 | obs = Kefir.stream(_em => { 11 | em = _em 12 | }) 13 | sub = obs.observe({value: () => count++, error: () => count--, end: () => (count = 0)}) 14 | }) 15 | 16 | it('should return a Subscription', () => { 17 | expect(sub.closed).to.equal(false) 18 | expect(typeof sub.unsubscribe).to.equal('function') 19 | }) 20 | 21 | it('should call Observer methods', () => { 22 | expect(count).to.equal(0) 23 | 24 | em.emit(1) 25 | expect(count).to.equal(1) 26 | 27 | em.emit(1) 28 | expect(count).to.equal(2) 29 | 30 | em.error(1) 31 | expect(count).to.equal(1) 32 | 33 | em.end() 34 | expect(count).to.equal(0) 35 | expect(sub.closed).to.equal(true) 36 | }) 37 | 38 | it('should unsubcribe early', () => { 39 | expect(count).to.equal(0) 40 | 41 | em.emit(1) 42 | expect(count).to.equal(1) 43 | 44 | sub.unsubscribe() 45 | 46 | em.emit(1) 47 | expect(count).to.equal(1) 48 | expect(sub.closed).to.equal(true) 49 | }) 50 | 51 | it('closed=true after end (w/o end handler)', () => { 52 | obs = Kefir.stream(_em => { 53 | em = _em 54 | }) 55 | sub = obs.observe(() => {}) 56 | em.end() 57 | expect(sub.closed).to.equal(true) 58 | }) 59 | }) 60 | }) 61 | -------------------------------------------------------------------------------- /test/specs/last.js: -------------------------------------------------------------------------------- 1 | const {stream, prop, send, value, error, end, Kefir, expect} = require('../test-helpers') 2 | 3 | describe('last', () => { 4 | describe('stream', () => { 5 | it('should return stream', () => { 6 | expect(stream().last()).to.be.observable.stream() 7 | }) 8 | 9 | it('should activate/deactivate source', () => { 10 | const a = stream() 11 | expect(a.last()).to.activate(a) 12 | }) 13 | 14 | it('should be ended if source was ended', () => { 15 | expect(send(stream(), [end()]).last()).to.emit([end({current: true})]) 16 | }) 17 | 18 | it('should handle events', () => { 19 | const a = stream() 20 | expect(a.last()).to.emit([error(5), error(6), value(3), end()], () => 21 | send(a, [value(1), error(5), error(6), value(2), value(3), end()]) 22 | ) 23 | }) 24 | }) 25 | 26 | describe('property', () => { 27 | it('should return property', () => { 28 | expect(prop().last()).to.be.observable.property() 29 | }) 30 | 31 | it('should activate/deactivate source', () => { 32 | const a = prop() 33 | expect(a.last()).to.activate(a) 34 | }) 35 | 36 | it('should be ended if source was ended', () => { 37 | expect(send(prop(), [end()]).last()).to.emit([end({current: true})]) 38 | expect(send(prop(), [value(1), end()]).last()).to.emit([value(1, {current: true}), end({current: true})]) 39 | }) 40 | 41 | it('should handle events and current', () => { 42 | let a = send(prop(), [value(1)]) 43 | expect(a.last()).to.emit([error(5), value(1), end()], () => send(a, [error(5), end()])) 44 | 45 | a = send(prop(), [error(0)]) 46 | expect(a.last()).to.emit([error(0, {current: true}), error(5), value(3), end()], () => 47 | send(a, [value(2), error(5), value(3), end()]) 48 | ) 49 | }) 50 | }) 51 | }) 52 | -------------------------------------------------------------------------------- /test/specs/later.js: -------------------------------------------------------------------------------- 1 | const {value, end, Kefir, expect} = require('../test-helpers') 2 | 3 | describe('later', () => { 4 | it('should return stream', () => { 5 | expect(Kefir.later(100, 1)).to.be.observable.stream() 6 | }) 7 | 8 | it('should emmit value after interval then end', () => { 9 | expect(Kefir.later(100, 1)).to.emitInTime([ 10 | [100, value(1)], 11 | [100, end()], 12 | ]) 13 | }) 14 | }) 15 | -------------------------------------------------------------------------------- /test/specs/log.js: -------------------------------------------------------------------------------- 1 | const {stream, send, value, error, end, expect} = require('../test-helpers') 2 | const sinon = require('sinon') 3 | 4 | describe('log', () => { 5 | describe('adding', () => { 6 | it('should return the stream', () => { 7 | expect(stream().log()).to.be.observable.stream() 8 | }) 9 | 10 | it('should activate the stream', () => { 11 | const a = stream().log() 12 | expect(a).to.be.active() 13 | }) 14 | }) 15 | 16 | describe('removing', () => { 17 | it('should return the stream', () => { 18 | expect( 19 | stream() 20 | .log() 21 | .offLog() 22 | ).to.be.observable.stream() 23 | }) 24 | 25 | it('should deactivate the stream', () => { 26 | const a = stream() 27 | .log() 28 | .offLog() 29 | expect(a).not.to.be.active() 30 | }) 31 | }) 32 | 33 | describe('console', () => { 34 | let stub 35 | beforeEach(() => (stub = sinon.stub(console, 'log'))) 36 | 37 | afterEach(() => stub.restore()) 38 | 39 | it('should have a default name', () => { 40 | const a = stream() 41 | a.log() 42 | expect(a).to.emit([value(1), value(2), value(3)], () => { 43 | send(a, [value(1), value(2), value(3)]) 44 | expect(console.log).to.have.been.calledWith('[stream]', '', 1) 45 | expect(console.log).to.have.been.calledWith('[stream]', '', 2) 46 | expect(console.log).to.have.been.calledWith('[stream]', '', 3) 47 | }) 48 | }) 49 | 50 | it('should use the name', () => { 51 | const a = stream() 52 | a.log('logged') 53 | expect(a).to.emit([value(1), value(2), value(3)], () => { 54 | send(a, [value(1), value(2), value(3)]) 55 | expect(console.log).to.have.been.calledWith('logged', '', 1) 56 | expect(console.log).to.have.been.calledWith('logged', '', 2) 57 | expect(console.log).to.have.been.calledWith('logged', '', 3) 58 | }) 59 | }) 60 | 61 | it('should not log if the log has been removed', () => { 62 | const a = stream() 63 | a.log() 64 | a.offLog() 65 | expect(a).to.emit([value(1), value(2), value(3)], () => { 66 | send(a, [value(1), value(2), value(3)]) 67 | expect(console.log).not.to.have.been.called 68 | }) 69 | }) 70 | }) 71 | }) 72 | -------------------------------------------------------------------------------- /test/specs/map-errors.js: -------------------------------------------------------------------------------- 1 | const {stream, prop, send, value, error, end, expect} = require('../test-helpers') 2 | 3 | describe('mapErrors', () => { 4 | describe('stream', () => { 5 | it('should return stream', () => { 6 | expect(stream().mapErrors(() => {})).to.be.observable.stream() 7 | }) 8 | 9 | it('should activate/deactivate source', () => { 10 | const a = stream() 11 | expect(a.mapErrors(() => {})).to.activate(a) 12 | }) 13 | 14 | it('should be ended if source was ended', () => 15 | expect(send(stream(), [end()]).mapErrors(() => {})).to.emit([end({current: true})])) 16 | 17 | it('should handle events', () => { 18 | const a = stream() 19 | expect(a.mapErrors(x => x * 2)).to.emit([value(1), error(-2), value(2), error(-4), end()], () => 20 | send(a, [value(1), error(-1, {current: true}), value(2), error(-2), end()]) 21 | ) 22 | }) 23 | }) 24 | 25 | describe('property', () => { 26 | it('should return property', () => { 27 | expect(prop().mapErrors(() => {})).to.be.observable.property() 28 | }) 29 | 30 | it('should activate/deactivate source', () => { 31 | const a = prop() 32 | expect(a.mapErrors(() => {})).to.activate(a) 33 | }) 34 | 35 | it('should be ended if source was ended', () => 36 | expect(send(prop(), [end()]).mapErrors(() => {})).to.emit([end({current: true})])) 37 | 38 | it('should handle events and current', () => { 39 | let a = send(prop(), [value(1)]) 40 | expect(a.mapErrors(x => x * 2)).to.emit( 41 | [value(1, {current: true}), value(2), error(-4), value(3), error(-6), end()], 42 | () => send(a, [value(2), error(-2), value(3), error(-3), end()]) 43 | ) 44 | a = send(prop(), [error(-1, {current: true})]) 45 | expect(a.mapErrors(x => x * 2)).to.emit( 46 | [error(-2, {current: true}), value(2), error(-4), value(3), error(-6), end()], 47 | () => send(a, [value(2), error(-2), value(3), error(-3), end()]) 48 | ) 49 | }) 50 | }) 51 | }) 52 | -------------------------------------------------------------------------------- /test/specs/map.js: -------------------------------------------------------------------------------- 1 | const {stream, prop, send, value, error, end, expect} = require('../test-helpers') 2 | 3 | describe('map', () => { 4 | describe('stream', () => { 5 | it('should return stream', () => { 6 | expect(stream().map(() => {})).to.be.observable.stream() 7 | }) 8 | 9 | it('should activate/deactivate source', () => { 10 | const a = stream() 11 | expect(a.map(() => {})).to.activate(a) 12 | }) 13 | 14 | it('should be ended if source was ended', () => 15 | expect(send(stream(), [end()]).map(() => {})).to.emit([end({current: true})])) 16 | 17 | it('should handle events', () => { 18 | const a = stream() 19 | expect(a.map(x => x * 2)).to.emit([value(2), error(5), value(4), end()], () => 20 | send(a, [value(1), error(5), value(2), end()]) 21 | ) 22 | }) 23 | 24 | it('should work with default `fn`', () => { 25 | const a = stream() 26 | expect(a.map()).to.emit([value(1), error(5), value(2), end()], () => 27 | send(a, [value(1), error(5), value(2), end()]) 28 | ) 29 | }) 30 | }) 31 | 32 | describe('property', () => { 33 | it('should return property', () => { 34 | expect(prop().map(() => {})).to.be.observable.property() 35 | }) 36 | 37 | it('should activate/deactivate source', () => { 38 | const a = prop() 39 | expect(a.map(() => {})).to.activate(a) 40 | }) 41 | 42 | it('should be ended if source was ended', () => 43 | expect(send(prop(), [end()]).map(() => {})).to.emit([end({current: true})])) 44 | 45 | it('should handle events and current', () => { 46 | let a = send(prop(), [value(1)]) 47 | expect(a.map(x => x * 2)).to.emit([value(2, {current: true}), value(4), error(5), value(6), end()], () => 48 | send(a, [value(2), error(5), value(3), end()]) 49 | ) 50 | a = send(prop(), [error(0)]) 51 | expect(a.map(x => x * 2)).to.emit([error(0, {current: true}), value(4), error(5), value(6), end()], () => 52 | send(a, [value(2), error(5), value(3), end()]) 53 | ) 54 | }) 55 | }) 56 | }) 57 | -------------------------------------------------------------------------------- /test/specs/merge.js: -------------------------------------------------------------------------------- 1 | const {stream, prop, send, value, end, activate, deactivate, Kefir, expect} = require('../test-helpers') 2 | 3 | describe('merge', () => { 4 | it('should return stream', () => { 5 | expect(Kefir.merge([])).to.be.observable.stream() 6 | expect(Kefir.merge([stream(), prop()])).to.be.observable.stream() 7 | expect(stream().merge(stream())).to.be.observable.stream() 8 | expect(prop().merge(prop())).to.be.observable.stream() 9 | }) 10 | 11 | it('should be ended if empty array provided', () => { 12 | expect(Kefir.merge([])).to.emit([end({current: true})]) 13 | }) 14 | 15 | it('should be ended if array of ended observables provided', () => { 16 | const a = send(stream(), [end()]) 17 | const b = send(prop(), [end()]) 18 | const c = send(stream(), [end()]) 19 | expect(Kefir.merge([a, b, c])).to.emit([end({current: true})]) 20 | expect(a.merge(b)).to.emit([end({current: true})]) 21 | }) 22 | 23 | it('should activate sources', () => { 24 | const a = stream() 25 | const b = prop() 26 | const c = stream() 27 | expect(Kefir.merge([a, b, c])).to.activate(a, b, c) 28 | expect(a.merge(b)).to.activate(a, b) 29 | }) 30 | 31 | it('should deliver events from observables, then end when all of them end', () => { 32 | let a = stream() 33 | let b = send(prop(), [value(0)]) 34 | const c = stream() 35 | expect(Kefir.merge([a, b, c])).to.emit( 36 | [value(0, {current: true}), value(1), value(2), value(3), value(4), value(5), value(6), end()], 37 | () => { 38 | send(a, [value(1)]) 39 | send(b, [value(2)]) 40 | send(c, [value(3)]) 41 | send(a, [end()]) 42 | send(b, [value(4), end()]) 43 | send(c, [value(5), value(6), end()]) 44 | } 45 | ) 46 | a = stream() 47 | b = send(prop(), [value(0)]) 48 | expect(a.merge(b)).to.emit([value(0, {current: true}), value(1), value(2), value(3), end()], () => { 49 | send(a, [value(1)]) 50 | send(b, [value(2)]) 51 | send(a, [end()]) 52 | send(b, [value(3), end()]) 53 | }) 54 | }) 55 | 56 | it('should deliver currents from all source properties, but only to first subscriber on each activation', () => { 57 | const a = send(prop(), [value(0)]) 58 | const b = send(prop(), [value(1)]) 59 | const c = send(prop(), [value(2)]) 60 | 61 | let merge = Kefir.merge([a, b, c]) 62 | expect(merge).to.emit([value(0, {current: true}), value(1, {current: true}), value(2, {current: true})]) 63 | 64 | merge = Kefir.merge([a, b, c]) 65 | activate(merge) 66 | expect(merge).to.emit([]) 67 | 68 | merge = Kefir.merge([a, b, c]) 69 | activate(merge) 70 | deactivate(merge) 71 | expect(merge).to.emit([value(0, {current: true}), value(1, {current: true}), value(2, {current: true})]) 72 | }) 73 | 74 | it('errors should flow', () => { 75 | let a = stream() 76 | let b = prop() 77 | let c = stream() 78 | expect(Kefir.merge([a, b, c])).to.flowErrors(a) 79 | a = stream() 80 | b = prop() 81 | c = stream() 82 | expect(Kefir.merge([a, b, c])).to.flowErrors(b) 83 | a = stream() 84 | b = prop() 85 | c = stream() 86 | expect(Kefir.merge([a, b, c])).to.flowErrors(c) 87 | }) 88 | 89 | it('should work correctly when unsuscribing after one sync event', () => { 90 | const a = Kefir.constant(1) 91 | const b = Kefir.interval(1000, 1) 92 | const c = a.merge(b) 93 | activate(c.take(1)) 94 | expect(b).not.to.be.active() 95 | }) 96 | }) 97 | -------------------------------------------------------------------------------- /test/specs/never.js: -------------------------------------------------------------------------------- 1 | const {end, Kefir, expect} = require('../test-helpers') 2 | 3 | describe('never', () => { 4 | it('should return stream', () => { 5 | expect(Kefir.never()).to.be.observable.stream() 6 | }) 7 | 8 | it('should be ended', () => { 9 | expect(Kefir.never()).to.emit([end({current: true})]) 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /test/specs/repeat.js: -------------------------------------------------------------------------------- 1 | const {stream, prop, send, value, error, end, activate, deactivate, Kefir, expect} = require('../test-helpers') 2 | 3 | describe('repeat', () => { 4 | it('should return stream', () => { 5 | expect(Kefir.repeat()).to.be.observable.stream() 6 | }) 7 | 8 | it('should work correctly (with .constant)', () => { 9 | const a = Kefir.repeat(i => Kefir[i === 2 ? 'constantError' : 'constant'](i)) 10 | expect(a.take(3)).to.emit([ 11 | value(0, {current: true}), 12 | value(1, {current: true}), 13 | error(2, {current: true}), 14 | value(3, {current: true}), 15 | end({current: true}), 16 | ]) 17 | }) 18 | 19 | it('should work correctly (with .later)', () => { 20 | const a = Kefir.repeat(i => Kefir.later(100, i)) 21 | expect(a.take(3)).to.emitInTime([ 22 | [100, value(0)], 23 | [200, value(1)], 24 | [300, value(2)], 25 | [300, end()], 26 | ]) 27 | }) 28 | 29 | it('should work correctly (with .sequentially)', () => { 30 | const a = Kefir.repeat(i => Kefir.sequentially(100, [1, 2, 3])) 31 | expect(a.take(5)).to.emitInTime([ 32 | [100, value(1)], 33 | [200, value(2)], 34 | [300, value(3)], 35 | [400, value(1)], 36 | [500, value(2)], 37 | [500, end()], 38 | ]) 39 | }) 40 | 41 | it('should not cause stack overflow', () => { 42 | const sum = (a, b) => a + b 43 | const genConstant = () => Kefir.constant(1) 44 | 45 | const a = Kefir.repeat(genConstant) 46 | .take(3000) 47 | .scan(sum, 0) 48 | .last() 49 | expect(a).to.emit([value(3000, {current: true}), end({current: true})]) 50 | }) 51 | 52 | it('should get new source only if previous one ended', () => { 53 | let a = stream() 54 | 55 | let callsCount = 0 56 | const b = Kefir.repeat(() => { 57 | callsCount++ 58 | if (!a._alive) { 59 | a = stream() 60 | } 61 | return a 62 | }) 63 | 64 | expect(callsCount).to.equal(0) 65 | activate(b) 66 | expect(callsCount).to.equal(1) 67 | deactivate(b) 68 | activate(b) 69 | expect(callsCount).to.equal(1) 70 | send(a, [end()]) 71 | expect(callsCount).to.equal(2) 72 | }) 73 | 74 | it('should unsubscribe from source', () => { 75 | const a = stream() 76 | const b = Kefir.repeat(() => a) 77 | expect(b).to.activate(a) 78 | }) 79 | 80 | it('should end when falsy value returned from generator', () => { 81 | const a = Kefir.repeat(i => { 82 | if (i < 3) { 83 | return Kefir.constant(i) 84 | } else { 85 | return false 86 | } 87 | }) 88 | expect(a).to.emit([ 89 | value(0, {current: true}), 90 | value(1, {current: true}), 91 | value(2, {current: true}), 92 | end({current: true}), 93 | ]) 94 | }) 95 | }) 96 | -------------------------------------------------------------------------------- /test/specs/sampled-by.js: -------------------------------------------------------------------------------- 1 | const {stream, prop, send, value, error, end, activate, deactivate, Kefir, expect} = require('../test-helpers') 2 | 3 | describe('sampledBy', () => { 4 | it('should return stream', () => { 5 | expect(prop().sampledBy(stream())).to.be.observable.stream() 6 | expect(stream().sampledBy(prop())).to.be.observable.stream() 7 | }) 8 | 9 | it('should be ended if array of ended observables provided', () => { 10 | const a = send(stream(), [end()]) 11 | expect(prop().sampledBy(a)).to.emit([end({current: true})]) 12 | }) 13 | 14 | it('should be ended and emmit current (once) if array of ended properties provided and each of them has current', () => { 15 | const a = send(prop(), [value(1), end()]) 16 | const b = send(prop(), [value(2), end()]) 17 | const s2 = a.sampledBy(b) 18 | expect(s2).to.emit([value(1, {current: true}), end({current: true})]) 19 | expect(s2).to.emit([end({current: true})]) 20 | }) 21 | 22 | it('should activate sources', () => { 23 | const a = stream() 24 | const b = prop() 25 | expect(a.sampledBy(b)).to.activate(a, b) 26 | }) 27 | 28 | it('should handle events and current from observables', () => { 29 | const a = stream() 30 | const b = send(prop(), [value(0)]) 31 | expect(a.sampledBy(b)).to.emit([value(2), value(4), value(4), end()], () => { 32 | send(b, [value(1)]) 33 | send(a, [value(2)]) 34 | send(b, [value(3)]) 35 | send(a, [value(4)]) 36 | send(b, [value(5), value(6), end()]) 37 | }) 38 | }) 39 | 40 | it('should accept optional combinator function', () => { 41 | const join = (...args) => args.join('+') 42 | const a = stream() 43 | const b = send(prop(), [value(0)]) 44 | expect(a.sampledBy(b, join)).to.emit([value('2+3'), value('4+5'), value('4+6'), end()], () => { 45 | send(b, [value(1)]) 46 | send(a, [value(2)]) 47 | send(b, [value(3)]) 48 | send(a, [value(4)]) 49 | send(b, [value(5), value(6), end()]) 50 | }) 51 | }) 52 | 53 | it('one sampledBy should remove listeners of another', () => { 54 | const a = send(prop(), [value(0)]) 55 | const b = stream() 56 | const s1 = a.sampledBy(b) 57 | const s2 = a.sampledBy(b) 58 | activate(s1) 59 | activate(s2) 60 | deactivate(s2) 61 | expect(s1).to.emit([value(0)], () => send(b, [value(1)])) 62 | }) 63 | 64 | // https://github.com/kefirjs/kefir/issues/98 65 | it('should work nice for emitating atomic updates', () => { 66 | const a = stream() 67 | const b = a.map(x => x + 2) 68 | const c = a.map(x => x * 2) 69 | expect(b.sampledBy(c, (x, y) => [x, y])).to.emit([value([3, 2]), value([4, 4]), value([5, 6])], () => 70 | send(a, [value(1), value(2), value(3)]) 71 | ) 72 | }) 73 | }) 74 | -------------------------------------------------------------------------------- /test/specs/sequentially.js: -------------------------------------------------------------------------------- 1 | const {value, end, Kefir, expect} = require('../test-helpers') 2 | 3 | describe('sequentially', () => { 4 | it('should return stream', () => { 5 | expect(Kefir.sequentially(100, [1, 2, 3])).to.be.observable.stream() 6 | }) 7 | 8 | it('should be ended if empty array provided', () => { 9 | expect(Kefir.sequentially(100, [])).to.emitInTime([[0, end({current: true})]]) 10 | }) 11 | 12 | it('should emmit values at certain time then end', () => { 13 | expect(Kefir.sequentially(100, [1, 2, 3])).to.emitInTime([ 14 | [100, value(1)], 15 | [200, value(2)], 16 | [300, value(3)], 17 | [300, end()], 18 | ]) 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /test/specs/skip-duplicates.js: -------------------------------------------------------------------------------- 1 | const {stream, prop, send, value, error, end, Kefir, expect} = require('../test-helpers') 2 | 3 | describe('skipDuplicates', () => { 4 | const roundlyEqual = (a, b) => Math.round(a) === Math.round(b) 5 | 6 | describe('stream', () => { 7 | it('should return stream', () => { 8 | expect(stream().skipDuplicates()).to.be.observable.stream() 9 | }) 10 | 11 | it('should activate/deactivate source', () => { 12 | const a = stream() 13 | expect(a.skipDuplicates()).to.activate(a) 14 | }) 15 | 16 | it('should be ended if source was ended', () => 17 | expect(send(stream(), [end()]).skipDuplicates()).to.emit([end({current: true})])) 18 | 19 | it('should handle events (default comparator)', () => { 20 | const a = stream() 21 | expect(a.skipDuplicates()).to.emit([value(1), value(2), value(3), end()], () => 22 | send(a, [value(1), value(1), value(2), value(3), value(3), end()]) 23 | ) 24 | }) 25 | 26 | it('should handle events (custom comparator)', () => { 27 | const a = stream() 28 | expect(a.skipDuplicates(roundlyEqual)).to.emit([value(1), value(2), value(3.8), end()], () => 29 | send(a, [value(1), value(1.1), value(2), value(3.8), value(4), end()]) 30 | ) 31 | }) 32 | 33 | it('errors should flow', () => { 34 | const a = stream() 35 | expect(a.skipDuplicates()).to.flowErrors(a) 36 | }) 37 | 38 | it('should help with creating circular dependencies', () => { 39 | // https://github.com/kefirjs/kefir/issues/42 40 | 41 | const a = stream() 42 | const b = Kefir.pool() 43 | b.plug(a) 44 | b.plug(b.map(x => x).skipDuplicates()) 45 | expect(b).to.emit([value(1), value(1)], () => send(a, [value(1)])) 46 | }) 47 | }) 48 | 49 | describe('property', () => { 50 | it('should return property', () => { 51 | expect(prop().skipDuplicates()).to.be.observable.property() 52 | }) 53 | 54 | it('should activate/deactivate source', () => { 55 | const a = prop() 56 | expect(a.skipDuplicates()).to.activate(a) 57 | }) 58 | 59 | it('should be ended if source was ended', () => 60 | expect(send(prop(), [end()]).skipDuplicates()).to.emit([end({current: true})])) 61 | 62 | it('should handle events and current (default comparator)', () => { 63 | const a = send(prop(), [value(1)]) 64 | expect(a.skipDuplicates()).to.emit([value(1, {current: true}), value(2), value(3), end()], () => 65 | send(a, [value(1), value(1), value(2), value(3), value(3), end()]) 66 | ) 67 | }) 68 | 69 | it('should handle events and current (custom comparator)', () => { 70 | const a = send(prop(), [value(1)]) 71 | expect(a.skipDuplicates(roundlyEqual)).to.emit([value(1, {current: true}), value(2), value(3), end()], () => 72 | send(a, [value(1.1), value(1.2), value(2), value(3), value(3.2), end()]) 73 | ) 74 | }) 75 | 76 | it('errors should flow', () => { 77 | const a = prop() 78 | expect(a.skipDuplicates()).to.flowErrors(a) 79 | }) 80 | }) 81 | }) 82 | -------------------------------------------------------------------------------- /test/specs/skip.js: -------------------------------------------------------------------------------- 1 | const {stream, prop, send, value, error, end, Kefir, expect} = require('../test-helpers') 2 | 3 | describe('skip', () => { 4 | describe('stream', () => { 5 | it('should return stream', () => { 6 | expect(stream().skip(3)).to.be.observable.stream() 7 | }) 8 | 9 | it('should activate/deactivate source', () => { 10 | const a = stream() 11 | expect(a.skip(3)).to.activate(a) 12 | }) 13 | 14 | it('should be ended if source was ended', () => { 15 | expect(send(stream(), [end()]).skip(3)).to.emit([end({current: true})]) 16 | }) 17 | 18 | it('should handle events (less than `n`)', () => { 19 | const a = stream() 20 | expect(a.skip(3)).to.emit([end()], () => send(a, [value(1), value(2), end()])) 21 | }) 22 | 23 | it('should handle events (more than `n`)', () => { 24 | const a = stream() 25 | expect(a.skip(3)).to.emit([value(4), value(5), end()], () => 26 | send(a, [value(1), value(2), value(3), value(4), value(5), end()]) 27 | ) 28 | }) 29 | 30 | it('should handle events (n == 0)', () => { 31 | const a = stream() 32 | expect(a.skip(0)).to.emit([value(1), value(2), value(3), end()], () => 33 | send(a, [value(1), value(2), value(3), end()]) 34 | ) 35 | }) 36 | 37 | it('should handle events (n == -1)', () => { 38 | const a = stream() 39 | expect(a.skip(-1)).to.emit([value(1), value(2), value(3), end()], () => 40 | send(a, [value(1), value(2), value(3), end()]) 41 | ) 42 | }) 43 | 44 | it('errors should flow', () => { 45 | const a = stream() 46 | expect(a.skip(1)).to.flowErrors(a) 47 | }) 48 | }) 49 | 50 | describe('property', () => { 51 | it('should return property', () => { 52 | expect(prop().skip(3)).to.be.observable.property() 53 | }) 54 | 55 | it('should activate/deactivate source', () => { 56 | const a = prop() 57 | expect(a.skip(3)).to.activate(a) 58 | }) 59 | 60 | it('should be ended if source was ended', () => 61 | expect(send(prop(), [end()]).skip(3)).to.emit([end({current: true})])) 62 | 63 | it('should handle events and current (less than `n`)', () => { 64 | const a = send(prop(), [value(1)]) 65 | expect(a.skip(3)).to.emit([end()], () => send(a, [value(2), end()])) 66 | }) 67 | 68 | it('should handle events and current (more than `n`)', () => { 69 | const a = send(prop(), [value(1)]) 70 | expect(a.skip(3)).to.emit([value(4), value(5), end()], () => 71 | send(a, [value(2), value(3), value(4), value(5), end()]) 72 | ) 73 | }) 74 | 75 | it('should handle events and current (n == 0)', () => { 76 | const a = send(prop(), [value(1)]) 77 | expect(a.skip(0)).to.emit([value(1, {current: true}), value(2), value(3), end()], () => 78 | send(a, [value(2), value(3), end()]) 79 | ) 80 | }) 81 | 82 | it('should handle events and current (n == -1)', () => { 83 | const a = send(prop(), [value(1)]) 84 | expect(a.skip(-1)).to.emit([value(1, {current: true}), value(2), value(3), end()], () => 85 | send(a, [value(2), value(3), end()]) 86 | ) 87 | }) 88 | 89 | it('errors should flow', () => { 90 | const a = prop() 91 | expect(a.skip(1)).to.flowErrors(a) 92 | }) 93 | }) 94 | }) 95 | -------------------------------------------------------------------------------- /test/specs/spy.js: -------------------------------------------------------------------------------- 1 | const {stream, send, value, error, end, expect} = require('../test-helpers') 2 | const sinon = require('sinon') 3 | 4 | describe('spy', () => { 5 | describe('adding', () => { 6 | it('should return the stream', () => { 7 | expect(stream().spy()).to.be.observable.stream() 8 | }) 9 | 10 | it('should not activate the stream', () => { 11 | const a = stream().spy() 12 | expect(a).not.to.be.active() 13 | }) 14 | }) 15 | 16 | describe('removing', () => { 17 | it('should return the stream', () => { 18 | expect( 19 | stream() 20 | .spy() 21 | .offSpy() 22 | ).to.be.observable.stream() 23 | }) 24 | 25 | it('should not activate the stream', () => { 26 | const a = stream() 27 | .spy() 28 | .offSpy() 29 | expect(a).not.to.be.active() 30 | }) 31 | }) 32 | 33 | describe('console', () => { 34 | let stub 35 | beforeEach(() => (stub = sinon.stub(console, 'log'))) 36 | 37 | afterEach(() => stub.restore()) 38 | 39 | it('should have a default name', () => { 40 | const a = stream() 41 | a.spy() 42 | expect(a).to.emit([value(1), value(2), value(3)], () => { 43 | send(a, [value(1), value(2), value(3)]) 44 | expect(console.log).to.have.been.calledWith('[stream]', '', 1) 45 | expect(console.log).to.have.been.calledWith('[stream]', '', 2) 46 | expect(console.log).to.have.been.calledWith('[stream]', '', 3) 47 | }) 48 | }) 49 | 50 | it('should use the name', () => { 51 | const a = stream() 52 | a.spy('spied') 53 | expect(a).to.emit([value(1), value(2), value(3)], () => { 54 | send(a, [value(1), value(2), value(3)]) 55 | expect(console.log).to.have.been.calledWith('spied', '', 1) 56 | expect(console.log).to.have.been.calledWith('spied', '', 2) 57 | expect(console.log).to.have.been.calledWith('spied', '', 3) 58 | }) 59 | }) 60 | 61 | it('should not log if the spy has been removed', () => { 62 | const a = stream() 63 | a.spy() 64 | a.offSpy() 65 | expect(a).to.emit([value(1), value(2), value(3)], () => { 66 | send(a, [value(1), value(2), value(3)]) 67 | expect(console.log).not.to.have.been.called 68 | }) 69 | }) 70 | }) 71 | }) 72 | -------------------------------------------------------------------------------- /test/specs/static-land.js: -------------------------------------------------------------------------------- 1 | const {value, error, end, Kefir, expect} = require('../test-helpers') 2 | const {Observable} = Kefir.staticLand 3 | 4 | describe('Kefir.staticLand.Observable', () => { 5 | it('of works', () => { 6 | expect(Observable.of(2)).to.emit([value(2, {current: true}), end({current: true})]) 7 | }) 8 | 9 | it('empty works', () => { 10 | expect(Observable.empty()).to.emit([end({current: true})]) 11 | }) 12 | 13 | it('concat works', () => { 14 | expect(Observable.concat(Observable.of(2), Observable.empty())).to.emit([ 15 | value(2, {current: true}), 16 | end({current: true}), 17 | ]) 18 | }) 19 | 20 | it('map works', () => { 21 | expect(Observable.map(x => x * 3, Observable.of(2))).to.emit([value(6, {current: true}), end({current: true})]) 22 | }) 23 | 24 | it('bimap works', () => { 25 | expect( 26 | Observable.bimap( 27 | x => x, 28 | x => x * 3, 29 | Observable.of(2) 30 | ) 31 | ).to.emit([value(6, {current: true}), end({current: true})]) 32 | expect( 33 | Observable.bimap( 34 | x => x * 3, 35 | x => x, 36 | Kefir.constantError(2) 37 | ) 38 | ).to.emit([error(6, {current: true}), end({current: true})]) 39 | }) 40 | 41 | it('ap works', () => { 42 | expect( 43 | Observable.ap( 44 | Observable.of(x => x * 3), 45 | Observable.of(2) 46 | ) 47 | ).to.emit([value(6, {current: true}), end({current: true})]) 48 | }) 49 | 50 | it('chain works', () => { 51 | expect(Observable.chain(x => Observable.of(x * 3), Observable.of(2))).to.emit([ 52 | value(6, {current: true}), 53 | end({current: true}), 54 | ]) 55 | }) 56 | }) 57 | -------------------------------------------------------------------------------- /test/specs/sugar.js: -------------------------------------------------------------------------------- 1 | const {stream, prop, send, value, error, end, expect} = require('../test-helpers') 2 | 3 | describe('setName', () => { 4 | it('should return same observable', () => { 5 | const a = stream() 6 | expect(a.setName('foo')).to.equal(a) 7 | expect(a.setName(stream(), 'foo')).to.equal(a) 8 | }) 9 | 10 | it('should update observable name', () => { 11 | const a = stream() 12 | expect(a.toString()).to.equal('[stream]') 13 | a.setName('foo') 14 | expect(a.toString()).to.equal('[foo]') 15 | a.setName(stream().setName('foo'), 'bar') 16 | expect(a.toString()).to.equal('[foo.bar]') 17 | }) 18 | }) 19 | 20 | describe('awaiting', () => { 21 | it('stream and stream', () => { 22 | const a = stream() 23 | const b = stream() 24 | expect(a.awaiting(b)).to.emit([value(false, {current: true}), value(true), value(false), value(true)], () => { 25 | send(a, [value(1)]) 26 | send(b, [value(1)]) 27 | send(b, [value(1)]) 28 | send(a, [value(1)]) 29 | send(a, [value(1)]) 30 | }) 31 | }) 32 | 33 | it('property and stream', () => { 34 | const a = send(prop(), [value(1)]) 35 | const b = stream() 36 | expect(a.awaiting(b)).to.emit([value(true, {current: true}), value(false), value(true)], () => { 37 | send(a, [value(1)]) 38 | send(b, [value(1)]) 39 | send(b, [value(1)]) 40 | send(a, [value(1)]) 41 | send(a, [value(1)]) 42 | }) 43 | }) 44 | 45 | it('property and property', () => { 46 | const a = send(prop(), [value(1)]) 47 | const b = send(prop(), [value(1)]) 48 | expect(a.awaiting(b)).to.emit([value(false, {current: true}), value(true), value(false), value(true)], () => { 49 | send(a, [value(1)]) 50 | send(b, [value(1)]) 51 | send(b, [value(1)]) 52 | send(a, [value(1)]) 53 | send(a, [value(1)]) 54 | }) 55 | }) 56 | }) 57 | -------------------------------------------------------------------------------- /test/specs/take-errors.js: -------------------------------------------------------------------------------- 1 | const {stream, prop, send, value, error, end, Kefir, pool, expect} = require('../test-helpers') 2 | 3 | describe('takeErrors', () => { 4 | describe('stream', () => { 5 | it('should return stream', () => { 6 | expect(stream().takeErrors(3)).to.be.observable.stream() 7 | }) 8 | 9 | it('should activate/deactivate source', () => { 10 | const a = stream() 11 | expect(a.takeErrors(3)).to.activate(a) 12 | }) 13 | 14 | it('should be ended if source was ended', () => 15 | expect(send(stream(), [end()]).takeErrors(3)).to.emit([end({current: true})])) 16 | 17 | it('should be ended if `n` is 0', () => { 18 | expect(stream().takeErrors(0)).to.emit([end({current: true})]) 19 | }) 20 | 21 | it('should handle events (less than `n`)', () => { 22 | const a = stream() 23 | expect(a.takeErrors(3)).to.emit([error(1), error(2), end()], () => send(a, [error(1), error(2), end()])) 24 | }) 25 | 26 | it('should handle events (more than `n`)', () => { 27 | const a = stream() 28 | expect(a.takeErrors(3)).to.emit([error(1), error(2), error(3), end()], () => 29 | send(a, [error(1), error(2), error(3), error(4), error(5), end()]) 30 | ) 31 | }) 32 | 33 | it('values should flow', () => { 34 | const a = stream() 35 | expect(a.takeErrors(1)).to.emit([value(1), value(2), value(3), end()], () => 36 | send(a, [value(1), value(2), value(3), end()]) 37 | ) 38 | }) 39 | 40 | it('should emit once on circular dependency', () => { 41 | const a = pool() 42 | const b = a.takeErrors(1).mapErrors(x => x + 1) 43 | a.plug(b) 44 | 45 | expect(b).to.emit([error(2), end()], () => send(a, [error(1), error(2), error(3), error(4), error(5)])) 46 | }) 47 | }) 48 | 49 | describe('property', () => { 50 | it('should return property', () => { 51 | expect(prop().takeErrors(3)).to.be.observable.property() 52 | }) 53 | 54 | it('should activate/deactivate source', () => { 55 | const a = prop() 56 | expect(a.takeErrors(3)).to.activate(a) 57 | }) 58 | 59 | it('should be ended if source was ended', () => 60 | expect(send(prop(), [end()]).takeErrors(3)).to.emit([end({current: true})])) 61 | 62 | it('should be ended if `n` is 0', () => expect(prop().takeErrors(0)).to.emit([end({current: true})])) 63 | 64 | it('should handle events and current (less than `n`)', () => { 65 | const a = send(prop(), [error(1)]) 66 | expect(a.takeErrors(3)).to.emit([error(1, {current: true}), error(2), end()], () => send(a, [error(2), end()])) 67 | }) 68 | 69 | it('should handle events and current (more than `n`)', () => { 70 | const a = send(prop(), [error(1)]) 71 | expect(a.takeErrors(3)).to.emit([error(1, {current: true}), error(2), error(3), end()], () => 72 | send(a, [error(2), error(3), error(4), error(5), end()]) 73 | ) 74 | }) 75 | 76 | it('should work correctly with .constant', () => 77 | expect(Kefir.constantError(1).takeErrors(1)).to.emit([error(1, {current: true}), end({current: true})])) 78 | 79 | it('values should flow', () => { 80 | const a = send(prop(), [value(1)]) 81 | expect(a.takeErrors(1)).to.emit([value(1, {current: true}), value(2), value(3), value(4), end()], () => 82 | send(a, [value(2), value(3), value(4), end()]) 83 | ) 84 | }) 85 | }) 86 | }) 87 | -------------------------------------------------------------------------------- /test/specs/take-while.js: -------------------------------------------------------------------------------- 1 | const {stream, prop, send, value, error, end, Kefir, expect} = require('../test-helpers') 2 | 3 | describe('takeWhile', () => { 4 | describe('stream', () => { 5 | it('should return stream', () => { 6 | expect(stream().takeWhile(() => true)).to.be.observable.stream() 7 | }) 8 | 9 | it('should activate/deactivate source', () => { 10 | const a = stream() 11 | expect(a.takeWhile(() => true)).to.activate(a) 12 | }) 13 | 14 | it('should be ended if source was ended', () => { 15 | expect(send(stream(), [end()]).takeWhile(() => true)).to.emit([end({current: true})]) 16 | }) 17 | 18 | it('should handle events', () => { 19 | const a = stream() 20 | expect(a.takeWhile(x => x < 4)).to.emit([value(1), value(2), value(3), end()], () => { 21 | send(a, [value(1), value(2), value(3), value(4), value(5), end()]) 22 | }) 23 | }) 24 | 25 | it('should handle events (natural end)', () => { 26 | const a = stream() 27 | expect(a.takeWhile(x => x < 4)).to.emit([value(1), value(2), end()], () => send(a, [value(1), value(2), end()])) 28 | }) 29 | 30 | it('should handle events (with `-> false`)', () => { 31 | const a = stream() 32 | expect(a.takeWhile(() => false)).to.emit([end()], () => send(a, [value(1), value(2), end()])) 33 | }) 34 | 35 | it('shoud use id as default predicate', () => { 36 | const a = stream() 37 | expect(a.takeWhile()).to.emit([value(1), value(2), end()], () => 38 | send(a, [value(1), value(2), value(0), value(5), end()]) 39 | ) 40 | }) 41 | 42 | it('errors should flow', () => { 43 | const a = stream() 44 | expect(a.takeWhile()).to.flowErrors(a) 45 | }) 46 | }) 47 | 48 | describe('property', () => { 49 | it('should return property', () => { 50 | expect(prop().takeWhile(() => true)).to.be.observable.property() 51 | }) 52 | 53 | it('should activate/deactivate source', () => { 54 | const a = prop() 55 | expect(a.takeWhile(() => true)).to.activate(a) 56 | }) 57 | 58 | it('should be ended if source was ended', () => 59 | expect(send(prop(), [end()]).takeWhile(() => true)).to.emit([end({current: true})])) 60 | 61 | it('should be ended if calback was `-> false` and source has a current', () => 62 | expect(send(prop(), [value(1)]).takeWhile(() => false)).to.emit([end({current: true})])) 63 | 64 | it('should handle events', () => { 65 | const a = send(prop(), [value(1)]) 66 | expect(a.takeWhile(x => x < 4)).to.emit([value(1, {current: true}), value(2), value(3), end()], () => 67 | send(a, [value(2), value(3), value(4), value(5), end()]) 68 | ) 69 | }) 70 | 71 | it('should handle events (natural end)', () => { 72 | const a = send(prop(), [value(1)]) 73 | expect(a.takeWhile(x => x < 4)).to.emit([value(1, {current: true}), value(2), end()], () => 74 | send(a, [value(2), end()]) 75 | ) 76 | }) 77 | 78 | it('should handle events (with `-> false`)', () => { 79 | const a = prop() 80 | expect(a.takeWhile(() => false)).to.emit([end()], () => send(a, [value(1), value(2), end()])) 81 | }) 82 | 83 | it('shoud use id as default predicate', () => { 84 | let a = send(prop(), [value(1)]) 85 | expect(a.takeWhile()).to.emit([value(1, {current: true}), value(2), end()], () => 86 | send(a, [value(2), value(0), value(5), end()]) 87 | ) 88 | a = send(prop(), [value(0)]) 89 | expect(a.takeWhile()).to.emit([end({current: true})], () => send(a, [value(2), value(0), value(5), end()])) 90 | }) 91 | 92 | it('errors should flow', () => { 93 | const a = prop() 94 | expect(a.takeWhile()).to.flowErrors(a) 95 | }) 96 | }) 97 | }) 98 | -------------------------------------------------------------------------------- /test/specs/take.js: -------------------------------------------------------------------------------- 1 | const {stream, prop, send, value, error, end, Kefir, pool, expect} = require('../test-helpers') 2 | 3 | describe('take', () => { 4 | describe('stream', () => { 5 | it('should return stream', () => { 6 | expect(stream().take(3)).to.be.observable.stream() 7 | }) 8 | 9 | it('should activate/deactivate source', () => { 10 | const a = stream() 11 | expect(a.take(3)).to.activate(a) 12 | }) 13 | 14 | it('should be ended if source was ended', () => { 15 | expect(send(stream(), [end()]).take(3)).to.emit([end({current: true})]) 16 | }) 17 | 18 | it('should be ended if `n` is 0', () => { 19 | expect(stream().take(0)).to.emit([end({current: true})]) 20 | }) 21 | 22 | it('should handle events (less than `n`)', () => { 23 | const a = stream() 24 | expect(a.take(3)).to.emit([value(1), value(2), end()], () => send(a, [value(1), value(2), end()])) 25 | }) 26 | 27 | it('should handle events (more than `n`)', () => { 28 | const a = stream() 29 | expect(a.take(3)).to.emit([value(1), value(2), value(3), end()], () => 30 | send(a, [value(1), value(2), value(3), value(4), value(5), end()]) 31 | ) 32 | }) 33 | 34 | it('errors should flow', () => { 35 | const a = stream() 36 | expect(a.take(1)).to.flowErrors(a) 37 | }) 38 | 39 | it('should emit once on circular dependency', () => { 40 | const a = pool() 41 | const b = a.take(1).map(x => x + 1) 42 | a.plug(b) 43 | 44 | expect(b).to.emit([value(2), end()], () => send(a, [value(1), value(2), value(3), value(4), value(5)])) 45 | }) 46 | }) 47 | 48 | describe('property', () => { 49 | it('should return property', () => expect(prop().take(3)).to.be.observable.property()) 50 | 51 | it('should activate/deactivate source', () => { 52 | const a = prop() 53 | expect(a.take(3)).to.activate(a) 54 | }) 55 | 56 | it('should be ended if source was ended', () => { 57 | expect(send(prop(), [end()]).take(3)).to.emit([end({current: true})]) 58 | }) 59 | 60 | it('should be ended if `n` is 0', () => { 61 | expect(prop().take(0)).to.emit([end({current: true})]) 62 | }) 63 | 64 | it('should handle events and current (less than `n`)', () => { 65 | const a = send(prop(), [value(1)]) 66 | expect(a.take(3)).to.emit([value(1, {current: true}), value(2), end()], () => send(a, [value(2), end()])) 67 | }) 68 | 69 | it('should handle events and current (more than `n`)', () => { 70 | const a = send(prop(), [value(1)]) 71 | expect(a.take(3)).to.emit([value(1, {current: true}), value(2), value(3), end()], () => 72 | send(a, [value(2), value(3), value(4), value(5), end()]) 73 | ) 74 | }) 75 | 76 | it('should work correctly with .constant', () => 77 | expect(Kefir.constant(1).take(1)).to.emit([value(1, {current: true}), end({current: true})])) 78 | 79 | it('errors should flow', () => { 80 | const a = prop() 81 | expect(a.take(1)).to.flowErrors(a) 82 | }) 83 | }) 84 | }) 85 | -------------------------------------------------------------------------------- /test/specs/thru.js: -------------------------------------------------------------------------------- 1 | const sinon = require('sinon') 2 | const {stream, expect} = require('../test-helpers') 3 | 4 | describe('thru', () => { 5 | it('should call function and return result', () => { 6 | const spy = sinon.spy(() => 0) 7 | const a = stream() 8 | 9 | expect(a.thru(spy)).to.equal(0) 10 | expect(spy.calledWith(a)).to.equal(true) 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /test/specs/values-to-errors.js: -------------------------------------------------------------------------------- 1 | const {stream, prop, send, value, error, end, Kefir, expect} = require('../test-helpers') 2 | 3 | const handler = x => ({ 4 | convert: x < 0, 5 | error: x * 3, 6 | }) 7 | 8 | describe('valuesToErrors', () => { 9 | describe('stream', () => { 10 | it('should return stream', () => { 11 | expect(stream().valuesToErrors(() => {})).to.be.observable.stream() 12 | }) 13 | 14 | it('should activate/deactivate source', () => { 15 | const a = stream() 16 | expect(a.valuesToErrors(() => {})).to.activate(a) 17 | }) 18 | 19 | it('should be ended if source was ended', () => 20 | expect(send(stream(), [end()]).valuesToErrors(() => {})).to.emit([end({current: true})])) 21 | 22 | it('should handle events', () => { 23 | const a = stream() 24 | expect(a.valuesToErrors(handler)).to.emit([value(1), error(-6), error(-3), error(-12), value(5), end()], () => 25 | send(a, [value(1), value(-2), error(-3), value(-4), value(5), end()]) 26 | ) 27 | }) 28 | 29 | it('default handler should convert all values', () => { 30 | const a = stream() 31 | expect(a.valuesToErrors()).to.emit([error(1), error(-2), error(-3), error(-4), error(5), end()], () => 32 | send(a, [value(1), value(-2), error(-3), value(-4), value(5), end()]) 33 | ) 34 | }) 35 | }) 36 | 37 | describe('property', () => { 38 | it('should return property', () => { 39 | expect(prop().valuesToErrors(() => {})).to.be.observable.property() 40 | }) 41 | 42 | it('should activate/deactivate source', () => { 43 | const a = prop() 44 | expect(a.valuesToErrors(() => {})).to.activate(a) 45 | }) 46 | 47 | it('should be ended if source was ended', () => 48 | expect(send(prop(), [end()]).valuesToErrors(() => {})).to.emit([end({current: true})])) 49 | 50 | it('should handle events', () => { 51 | const a = send(prop(), [value(1)]) 52 | expect(a.valuesToErrors(handler)).to.emit( 53 | [value(1, {current: true}), error(-6), error(-3), error(-12), value(5), end()], 54 | () => send(a, [value(-2), error(-3), value(-4), value(5), end()]) 55 | ) 56 | }) 57 | 58 | it('should handle currents', () => { 59 | let a = send(prop(), [value(2)]) 60 | expect(a.valuesToErrors(handler)).to.emit([value(2, {current: true})]) 61 | a = send(prop(), [value(-2)]) 62 | expect(a.valuesToErrors(handler)).to.emit([error(-6, {current: true})]) 63 | a = send(prop(), [error(-2)]) 64 | expect(a.valuesToErrors(handler)).to.emit([error(-2, {current: true})]) 65 | }) 66 | }) 67 | }) 68 | -------------------------------------------------------------------------------- /test/specs/with-interval.js: -------------------------------------------------------------------------------- 1 | const {value, error, end, Kefir, expect} = require('../test-helpers') 2 | 3 | describe('withInterval', () => { 4 | it('should return stream', () => { 5 | expect(Kefir.withInterval(100, () => {})).to.be.observable.stream() 6 | }) 7 | 8 | it('should work as expected', () => { 9 | let i = 0 10 | const fn = emitter => { 11 | i++ 12 | if (i === 2) { 13 | emitter.error(-1) 14 | } else { 15 | emitter.emit(i) 16 | emitter.emit(i * 2) 17 | } 18 | if (i === 3) { 19 | return emitter.end() 20 | } 21 | } 22 | expect(Kefir.withInterval(100, fn)).to.emitInTime([ 23 | [100, value(1)], 24 | [100, value(2)], 25 | [200, error(-1)], 26 | [300, value(3)], 27 | [300, value(6)], 28 | [300, end()], 29 | ]) 30 | }) 31 | 32 | it('should support emitter.emitEvent', () => { 33 | let i = 0 34 | const fn = emitter => { 35 | i++ 36 | if (i === 2) { 37 | emitter.emitEvent({type: 'error', value: -1, current: false}) 38 | } else { 39 | emitter.emitEvent({type: 'value', value: i, current: true}) // current should be ignored 40 | emitter.emitEvent({type: 'value', value: i * 2, current: false}) 41 | } 42 | if (i === 3) { 43 | return emitter.emitEvent({type: 'end', value: undefined, current: false}) 44 | } 45 | } 46 | expect(Kefir.withInterval(100, fn)).to.emitInTime([ 47 | [100, value(1)], 48 | [100, value(2)], 49 | [200, error(-1)], 50 | [300, value(3)], 51 | [300, value(6)], 52 | [300, end()], 53 | ]) 54 | }) 55 | }) 56 | --------------------------------------------------------------------------------