···11+# Changesets
22+33+Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
44+with multi-package repos, or single-package repos to help you version and publish your code. You can
55+find the full documentation for it [in our repository](https://github.com/changesets/changesets)
66+77+We have a quick list of common questions to get you started engaging with this project in
88+[our documentation](https://github.com/changesets/changesets/blob/master/docs/common-questions.md)
···11+---
22+name: 'RFC'
33+about: Propose an enhancement / feature and start a discussion
44+title: 'RFC: Your Proposal'
55+labels: "future \U0001F52E"
66+---
77+88+<!--
99+ 🚨 RFCs are for proposed changes (not bugs or questions)
1010+ Specifically they are whenever you'd like to see new features
1111+ being added, or enable new use-cases.
1212+1313+ Please open a Bug Report for issues/bugs, and use GitHub Discussions
1414+ or the Discord channel for questions instead.
1515+-->
1616+1717+## Summary
1818+1919+<!--
2020+ Describe in a couple of words *what* you're proposing.
2121+ If relevant, include *why* this should be addressed now.
2222+ The problem should be clearly stated and the solution
2323+ should be summarised.
2424+-->
2525+2626+## Proposed Solution
2727+2828+<!--
2929+ Explain the solution you're proposing in detail.
3030+ *How* will this change be implemented, and how does it work?
3131+-->
3232+3333+## Requirements
3434+3535+<!--
3636+ This section is *optional*.
3737+ But if your proposed solution has multiple ways
3838+ of being implemented, you don't want to state how
3939+ it may be implemented, or you don't know yet how
4040+ it will be implemented, then:
4141+ *List* what the implementation needs to achieve to fulfil this RFC;
4242+-->
+46
.github/ISSUE_TEMPLATE/bug_report.yaml
···11+name: "\U0001F41E Bug report"
22+description: Report an issue with graphql.web
33+labels: []
44+body:
55+ - type: markdown
66+ attributes:
77+ value: |
88+ Thanks for taking the time to fill out this bug report!
99+ - type: markdown
1010+ attributes:
1111+ value: |
1212+ Thanks for taking the time to fill out this bug report!
1313+ - type: textarea
1414+ id: bug-description
1515+ attributes:
1616+ label: Describe the bug
1717+ description: Please describe your bug clearly and concisely.
1818+ placeholder: Bug description
1919+ validations:
2020+ required: true
2121+ - type: input
2222+ id: reproduction
2323+ attributes:
2424+ label: Reproduction
2525+ description: Please provide a reproduction link, e.g. to a sandbox
2626+ placeholder: Reproduction
2727+ validations:
2828+ required: true
2929+ - type: textarea
3030+ id: version
3131+ attributes:
3232+ label: Package version
3333+ description: The versions of the relevant urql packages you are using
3434+ placeholder: "wonka@6.2.5"
3535+ validations:
3636+ required: true
3737+ - type: checkboxes
3838+ id: checkboxes
3939+ attributes:
4040+ label: Validations
4141+ description: Before submitting the issue, please make sure you do the following
4242+ options:
4343+ - label: I can confirm that this is a bug report, and not a feature request, RFC, question, or discussion, for which GitHub Discussions should be used
4444+ required: true
4545+ - label: Follow our [Code of Conduct](https://github.com/0no-co/graphql.web/blob/main/CODE_OF_CONDUCT.md)
4646+ required: true
+8
.github/ISSUE_TEMPLATE/config.yml
···11+blank_issues_enabled: true
22+contact_links:
33+ - name: Ask a question
44+ url: https://github.com/@0no-co/wonka/discussions
55+ about: Ask questions and discuss with other community members
66+ - name: Join the Discord
77+ url: https://discord.gg/3EYgqrYJFS
88+ about: Chat with maintainers and other community members
+22
.github/PULL_REQUEST_TEMPLATE.md
···11+<!--
22+ Thanks for opening a pull request! We appreciate your dedication and help!
33+ Before submitting your pull request, please make sure to read our CONTRIBUTING guide.
44+55+ The best contribution is always a PR, but please make sure to open an issue or discuss
66+ your changes first, if you’re looking to submit a larger PR.
77+88+ If this PR is already related to an issue, please reference it like so:
99+ Resolves #123
1010+-->
1111+1212+## Summary
1313+1414+<!-- What's the motivation of this change? What does it solve? -->
1515+1616+## Set of changes
1717+1818+<!--
1919+ Roughly list the changes you've made and which packages are affected.
2020+ Leave some notes on what may be noteworthy files you've changed.
2121+ And lastly, please let us know if you think this is a breaking change.
2222+-->
···11+# wonka
22+33+## 6.3.5
44+55+### Patch Changes
66+77+- Exclude `sourcesContent` from published sourcemaps
88+ Submitted by [@kitten](https://github.com/kitten) (See [`68e608f`](https://github.com/0no-co/wonka/commit/68e608f46244e82d41c952ecfa1d7f0096e168f6))
99+1010+## 6.3.4
1111+1212+### Patch Changes
1313+1414+- Add missing `Symbol.observable` global declaration back to typings
1515+ Submitted by [@kitten](https://github.com/kitten) (See [#168](https://github.com/0no-co/wonka/pull/168))
1616+1717+## 6.3.3
1818+1919+### Patch Changes
2020+2121+- Improve compatibility of `fromAsyncIterable` and `toAsyncIterable`. The `toAsyncIterable` will now output an object that's both an `AsyncIterator` and an `AsyncIterable`. Both helpers will now use a polyfill for `Symbol.asyncIterator` to improve compatibility with the Hermes engine and Babel transpilation
2222+ Submitted by [@kitten](https://github.com/kitten) (See [#165](https://github.com/0no-co/wonka/pull/165))
2323+2424+## 6.3.2
2525+2626+### Patch Changes
2727+2828+- Publish with npm provenance
2929+ Submitted by [@kitten](https://github.com/kitten) (See [#161](https://github.com/0no-co/wonka/pull/161))
3030+3131+## 6.3.1
3232+3333+### Patch Changes
3434+3535+- ⚠️ Fix missing `declare` keyword on internal ambient enums
3636+ Submitted by [@kitten](https://github.com/kitten) (See [#159](https://github.com/0no-co/wonka/pull/159))
3737+3838+## 6.3.0
3939+4040+### Minor Changes
4141+4242+- Add `addOne` argument to `takeWhile`, allowing an additional value to be issued
4343+ Submitted by [@kitten](https://github.com/kitten) (See [#156](https://github.com/0no-co/wonka/pull/156))
4444+4545+### Patch Changes
4646+4747+- Convert `Push<T>` and `Start<T>` signals to `{ tag, 0: value }` objects, which are sufficiently backwards compatible and result in slightly faster execution in v8
4848+ Submitted by [@kitten](https://github.com/kitten) (See [#155](https://github.com/0no-co/wonka/pull/155))
4949+5050+## 6.2.6
5151+5252+### Patch Changes
5353+5454+- ⚠️ Fix missing source contents in Wonka sourcemaps
5555+ Submitted by [@kitten](https://github.com/kitten) (See [`56d9708`](https://github.com/0no-co/wonka/commit/56d970861424fddd403262bf85d7e1e3572b15e2))
5656+- ⚠️ Fix internal `SignalKind` and `TalkbackKind` enums not compiling away
5757+ Submitted by [@kitten](https://github.com/kitten) (See [#154](https://github.com/0no-co/wonka/pull/154))
5858+5959+## 6.2.5
6060+6161+### Patch Changes
6262+6363+- Make `closed: boolean` on `ObservableSubscription`s a required field to comply with the Observable proposal's type spec
6464+ Submitted by [@naporin0624](https://github.com/naporin0624) (See [#151](https://github.com/0no-co/wonka/pull/151))
6565+6666+## 6.2.4
6767+6868+### Patch Changes
6969+7070+- Add missing overload definition for `filter`, which allows types to be narrowed, e.g. by specifying a type predicate return type
7171+ Submitted by [@kitten](https://github.com/kitten) (See [#149](https://github.com/0no-co/wonka/pull/149))
7272+7373+## 6.2.3
7474+7575+### Patch Changes
7676+7777+- ⚠️ Fix overload of `pipe` type not being applied in bundled `d.ts` file, by [@kitten](https://github.com/kitten) (See [#144](https://github.com/0no-co/wonka/pull/144))
7878+7979+## 6.2.2
8080+8181+### Patch Changes
8282+8383+- ⚠️ Fix missing `Symbol.observable` typings and remove `const enum` exports, which aren't usable in isolated modules, by [@kitten](https://github.com/kitten) (See [#141](https://github.com/0no-co/wonka/pull/141))
8484+8585+## 6.2.1
8686+8787+### Patch Changes
8888+8989+- ⚠️ Fix accidental addition of `postinstall` script rather than `prepare` script, by [@kitten](https://github.com/kitten) (See [#138](https://github.com/0no-co/wonka/pull/138))
9090+9191+## 6.2.0
9292+9393+### Minor Changes
9494+9595+- Implement `toAsyncIterable`, converting a Wonka source to a JS Async Iterable, by [@kitten](https://github.com/kitten) (See [#133](https://github.com/0no-co/wonka/pull/133))
9696+- Implement `d.ts` bundling. Only a single `wonka.d.ts` typings file will now be available to TypeScript, by [@kitten](https://github.com/kitten) (See [#135](https://github.com/0no-co/wonka/pull/135))
9797+- Add extensive TSDoc documentation for all `wonka` internals and exports. This will replace the documentation and give consumers more guidance on each of the library's extensive utilities, by [@kitten](https://github.com/kitten) (See [#136](https://github.com/0no-co/wonka/pull/136))
9898+9999+### Patch Changes
100100+101101+- ⚠️ Fix promise timing by adding missing `Promise.resolve()` tick to `toPromise` sink function, by [@kitten](https://github.com/kitten) (See [#131](https://github.com/0no-co/wonka/pull/131))
102102+- ⚠️ Fix implementation of Observable spec as such that Observable.subscribe(onNext, onError, onComplete) becomes valid, by [@kitten](https://github.com/kitten) (See [#132](https://github.com/0no-co/wonka/pull/132))
+163
CONTRIBUTING.md
···11+# Development
22+33+Thanks for contributing! We want to ensure that `wonka` evolves
44+and fulfills its idea of being a tiny & capable push & pull stream library!
55+66+## How to contribute?
77+88+We follow fairly standard but lenient rules around pull requests and issues.
99+Please pick a title that describes your change briefly, optionally in the imperative
1010+mood if possible.
1111+1212+If you have an idea for a feature or want to fix a bug, consider opening an issue
1313+first. We're also happy to discuss and help you open a PR and get your changes
1414+in!
1515+1616+- If you have a question, try [creating a GitHub Discussions thread.](https://github.com/0no-co/wonka/discussions/new/choose)
1717+- If you think you've found a bug, [open a new issue.](https://github.com/0no-co/wonka/issues/new)
1818+- or, if you found a bug you'd like to fix, [open a PR.](https://github.com/0no-co/wonka/compare)
1919+- If you'd like to propose a change [open an RFC issue.](https://github.com/0no-co/wonka/issues/new?labels=future+%F0%9F%94%AE&template=RFC.md&title=RFC%3A+Your+Proposal) You can read more about the RFC process [below](#how-do-i-propose-changes).
2020+2121+### What are the issue conventions?
2222+2323+There are **no strict conventions**, but we do have two templates in place that will fit most
2424+issues, since questions and other discussion start on GitHub Discussions. The bug template is fairly
2525+standard and the rule of thumb is to try to explain **what you expected** and **what you got
2626+instead.** Following this makes it very clear whether it's a known behavior, an unexpected issue,
2727+or an undocumented quirk.
2828+2929+### How do I propose changes?
3030+3131+We follow an **RFC proposal process**. This allows anyone to propose a new feature or a change, and
3232+allows us to communicate our current planned features or changes, so any technical discussion,
3333+progress, or upcoming changes are always **documented transparently.** You can [find the RFC
3434+template](https://github.com/0no-co/wonka/issues/new/choose) in our issue creator.
3535+3636+### What are the PR conventions?
3737+3838+This also comes with **no strict conventions**. We only ask you to follow the PR template we have
3939+in place more strictly here than the templates for issues, since it asks you to list a summary
4040+(maybe even with a short explanation) and a list of technical changes.
4141+4242+If you're **resolving** an issue please don't forget to add `Resolve #123` to the description so that
4343+it's automatically linked, so that there's no ambiguity and which issue is being addressed (if any)
4444+4545+You'll find that a comment by the "Changeset" bot may pop up. If you don't know what a **changeset**
4646+is and why it's asking you to document your changes, read on at ["How do I document a change for the
4747+changelog"](#how-do-i-document-a-change-for-the-changelog)
4848+4949+We also typically **name** our PRs with a slightly descriptive title, e.g. `feat: Title`.
5050+5151+## How do I set up the project?
5252+5353+Luckily it's not hard to get started. You can install dependencie
5454+[using `pnpm`](https://pnpm.io/installation#using-corepack).
5555+Please don't use `npm` or `yarn` to respect the lockfile.
5656+5757+```sh
5858+pnpm install
5959+```
6060+6161+There are multiple commands you can run in the root folder to test your changes:
6262+6363+```sh
6464+# TypeScript checks:
6565+pnpm run check
6666+6767+# Linting (prettier & eslint):
6868+pnpm run lint
6969+7070+# Unit Tests:
7171+pnpm run test
7272+7373+# Builds:
7474+pnpm run build
7575+```
7676+7777+## How do I test my changes?
7878+7979+It's always good practice to run the tests when making changes. If you're unsure which packages
8080+may be affected by your new tests or changes you may run `pnpm test` in the root of
8181+the repository.
8282+8383+If your editor is not set up with type checks you may also want to run `pnpm run check` on your
8484+changes.
8585+8686+Additionally you can head to any example in the `examples/` folder
8787+and run them. There you'll also need to install their dependencies as they're isolated projects,
8888+without a lockfile and without linking to packages in the monorepos.
8989+All examples are started using the `package.json`'s `start` script.
9090+9191+## How do I lint my code?
9292+9393+We ensure consistency in this codebase using `eslint` and `prettier`.
9494+They are run on a `precommit` hook, so if something's off they'll try
9595+to automatically fix up your code, or display an error.
9696+9797+If you have them set up in your editor, even better!
9898+9999+## How do I document a change for the changelog?
100100+101101+This project uses [changesets](https://github.com/atlassian/changesets). This means that for
102102+every PR there must be documentation for what has been changed and which package is affected.
103103+104104+You can document a change by running `changeset`, which will ask you which packages
105105+have changed and whether the change is major/minor/patch. It will then ask you to write
106106+a change entry as markdown.
107107+108108+```sh
109109+# In the root of the urql repository call:
110110+pnpm changeset
111111+```
112112+113113+This will create a new "changeset file" in the `.changeset` folder, which you should commit and
114114+push, so that it's added to your PR.
115115+This will eventually end up in the package's `CHANGELOG.md` file when we do a release.
116116+117117+You won't need to add a changeset if you're simply making "non-visible" changes to the docs or other
118118+files that aren't published to the npm registry.
119119+120120+[Read more about adding a `changeset` here.](https://github.com/atlassian/changesets/blob/master/docs/adding-a-changeset.md#i-am-in-a-multi-package-repository-a-mono-repo)
121121+122122+## How do I release new versions of our packages?
123123+124124+Hold up, that's **automated**! Since we use `changeset` to document our changes, which determines what
125125+goes into the changelog and what kind of version bump a change should make, you can also use the
126126+tool to check what's currently posed to change after a release batch using: `pnpm changeset status`.
127127+128128+We have a [GitHub Actions workflow](./.github/workflow/release.yml) which is triggered whenever new
129129+changes are merged. It will always open a **"Version Packages" PR** which is kept up-to-date. This PR
130130+documents all changes that are made and will show in its description what all new changelogs are
131131+going to contain for their new entries.
132132+133133+Once a "Version Packages" PR is approved by a contributor and merged, the action will automatically
134134+take care of creating the release, publishing all updated packages to the npm registry, and creating
135135+appropriate tags on GitHub too.
136136+137137+This process is automated, but the changelog should be checked for errors.
138138+139139+As to **when** to merge the automated PR and publish? Maybe not after every change. Typically there
140140+are two release batches: hotfixes and release batches. We expect that a hotfix for a single package
141141+should go out as quickly as possible if it negatively affects users. For **release batches**
142142+however, it's common to assume that if one change is made to a package that more will follow in the
143143+same week. So waiting for **a day or two** when other changes are expected will make sense to keep the
144144+fatigue as low as possible for downstream maintainers.
145145+146146+## How do I upgrade all dependencies?
147147+148148+It may be a good idea to keep all dependencies on this repository **up-to-date** every now and
149149+then. Typically we do this by running `pnpm update --interactive --latest` and checking one-by-one
150150+which dependencies will need to be bumped. In case of any security issues it may make sense to
151151+just run `pnpm update [package]`.
152152+153153+While this is rare with `pnpm`, upgrading some transitive dependencies may accidentally duplicate
154154+them if two packages depend on different compatible version ranges. This can be fixed by running:
155155+156156+```sh
157157+pnpm dedupe
158158+pnpm install
159159+```
160160+161161+It's common to then **create a PR** (with a changeset documenting the packages that need to reflect
162162+new changes if any `dependencies` have changed) with the name of
163163+"(chore) - Upgrade direct and transitive dependencies" or something similar.
+1-1
LICENSE.md
···11MIT License
2233-Copyright (c) 2018 Phil Plückthun
33+Copyright (c) 0no.co
4455Permission is hereby granted, free of charge, to any person obtaining a copy
66of this software and associated documentation files (the "Software"), to deal
+31-106
README.md
···11# Wonka
2233-A fast push & pull stream library for Reason, loosely following the [callbag spec](https://github.com/callbag/callbag)
33+A tiny but capable push & pull stream library for TypeScript and Flow,
44+loosely following the [callbag spec](https://github.com/callbag/callbag)
55+66+> **NOTE:** The currently released version v6 is only compatible now with TypeScript, Flow, and JavaScript.
77+> If you're looking for Reason/OCaml/esy/dune support, please check v5, and if you're looking for the legacy version
88+> of this library check v4.
4955-[](https://travis-ci.org/kitten/wonka)
66-[](https://coveralls.io/github/kitten/wonka?branch=master)
77-[](https://www.npmjs.com/package/wonka)
88-[](https://www.npmjs.com/package/wonka)
1010+<br>
1111+<a href="https://npmjs.com/package/wonka">
1212+ <img alt="NPM Version" src="https://img.shields.io/npm/v/wonka.svg" />
1313+</a>
1414+<a href="https://npmjs.com/package/wonka">
1515+ <img alt="License" src="https://img.shields.io/npm/l/wonka.svg" />
1616+</a>
1717+<a href="https://coveralls.io/github/kitten/wonka?branch=master">
1818+ <img src="https://coveralls.io/repos/github/kitten/wonka/badge.svg?branch=master" alt="Test Coverage" />
1919+</a>
2020+<a href="https://bundlephobia.com/result?p=wonka">
2121+ <img alt="Minified gzip size" src="https://img.shields.io/bundlephobia/minzip/wonka.svg?label=gzip%20size" />
2222+</a>
2323+<br>
9241025> “There’s no earthly way of knowing<br>
1126> Which direction we are going<br>
···16311732
18331919-* [What is `Wonka`](#what-is-wonka)
2020-* [Why it exists](#why-it-exists)
2121-* [Installation](#installation)
2222-* [Getting Started](#getting-started)
2323-* [Documentation (In Progress)](#documentation)
3434+Wonka is a lightweight iterable and observable library loosely based on
3535+the [callbag spec](https://github.com/callbag/callbag). It exposes a set of helpers to create streams,
3636+which are sources of multiple values, which allow you to create, transform
3737+and consume event streams or iterable sets of data.
24382525-## What is `Wonka`
3939+## [Documentation](https://wonka.kitten.sh/)
26402727-`Wonka` is a library for lightweight observables and iterables loosely based on the [callbag spec](https://github.com/callbag/callbag).
2828-It exposes a set of helpers to create and transform sources and output sinks, meaning it helps you to turn an event source or an
2929-iterable set of data into streams, and manipulate these streams.
3030-3131-## Why it exists
3232-3333-Reason has been becoming increasingly popular, but it's missing a good pattern for streams that feels native to the language.
3434-The functional nature of callbags make them a perfect starting point to fix this, and to introduce a reactive programming
3535-pattern to a language that is well suited for it.
3636-3737-Hence `Wonka` is a library that aims to make complex streams of data easy to deal with.
3838-3939-## Installation
4040-4141-Install the library first: `yarn add wonka` or `npm install --save wonka`,
4141+**See the documentation at [wonka.kitten.sh](https://wonka.kitten.sh)** for more information about using `wonka`!
42424343-Then add `wonka` to `bs-dependencies` in your `bsconfig.json` file like so:
4343+- [Introduction](https://wonka.kitten.sh/)
4444+- [**Getting started**](https://wonka.kitten.sh/getting-started)
4545+- [Basics](https://wonka.kitten.sh/basics/)
4646+- [API Reference](https://wonka.kitten.sh/api/)
44474545-```diff
4646-{
4747- "name": "<your name>",
4848- "version": "0.1.0",
4949- "sources": ["src"],
5050- "bsc-flags": ["-bs-super-errors"],
5151- "bs-dependencies": [
5252-+ "wonka"
5353- ]
5454-}
5555-```
5656-5757-## Getting Started
5858-5959-Writing your first stream is very easy! Let's suppose you would like to create a stream from a list, filter out some values,
6060-then map over the remaining ones, and lastly iterate over the final values.
6161-6262-This can be done with a few operators that might remind you of functions you would also call on iterables.
6363-6464-```reason
6565-let example = [1, 2, 3, 4, 5, 6];
6666-6767-Wonka.fromList(example)
6868- |> Wonka.filter((.x) => x mod 2 === 0)
6969- |> Wonka.map((.x )=> x * 2)
7070- |> Wonka.forEach((.x) => print_endline(string_of_int(x)));
7171-7272-/* prints: 4, 8, 12 */
7373-```
7474-7575-To explain what's going on:
7676-7777-- `fromList` creates a pullable source with values from the list
7878-- `filter` only lets even values through
7979-- `map` multiplies the values by `2`
8080-- `forEach` pulls values from the resulting source and prints them
8181-8282-As you can see, all helpers that we've used are exposed on the `Wonka` module.
8383-But if we would like to use JavaScript-based APIs, then we need to use the `WonkaJs` module.
8484-8585-Let's look at the same example, but instead of a list we will use an `interval` stream.
8686-This stream will output ascending numbers starting from `0` indefinitely.
8787-8888-We will code the same example as before but we'd like the `interval` to push
8989-a new number every `50ms` and to stop after seven values.
9090-9191-```reason
9292-WonkaJs.interval(50)
9393- |> Wonka.take(7)
9494- |> Wonka.filter((.x) => x mod 2 === 0)
9595- |> Wonka.map((.x) => x * 2)
9696- |> Wonka.forEach((.x) => print_endline(string_of_int(x)));
9797-9898-/* prints: 4, 8, 12 */
9999-```
100100-101101-The last three functions stay the same, but we are now using `interval` as our source.
102102-This is a listenable source, meaning that it pushes values downwards when it sees fit.
103103-And the `take` operator tells our source to stop sending values after having received seven
104104-values.
105105-106106-And already you have mastered all the basics of `Wonka` and learned about a couple of its operators!
107107-Go, you! :tada:
108108-109109-## Documentation
110110-111111-I am currently still working on getting some documentation up and running. Those will contain:
112112-113113-- The API, i.e. a list of all helpers
114114-- Examples
115115-- Usage Guides & Recipes
116116-- Developer Guides (How to write a source/operator/sink)
117117-- Modified Callbag spec
118118-119119-Stay tuned and read the signature files in the meantime please:
120120-121121-- [wonka.rei](./src/wonka.rei)
122122-- [wonkaJs.rei](./src/wonka.rei)
123123-4848+The raw markdown files can be found [in this repository in the `docs` folder](https://github.com/kitten/wonka/tree/master/docs).
-1948
__tests__/wonka_test.re
···11-open Jest;
22-open Wonka_types;
33-44-let it = test;
55-66-describe("source factories", () => {
77- describe("fromList", () => {
88- open Expect;
99- open! Expect.Operators;
1010-1111- it("sends list items to a puller sink", () => {
1212- let source = Wonka.fromList([10, 20, 30]);
1313- let talkback = ref((. _: Wonka_types.talkbackT) => ());
1414- let signals = [||];
1515-1616- source((. signal) =>
1717- switch (signal) {
1818- | Start(x) => talkback := x
1919- | Push(_) => ignore(Js.Array.push(signal, signals))
2020- | End => ignore(Js.Array.push(signal, signals))
2121- }
2222- );
2323-2424- talkback^(. Pull);
2525- talkback^(. Pull);
2626- talkback^(. Pull);
2727- talkback^(. Pull);
2828-2929- expect(signals) == [|Push(10), Push(20), Push(30), End|];
3030- });
3131- });
3232-3333- describe("fromArray", () => {
3434- open Expect;
3535- open! Expect.Operators;
3636-3737- it("sends array items to a puller sink", () => {
3838- let source = Wonka.fromArray([|10, 20, 30|]);
3939- let talkback = ref((. _: Wonka_types.talkbackT) => ());
4040- let signals = ref([||]);
4141-4242- source((. signal) =>
4343- switch (signal) {
4444- | Start(x) =>
4545- talkback := x;
4646- x(. Pull);
4747- | Push(_) =>
4848- signals := Array.append(signals^, [|signal|]);
4949- talkback^(. Pull);
5050- | End => signals := Array.append(signals^, [|signal|])
5151- }
5252- );
5353-5454- expect(signals^) == [|Push(10), Push(20), Push(30), End|];
5555- });
5656-5757- it("does not blow up the stack when iterating something huge", () => {
5858- let arr = Array.make(100000, 123);
5959- let source = Wonka.fromArray(arr);
6060- let talkback = ref((. _: Wonka_types.talkbackT) => ());
6161- let values = [||];
6262-6363- source((. signal) =>
6464- switch (signal) {
6565- | Start(x) =>
6666- talkback := x;
6767- x(. Pull);
6868- | Push(x) =>
6969- ignore(Js.Array.push(x, values));
7070- talkback^(. Pull);
7171- | End => ()
7272- }
7373- );
7474-7575- expect(Array.length(values)) == Array.length(arr);
7676- });
7777- });
7878-7979- describe("fromValue", () => {
8080- open Expect;
8181- open! Expect.Operators;
8282-8383- it("sends a single item to a puller sink", () => {
8484- let source = Wonka.fromValue(123);
8585- let talkback = ref((. _: Wonka_types.talkbackT) => ());
8686- let signals = [||];
8787-8888- source((. signal) =>
8989- switch (signal) {
9090- | Start(x) => talkback := x
9191- | Push(_) => ignore(Js.Array.push(signal, signals))
9292- | End => ignore(Js.Array.push(signal, signals))
9393- }
9494- );
9595-9696- talkback^(. Pull);
9797- talkback^(. Pull);
9898- talkback^(. Pull); /* one extra to check whether no signal comes back after it has ended */
9999-100100- expect(signals) == [|Push(123), End|];
101101- });
102102- });
103103-104104- describe("empty", () => {
105105- open Expect;
106106- open! Expect.Operators;
107107-108108- it("ends immediately", () => {
109109- let talkback = ref((. _: Wonka_types.talkbackT) => ());
110110- let signals = [||];
111111-112112- Wonka.empty((. signal) =>
113113- switch (signal) {
114114- | Start(x) => talkback := x
115115- | _ => ignore(Js.Array.push(signal, signals))
116116- }
117117- );
118118-119119- let _signals = Array.copy(signals);
120120-121121- talkback^(. Pull);
122122- talkback^(. Pull);
123123-124124- expect((_signals, signals)) == ([|End|], [|End|]);
125125- });
126126- });
127127-128128- describe("never", () => {
129129- open Expect;
130130- open! Expect.Operators;
131131-132132- it("does not end", () => {
133133- let talkback = ref((. _: Wonka_types.talkbackT) => ());
134134- let ended = ref(false);
135135-136136- Wonka.never((. signal) =>
137137- switch (signal) {
138138- | Start(x) => talkback := x
139139- | End => ended := true
140140- | _ => ()
141141- }
142142- );
143143-144144- talkback^(. Pull);
145145- talkback^(. Pull);
146146-147147- expect(ended^) === false;
148148- });
149149- });
150150-});
151151-152152-describe("operator factories", () => {
153153- describe("map", () => {
154154- open Expect;
155155-156156- it("maps all emissions of a source", () => {
157157- let num = ref(1);
158158- let nums = [||];
159159- let talkback = ref((. _: Wonka_types.talkbackT) => ());
160160-161161- Wonka.map(
162162- (. _) => {
163163- let res = num^;
164164- num := num^ + 1;
165165- res;
166166- },
167167- sink =>
168168- sink(.
169169- Start(
170170- (. signal) =>
171171- switch (signal) {
172172- | Pull => sink(. Push(1))
173173- | _ => ()
174174- },
175175- ),
176176- ),
177177- (. signal) =>
178178- switch (signal) {
179179- | Start(x) =>
180180- talkback := x;
181181- x(. Pull);
182182- | Push(x) when num^ < 6 =>
183183- ignore(Js.Array.push(x, nums));
184184- talkback^(. Pull);
185185- | _ => ()
186186- },
187187- );
188188-189189- expect(nums) |> toEqual([|1, 2, 3, 4|]);
190190- });
191191-192192- testPromise("follows the spec for listenables", () =>
193193- Wonka_thelpers.testWithListenable(Wonka.map((. x) => x))
194194- |> Js.Promise.then_(x =>
195195- expect(x)
196196- |> toEqual(([||], [|Push(1), Push(2), End|]))
197197- |> Js.Promise.resolve
198198- )
199199- );
200200-201201- testPromise(
202202- "ends itself and source when its talkback receives the End signal", () => {
203203- let end_: talkbackT = Close;
204204-205205- Wonka_thelpers.testTalkbackEnd(Wonka.map((. x) => x))
206206- |> Js.Promise.then_(x =>
207207- expect(x)
208208- |> toEqual(([|end_|], [|Push(1)|]))
209209- |> Js.Promise.resolve
210210- );
211211- });
212212- });
213213-214214- describe("filter", () => {
215215- open Expect;
216216-217217- it("filters emissions according to a predicate", () => {
218218- let i = ref(1);
219219- let nums = [||];
220220- let talkback = ref((. _: Wonka_types.talkbackT) => ());
221221-222222- Wonka.filter(
223223- (. x) => x mod 2 === 0,
224224- sink =>
225225- sink(.
226226- Start(
227227- (. signal) =>
228228- switch (signal) {
229229- | Pull =>
230230- let num = i^;
231231- i := i^ + 1;
232232- sink(. Push(num));
233233- | _ => ()
234234- },
235235- ),
236236- ),
237237- (. signal) =>
238238- switch (signal) {
239239- | Start(x) =>
240240- talkback := x;
241241- x(. Pull);
242242- | Push(x) when x < 6 =>
243243- ignore(Js.Array.push(x, nums));
244244- talkback^(. Pull);
245245- | _ => ()
246246- },
247247- );
248248-249249- expect(nums) |> toEqual([|2, 4|]);
250250- });
251251-252252- testPromise("follows the spec for listenables", () =>
253253- Wonka_thelpers.testWithListenable(Wonka.filter((. _) => true))
254254- |> Js.Promise.then_(x =>
255255- expect(x)
256256- |> toEqual(([||], [|Push(1), Push(2), End|]))
257257- |> Js.Promise.resolve
258258- )
259259- );
260260-261261- testPromise("follows the spec for listenables when filtering", () =>
262262- Wonka_thelpers.testWithListenable(Wonka.filter((. _) => false))
263263- |> Js.Promise.then_(x =>
264264- expect(x)
265265- |> toEqual(([|Pull, Pull|], [|End|]))
266266- |> Js.Promise.resolve
267267- )
268268- );
269269-270270- testPromise(
271271- "ends itself and source when its talkback receives the End signal", () => {
272272- let end_: talkbackT = Close;
273273-274274- Wonka_thelpers.testTalkbackEnd(Wonka.filter((. _) => true))
275275- |> Js.Promise.then_(x =>
276276- expect(x)
277277- |> toEqual(([|end_|], [|Push(1)|]))
278278- |> Js.Promise.resolve
279279- );
280280- });
281281- });
282282-283283- describe("scan", () => {
284284- open Expect;
285285-286286- it("folds emissions using an initial seed value", () => {
287287- let talkback = ref((. _: Wonka_types.talkbackT) => ());
288288- let num = ref(1);
289289-290290- let source =
291291- Wonka.scan(
292292- (. acc, x) => acc + x,
293293- 0,
294294- sink =>
295295- sink(.
296296- Start(
297297- (. signal) =>
298298- switch (signal) {
299299- | Pull =>
300300- let i = num^;
301301- if (i <= 3) {
302302- num := num^ + 1;
303303- sink(. Push(i));
304304- } else {
305305- sink(. End);
306306- };
307307- | _ => ()
308308- },
309309- ),
310310- ),
311311- );
312312-313313- let res = [||];
314314-315315- source((. signal) =>
316316- switch (signal) {
317317- | Start(x) => talkback := x
318318- | _ => ignore(Js.Array.push(signal, res))
319319- }
320320- );
321321-322322- talkback^(. Pull);
323323- talkback^(. Pull);
324324- talkback^(. Pull);
325325- talkback^(. Pull);
326326- expect(res) |> toEqual([|Push(1), Push(3), Push(6), End|]);
327327- });
328328-329329- testPromise("follows the spec for listenables", () =>
330330- Wonka_thelpers.testWithListenable(Wonka.scan((. _, x) => x, 0))
331331- |> Js.Promise.then_(x =>
332332- expect(x)
333333- |> toEqual(([||], [|Push(1), Push(2), End|]))
334334- |> Js.Promise.resolve
335335- )
336336- );
337337-338338- testPromise(
339339- "ends itself and source when its talkback receives the End signal", () => {
340340- let end_: talkbackT = Close;
341341-342342- Wonka_thelpers.testTalkbackEnd(Wonka.scan((. _, x) => x, 0))
343343- |> Js.Promise.then_(x =>
344344- expect(x)
345345- |> toEqual(([|end_|], [|Push(1)|]))
346346- |> Js.Promise.resolve
347347- );
348348- });
349349- });
350350-351351- describe("merge", () => {
352352- open Expect;
353353- open! Expect.Operators;
354354-355355- it("merges different sources into a single one", () => {
356356- let a = Wonka.fromList([1, 2, 3]);
357357- let b = Wonka.fromList([4, 5, 6]);
358358- let talkback = ref((. _: Wonka_types.talkbackT) => ());
359359- let signals = [||];
360360- let source = Wonka.merge([|a, b|]);
361361-362362- source((. signal) =>
363363- switch (signal) {
364364- | Start(x) =>
365365- talkback := x;
366366- x(. Pull);
367367- | Push(_) =>
368368- ignore(Js.Array.push(signal, signals));
369369- talkback^(. Pull);
370370- | End => ignore(Js.Array.push(signal, signals))
371371- }
372372- );
373373-374374- expect(signals)
375375- == [|Push(1), Push(2), Push(3), Push(4), Push(5), Push(6), End|];
376376- });
377377-378378- testPromise("follows the spec for listenables", () =>
379379- Wonka_thelpers.testWithListenable(source => Wonka.merge([|source|]))
380380- |> Js.Promise.then_(x =>
381381- expect(x)
382382- |> toEqual(([|Pull, Pull, Pull|], [|Push(1), Push(2), End|]))
383383- |> Js.Promise.resolve
384384- )
385385- );
386386-387387- testPromise(
388388- "ends itself and source when its talkback receives the End signal", () =>
389389- Wonka_thelpers.testTalkbackEnd(source => Wonka.merge([|source|]))
390390- |> Js.Promise.then_(x =>
391391- expect(x)
392392- |> toEqual(([|Pull, Pull, Close|], [|Push(1)|]))
393393- |> Js.Promise.resolve
394394- )
395395- );
396396- });
397397-398398- describe("concat", () => {
399399- open Expect;
400400- open! Expect.Operators;
401401-402402- it("concatenates different sources into a single one", () => {
403403- let a = Wonka.fromList([1, 2, 3]);
404404- let b = Wonka.fromList([4, 5, 6]);
405405- let talkback = ref((. _: Wonka_types.talkbackT) => ());
406406- let signals = [||];
407407- let source = Wonka.concat([|a, b|]);
408408-409409- source((. signal) =>
410410- switch (signal) {
411411- | Start(x) =>
412412- talkback := x;
413413- x(. Pull);
414414- | Push(_) =>
415415- ignore(Js.Array.push(signal, signals));
416416- talkback^(. Pull);
417417- | End => ignore(Js.Array.push(signal, signals))
418418- }
419419- );
420420-421421- expect(signals)
422422- == [|Push(1), Push(2), Push(3), Push(4), Push(5), Push(6), End|];
423423- });
424424-425425- testPromise("follows the spec for listenables", () =>
426426- Wonka_thelpers.testWithListenable(source => Wonka.concat([|source|]))
427427- |> Js.Promise.then_(x =>
428428- expect(x)
429429- |> toEqual(([|Pull, Pull, Pull|], [|Push(1), Push(2), End|]))
430430- |> Js.Promise.resolve
431431- )
432432- );
433433-434434- testPromise(
435435- "ends itself and source when its talkback receives the End signal", () =>
436436- Wonka_thelpers.testTalkbackEnd(source => Wonka.concat([|source|]))
437437- |> Js.Promise.then_(x =>
438438- expect(x)
439439- |> toEqual(([|Pull, Pull, Close|], [|Push(1)|]))
440440- |> Js.Promise.resolve
441441- )
442442- );
443443- });
444444-445445- describe("share", () => {
446446- open Expect;
447447-448448- it("shares an underlying source with all sinks", () => {
449449- let talkback = ref((. _: Wonka_types.talkbackT) => ());
450450- let aborterTb = ref((. _: Wonka_types.talkbackT) => ());
451451- let num = ref(1);
452452- let nums = [||];
453453-454454- let source =
455455- Wonka.share(sink =>
456456- sink(.
457457- Start(
458458- (. signal) =>
459459- switch (signal) {
460460- | Pull =>
461461- let i = num^;
462462- if (i <= 2) {
463463- num := num^ + 1;
464464- sink(. Push(i));
465465- } else {
466466- sink(. End);
467467- };
468468- | _ => ()
469469- },
470470- ),
471471- )
472472- );
473473-474474- source((. signal) =>
475475- switch (signal) {
476476- | Start(x) => talkback := x
477477- | _ => ignore(Js.Array.push(signal, nums))
478478- }
479479- );
480480-481481- source((. signal) =>
482482- switch (signal) {
483483- | Start(_) => ()
484484- | _ => ignore(Js.Array.push(signal, nums))
485485- }
486486- );
487487-488488- source((. signal) =>
489489- switch (signal) {
490490- | Start(tb) => aborterTb := tb
491491- | _ =>
492492- ignore(Js.Array.push(signal, nums));
493493- aborterTb^(. Close);
494494- }
495495- );
496496-497497- talkback^(. Pull);
498498- let numsA = Array.copy(nums);
499499- talkback^(. Pull);
500500- talkback^(. Pull);
501501- talkback^(. Pull);
502502- expect((numsA, nums))
503503- |> toEqual((
504504- [|Push(1), Push(1), Push(1)|],
505505- [|Push(1), Push(1), Push(1), Push(2), Push(2), End, End|],
506506- ));
507507- });
508508-509509- testPromise("follows the spec for listenables", () =>
510510- Wonka_thelpers.testWithListenable(Wonka.share)
511511- |> Js.Promise.then_(x =>
512512- expect(x)
513513- |> toEqual(([||], [|Push(1), Push(2), End|]))
514514- |> Js.Promise.resolve
515515- )
516516- );
517517-518518- testPromise(
519519- "ends itself and source when its talkback receives the End signal", () => {
520520- let end_: talkbackT = Close;
521521-522522- Wonka_thelpers.testTalkbackEnd(Wonka.share)
523523- |> Js.Promise.then_(x =>
524524- expect(x)
525525- |> toEqual(([|end_|], [|Push(1)|]))
526526- |> Js.Promise.resolve
527527- );
528528- });
529529- });
530530-531531- describe("combine", () => {
532532- open Expect;
533533-534534- it("combines the latest values of two sources", () => {
535535- let talkback = ref((. _: Wonka_types.talkbackT) => ());
536536-537537- let makeSource = (factor: int) => {
538538- let num = ref(1);
539539-540540- sink => {
541541- sink(.
542542- Start(
543543- (. signal) =>
544544- switch (signal) {
545545- | Pull =>
546546- if (num^ <= 2) {
547547- let i = num^ * factor;
548548- num := num^ + 1;
549549- sink(. Push(i));
550550- } else {
551551- sink(. End);
552552- }
553553- | _ => ()
554554- },
555555- ),
556556- );
557557- };
558558- };
559559-560560- let sourceA = makeSource(1);
561561- let sourceB = makeSource(2);
562562- let source = Wonka.combine(sourceA, sourceB);
563563- let res = [||];
564564-565565- source((. signal) =>
566566- switch (signal) {
567567- | Start(x) => talkback := x
568568- | _ => ignore(Js.Array.push(signal, res))
569569- }
570570- );
571571-572572- talkback^(. Pull);
573573- talkback^(. Pull);
574574- talkback^(. Pull);
575575- talkback^(. Pull);
576576- expect(res)
577577- |> toEqual([|Push((1, 2)), Push((2, 2)), Push((2, 4)), End|]);
578578- });
579579-580580- testPromise("follows the spec for listenables", () =>
581581- Wonka_thelpers.testWithListenable(source => {
582582- let shared = Wonka.share(source);
583583- Wonka.combine(shared, shared);
584584- })
585585- |> Js.Promise.then_(x =>
586586- expect(x)
587587- |> toEqual((
588588- [||],
589589- [|Push((1, 1)), Push((2, 1)), Push((2, 2)), End|],
590590- ))
591591- |> Js.Promise.resolve
592592- )
593593- );
594594-595595- testPromise(
596596- "ends itself and source when its talkback receives the End signal", () => {
597597- let end_: talkbackT = Close;
598598-599599- Wonka_thelpers.testTalkbackEnd(source => {
600600- let shared = Wonka.share(source);
601601- Wonka.combine(shared, shared);
602602- })
603603- |> Js.Promise.then_(x =>
604604- expect(x)
605605- |> toEqual(([|end_|], [|Push((1, 1))|]))
606606- |> Js.Promise.resolve
607607- );
608608- });
609609- });
610610-611611- describe("take", () => {
612612- open Expect;
613613-614614- it("only lets a maximum number of values through", () => {
615615- let talkback = ref((. _: Wonka_types.talkbackT) => ());
616616- let num = ref(1);
617617-618618- let source =
619619- Wonka.take(2, sink =>
620620- sink(.
621621- Start(
622622- (. signal) =>
623623- switch (signal) {
624624- | Pull =>
625625- let i = num^;
626626- num := num^ + 1;
627627- sink(. Push(i));
628628- | _ => ()
629629- },
630630- ),
631631- )
632632- );
633633-634634- let res = [||];
635635-636636- source((. signal) =>
637637- switch (signal) {
638638- | Start(x) => talkback := x
639639- | _ => ignore(Js.Array.push(signal, res))
640640- }
641641- );
642642-643643- talkback^(. Pull);
644644- talkback^(. Pull);
645645- talkback^(. Pull);
646646- talkback^(. Pull);
647647- expect(res) |> toEqual([|Push(1), Push(2), End|]);
648648- });
649649-650650- it(
651651- "accepts the end of the source when max number of emissions is not reached",
652652- () => {
653653- let talkback = ref((. _: Wonka_types.talkbackT) => ());
654654- let num = ref(1);
655655-656656- let source =
657657- Wonka.take(2, sink =>
658658- sink(.
659659- Start(
660660- (. signal) =>
661661- switch (signal) {
662662- | Pull =>
663663- let i = num^;
664664- if (i < 2) {
665665- num := num^ + 1;
666666- sink(. Push(i));
667667- } else {
668668- sink(. End);
669669- };
670670- | _ => ()
671671- },
672672- ),
673673- )
674674- );
675675-676676- let res = [||];
677677-678678- source((. signal) =>
679679- switch (signal) {
680680- | Start(x) => talkback := x
681681- | _ => ignore(Js.Array.push(signal, res))
682682- }
683683- );
684684-685685- talkback^(. Pull);
686686- talkback^(. Pull);
687687- talkback^(. Pull);
688688- expect(res) |> toEqual([|Push(1), End|]);
689689- });
690690-691691- testPromise("follows the spec for listenables", () =>
692692- Wonka_thelpers.testWithListenable(Wonka.take(10))
693693- |> Js.Promise.then_(x =>
694694- expect(x)
695695- |> toEqual(([||], [|Push(1), Push(2), End|]))
696696- |> Js.Promise.resolve
697697- )
698698- );
699699-700700- testPromise("follows the spec for listenables when ending the source", () => {
701701- let end_: talkbackT = Close;
702702-703703- Wonka_thelpers.testWithListenable(Wonka.take(1))
704704- |> Js.Promise.then_(x =>
705705- expect(x)
706706- |> toEqual(([|end_|], [|Push(1), End|]))
707707- |> Js.Promise.resolve
708708- );
709709- });
710710-711711- testPromise(
712712- "ends itself and source when its talkback receives the End signal", () => {
713713- let end_: talkbackT = Close;
714714-715715- Wonka_thelpers.testTalkbackEnd(Wonka.take(10))
716716- |> Js.Promise.then_(x =>
717717- expect(x)
718718- |> toEqual(([|end_|], [|Push(1)|]))
719719- |> Js.Promise.resolve
720720- );
721721- });
722722- });
723723-724724- describe("takeLast", () => {
725725- open Expect;
726726-727727- it("only lets the last n values through on an entirely new source", () => {
728728- let talkback = ref((. _: Wonka_types.talkbackT) => ());
729729- let num = ref(1);
730730-731731- let source =
732732- Wonka.takeLast(2, sink =>
733733- sink(.
734734- Start(
735735- (. signal) =>
736736- switch (signal) {
737737- | Pull when num^ <= 4 =>
738738- let i = num^;
739739- num := num^ + 1;
740740- sink(. Push(i));
741741- | Pull => sink(. End)
742742- | _ => ()
743743- },
744744- ),
745745- )
746746- );
747747-748748- let res = [||];
749749-750750- source((. signal) =>
751751- switch (signal) {
752752- | Start(x) => talkback := x
753753- | _ => ignore(Js.Array.push(signal, res))
754754- }
755755- );
756756-757757- talkback^(. Pull);
758758- talkback^(. Pull);
759759- talkback^(. Pull);
760760- expect(res) |> toEqual([|Push(3), Push(4), End|]);
761761- });
762762-763763- testPromise("follows the spec for listenables", () =>
764764- Wonka_thelpers.testWithListenable(Wonka.takeLast(10))
765765- |> Js.Promise.then_(x =>
766766- expect(x)
767767- |> toEqual((
768768- [|Pull, Pull, Pull|],
769769- [|/* empty since the source is a pullable */|],
770770- ))
771771- |> Js.Promise.resolve
772772- )
773773- );
774774-775775- testPromise(
776776- "ends itself and source when its talkback receives the End signal", () =>
777777- Wonka_thelpers.testTalkbackEnd(Wonka.takeLast(10))
778778- |> Js.Promise.then_(x =>
779779- expect(x)
780780- |> toEqual(([|Pull, Pull|], [||]))
781781- |> Js.Promise.resolve
782782- )
783783- );
784784- });
785785-786786- describe("takeWhile", () => {
787787- open Expect;
788788-789789- it("only lets the last n values through on an entirely new source", () => {
790790- let talkback = ref((. _: Wonka_types.talkbackT) => ());
791791- let num = ref(1);
792792-793793- let source =
794794- Wonka.takeWhile(
795795- (. x) => x <= 2,
796796- sink =>
797797- sink(.
798798- Start(
799799- (. signal) =>
800800- switch (signal) {
801801- | Pull =>
802802- let i = num^;
803803- num := num^ + 1;
804804- sink(. Push(i));
805805- | _ => ()
806806- },
807807- ),
808808- ),
809809- );
810810-811811- let res = [||];
812812-813813- source((. signal) =>
814814- switch (signal) {
815815- | Start(x) => talkback := x
816816- | _ => ignore(Js.Array.push(signal, res))
817817- }
818818- );
819819-820820- talkback^(. Pull);
821821- talkback^(. Pull);
822822- talkback^(. Pull);
823823- talkback^(. Pull);
824824-825825- expect(res) |> toEqual([|Push(1), Push(2), End|]);
826826- });
827827-828828- it(
829829- "accepts the end of the source when max number of emissions is not reached",
830830- () => {
831831- let talkback = ref((. _: Wonka_types.talkbackT) => ());
832832- let num = ref(1);
833833-834834- let source =
835835- Wonka.takeWhile(
836836- (. x) => x <= 5,
837837- sink =>
838838- sink(.
839839- Start(
840840- (. signal) =>
841841- switch (signal) {
842842- | Pull =>
843843- let i = num^;
844844- if (i < 2) {
845845- num := num^ + 1;
846846- sink(. Push(i));
847847- } else {
848848- sink(. End);
849849- };
850850- | _ => ()
851851- },
852852- ),
853853- ),
854854- );
855855-856856- let res = [||];
857857-858858- source((. signal) =>
859859- switch (signal) {
860860- | Start(x) => talkback := x
861861- | _ => ignore(Js.Array.push(signal, res))
862862- }
863863- );
864864-865865- talkback^(. Pull);
866866- talkback^(. Pull);
867867- talkback^(. Pull);
868868-869869- expect(res) |> toEqual([|Push(1), End|]);
870870- });
871871-872872- testPromise("follows the spec for listenables", () =>
873873- Wonka_thelpers.testWithListenable(Wonka.takeWhile((. _) => true))
874874- |> Js.Promise.then_(x =>
875875- expect(x)
876876- |> toEqual(([||], [|Push(1), Push(2), End|]))
877877- |> Js.Promise.resolve
878878- )
879879- );
880880-881881- testPromise("follows the spec for listenables when ending the source", () => {
882882- let end_: talkbackT = Close;
883883-884884- Wonka_thelpers.testWithListenable(Wonka.takeWhile((. _) => false))
885885- |> Js.Promise.then_(x =>
886886- expect(x) |> toEqual(([|end_|], [|End|])) |> Js.Promise.resolve
887887- );
888888- });
889889-890890- testPromise(
891891- "ends itself and source when its talkback receives the End signal", () => {
892892- let end_: talkbackT = Close;
893893-894894- Wonka_thelpers.testTalkbackEnd(Wonka.takeWhile((. _) => true))
895895- |> Js.Promise.then_(x =>
896896- expect(x)
897897- |> toEqual(([|end_|], [|Push(1)|]))
898898- |> Js.Promise.resolve
899899- );
900900- });
901901- });
902902-903903- describe("takeUntil", () => {
904904- open Expect;
905905-906906- it("only lets the last n values through on an entirely new source", () => {
907907- let talkback = ref((. _: Wonka_types.talkbackT) => ());
908908- let notify = ref((_: Wonka_types.talkbackT) => ());
909909- let num = ref(1);
910910-911911- let notifier = sink => {
912912- notify :=
913913- (
914914- signal =>
915915- switch (signal) {
916916- | Pull => sink(. Push(0))
917917- | _ => ()
918918- }
919919- );
920920-921921- sink(. Start(Wonka_helpers.talkbackPlaceholder));
922922- };
923923-924924- let source =
925925- Wonka.takeUntil(notifier, sink =>
926926- sink(.
927927- Start(
928928- (. signal) =>
929929- switch (signal) {
930930- | Pull when num^ <= 4 =>
931931- let i = num^;
932932- if (i === 3) {
933933- notify^(Pull);
934934- };
935935- num := num^ + 1;
936936- sink(. Push(i));
937937- | _ => ()
938938- },
939939- ),
940940- )
941941- );
942942-943943- let res = [||];
944944-945945- source((. signal) =>
946946- switch (signal) {
947947- | Start(x) => talkback := x
948948- | _ => ignore(Js.Array.push(signal, res))
949949- }
950950- );
951951-952952- talkback^(. Pull);
953953- talkback^(. Pull);
954954- talkback^(. Pull);
955955- talkback^(. Pull);
956956-957957- expect(res) |> toEqual([|Push(1), Push(2), End|]);
958958- });
959959-960960- it(
961961- "accepts the end of the source when max number of emissions is not reached",
962962- () => {
963963- let talkback = ref((. _: Wonka_types.talkbackT) => ());
964964- let num = ref(1);
965965- let notifier = sink =>
966966- sink(. Start(Wonka_helpers.talkbackPlaceholder));
967967-968968- let source =
969969- Wonka.takeUntil(notifier, sink =>
970970- sink(.
971971- Start(
972972- (. signal) =>
973973- switch (signal) {
974974- | Pull =>
975975- let i = num^;
976976- if (num^ <= 2) {
977977- num := num^ + 1;
978978- sink(. Push(i));
979979- } else {
980980- sink(. End);
981981- };
982982- | _ => ()
983983- },
984984- ),
985985- )
986986- );
987987-988988- let res = [||];
989989-990990- source((. signal) =>
991991- switch (signal) {
992992- | Start(x) => talkback := x
993993- | _ => ignore(Js.Array.push(signal, res))
994994- }
995995- );
996996-997997- talkback^(. Pull);
998998- talkback^(. Pull);
999999- talkback^(. Pull);
10001000- talkback^(. Pull);
10011001-10021002- expect(res) |> toEqual([|Push(1), Push(2), End|]);
10031003- });
10041004-10051005- testPromise("follows the spec for listenables", () =>
10061006- Wonka_thelpers.testWithListenable(Wonka.takeUntil(Wonka.never))
10071007- |> Js.Promise.then_(x =>
10081008- expect(x)
10091009- |> toEqual(([||], [|Push(1), Push(2), End|]))
10101010- |> Js.Promise.resolve
10111011- )
10121012- );
10131013-10141014- testPromise("follows the spec for listenables when ending the source", () => {
10151015- let end_: talkbackT = Close;
10161016-10171017- Wonka_thelpers.testWithListenable(Wonka.takeUntil(Wonka.fromValue(0)))
10181018- |> Js.Promise.then_(x =>
10191019- expect(x) |> toEqual(([|end_|], [|End|])) |> Js.Promise.resolve
10201020- );
10211021- });
10221022-10231023- testPromise(
10241024- "ends itself and source when its talkback receives the End signal", () => {
10251025- let end_: talkbackT = Close;
10261026-10271027- Wonka_thelpers.testTalkbackEnd(Wonka.takeUntil(Wonka.never))
10281028- |> Js.Promise.then_(x =>
10291029- expect(x)
10301030- |> toEqual(([|end_|], [|Push(1)|]))
10311031- |> Js.Promise.resolve
10321032- );
10331033- });
10341034- });
10351035-10361036- describe("skip", () => {
10371037- open Expect;
10381038-10391039- it(
10401040- "only lets values through after a number of values have been filtered out",
10411041- () => {
10421042- let talkback = ref((. _: Wonka_types.talkbackT) => ());
10431043- let num = ref(1);
10441044-10451045- let source =
10461046- Wonka.skip(2, sink =>
10471047- sink(.
10481048- Start(
10491049- (. signal) =>
10501050- switch (signal) {
10511051- | Pull when num^ <= 4 =>
10521052- let i = num^;
10531053- num := num^ + 1;
10541054- sink(. Push(i));
10551055- | Pull => sink(. End)
10561056- | _ => ()
10571057- },
10581058- ),
10591059- )
10601060- );
10611061-10621062- let res = [||];
10631063-10641064- source((. signal) =>
10651065- switch (signal) {
10661066- | Start(x) => talkback := x
10671067- | _ => ignore(Js.Array.push(signal, res))
10681068- }
10691069- );
10701070-10711071- talkback^(. Pull);
10721072- talkback^(. Pull);
10731073- talkback^(. Pull);
10741074- expect(res) |> toEqual([|Push(3), Push(4), End|]);
10751075- });
10761076-10771077- testPromise("follows the spec for listenables", () =>
10781078- Wonka_thelpers.testWithListenable(Wonka.skip(0))
10791079- |> Js.Promise.then_(x =>
10801080- expect(x)
10811081- |> toEqual(([||], [|Push(1), Push(2), End|]))
10821082- |> Js.Promise.resolve
10831083- )
10841084- );
10851085-10861086- testPromise(
10871087- "follows the spec for listenables when skipping the source", () =>
10881088- Wonka_thelpers.testWithListenable(Wonka.skip(10))
10891089- |> Js.Promise.then_(x =>
10901090- expect(x)
10911091- |> toEqual(([|Pull, Pull|], [|End|]))
10921092- |> Js.Promise.resolve
10931093- )
10941094- );
10951095-10961096- testPromise(
10971097- "ends itself and source when its talkback receives the End signal", () => {
10981098- let end_: talkbackT = Close;
10991099-11001100- Wonka_thelpers.testTalkbackEnd(Wonka.skip(10))
11011101- |> Js.Promise.then_(x =>
11021102- expect(x)
11031103- |> toEqual(([|Pull, end_|], [||]))
11041104- |> Js.Promise.resolve
11051105- );
11061106- });
11071107- });
11081108-11091109- describe("skipWhile", () => {
11101110- open Expect;
11111111-11121112- it(
11131113- "only lets values through after the predicate returned false, including the first such value",
11141114- () => {
11151115- let talkback = ref((. _: Wonka_types.talkbackT) => ());
11161116- let num = ref(1);
11171117-11181118- let source =
11191119- Wonka.skipWhile(
11201120- (. x) => x <= 2,
11211121- sink =>
11221122- sink(.
11231123- Start(
11241124- (. signal) =>
11251125- switch (signal) {
11261126- | Pull when num^ <= 4 =>
11271127- let i = num^;
11281128- num := num^ + 1;
11291129- sink(. Push(i));
11301130- | Pull => sink(. End)
11311131- | _ => ()
11321132- },
11331133- ),
11341134- ),
11351135- );
11361136-11371137- let res = [||];
11381138-11391139- source((. signal) =>
11401140- switch (signal) {
11411141- | Start(x) => talkback := x
11421142- | _ => ignore(Js.Array.push(signal, res))
11431143- }
11441144- );
11451145-11461146- talkback^(. Pull);
11471147- talkback^(. Pull);
11481148- talkback^(. Pull);
11491149- expect(res) |> toEqual([|Push(3), Push(4), End|]);
11501150- },
11511151- );
11521152-11531153- testPromise("follows the spec for listenables", () =>
11541154- Wonka_thelpers.testWithListenable(Wonka.skipWhile((. _) => false))
11551155- |> Js.Promise.then_(x =>
11561156- expect(x)
11571157- |> toEqual(([||], [|Push(1), Push(2), End|]))
11581158- |> Js.Promise.resolve
11591159- )
11601160- );
11611161-11621162- testPromise(
11631163- "follows the spec for listenables when skipping the source", () =>
11641164- Wonka_thelpers.testWithListenable(Wonka.skipWhile((. _) => true))
11651165- |> Js.Promise.then_(x =>
11661166- expect(x)
11671167- |> toEqual(([|Pull, Pull|], [|End|]))
11681168- |> Js.Promise.resolve
11691169- )
11701170- );
11711171-11721172- testPromise(
11731173- "ends itself and source when its talkback receives the End signal", () => {
11741174- let end_: talkbackT = Close;
11751175-11761176- Wonka_thelpers.testTalkbackEnd(Wonka.skipWhile((. _) => false))
11771177- |> Js.Promise.then_(x =>
11781178- expect(x)
11791179- |> toEqual(([|end_|], [|Push(1)|]))
11801180- |> Js.Promise.resolve
11811181- );
11821182- });
11831183- });
11841184-11851185- describe("skipUntil", () => {
11861186- open Expect;
11871187-11881188- it("only lets values through after the notifier emits a value", () => {
11891189- let talkback = ref((. _: Wonka_types.talkbackT) => ());
11901190- let notify = ref((_: Wonka_types.talkbackT) => ());
11911191- let num = ref(1);
11921192-11931193- let notifier = sink => {
11941194- notify :=
11951195- (
11961196- signal =>
11971197- switch (signal) {
11981198- | Pull => sink(. Push(0))
11991199- | _ => ()
12001200- }
12011201- );
12021202-12031203- sink(. Start(Wonka_helpers.talkbackPlaceholder));
12041204- };
12051205-12061206- let source =
12071207- Wonka.skipUntil(notifier, sink =>
12081208- sink(.
12091209- Start(
12101210- (. signal) =>
12111211- switch (signal) {
12121212- | Pull when num^ <= 4 =>
12131213- let i = num^;
12141214- if (i === 3) {
12151215- notify^(Pull);
12161216- };
12171217- num := num^ + 1;
12181218- sink(. Push(i));
12191219- | Pull => sink(. End)
12201220- | _ => ()
12211221- },
12221222- ),
12231223- )
12241224- );
12251225-12261226- let res = [||];
12271227-12281228- source((. signal) =>
12291229- switch (signal) {
12301230- | Start(x) => talkback := x
12311231- | _ => ignore(Js.Array.push(signal, res))
12321232- }
12331233- );
12341234-12351235- talkback^(. Pull);
12361236- talkback^(. Pull);
12371237- talkback^(. Pull);
12381238- talkback^(. Pull);
12391239-12401240- expect(res) |> toEqual([|Push(3), Push(4), End|]);
12411241- });
12421242-12431243- it(
12441244- "accepts the end of the source when max number of emissions is not reached",
12451245- () => {
12461246- let talkback = ref((. _: Wonka_types.talkbackT) => ());
12471247- let num = ref(1);
12481248- let notifier = sink =>
12491249- sink(. Start(Wonka_helpers.talkbackPlaceholder));
12501250-12511251- let source =
12521252- Wonka.skipUntil(notifier, sink =>
12531253- sink(.
12541254- Start(
12551255- (. signal) =>
12561256- switch (signal) {
12571257- | Pull =>
12581258- let i = num^;
12591259- if (i < 2) {
12601260- num := num^ + 1;
12611261- sink(. Push(i));
12621262- } else {
12631263- sink(. End);
12641264- };
12651265- | _ => ()
12661266- },
12671267- ),
12681268- )
12691269- );
12701270-12711271- let res = [||];
12721272-12731273- source((. signal) =>
12741274- switch (signal) {
12751275- | Start(x) => talkback := x
12761276- | _ => ignore(Js.Array.push(signal, res))
12771277- }
12781278- );
12791279-12801280- talkback^(. Pull);
12811281- talkback^(. Pull);
12821282- talkback^(. Pull);
12831283-12841284- expect(res) |> toEqual([|End|]);
12851285- });
12861286-12871287- testPromise("follows the spec for listenables", () =>
12881288- Wonka_thelpers.testWithListenable(Wonka.skipUntil(Wonka.never))
12891289- |> Js.Promise.then_(x =>
12901290- expect(x)
12911291- |> toEqual(([|Pull, Pull, Pull|], [|End|]))
12921292- |> Js.Promise.resolve
12931293- )
12941294- );
12951295-12961296- testPromise(
12971297- "follows the spec for listenables when skipping the source", () =>
12981298- Wonka_thelpers.testWithListenable(Wonka.skipUntil(Wonka.fromValue(0)))
12991299- |> Js.Promise.then_(x =>
13001300- expect(x)
13011301- |> toEqual(([|Pull|], [|Push(1), Push(2), End|]))
13021302- |> Js.Promise.resolve
13031303- )
13041304- );
13051305-13061306- testPromise(
13071307- "ends itself and source when its talkback receives the End signal", () => {
13081308- let end_: talkbackT = Close;
13091309-13101310- Wonka_thelpers.testTalkbackEnd(Wonka.skipUntil(Wonka.fromValue(0)))
13111311- |> Js.Promise.then_(x =>
13121312- expect(x)
13131313- |> toEqual(([|Pull, end_|], [|Push(1)|]))
13141314- |> Js.Promise.resolve
13151315- );
13161316- });
13171317- });
13181318-13191319- describe("flatten", () =>
13201320- Expect.(
13211321- it("merges the result of multiple pullables into its source", () => {
13221322- let talkback = ref((. _: Wonka_types.talkbackT) => ());
13231323- let source =
13241324- Wonka.fromList([Wonka.fromList([1, 2]), Wonka.fromList([1, 2])])
13251325- |> Wonka.flatten;
13261326-13271327- let res = [||];
13281328-13291329- source((. signal) =>
13301330- switch (signal) {
13311331- | Start(x) => talkback := x
13321332- | _ => ignore(Js.Array.push(signal, res))
13331333- }
13341334- );
13351335-13361336- talkback^(. Pull);
13371337- talkback^(. Pull);
13381338- talkback^(. Pull);
13391339- talkback^(. Pull);
13401340- talkback^(. Pull);
13411341- expect(res)
13421342- |> toEqual([|Push(1), Push(2), Push(1), Push(2), End|]);
13431343- })
13441344- )
13451345- );
13461346-13471347- describe("switchMap", () => {
13481348- afterEach(() => Jest.useRealTimers());
13491349- open Expect;
13501350- open! Expect.Operators;
13511351-13521352- it("maps from a source and switches to a new source", () => {
13531353- let a = Wonka.fromList([1, 2, 3]);
13541354-13551355- let talkback = ref((. _: Wonka_types.talkbackT) => ());
13561356- let signals = [||];
13571357- let source = Wonka.switchMap((. x) => Wonka.fromList([x * x]), a);
13581358-13591359- source((. signal) =>
13601360- switch (signal) {
13611361- | Start(x) =>
13621362- talkback := x;
13631363- x(. Pull);
13641364- | Push(_) =>
13651365- ignore(Js.Array.push(signal, signals));
13661366- talkback^(. Pull);
13671367- | End => ignore(Js.Array.push(signal, signals))
13681368- }
13691369- );
13701370-13711371- expect(signals) == [|Push(1), Push(4), Push(9), End|];
13721372- });
13731373-13741374- it("unsubscribes from previous subscriptions", () => {
13751375- Jest.useFakeTimers();
13761376-13771377- let a = Wonka.interval(100);
13781378-13791379- let talkback = ref((. _: Wonka_types.talkbackT) => ());
13801380- let signals = [||];
13811381- let source =
13821382- Wonka.switchMap((. _) => Wonka.interval(25), a) |> Wonka.take(5);
13831383-13841384- source((. signal) =>
13851385- switch (signal) {
13861386- | Start(x) =>
13871387- talkback := x;
13881388- x(. Pull);
13891389- | Push(_) =>
13901390- ignore(Js.Array.push(signal, signals));
13911391- talkback^(. Pull);
13921392- | End => ignore(Js.Array.push(signal, signals))
13931393- }
13941394- );
13951395-13961396- Jest.runTimersToTime(300);
13971397-13981398- expect(signals)
13991399- == [|Push(0), Push(1), Push(2), Push(0), Push(1), End|];
14001400- });
14011401-14021402- testPromise("follows the spec for listenables", () =>
14031403- Wonka_thelpers.testWithListenable(source =>
14041404- Wonka.switchMap((. x) => x, Wonka.fromList([source]))
14051405- )
14061406- |> Js.Promise.then_(x =>
14071407- expect(x)
14081408- |> toEqual(([|Pull, Pull, Pull|], [|Push(1), Push(2), End|]))
14091409- |> Js.Promise.resolve
14101410- )
14111411- );
14121412-14131413- testPromise(
14141414- "ends itself and source when its talkback receives the End signal", () =>
14151415- Wonka_thelpers.testTalkbackEnd(source =>
14161416- Wonka.switchMap((. x) => x, Wonka.fromList([source]))
14171417- )
14181418- |> Js.Promise.then_(x =>
14191419- expect(x)
14201420- |> toEqual(([|Pull, Pull, Close|], [|Push(1)|]))
14211421- |> Js.Promise.resolve
14221422- )
14231423- );
14241424- });
14251425-});
14261426-14271427-describe("sink factories", () => {
14281428- describe("forEach", () =>
14291429- Expect.(
14301430- it("calls a function for each emission of the passed source", () => {
14311431- let i = ref(0);
14321432- let nums = [||];
14331433-14341434- let source = sink => {
14351435- sink(.
14361436- Start(
14371437- (. signal) =>
14381438- switch (signal) {
14391439- | Pull when i^ < 4 =>
14401440- let num = i^;
14411441- i := i^ + 1;
14421442- sink(. Push(num));
14431443- | Pull => sink(. End)
14441444- | _ => ()
14451445- },
14461446- ),
14471447- );
14481448- };
14491449-14501450- Wonka.forEach((. x) => ignore(Js.Array.push(x, nums)), source);
14511451- expect(nums) |> toEqual([|0, 1, 2, 3|]);
14521452- })
14531453- )
14541454- );
14551455-14561456- describe("subscribe", () =>
14571457- Expect.(
14581458- it(
14591459- "calls a function for each emission of the passed source and stops when unsubscribed",
14601460- () => {
14611461- let i = ref(0);
14621462- let nums = [||];
14631463- let push = ref(() => ());
14641464-14651465- let source = sink => {
14661466- push :=
14671467- (
14681468- () => {
14691469- let num = i^;
14701470- i := i^ + 1;
14711471- sink(. Push(num));
14721472- }
14731473- );
14741474-14751475- sink(. Start(Wonka_helpers.talkbackPlaceholder));
14761476- };
14771477-14781478- let {unsubscribe} =
14791479- Wonka.subscribe(
14801480- (. x) => ignore(Js.Array.push(x, nums)),
14811481- source,
14821482- );
14831483-14841484- push^();
14851485- push^();
14861486- unsubscribe();
14871487- push^();
14881488- push^();
14891489-14901490- expect(nums) |> toEqual([|0, 1|]);
14911491- },
14921492- )
14931493- )
14941494- );
14951495-});
14961496-14971497-describe("chains (integration)", () =>
14981498- Expect.(
14991499- it("fromArray, map, forEach", () => {
15001500- let input = Array.mapi((i, _) => i, Array.make(1000, 1));
15011501- let output = Array.map(x => string_of_int(x));
15021502- let actual = [||];
15031503-15041504- input
15051505- |> Wonka.fromArray
15061506- |> Wonka.map((. x) => string_of_int(x))
15071507- |> Wonka.forEach((. x) => ignore(Js.Array.push(x, actual)));
15081508-15091509- expect(output) |> toEqual(output);
15101510- })
15111511- )
15121512-);
15131513-15141514-describe("subject", () => {
15151515- open Expect;
15161516- open! Expect.Operators;
15171517-15181518- it("sends values passed to .next to puller sinks", () => {
15191519- let signals = [||];
15201520-15211521- let subject = Wonka.makeSubject();
15221522-15231523- subject.source((. signal) =>
15241524- switch (signal) {
15251525- | Start(_) => ignore()
15261526- | Push(_) => ignore(Js.Array.push(signal, signals))
15271527- | End => ignore(Js.Array.push(signal, signals))
15281528- }
15291529- );
15301530-15311531- subject.next(10);
15321532- subject.next(20);
15331533- subject.next(30);
15341534- subject.next(40);
15351535- subject.complete();
15361536-15371537- expect(signals) == [|Push(10), Push(20), Push(30), Push(40), End|];
15381538- });
15391539-15401540- it("handles multiple sinks", () => {
15411541- let talkback = ref((. _: Wonka_types.talkbackT) => ());
15421542- let signalsOne = [||];
15431543- let signalsTwo = [||];
15441544-15451545- let subject = Wonka.makeSubject();
15461546-15471547- subject.source((. signal) =>
15481548- switch (signal) {
15491549- | Start(x) => talkback := x
15501550- | Push(_) => ignore(Js.Array.push(signal, signalsOne))
15511551- | End => ignore(Js.Array.push(signal, signalsOne))
15521552- }
15531553- );
15541554-15551555- subject.source((. signal) =>
15561556- switch (signal) {
15571557- | Start(_) => ignore()
15581558- | Push(_) => ignore(Js.Array.push(signal, signalsTwo))
15591559- | End => ignore(Js.Array.push(signal, signalsTwo))
15601560- }
15611561- );
15621562-15631563- subject.next(10);
15641564- subject.next(20);
15651565- subject.next(30);
15661566-15671567- talkback^(. Close);
15681568-15691569- subject.next(40);
15701570- subject.next(50);
15711571-15721572- subject.complete();
15731573-15741574- expect((signalsOne, signalsTwo))
15751575- == (
15761576- [|Push(10), Push(20), Push(30)|],
15771577- [|Push(10), Push(20), Push(30), Push(40), Push(50), End|],
15781578- );
15791579- });
15801580-15811581- it("handles multiple sinks that subscribe and close at different times", () => {
15821582- let talkbackOne = ref((. _: Wonka_types.talkbackT) => ());
15831583- let talkbackTwo = ref((. _: Wonka_types.talkbackT) => ());
15841584- let signalsOne = [||];
15851585- let signalsTwo = [||];
15861586-15871587- let subject = Wonka.makeSubject();
15881588-15891589- subject.next(10);
15901590- subject.next(20);
15911591-15921592- subject.source((. signal) =>
15931593- switch (signal) {
15941594- | Start(x) => talkbackOne := x
15951595- | Push(_) => ignore(Js.Array.push(signal, signalsOne))
15961596- | End => ignore(Js.Array.push(signal, signalsOne))
15971597- }
15981598- );
15991599-16001600- subject.next(30);
16011601-16021602- subject.source((. signal) =>
16031603- switch (signal) {
16041604- | Start(x) => talkbackTwo := x
16051605- | Push(_) => ignore(Js.Array.push(signal, signalsTwo))
16061606- | End => ignore(Js.Array.push(signal, signalsTwo))
16071607- }
16081608- );
16091609-16101610- subject.next(40);
16111611- subject.next(50);
16121612-16131613- talkbackTwo^(. Close);
16141614-16151615- subject.next(60);
16161616-16171617- talkbackOne^(. Close);
16181618-16191619- subject.next(70);
16201620- subject.complete();
16211621-16221622- expect((signalsOne, signalsTwo))
16231623- == (
16241624- [|Push(30), Push(40), Push(50), Push(60)|],
16251625- [|Push(40), Push(50)|],
16261626- );
16271627- });
16281628-});
16291629-16301630-describe("web operators", () => {
16311631- describe("delay", () => {
16321632- open Expect;
16331633- open! Expect.Operators;
16341634-16351635- afterEach(() => Jest.useRealTimers());
16361636-16371637- it("should not emit values before specified delay", () => {
16381638- Jest.useFakeTimers();
16391639- let a = Wonka.fromList([1, 2, 3]);
16401640-16411641- let talkback = ref((. _: Wonka_types.talkbackT) => ());
16421642- let signals = [||];
16431643-16441644- let source = WonkaJs.delay(200, a) |> Wonka.take(3);
16451645-16461646- source((. signal) =>
16471647- switch (signal) {
16481648- | Start(x) =>
16491649- talkback := x;
16501650- x(. Pull);
16511651- | Push(_) =>
16521652- ignore(Js.Array.push(signal, signals));
16531653- talkback^(. Pull);
16541654- | End => ignore(Js.Array.push(signal, signals))
16551655- }
16561656- );
16571657-16581658- expect(signals) == [||];
16591659- });
16601660-16611661- it("should emit values after specified delay", () => {
16621662- Jest.useFakeTimers();
16631663- let a = Wonka.fromList([1, 2, 3]);
16641664-16651665- let talkback = ref((. _: Wonka_types.talkbackT) => ());
16661666- let signals = [||];
16671667-16681668- let source = WonkaJs.delay(200, a) |> Wonka.take(3);
16691669-16701670- source((. signal) =>
16711671- switch (signal) {
16721672- | Start(x) =>
16731673- talkback := x;
16741674- x(. Pull);
16751675- | Push(_) =>
16761676- ignore(Js.Array.push(signal, signals));
16771677- talkback^(. Pull);
16781678- | End => ignore(Js.Array.push(signal, signals))
16791679- }
16801680- );
16811681-16821682- Jest.runTimersToTime(400);
16831683-16841684- expect(signals) == [|Push(1), Push(2)|];
16851685- });
16861686-16871687- it("should emit an End signal when the source has emitted all values", () => {
16881688- Jest.useFakeTimers();
16891689- let a = Wonka.fromList([1, 2, 3]);
16901690-16911691- let talkback = ref((. _: Wonka_types.talkbackT) => ());
16921692- let signals = [||];
16931693-16941694- let source = WonkaJs.delay(200, a) |> Wonka.take(3);
16951695-16961696- source((. signal) =>
16971697- switch (signal) {
16981698- | Start(x) =>
16991699- talkback := x;
17001700- x(. Pull);
17011701- | Push(_) =>
17021702- ignore(Js.Array.push(signal, signals));
17031703- talkback^(. Pull);
17041704- | End => ignore(Js.Array.push(signal, signals))
17051705- }
17061706- );
17071707-17081708- Jest.runTimersToTime(600);
17091709-17101710- expect(signals) == [|Push(1), Push(2), Push(3), End|];
17111711- });
17121712- });
17131713-17141714- describe("throttle", () => {
17151715- open Expect;
17161716- open! Expect.Operators;
17171717-17181718- afterEach(() => Jest.useRealTimers());
17191719-17201720- it(
17211721- "should not emit values before specified throttle (but include values on leading edge)",
17221722- () => {
17231723- Jest.useFakeTimers();
17241724- let a = Wonka.interval(100);
17251725-17261726- let talkback = ref((. _: Wonka_types.talkbackT) => ());
17271727- let signals = [||];
17281728-17291729- let source = WonkaJs.throttle((. _) => 600, a) |> Wonka.take(3);
17301730-17311731- source((. signal) =>
17321732- switch (signal) {
17331733- | Start(x) =>
17341734- talkback := x;
17351735- x(. Pull);
17361736- | Push(_) =>
17371737- ignore(Js.Array.push(signal, signals));
17381738- talkback^(. Pull);
17391739- | End => ignore(Js.Array.push(signal, signals))
17401740- }
17411741- );
17421742-17431743- Jest.runTimersToTime(400);
17441744-17451745- expect(signals) == [|Push(0)|];
17461746- },
17471747- );
17481748-17491749- it("should throttle emissions by the specified throttle", () => {
17501750- Jest.useFakeTimers();
17511751- let a = Wonka.interval(100);
17521752-17531753- let talkback = ref((. _: Wonka_types.talkbackT) => ());
17541754- let signals = [||];
17551755-17561756- let source = WonkaJs.throttle((. _) => 600, a) |> Wonka.take(3);
17571757-17581758- source((. signal) =>
17591759- switch (signal) {
17601760- | Start(x) =>
17611761- talkback := x;
17621762- x(. Pull);
17631763- | Push(_) =>
17641764- ignore(Js.Array.push(signal, signals));
17651765- talkback^(. Pull);
17661766- | End => ignore(Js.Array.push(signal, signals))
17671767- }
17681768- );
17691769-17701770- Jest.runTimersToTime(1000);
17711771-17721772- expect(signals) == [|Push(0), Push(7)|];
17731773- });
17741774-17751775- it("should emit an End signal when the source has emitted all values", () => {
17761776- Jest.useFakeTimers();
17771777- let a = Wonka.interval(100);
17781778-17791779- let talkback = ref((. _: Wonka_types.talkbackT) => ());
17801780- let signals = [||];
17811781-17821782- let source = WonkaJs.throttle((. _) => 600, a) |> Wonka.take(3);
17831783-17841784- source((. signal) =>
17851785- switch (signal) {
17861786- | Start(x) =>
17871787- talkback := x;
17881788- x(. Pull);
17891789- | Push(_) =>
17901790- ignore(Js.Array.push(signal, signals));
17911791- talkback^(. Pull);
17921792- | End => ignore(Js.Array.push(signal, signals))
17931793- }
17941794- );
17951795-17961796- Jest.runTimersToTime(1500);
17971797-17981798- expect(signals) == [|Push(0), Push(7), Push(14), End|];
17991799- });
18001800- });
18011801-18021802- describe("debounce", () => {
18031803- open Expect;
18041804- open! Expect.Operators;
18051805-18061806- afterEach(() => Jest.useRealTimers());
18071807-18081808- it(
18091809- "should not emit values if emitted before the debounce specified by the duration selector",
18101810- () => {
18111811- Jest.useFakeTimers();
18121812- let a = Wonka.fromList([1, 2, 3]);
18131813-18141814- let talkback = ref((. _: Wonka_types.talkbackT) => ());
18151815- let signals = [||];
18161816-18171817- let source = WonkaJs.debounce((. _) => 1000, a) |> Wonka.take(3);
18181818-18191819- source((. signal) =>
18201820- switch (signal) {
18211821- | Start(x) =>
18221822- talkback := x;
18231823- x(. Pull);
18241824- | Push(_) =>
18251825- ignore(Js.Array.push(signal, signals));
18261826- talkback^(. Pull);
18271827- | End => ignore(Js.Array.push(signal, signals))
18281828- }
18291829- );
18301830-18311831- Jest.runTimersToTime(500);
18321832-18331833- expect(signals) == [||];
18341834- },
18351835- );
18361836-18371837- it("should debounce emissions based on the duration selector", () => {
18381838- Jest.useFakeTimers();
18391839- let a = Wonka.fromList([1, 2, 3]);
18401840-18411841- let talkback = ref((. _: Wonka_types.talkbackT) => ());
18421842- let signals = [||];
18431843-18441844- let source = WonkaJs.debounce((. _) => 1000, a) |> Wonka.take(3);
18451845-18461846- source((. signal) =>
18471847- switch (signal) {
18481848- | Start(x) =>
18491849- talkback := x;
18501850- x(. Pull);
18511851- | Push(_) =>
18521852- ignore(Js.Array.push(signal, signals));
18531853- talkback^(. Pull);
18541854- | End => ignore(Js.Array.push(signal, signals))
18551855- }
18561856- );
18571857-18581858- Jest.runTimersToTime(2000);
18591859-18601860- expect(signals) == [|Push(1), Push(2)|];
18611861- });
18621862-18631863- it("should emit an End signal when the source has emitted all values", () => {
18641864- Jest.useFakeTimers();
18651865- let a = Wonka.fromList([1, 2, 3]);
18661866-18671867- let talkback = ref((. _: Wonka_types.talkbackT) => ());
18681868- let signals = [||];
18691869-18701870- let source = WonkaJs.debounce((. _) => 1000, a) |> Wonka.take(3);
18711871-18721872- source((. signal) =>
18731873- switch (signal) {
18741874- | Start(x) =>
18751875- talkback := x;
18761876- x(. Pull);
18771877- | Push(_) =>
18781878- ignore(Js.Array.push(signal, signals));
18791879- talkback^(. Pull);
18801880- | End => ignore(Js.Array.push(signal, signals))
18811881- }
18821882- );
18831883-18841884- Jest.runTimersToTime(3000);
18851885-18861886- expect(signals) == [|Push(1), Push(2), Push(3), End|];
18871887- });
18881888- });
18891889-18901890- describe("sample", () => {
18911891- open Expect;
18921892- open! Expect.Operators;
18931893-18941894- afterEach(() => Jest.useRealTimers());
18951895-18961896- it("should sample the last emitted value from a source", () => {
18971897- Jest.useFakeTimers();
18981898- let a = Wonka.interval(50);
18991899-19001900- let talkback = ref((. _: Wonka_types.talkbackT) => ());
19011901- let signals = [||];
19021902-19031903- let source = WonkaJs.sample(Wonka.interval(100), a);
19041904-19051905- source((. signal) =>
19061906- switch (signal) {
19071907- | Start(x) =>
19081908- talkback := x;
19091909- x(. Pull);
19101910- | Push(_) =>
19111911- ignore(Js.Array.push(signal, signals));
19121912- talkback^(. Pull);
19131913- | End => ignore(Js.Array.push(signal, signals))
19141914- }
19151915- );
19161916-19171917- Jest.runTimersToTime(200);
19181918-19191919- expect(signals) == [|Push(1), Push(3)|];
19201920- });
19211921-19221922- it("should emit an End signal when the source has emitted all values", () => {
19231923- Jest.useFakeTimers();
19241924- let a = Wonka.interval(50);
19251925-19261926- let talkback = ref((. _: Wonka_types.talkbackT) => ());
19271927- let signals = [||];
19281928-19291929- let source = WonkaJs.sample(Wonka.interval(100), a) |> Wonka.take(3);
19301930-19311931- source((. signal) =>
19321932- switch (signal) {
19331933- | Start(x) =>
19341934- talkback := x;
19351935- x(. Pull);
19361936- | Push(_) =>
19371937- ignore(Js.Array.push(signal, signals));
19381938- talkback^(. Pull);
19391939- | End => ignore(Js.Array.push(signal, signals))
19401940- }
19411941- );
19421942-19431943- Jest.runTimersToTime(300);
19441944-19451945- expect(signals) == [|Push(1), Push(3), Push(5), End|];
19461946- });
19471947- });
19481948-});
···11+---
22+title: API Reference
33+order: 4
44+---
55+66+Wonka, in essence, can be used to create sources, to transform sources with operators,
77+and to consume values from a source with sinks.
88+99+Looking at the type definition for what a sink is, it's just a function that can be
1010+called with a signal, which is either `Start`, `Push`, or `End`.
1111+Building on that, a source is just a function
1212+that takes a sink and calls it with signals over time. And lastly an operator is
1313+a function that accepts a source, alongside some options most of the time, and returns
1414+a new source.
1515+1616+Wonka comes with plenty of sources, operators, and sinks built in. This section
1717+describes these and explains what they can be used for.
1818+1919+- [Sources](./sources.md) — learn the Wonka source APIs
2020+- [Operators](./operators.md) — learn the Wonka operator APIs
2121+- [Sinks](./sinks.md) — learn the Wonka sink APIs
+553
docs/api/operators.md
···11+---
22+title: Operators
33+order: 1
44+---
55+66+Operators in Wonka allow you to transform values from a source before they are sent to a sink. Wonka has the following operators.
77+88+## buffer
99+1010+Buffers emissions from an outer source and emits a buffer array of items every time an
1111+inner source (notifier) emits.
1212+1313+This operator can be used to group values into a arrays on a source. The emitted values will
1414+be sent when a notifier fires and will be arrays of all items before the notification event.
1515+1616+In combination with `interval` this can be used to group values in chunks regularly.
1717+1818+```typescript
1919+import { pipe, interval, buffer, take, subscribe } from 'wonka';
2020+2121+pipe(
2222+ interval(50),
2323+ buffer(interval(100)),
2424+ take(2),
2525+ subscribe((buffer) => {
2626+ buffer.forEach((x) => console.log(x));
2727+ console.log(';');
2828+ })
2929+); // Prints 1 2; 2 3 to the console.
3030+```
3131+3232+## combine
3333+3434+`combine` two sources together to a single source. The emitted value will be a combination of the two sources, with all values from the first source being emitted with the first value of the second source _before_ values of the second source are emitted.
3535+3636+```typescript
3737+import { fromArray, pipe, combine, subscribe } from 'wonka';
3838+3939+const sourceOne = fromArray([1, 2, 3]);
4040+const sourceTwo = fromArray([4, 5, 6]);
4141+4242+pipe(
4343+ combine(sourceOne, sourceTwo),
4444+ subscribe(([valOne, valTwo]) => {
4545+ console.log(valOne + valTwo);
4646+ })
4747+); // Prints 56789 (1+4, 2+4, 3+4, 3+5, 3+6) to the console.
4848+```
4949+5050+## concat
5151+5252+`concat` will combine two sources together, subscribing to the next source after the previous source completes.
5353+5454+```typescript
5555+import { fromArray, pipe, concat, subscribe } from 'wonka';
5656+5757+const sourceOne = fromArray([1, 2, 3]);
5858+const sourceTwo = fromArray([6, 5, 4]);
5959+6060+pipe(
6161+ concat([sourceOne, sourceTwo]),
6262+ subscribe((val) => console.log(val))
6363+); // Prints 1 2 3 6 5 4 to the console.
6464+```
6565+6666+## concatAll
6767+6868+`concatAll` will combine all sources emitted on an outer source together, subscribing to the
6969+next source after the previous source completes.
7070+7171+It's very similar to `concat`, but instead accepts a source of sources as an input.
7272+7373+```typescript
7474+import { pipe, fromArray, concatAll, subscribe } from 'wonka';
7575+7676+const sourceOne = fromArray([1, 2, 3]);
7777+const sourceTwo = fromArray([6, 5, 4]);
7878+7979+pipe(
8080+ fromArray([sourceOne, sourceTwo]),
8181+ concatAll,
8282+ subscribe((val) => console.log(val))
8383+); // Prints 1 2 3 6 5 4 to the console.
8484+```
8585+8686+## concatMap
8787+8888+`concatMap` allows you to map values of an outer source to an inner source. The sink will not dispatch the `Pull` signal until the previous value has been emitted. This is in contrast to `mergeMap`, which will dispatch the `Pull` signal for new values even if the previous value has not yet been emitted.
8989+9090+```typescript
9191+import { fromArray, pipe, concatMap, delay, fromValue, subscribe } from 'wonka';
9292+9393+const source = fromArray([1, 2, 3, 4, 5, 6]);
9494+9595+pipe(
9696+ source,
9797+ concatMap((val) => {
9898+ return pipe(fromValue(val), delay(val * 1000));
9999+ }),
100100+ subscribe((val) => console.log(val))
101101+);
102102+```
103103+104104+## delay
105105+106106+`delay` delays all emitted values of a source by the given amount of milliseconds.
107107+108108+```typescript
109109+import { pipe, fromArray, delay, subscribe } from 'wonka';
110110+111111+pipe(
112112+ fromArray([1, 2]),
113113+ delay(10)
114114+ subscribe(val => console.log(val))
115115+);
116116+// waits 10ms then prints 1, waits 10ms then prints 2, waits 10ms then ends
117117+```
118118+119119+## debounce
120120+121121+`debounce` doesn't emit values of a source until no values have been emitted after
122122+a given amount of milliseconds. Once this threshold of silence has been reached, the
123123+last value that has been received will be emitted.
124124+125125+```typescript
126126+import { pipe, interval, take, fromValue, concat, debounce, subscribe } from 'wonka';
127127+128128+const sourceA = pipe(interval(10), take(5));
129129+const sourceB = fromValue(1);
130130+131131+pipe(
132132+ concat([sourceA, sourceB])
133133+ debounce(() => 20),
134134+ subscribe(val => console.log(val))
135135+);
136136+137137+// The five values from sourceA will be omitted
138138+// After these values and after 20ms `1` will be logged
139139+```
140140+141141+## filter
142142+143143+`filter` will remove values from a source by passing them through an iteratee that returns a `bool`.
144144+145145+```typescript
146146+import { fromArray, filter, subscribe } from 'wonka';
147147+148148+const isEven = (n) => n % 2 === 0;
149149+150150+pipe(
151151+ fromArray([1, 2, 3, 4, 5, 6]),
152152+ filter(isEven),
153153+ subscribe((val) => console.log(val))
154154+);
155155+156156+// Prints 246 to the console.
157157+```
158158+159159+## map
160160+161161+`map` will transform values from a source by passing them through an iteratee that returns a new value.
162162+163163+```typescript
164164+import { fromArray, pipe, map, subscribe } from 'wonka';
165165+166166+const square = (n) => n * n;
167167+168168+pipe(
169169+ fromArray([1, 2, 3, 4, 5, 6]),
170170+ map(square),
171171+ subscribe((val) => console.log(val))
172172+);
173173+174174+// Prints 1 4 9 16 25 36 to the console.
175175+```
176176+177177+## merge
178178+179179+`merge` merges an array of sources together into a single source. It subscribes
180180+to all sources that it's passed and emits all their values on the output source.
181181+182182+```typescript
183183+import { fromArray, pipe, merge, subscribe } from 'wonka';
184184+185185+const sourceOne = fromArray([1, 2, 3]);
186186+const sourceTwo = fromArray([4, 5, 6]);
187187+188188+pipe(
189189+ merge(sourceOne, sourceTwo),
190190+ subscribe((val) => console.log(val))
191191+); // Prints 1 2 3 4 5 6 to the console.
192192+```
193193+194194+## mergeAll
195195+196196+`mergeAll` will merge all sources emitted on an outer source into a single one.
197197+It's very similar to `merge`, but instead accepts a source of sources as an input.
198198+199199+> _Note:_ This operator is also exported as `flatten` which is just an alias for `mergeAll`
200200+201201+```typescript
202202+import { pipe, fromArray, mergeAll, subscribe } from 'wonka';
203203+204204+const sourceOne = fromArray([1, 2, 3]);
205205+const sourceTwo = fromArray([4, 5, 6]);
206206+207207+pipe(
208208+ fromArray([sourceOne, sourceTwo]),
209209+ mergeAll,
210210+ subscribe((val) => console.log(val))
211211+); // Prints 1 2 3 4 5 6 to the console.
212212+```
213213+214214+## mergeMap
215215+216216+`mergeMap` allows you to map values of an outer source to an inner source.
217217+This allows you to create nested sources for each emitted value, which will
218218+all be merged into a single source, like with `mergeAll`.
219219+220220+Unlike `concatMap` all inner sources will be subscribed to at the same time
221221+and all their values will be emitted on the output source as they come in.
222222+223223+```typescript
224224+import { pipe, fromArray, mergeMap, subscribe } from 'wonka';
225225+226226+pipe(
227227+ fromArray([1, 2]),
228228+ mergeMap((x) => fromArray([x - 1, x])),
229229+ subscribe((val) => console.log(val))
230230+); // Prints 0 1 1 2 to the console.
231231+```
232232+233233+## onEnd
234234+235235+Run a callback when the `End` signal has been sent to the sink by the source, whether by way of the talkback passing the `End` signal or the source being exhausted of values.
236236+237237+```typescript
238238+import { fromPromise, pipe, concat, onEnd, subscribe } from 'wonka';
239239+240240+const promiseOne = new Promise((resolve) => {
241241+ setTimeout(() => {
242242+ resolve('ResolveOne');
243243+ }, 1000);
244244+});
245245+const promiseTwo = new Promise((resolve) => {
246246+ setTimeout(() => {
247247+ resolve('ResolveTwo');
248248+ }, 2000);
249249+});
250250+251251+const sourceOne = fromPromise(promiseOne);
252252+const sourceTwo = fromPromise(promiseTwo);
253253+254254+pipe(
255255+ concat([sourceOne, sourceTwo]),
256256+ onEnd(() => console.log('onEnd')),
257257+ subscribe((val) => console.log(val))
258258+);
259259+260260+// Logs ResolveOne after one second, then ResolveTwo after an additional second, then onEnd immediately.
261261+```
262262+263263+## onPush
264264+265265+Run a callback on each `Push` signal sent to the sink by the source.
266266+267267+> _Note:_ This operator is also exported as `tap` which is just an alias for `onPush`
268268+269269+```typescript
270270+import { fromArray, pipe, onPush, subscribe } from 'wonka';
271271+272272+pipe(
273273+ fromArray([1, 2, 3, 4, 5, 6]),
274274+ onPush((val) => console.log(`Push ${val}`)),
275275+ subscribe((val) => console.log(val))
276276+); // Prints Push 1 1 Push 2 2 Push 3 3 Push 4 4 Push 5 5 Push 6 6 to the console.
277277+```
278278+279279+## onStart
280280+281281+Run a callback when the `Start` signal is sent to the sink by the source.
282282+283283+```typescript
284284+import { pipe, onStart, fromPromise, subscribe } from 'wonka';
285285+286286+const promise = new Promise((resolve) => {
287287+ setTimeout(() => {
288288+ resolve('Resolve');
289289+ }, 1000);
290290+});
291291+292292+pipe(
293293+ fromPromise(promise),
294294+ onStart(() => console.log('onStart')),
295295+ subscribe((val) => console.log(val))
296296+);
297297+298298+// Logs onStart to the console, pauses for one second to allow the timeout to finish,
299299+// then logs "Resolve" to the console.
300300+```
301301+302302+## sample
303303+304304+`sample` emits the previously emitted value from an outer source every time
305305+an inner source (notifier) emits.
306306+307307+In combination with `interval` it can be used to get values from a noisy source
308308+more regularly.
309309+310310+```typescript
311311+import { pipe, interval, sample, take, subscribe } from 'wonka';
312312+313313+pipe(
314314+ interval(10),
315315+ sample(interval(100)),
316316+ take(2),
317317+ subscribe((x) => console.log(x))
318318+); // Prints 10 20 to the console.
319319+```
320320+321321+## scan
322322+323323+Accumulate emitted values of a source in a accumulator, similar to JavaScript `reduce`.
324324+325325+```typescript
326326+import { fromArray, pipe, scan, subscribe } from 'wonka';
327327+328328+pipe(
329329+ fromArray([1, 2, 3, 4, 5, 6]),
330330+ scan((acc, val) => acc + val, 0),
331331+ subscribe((val) => console.log(val))
332332+);
333333+334334+// Prints 1 3 6 10 15 21 to the console.
335335+```
336336+337337+## share
338338+339339+`share` ensures that all subscriptions to the underlying source are shared.
340340+341341+By default Wonka's sources are lazy. They only instantiate themselves and begin
342342+emitting signals when they're being subscribed to, since they're also immutable.
343343+This means that when a source is used in multiple places, their underlying subscription
344344+is not shared. Instead, the entire chain of sources and operators will be instantiated
345345+separately every time.
346346+347347+The `share` operator prevents this by creating an output source that will reuse a single
348348+subscription to the parent source, which will be unsubscribed from when no sinks are
349349+listening to it anymore.
350350+351351+This is especially useful if you introduce side-effects to your sources,
352352+for instance with `onStart`.
353353+354354+```typescript
355355+import { pipe, never, onStart, share, publish } from 'wonka';
356356+357357+const source = pipe(
358358+ never
359359+ onStart(() => console.log('start')),
360360+ share
361361+);
362362+363363+// Without share this would print "start" twice:
364364+publish(source);
365365+publish(source);
366366+```
367367+368368+## skip
369369+370370+`skip` the specified number of emissions from the source.
371371+372372+```typescript
373373+import { fromArray, pipe, skip, subscribe } from 'wonka';
374374+375375+pipe(
376376+ fromArray([1, 2, 3, 4, 5, 6]),
377377+ skip(2),
378378+ subscribe((val) => console.log(val))
379379+);
380380+```
381381+382382+## skipUntil
383383+384384+Skip emissions from an outer source until an inner source (notifier) emits.
385385+386386+```typescript
387387+import { interval, pipe, skipUntil, subscribe } from 'wonka';
388388+389389+const source = interval(100);
390390+const notifier = interval(500);
391391+392392+pipe(
393393+ source,
394394+ skipUntil(notifier),
395395+ subscribe((val) => console.log(val))
396396+);
397397+398398+// Skips all values emitted by source (0, 1, 2, 3) until notifier emits at 500ms.
399399+// Then logs 4 5 6 7 8 9 10... to the console every 500ms.
400400+```
401401+402402+## skipWhile
403403+404404+Skip values emitted from the source while they return `true` for the provided predicate function.
405405+406406+```typescript
407407+import { fromArray, pipe, skipWhile, subscribe } from 'wonka';
408408+409409+pipe(
410410+ fromArray([1, 2, 3, 4, 5, 6]),
411411+ skipWhile((val) => val < 5),
412412+ subscribe((val) => console.log(val))
413413+);
414414+415415+// Prints 5 6 to the console, as 1 2 3 4 all return true for the predicate function.
416416+```
417417+418418+## switchMap
419419+420420+`switchMap` allows you to map values of an outer source to an inner source.
421421+The inner source's values will be emitted on the returned output source. If
422422+a new inner source is returned, because the outer source emitted a new value
423423+before the previous inner source completed, the inner source is closed and unsubscribed
424424+from.
425425+426426+This is similar to `concatMap` but instead of waiting for the last inner source to complete
427427+before emitting values from the next, `switchMap` just cancels the previous inner source.
428428+429429+```typescript
430430+import { pipe, interval, switchMap, take, subscribe } from 'wonka';
431431+432432+pipe(
433433+ interval(50),
434434+ // The inner interval is cancelled after its first value every time
435435+ switchMap((value) => interval(40)),
436436+ take(3),
437437+ subscribe((x) => console.log(x))
438438+); // Prints 1 2 3 to the console
439439+```
440440+441441+## switchAll
442442+443443+`switchAll` will combined sources emitted on an outer source together, subscribing
444444+to only one source at a time, and cancelling the previous inner source, when it hasn't
445445+ended while the next inner source is created.
446446+447447+It's very similar to `switchMap`, but instead accepts a source of sources.
448448+449449+```typescript
450450+import { pipe, interval, map, switchAll, take, subscribe } from 'wonka';
451451+452452+pipe(
453453+ interval(50),
454454+ map(() => interval(40)),
455455+ switchAll,
456456+ take(3),
457457+ subscribe((x) => console.log(x))
458458+); // Prints 1 2 3 to the console
459459+```
460460+461461+These examples are practically identical to the `switchMap` examples, but note
462462+that `map` was used instead of using `switchMap` directly. This is because combining
463463+`map` with a subsequent `switchAll` is the same as using `switchMap`.
464464+465465+## take
466466+467467+`take` only a specified number of emissions from the source before completing. `take` is the opposite of `skip`.
468468+469469+```typescript
470470+import { fromArray, pipe, take, subscribe } from 'wonka';
471471+472472+pipe(
473473+ fromArray([1, 2, 3, 4, 5, 6]),
474474+ take(3),
475475+ subscribe((val) => console.log(val))
476476+);
477477+478478+// Prints 1 2 3 to the console.
479479+```
480480+481481+## takeLast
482482+483483+`takeLast` will take only the last n emissions from the source.
484484+485485+```typescript
486486+import { fromArray, pipe, takeLast, subscribe } from 'wonka';
487487+488488+pipe(
489489+ fromArray([1, 2, 3, 4, 5, 6]),
490490+ takeLast(3),
491491+ subscribe((val) => console.log(val))
492492+);
493493+494494+// Prints 4 5 6 to the console.
495495+```
496496+497497+## takeUntil
498498+499499+Take emissions from an outer source until an inner source (notifier) emits.
500500+501501+```typescript
502502+import { interval, pipe, takeUntil, subscribe } from 'wonka';
503503+504504+const source = interval(100);
505505+const notifier = interval(500);
506506+507507+pipe(
508508+ source,
509509+ takeUntil(notifier),
510510+ subscribe((val) => console.log(val))
511511+);
512512+513513+// Pauses 100ms, prints 0, pauses 100ms, prints 1, pauses 100ms, prints 2, pauses 100ms,
514514+// prints 3, pauses 100, then completes (notifier emits).
515515+```
516516+517517+## takeWhile
518518+519519+Take emissions from the stream while they return `true` for the provided predicate function. If the first emission does not return `true`, no values will be `Push`ed to the sink.
520520+521521+```typescript
522522+import { pipe, fromArray, takeWhile, subscribe } from 'wonka';
523523+524524+const source = fromArray([1, 2, 3, 4, 5, 6]);
525525+526526+pipe(
527527+ source,
528528+ takeWhile((val) => val < 5),
529529+ subscribe((val) => console.log(val))
530530+);
531531+532532+// Prints 1 2 3 4 to the console.
533533+```
534534+535535+## throttle
536536+537537+`throttle` emits values of a source, but after each value it will omit all values for
538538+the given amount of milliseconds. It enforces a time of silence after each value it
539539+receives and skips values while the silence is still ongoing.
540540+541541+This is very similar to `debounce` but instead of waiting for leading time before a
542542+value it waits for trailing time after a value.
543543+544544+```typescript
545545+import { pipe, interval, throttle, take, subscribe } from 'wonka';
546546+547547+pipe(
548548+ interval(10),
549549+ throttle(() => 50)
550550+ take(2),
551551+ subscribe(val => console.log(val))
552552+); // Outputs 0 6 to the console.
553553+```
+145
docs/api/sinks.md
···11+---
22+title: Sinks
33+order: 2
44+---
55+66+A sink in Wonka expects to be delivered data. A `sink` communicates with a source via the "talkback" function provided by the source. Wonka has the following `sink` operators.
77+88+## subscribe
99+1010+`subscribe` accepts a callback function to execute when data is received from the source, in addition to the source itself.
1111+1212+```typescript
1313+import { pipe, fromArray, subscribe } from 'wonka';
1414+1515+pipe(
1616+ fromArray([1, 2, 3]),
1717+ subscribe((x) => console.log(x))
1818+); // Prints 123 to the console.
1919+```
2020+2121+`subscribe` also returns a "subscription" type, which can be used to
2222+unsubscribe from the source. This allows you to cancel a source and stop receiving
2323+new incoming values.
2424+2525+```typescript
2626+import { pipe, subscribe } from 'wonka';
2727+2828+const { unsubscribe } = pipe(
2929+ source,
3030+ subscribe((x) => console.log(x));
3131+);
3232+3333+unsubscribe();
3434+```
3535+3636+## forEach
3737+3838+`forEach` works the same as `subscribe` but doesn't return a subscription.
3939+It will just call the passed callback for each incoming value.
4040+4141+```typescript
4242+import { pipe, fromArray, forEach } from 'wonka';
4343+4444+pipe(
4545+ fromArray([1, 2, 3]),
4646+ forEach((x) => console.log(x))
4747+); // Returns undefined; Prints 123 to the console.
4848+```
4949+5050+## publish
5151+5252+`publish` subscribes to a source, like `subscribe` does, but doesn't accept
5353+a callback function. It's useful for side-effects, where the values are already being
5454+used as part of the stream itself.
5555+5656+In this example we're using [`onPush`](./operators.md#onpush) to pass a callback to react to incoming
5757+values instead.
5858+5959+```typescript
6060+import { pipe, fromArray, onPush, publish } from 'wonka';
6161+6262+pipe(
6363+ fromArray([1, 2, 3]),
6464+ onPush((x) => console.log(x)),
6565+ publish
6666+); // Prints 123 to the console.
6767+```
6868+6969+## toArray
7070+7171+`toArray` returns an array, which contains all values from a pull source.
7272+This sink is primarily intended for synchronous pull streams. Passing it
7373+an asynchronous push streams may result in an empty array being returned.
7474+7575+If you're passing an asynchronous push stream `toArray` will cancel it
7676+before it returns an array.
7777+7878+> _Note:_ If you're using this sink, make sure that your input source streams
7979+> the values you're collecting partly or fully synchronously.
8080+8181+```typescript
8282+import { pipe, fromArray, map, toArray } from 'wonka';
8383+8484+pipe(
8585+ fromArray([1, 2, 3]),
8686+ map((x) => x * 2),
8787+ toArray
8888+); // Returns [2, 4, 6]
8989+```
9090+9191+## toPromise
9292+9393+`toPromise` returns a promise, which resolves on the last value of a source.
9494+9595+```typescript
9696+import { pipe, fromArray, toPromise } from 'wonka';
9797+9898+const promise = pipe(fromArray([1, 2, 3]), toPromise);
9999+100100+promise.then((x) => console.log(x));
101101+// Prints 3 to the console.
102102+```
103103+104104+If you have a source that doesn't complete and are looking to resolve on the first
105105+value instead of the last, you may have to apply `take(1)` to your source.
106106+107107+## toObservable
108108+109109+`toObservable` returns a [spec-compliant JS Observable](https://github.com/tc39/proposal-observable), which emits the same
110110+values as a source.
111111+112112+As per the specification, the Observable is annotated using `Symbol.observable`.
113113+114114+```typescript
115115+import { pipe, fromArray, toObservable } from 'wonka';
116116+117117+const observable = pipe(fromArray([1, 2, 3]), toObservable);
118118+119119+observable.subscribe({
120120+ next: (value) => console.log(value),
121121+ complete: () => {},
122122+ error: () => {},
123123+}); // Prints 1 2 3 to the console.
124124+```
125125+126126+## toCallbag
127127+128128+`toCallbag` returns a [spec-compliant JS Callbag](https://github.com/callbag/callbag), which emits the same signals
129129+as a Wonka source.
130130+131131+Since Wonka's sources are very similar to callbags and only diverge from the specification
132132+minimally, Callbags map to Wonka's sources very closely and `toCallbag` only creates a thin
133133+wrapper which is mostly concerned with converting between the type signatures.
134134+135135+```typescript
136136+import { pipe, fromArray, toCallbag } from 'wonka';
137137+138138+// This example uses the callbag-iterate package for illustrative purposes
139139+import callbagIterate from 'callbag-iterate';
140140+141141+const callbag = pipe(fromArray([1, 2, 3]), toCallbag);
142142+143143+callbagIterate((value) => console.log(value))(callbag);
144144+// Prints 1 2 3 to the console.
145145+```
+209
docs/api/sources.md
···11+---
22+title: Sources
33+order: 0
44+---
55+66+A "source" in Wonka is a provider of data. It provides data to a "sink" when the "sink" requests it. This is called a pull signal and for synchronous sources no time will pass between the sink pulling a new value and a source sending it. For asynchronous sources, the source may either ignore pull signals and just push values or send one some time after the pull signal.
77+88+## fromArray
99+1010+`fromArray` transforms an array into a source, emitting each item synchronously.
1111+1212+```typescript
1313+import { fromArray } from 'wonka';
1414+fromArray([1, 2, 3]);
1515+```
1616+1717+## fromValue
1818+1919+`fromValue` takes a single value and creates a source that emits the value and
2020+completes immediately afterwards.
2121+2222+```typescript
2323+import { fromValue } from 'wonka';
2424+fromValue(1);
2525+```
2626+2727+## make
2828+2929+`make` can be used to create an arbitrary source. It allows you to make a source
3030+from any other data.
3131+It accepts a function that receives an "observer" and should return a teardown
3232+function. It's very similar to creating an [Observable in `zen-observable`](https://github.com/zenparsing/zen-observable#new-observablesubscribe).
3333+3434+The function you pass to `make` is called lazily when a sink subscribes to the
3535+source you're creating. The first argument `observer` is a tuple with two methods:
3636+3737+- `next(value)` emits a value on the sink
3838+- `complete()` ends the source and completes the sink
3939+4040+The subscriber function also needs to return a `teardown` function. This function
4141+is called when either `complete()` is called and the source ends, or if the source
4242+is being cancelled, since the sink unsubscribed.
4343+4444+In this example we create a source that waits for a promise to resolve and emits
4545+values from the array of that promise.
4646+4747+```typescript
4848+import { make } from 'wonka';
4949+5050+const waitForArray = () => Promise.resolve([1, 2, 3]);
5151+5252+const source = make((observer) => {
5353+ const { next, complete } = observer;
5454+ let cancelled = false;
5555+5656+ waitForArray().then((arr) => {
5757+ if (!cancelled) {
5858+ arr.forEach(next);
5959+ complete();
6060+ }
6161+ });
6262+6363+ return () => {
6464+ cancelled = true;
6565+ };
6666+});
6767+```
6868+6969+## makeSubject
7070+7171+`makeSubject` can be used to create a subject. This is similar to [`make`](#make) without
7272+having to define a source function. Instead a subject is a tuple of a source and
7373+the observer's `next` and `complete` functions combined.
7474+7575+A subject can be very useful as a full event emitter. It allows you to pass a source
7676+around but also have access to the observer functions to emit events away from
7777+the source itself.
7878+7979+```typescript
8080+import { makeSubject } from 'wonka';
8181+const subject = makeSubject();
8282+const { source, next, complete } = subject;
8383+8484+/* This will push the values synchronously to any subscribers of source */
8585+next(1);
8686+next(2);
8787+next(complete);
8888+```
8989+9090+## fromDomEvent
9191+9292+`fromDomEvent` will turn a DOM event into a Wonka source, emitting the DOM events
9393+on the source whenever the DOM emits them on the passed element.
9494+9595+```typescript
9696+import { pipe, fromDomEvent, subscribe } from 'wonka';
9797+9898+const element = document.getElementById('root');
9999+100100+pipe(
101101+ fromDomEvent(element, 'click'),
102102+ subscribe((e) => console.log(e))
103103+);
104104+```
105105+106106+## fromPromise
107107+108108+`fromPromise` transforms a promise into a source, emitting the promisified value on
109109+the source once it resolves.
110110+111111+```typescript
112112+import { pipe, fromPromise, subscribe } from 'wonka';
113113+114114+const promise = Promise.resolve(1); // Just an example promise
115115+116116+pipe(
117117+ fromPromise(promise),
118118+ subscribe((e) => console.log(e))
119119+); // Prints 1 to the console.
120120+```
121121+122122+## fromObservable
123123+124124+`fromObservable` transforms a [spec-compliant JS Observable](https://github.com/tc39/proposal-observable) into a source.
125125+The resulting source will behave exactly the same as the Observable that it was
126126+passed, so it will start, end, and push values identically.
127127+128128+```typescript
129129+import { pipe, fromObservable, subscribe } from 'wonka';
130130+131131+// This example uses zen-observable for illustrative purposes
132132+import Observable from 'zen-observable';
133133+134134+const observable = Observable.from([1, 2, 3]);
135135+136136+pipe(
137137+ fromObservable(observable),
138138+ subscribe((e) => console.log(e))
139139+); // Prints 1 2 3 to the console
140140+```
141141+142142+## fromCallbag
143143+144144+`fromCallbag` transforms a [spec-compliant JS Callbag](https://github.com/callbag/callbag) into a source.
145145+146146+Since Wonka's sources are very similar to callbags and only diverge from the specification
147147+minimally, Callbags map to Wonka's sources very closely and the `fromCallbag` wrapper
148148+is very thin and mostly concerned with converting between the type signatures.
149149+150150+```typescript
151151+import { pipe, fromCallbag, subscribe } from 'wonka';
152152+153153+// This example uses the callbag-from-iter package for illustrative purposes
154154+import callbagFromArray from 'callbag-from-iter';
155155+156156+const callbag = callbagFromArray([1, 2, 3]);
157157+158158+pipe(
159159+ fromCallbag(callbag),
160160+ subscribe((e) => console.log(e))
161161+); // Prints 1 2 3 to the console.
162162+```
163163+164164+## interval
165165+166166+`interval` creates a source that emits values after the given amount of milliseconds.
167167+Internally it uses `setInterval` to accomplish this.
168168+169169+```typescript
170170+import { pipe, interval, subscribe } from 'wonka';
171171+172172+pipe(
173173+ interval(50),
174174+ subscribe((e) => console.log(e))
175175+); // Prints 0 1 2... to the console.
176176+// The incrementing number is logged every 50ms
177177+```
178178+179179+## empty
180180+181181+This is a source that doesn't emit any values when subscribed to and
182182+immediately completes.
183183+184184+```typescript
185185+import { pipe, empty, forEach } from 'wonka';
186186+187187+pipe(
188188+ empty,
189189+ forEach((value) => {
190190+ /* This will never be called */
191191+ })
192192+);
193193+```
194194+195195+## never
196196+197197+This is source is similar to [`empty`](#empty).
198198+It doesn't emit any values but also never completes.
199199+200200+```typescript
201201+import { pipe, never, forEach } from 'wonka';
202202+203203+pipe(
204204+ never,
205205+ forEach((value) => {
206206+ /* This will never be called */
207207+ })
208208+);
209209+```
+129
docs/basics/architecture.md
···11+---
22+title: Architecture
33+order: 1
44+---
55+66+It may be useful to understand how Wonka's sources work internally
77+if you want to write a new operator from scratch or contribute to it.
88+99+This section explains how Wonka works internally and how it differs from
1010+the callbag specification.
1111+1212+## Just Functions
1313+1414+Internally Wonka only uses functions with rather simple signatures to
1515+make its streams work.
1616+1717+We have sinks on one end, which need to receive values, and sources
1818+on the other, which need to send values.
1919+The sink is therefore just a function that we call with values over time.
2020+This is called a "push" signal.
2121+2222+Because a sink has a start, incoming values, and an end, there are three
2323+signals that a sink can receive: `Start`, `Push`, and `End`.
2424+2525+```typescript
2626+type Start = { tag: 0 }; // & [TalkbackFn]
2727+type Push<T> = { tag: 1 } & [T];
2828+type End = 0;
2929+3030+type Signal<T> = Start | Push<T> | End;
3131+3232+type Sink<T> = (signal: Signal<T>) => void;
3333+```
3434+3535+As shown, the sink is just a function accepting a signal as its argument.
3636+3737+When the stream starts then the sink is called with `Start`,
3838+Then for every incoming, new value it's called with `Push<T>`,
3939+and when the stream ends it's finally called with `End`.
4040+4141+Since we want a source to send these values to the sink, the source is
4242+also just a function and it accepts a sink as its argument.
4343+4444+```typescript
4545+type Source<T> = (sink: Sink<T>) => void;
4646+```
4747+4848+This is completely sufficient to represent simple "push" streams, where
4949+values are pushed from the source to the sink. They essentially flow from
5050+the "top" to the "bottom".
5151+5252+Operators are just functions that transform a source. They take a
5353+source and some number of arguments and return a new source.
5454+Internally they may also create a new sink function that wraps the
5555+sink that their source will be called with.
5656+5757+The type signature of an operator with no other arguments is thus:
5858+5959+```typescript
6060+type Operator<In, Out> = (source: Source<In>) => Source<Out>;
6161+/* which is the same as: */
6262+type Operator<In, Out> = (source: Source<In>) => (sink: Sink<Out>) => void;
6363+```
6464+6565+## Adding Callbacks
6666+6767+To complete this pattern we're still missing a single piece: callbacks!
6868+6969+Previously, we've looked at how sources are functions that accept sinks, which
7070+in turn are functions accepting a signal. What we're now missing is what makes
7171+Wonka's streams also work as iterables.
7272+7373+We'd also like to be able to _cancel_ streams, so that we can interrupt
7474+them and not receive any more values.
7575+7676+We can achieve this by passing a callback function on when a stream starts.
7777+In Wonka, a sink's `Start` signal also carries a callback that is used to communicate
7878+back to the source, making these "talkback signals" flow from the bottom to the top.
7979+8080+```typescript
8181+const enum TalkbackKind {
8282+ Pull = 0,
8383+ Close = 1,
8484+}
8585+8686+type TalkbackFn = (signal: TalkbackKind) => void;
8787+type Start = { tag: 0 } & [TalkbackFn];
8888+```
8989+9090+This is like the previous `Signal<T>` definition, but the `Start` signal has the
9191+callback definition now. The callback accepts one of two signals: `Pull` or `Close`.
9292+9393+`Close` is a signal that will cancel the stream. It tells the source to stop sending
9494+new values.
9595+9696+The `Pull` signal is a signal that asks the source to send the next value. This is
9797+especially useful to represent iterables. In practice a user would never send this
9898+signal explicitly, but sinks would send the signal automatically after receiving the
9999+previous value from the stream.
100100+101101+In asynchronous streams the `Pull` signal is of course a no-op. It won't do
102102+anything since we can't ask for asynchronous values.
103103+104104+## Comparison to Callbags
105105+106106+This is the full pattern of Wonka's streams and it's a little different from callbags.
107107+These changes have been made to make Wonka's streams typesafe. But there's
108108+also a small omission that makes Wonka's streams easier to explain.
109109+110110+In Callbags, sources don't just accept sinks as their only argument. In fact, in
111111+callbags the source would also receive three different signals. This can be useful
112112+to represent "subjects".
113113+114114+A subject is a sink and source combined. It can be used to dispatch values imperatively,
115115+like an event dispatcher.
116116+117117+In Wonka there's a separate type for subjects however, since this reduces the
118118+complexity of its streams a lot:
119119+120120+```reason
121121+interface Subject<T> {
122122+ next(value: T): void;
123123+ complete(): void;
124124+ source: Source<T>;
125125+}
126126+```
127127+128128+Hence in Wonka a subject is simply a wrapper around a source and a `next` and `complete`
129129+method.
+66
docs/basics/background.md
···11+---
22+title: Background
33+order: 0
44+---
55+66+In a lot of daily tasks in programming we come across patterns where
77+we deal with lists of values. In JavaScript we'd reach to arrays to
88+collect them, and luckily there are plenty of methods built-in
99+to modify such an array, such as `map`, `filter` and `reduce`.
1010+1111+Things become more complex when we're dealing with lists that
1212+are infinite. In such a case we may reach to iterables. We could
1313+expect an iterable that continuously outputs numbers, counting up
1414+infinitely, or rather until it reaches the maximum integer.
1515+1616+When we're dealing with asynchronous lists of values things also
1717+become more complex. We're often confronted with event streams,
1818+where events or even regular values come in over time.
1919+2020+In either case what we're dealing with are essentially [immutable,
2121+asynchronous iterables](https://medium.com/@andrestaltz/2-minute-introduction-to-rx-24c8ca793877).
2222+2323+Wonka is a library to provide a primitive to solve these problems and
2424+is both an iterable programming library _and_ a reactive stream programming
2525+library.
2626+2727+It can be compared to observables and iterables in one library, but is
2828+based on and essentially a ["callbag" library](https://staltz.com/why-we-need-callbags.html).
2929+3030+## Sources, Operators, and Sinks
3131+3232+When we're thinking of solving problems with streams, it's always
3333+a good idea to look at how we're solving problems with arrays.
3434+3535+Since Wonka's streams are an entirely new primitive, Wonka has to provide
3636+all utilities that you as a developer may need to work with them.
3737+Specifically we have to make sure that it's easy to _create_, _transform_,
3838+and _consume_ these streams.
3939+4040+If we compare these utilities to arrays, _creating_ an array is similar to
4141+creating a stream. So Wonka has utilities such as [`fromArray`](../api/sources.md#fromArray) to
4242+create a new source.
4343+4444+A **source** is what we call a stream in Wonka. This is because it
4545+doesn't strictly follow the definition or specification of observables nor
4646+iterables. So we're calling them **sources** since they're just a **source**
4747+of values over time.
4848+4949+Next we would like to _transform_ sources to make them useful.
5050+Like with arrays we may want to map, filter, and reduce them,
5151+so Wonka has **operators** like [`filter`](../api/operators.md#filter) and [`map`](../api/operators.md#map).
5252+But since Wonka is like a toolkit, it comes with a lot more utilities than
5353+just that.
5454+5555+In general, **operators** will accept some arguments and a source
5656+and output a new, transformed source.
5757+5858+Lastly, the sources we create wouldn't be of much use if we weren't
5959+able to _consume_ them. This is similar to using `forEach` on an
6060+array to iterate over its values. Wonka has a [`subscribe`](../api/sinks.md#subscribe) function which
6161+works similarly to how an observable's subscribe method may work.
6262+This is because Wonka's sources are entirely cancellable.
6363+6464+To summarise, Wonka's streams are _sources_ of values, which
6565+can be transformed using _operators_, which create new _sources_.
6666+If we want to consume a _source_ we use a _sink_.
+12
docs/basics/index.md
···11+---
22+title: Basics
33+order: 3
44+---
55+66+Wonka introduces a new primitive for streams.
77+This part of the documentation explains both the motivation
88+behind creating and using a new stream primitive and how these
99+work internally in Wonka.
1010+1111+- [Background](./background.md) — learn what streams are
1212+- [Architecture](./architecture.md) — learn how Wonka's streams work internally
+136
docs/getting-started.md
···11+---
22+title: Getting Started
33+order: 1
44+---
55+66+This page will explain how to install the Wonka package and
77+its basic usage and helper functions.
88+99+## Installation
1010+1111+The `wonka` package from `npm` is all you need to install to use
1212+Wonka. The process is the same with `yarn` and `esy`.
1313+1414+```bash
1515+yarn add wonka
1616+# or with npm:
1717+npm install --save wonka
1818+# or with esy:
1919+esy add wonka
2020+```
2121+2222+For **JavaScript projects**, the package contains both CommonJS and
2323+ES Modules bundles. For Flow and TypeScript the package also contains
2424+typings files already, so if you're using either you're already done and
2525+ready to go.
2626+2727+If you're using **BuckleScript** or `bs-native` you will need to add `"wonka"`
2828+to your `bs-dependencies` in your `bsconfig.json` configuration file:
2929+3030+```diff
3131+{
3232+ "name": "<some_name>",
3333+ "version": "0.1.0",
3434+ "sources": ["src"],
3535+ "bsc-flags": ["-bs-super-errors"],
3636+ "bs-dependencies": [
3737++ "wonka"
3838+ ]
3939+}
4040+```
4141+4242+If you're using **Dune** and **Esy** you will need to add `wonka` to
4343+your `libraries` entry in the respective `dune` configuration file:
4444+4545+```diff
4646+(library
4747+ (name some_name)
4848+ (public_name some_name)
4949++ (libraries wonka)
5050+)
5151+```
5252+5353+## Usage with JavaScript
5454+5555+In most cases you'll simply import or require `wonka` and use its exposed
5656+methods and utilities. In both CommonJS and ES Modules the Wonka package
5757+simply exposes all its utilities.
5858+5959+```js
6060+// With CommonJS
6161+const { fromArray } = require('wonka');
6262+// With ES Modules
6363+import { fromArray } from 'wonka';
6464+```
6565+6666+There are also some special operators in Wonka that will only be exposed in
6767+Web/JavaScript environments, like `fromPromise`, `toPromise`,
6868+or `fromEvent`, or even `debounce` and `throttle`.
6969+In TypeScript and Flow the typings also expose all types.
7070+7171+There's also a special utility in JavaScript environments to replace the pipeline
7272+operator. This function is called `pipe` and simply calls functions that it's
7373+being passed in order with the previous return value.
7474+7575+```js
7676+import { pipe } from 'wonka';
7777+7878+const output = pipe(
7979+ 'test',
8080+ (x) => x + ' this',
8181+ (x) => x.toUpperCase()
8282+);
8383+8484+output; // "TEST THIS"
8585+```
8686+8787+As shown above, the `pipe` function takes the first argument and passes it
8888+in order to the other function arguments. The return value of one function will
8989+be passed on to the next function.
9090+9191+In TypeScript and Flow the `pipe` function is also typed to handle all generics
9292+in Wonka utilities correctly. Using it will ensure that most of the time you won't
9393+have to specify the types of any generics manually.
9494+9595+If you're using Babel and the [pipeline proposal plugin](https://babeljs.io/docs/en/babel-plugin-proposal-pipeline-operator), you can just use
9696+the pipeline operator to do the same and not use the `pipe` helper.
9797+9898+## Usage with Reason
9999+100100+Everything in the Wonka package is exposed under a single module called `Wonka`.
101101+This module also contains `Wonka.Types`, which contains all internal types of the Wonka
102102+library, but you will typically not need it.
103103+104104+In `BuckleScript` when you're compiling to JavaScript you will also have access to
105105+more utilities like `fromPromise`, `toPromise`, `fromEvent`, or even `debounce` and `throttle`.
106106+These utilities are missing in native compilation, like Dune or `bsb-native`, since they're
107107+relying on JavaScript APIs like Promises, `window.addEventListener`, and `setTimeout`.
108108+109109+When using Wonka you'd simply either open the module and use its utilities or just
110110+access them from the `Wonka` module:
111111+112112+```reason
113113+Wonka.fromValue("test")
114114+ |> Wonka.map((.x) => x ++ " this")
115115+ |> Wonka.forEach((.x) => print_endline(x));
116116+```
117117+118118+It's worth noting that most callbacks in Wonka need to be explicitly uncurried, since
119119+this will help them compile cleanly to JavaScript.
120120+121121+## Interoperability
122122+123123+In JavaScript environments, Wonka comes with several utilities that make it easier
124124+to interoperate with JavaScript primitives and other libraries:
125125+126126+- [`fromPromise`](./api/sources.md#frompromise) & [`toPromise`](./api/sinks.md#topromise) can be used to interoperate with Promises
127127+- [`fromObservable`](./api/sources.md#fromobservable) & [`toObservable`](./api/sinks.md#toobservable) can be used to interoperate with spec-compliant Observables
128128+- [`fromCallbag`](./api/sources.md#fromcallbag) & [`toCallbag`](./api/sinks.md#tocallbag) can be used to interoperate with spec-compliant Callbags
129129+130130+Furthermore there are a couple of operators that only work in JavaScript environments
131131+since they need timing primitives, like `setTimeout` and `setInterval`:
132132+133133+- [`delay`](./api/operators.md#delay)
134134+- [`debounce`](./api/operators.md#debounce)
135135+- [`throttle`](./api/operators.md#throttle)
136136+- [`interval`](./api/sources.md#interval)
+42
docs/index.md
···11+---
22+title: Introduction
33+order: 0
44+---
55+66+Wonka is a lightweight iterable and observable library loosely based on
77+the [callbag spec](https://github.com/callbag/callbag). It exposes a set of helpers to create streams,
88+which are sources of multiple values, which allow you to create, transform
99+and consume event streams or iterable sets of data.
1010+1111+## What it is
1212+1313+Wonka is a library for streams _and_ iterables that behaves predictably
1414+and can be used for many problems where you're dealing with streams of
1515+values, asynchronous or not.
1616+1717+It's similar to [RxJS](https://github.com/ReactiveX/rxjs) in that it enables asynchronous programming with
1818+observable streams, with an API that looks like functional programming on
1919+iterables, but it's also similar to [IxJS](https://github.com/ReactiveX/IxJS) since Wonka streams will run
2020+synchronously if an iterable source runs synchronously.
2121+2222+It also comes with many operators that users from [RxJS](https://github.com/ReactiveX/rxjs) will be used to.
2323+2424+## Reason Support
2525+2626+Wonka used to be written in [Reason](https://reasonml.github.io/),, a dialect of OCaml, and was usable
2727+for native development and compileable with [BuckleScript](https://bucklescript.github.io).
2828+Out of the box it supported usage with BuckleScript, `bs-native`, Dune, and Esy.
2929+3030+If you're looking for the legacy version that supported this, you may want to install v4 or v5 rather
3131+than v6 onwards, which converted the project to TypeScript.
3232+3333+## About the docs
3434+3535+As mentioned in the prior section, Wonka supports not one but a couple of
3636+environments and languages. To accommodate for this, most of the docs
3737+are written with examples and sections for TypeScript and Reason.
3838+3939+We don't provide examples in most parts of the docs for Flow and OCaml because
4040+their respective usage is almost identical to TypeScript and Reason, so for
4141+the most part the examples mostly deal with the differences between a
4242+TypeScript and a Reason project.
+127
docs/migration.md
···11+---
22+title: Migration
33+order: 2
44+---
55+66+This page lists breaking changes and migration guides for
77+various major releases of Wonka.
88+99+## v6.0.0
1010+1111+In `v6.0.0` of Wonka, we've migrated fully to TypeScript.
1212+If you're using this project with Reason or OCaml before, we're sorry for
1313+the inconvenience. However, v4 and v5-rc will remain usable for these
1414+platforms and languages.
1515+1616+The internal API and data structures of Wonka haven't changed in `v6.0.0`
1717+compared to the prior releases and are based on `v4.0.14`. This means that
1818+from a TypeScript, Flow, and JS perspective, `v6.0.0` is backwards compatible
1919+and continues to function as before.
2020+2121+However, the `fromList` API has been removed so far, and we reserve ourselves
2222+room to make more breaking changes were behaviour before was broken.
2323+2424+We're also dropping IE11 support and are now bundling against an ES2015 target.
2525+2626+## v4.0.0
2727+2828+In `v4.0.0` of Wonka, we've migrated to BuckleScript v7 and
2929+`genType` for automatic type generation for TypeScript. The
3030+Flow types are derived from the automatic types and are generated
3131+by `flowgen`.
3232+3333+This may mean that `bsb-native` and Dune/Esy builds are temporarily
3434+broken, as they haven't been tested yet. If so, they will be fixed
3535+in a future minor release. Please stick with `v3.2.2` if you're having
3636+trouble.
3737+3838+This release has no breaking changes for Reason/OCaml in terms of
3939+API changes. You can use the library exactly as you have before.
4040+4141+**For TypeScript and Flow some APIs have changed**.
4242+4343+### New TypeScript and Flow typings
4444+4545+The type for `Subscription`, `Observer`, and `Subject` have changed.
4646+These used to be exposed as tuples (fixed-size arrays) in the past,
4747+but are now compiled to objects, due to the upgrade to BuckleScript v7.
4848+4949+If you're using `subscribe`, `makeSubject`, or `make` you will have
5050+to change some of your types. If you don't, you won't have to update
5151+any of your code and can even mix Wonka `v4.0.0` with `v3.2.2` in the
5252+same bundle.
5353+5454+The `Subscription` type has changed from `[() => void]` to
5555+`{ unsubscribe: (_: void) => void }`:
5656+5757+```ts
5858+import { subscribe } from 'wonka';
5959+6060+// Before:
6161+const [unsubscribe] = subscribe(source);
6262+// After:
6363+const { unsubscribe } = subscribe(source);
6464+```
6565+6666+The `Observer` type has changed similarly, so you'll have to
6767+update your code if you're using `make`:
6868+6969+```ts
7070+import { make } from 'wonka';
7171+7272+// Before:
7373+const source = make(([next, complete]) => /* ... */);
7474+// After:
7575+const source = make(({ next, complete }) => /* ... */);
7676+```
7777+7878+And lastly the `Subject` type has changed as well, so update
7979+your usage of `makeSubject`:
8080+8181+```ts
8282+import { makeSubject } from 'wonka';
8383+8484+// Before:
8585+const [source, next, complete] = makeSubject();
8686+// After:
8787+const { source, next, complete } = makeSubject();
8888+```
8989+9090+### Improvements
9191+9292+The test suite has been rewritten from scratch to improve our
9393+testing of some tricky edge cases. In most cases operators have
9494+been updated to behave more nicely and closer to the spec and
9595+as expected. This is especially true if you're using synchronous
9696+sources or iterables a lot.
9797+9898+Wonka has reached a much higher test coverage and operators like
9999+`merge` and `switchMap` will now behave as expected with synchronous
100100+sources.
101101+102102+This is the list of operators that have changed. If your code has
103103+been working before, you _shouldn't see any different behaviour_.
104104+The changed operators will simply have received bugfixes and will
105105+behave more predictably (and hopefully correctly) in certain edge cases!
106106+107107+- [`buffer`](./api/operators.md#buffer)
108108+- [`combine`](./api/operators.md#combine)
109109+- [`debounce`](./api/operators.md#debounce)
110110+- [`delay`](./api/operators.md#delay)
111111+- [`sample`](./api/operators.md#sample)
112112+- [`skipUntil`](./api/operators.md#skipuntil)
113113+- [`take`](./api/operators.md#take)
114114+- [`takeLast`](./api/operators.md#takelast)
115115+- [`takeWhile`](./api/operators.md#takewhile)
116116+- [`switchMap`](./api/operators.md#switchmap)
117117+- [`mergeMap`](./api/operators.md#mergemap)
118118+- [`concatMap`](./api/operators.md#concatmap)
119119+- [`switchAll`](./api/operators.md#switchall)
120120+- [`mergeAll`](./api/operators.md#mergeall)
121121+- [`concatAll`](./api/operators.md#concatall)
122122+- [`merge`](./api/operators.md#merge)
123123+- [`concat`](./api/operators.md#concat)
124124+125125+The `take` operator is the only one that has been changed to fix
126126+a notable new usage case. It can now accept a maximum of `0` or below,
127127+to close the source immediately.
+3
index.js.flow
···11+// @flow
22+33+declare export * from "./dist/wonka.js.flow"
···11+import { it, expect, vi } from 'vitest';
22+33+import { Source, Sink, Operator, Signal, SignalKind, TalkbackKind, TalkbackFn } from '../types';
44+import { push, start } from '../helpers';
55+66+/* This tests a noop operator for passive Pull talkback signals.
77+ A Pull will be sent from the sink upwards and should pass through
88+ the operator until the source receives it, which then pushes a
99+ value down. */
1010+export const passesPassivePull = (operator: Operator<any, any>, output: any = 0) => {
1111+ it('responds to Pull talkback signals (spec)', () => {
1212+ let talkback: TalkbackFn | null = null;
1313+ let pushes = 0;
1414+ const values: any[] = [];
1515+1616+ const source: Source<any> = sink => {
1717+ sink(
1818+ start(signal => {
1919+ if (!pushes && signal === TalkbackKind.Pull) {
2020+ pushes++;
2121+ sink(push(0));
2222+ }
2323+ })
2424+ );
2525+ };
2626+2727+ const sink: Sink<any> = signal => {
2828+ expect(signal).not.toBe(SignalKind.End);
2929+ if (signal === SignalKind.End) {
3030+ /*noop*/
3131+ } else if (signal.tag === SignalKind.Push) {
3232+ values.push(signal[0]);
3333+ } else {
3434+ talkback = signal[0];
3535+ }
3636+ };
3737+3838+ operator(source)(sink);
3939+ // The Start signal should always come in immediately
4040+ expect(talkback).not.toBe(null);
4141+ // No Push signals should be issued initially
4242+ expect(values).toEqual([]);
4343+4444+ // When pulling a value we expect an immediate response
4545+ talkback!(TalkbackKind.Pull);
4646+ vi.runAllTimers();
4747+ expect(values).toEqual([output]);
4848+ });
4949+};
5050+5151+/* This tests a noop operator for regular, active Push signals.
5252+ A Push will be sent downwards from the source, through the
5353+ operator to the sink. Pull events should be let through from
5454+ the sink after every Push event. */
5555+export const passesActivePush = (operator: Operator<any, any>, result: any = 0) => {
5656+ it('responds to eager Push signals (spec)', () => {
5757+ const values: any[] = [];
5858+ let talkback: TalkbackFn | null = null;
5959+ let sink: Sink<any> | null = null;
6060+ let pulls = 0;
6161+6262+ const source: Source<any> = _sink => {
6363+ (sink = _sink)(
6464+ start(signal => {
6565+ if (signal === TalkbackKind.Pull) pulls++;
6666+ })
6767+ );
6868+ };
6969+7070+ operator(source)(signal => {
7171+ expect(signal).not.toBe(SignalKind.End);
7272+ if (signal === SignalKind.End) {
7373+ /*noop*/
7474+ } else if (signal.tag === SignalKind.Start) {
7575+ talkback = signal[0];
7676+ } else if (signal.tag === SignalKind.Push) {
7777+ values.push(signal[0]);
7878+ talkback!(TalkbackKind.Pull);
7979+ }
8080+ });
8181+8282+ // No Pull signals should be issued initially
8383+ expect(pulls).toBe(0);
8484+8585+ // When pushing a value we expect an immediate response
8686+ sink!(push(0));
8787+ vi.runAllTimers();
8888+ expect(values).toEqual([result]);
8989+ // Subsequently the Pull signal should have travelled upwards
9090+ expect(pulls).toBe(1);
9191+ });
9292+};
9393+9494+/* This tests a noop operator for Close talkback signals from the sink.
9595+ A Close signal will be sent, which should be forwarded to the source,
9696+ which then ends the communication without sending an End signal. */
9797+export const passesSinkClose = (operator: Operator<any, any>) => {
9898+ it('responds to Close signals from sink (spec)', () => {
9999+ let talkback: TalkbackFn | null = null;
100100+ let closing = 0;
101101+102102+ const source: Source<any> = sink => {
103103+ sink(
104104+ start(signal => {
105105+ if (signal === TalkbackKind.Pull && !closing) {
106106+ sink(push(0));
107107+ } else if (signal === TalkbackKind.Close) {
108108+ closing++;
109109+ }
110110+ })
111111+ );
112112+ };
113113+114114+ const sink: Sink<any> = signal => {
115115+ expect(signal).not.toBe(SignalKind.End);
116116+ if (signal === SignalKind.End) {
117117+ /*noop*/
118118+ } else if (signal.tag === SignalKind.Push) {
119119+ talkback!(TalkbackKind.Close);
120120+ } else {
121121+ talkback = signal[0];
122122+ }
123123+ };
124124+125125+ operator(source)(sink);
126126+127127+ // When pushing a value we expect an immediate close signal
128128+ talkback!(TalkbackKind.Pull);
129129+ vi.runAllTimers();
130130+ expect(closing).toBe(1);
131131+ });
132132+};
133133+134134+/* This tests a noop operator for End signals from the source.
135135+ A Push and End signal will be sent after the first Pull talkback
136136+ signal from the sink, which shouldn't lead to any extra Close or Pull
137137+ talkback signals. */
138138+export const passesSourceEnd = (operator: Operator<any, any>, result: any = 0) => {
139139+ it('passes on immediate Push then End signals from source (spec)', () => {
140140+ const signals: Signal<any>[] = [];
141141+ let talkback: TalkbackFn | null = null;
142142+ let pulls = 0;
143143+ let ending = 0;
144144+145145+ const source: Source<any> = sink => {
146146+ sink(
147147+ start(signal => {
148148+ expect(signal).not.toBe(TalkbackKind.Close);
149149+ if (signal === TalkbackKind.Pull) {
150150+ pulls++;
151151+ if (pulls === 1) {
152152+ sink(push(0));
153153+ sink(SignalKind.End);
154154+ }
155155+ }
156156+ })
157157+ );
158158+ };
159159+160160+ const sink: Sink<any> = signal => {
161161+ if (signal === SignalKind.End) {
162162+ signals.push(signal);
163163+ ending++;
164164+ } else if (signal.tag === SignalKind.Push) {
165165+ signals.push(signal);
166166+ } else {
167167+ talkback = signal[0];
168168+ }
169169+ };
170170+171171+ operator(source)(sink);
172172+173173+ // When pushing a value we expect an immediate Push then End signal
174174+ talkback!(TalkbackKind.Pull);
175175+ vi.runAllTimers();
176176+ expect(ending).toBe(1);
177177+ expect(signals).toEqual([push(result), SignalKind.End]);
178178+ // Also no additional pull event should be created by the operator
179179+ expect(pulls).toBe(1);
180180+ });
181181+};
182182+183183+/* This tests a noop operator for End signals from the source
184184+ after the first pull in response to another.
185185+ This is similar to passesSourceEnd but more well behaved since
186186+ mergeMap/switchMap/concatMap are eager operators. */
187187+export const passesSourcePushThenEnd = (operator: Operator<any, any>, result: any = 0) => {
188188+ it('passes on End signals from source (spec)', () => {
189189+ const signals: Signal<any>[] = [];
190190+ let talkback: TalkbackFn | null = null;
191191+ let pulls = 0;
192192+ let ending = 0;
193193+194194+ const source: Source<any> = sink => {
195195+ sink(
196196+ start(signal => {
197197+ expect(signal).not.toBe(TalkbackKind.Close);
198198+ if (signal === TalkbackKind.Pull) {
199199+ pulls++;
200200+ if (pulls <= 2) {
201201+ sink(push(0));
202202+ } else {
203203+ sink(SignalKind.End);
204204+ }
205205+ }
206206+ })
207207+ );
208208+ };
209209+210210+ const sink: Sink<any> = signal => {
211211+ if (signal === SignalKind.End) {
212212+ signals.push(signal);
213213+ ending++;
214214+ } else if (signal.tag === SignalKind.Push) {
215215+ signals.push(signal);
216216+ talkback!(TalkbackKind.Pull);
217217+ } else {
218218+ talkback = signal[0];
219219+ }
220220+ };
221221+222222+ operator(source)(sink);
223223+224224+ // When pushing a value we expect an immediate Push then End signal
225225+ talkback!(TalkbackKind.Pull);
226226+ vi.runAllTimers();
227227+ expect(ending).toBe(1);
228228+ expect(pulls).toBe(3);
229229+ expect(signals).toEqual([push(result), push(result), SignalKind.End]);
230230+ });
231231+};
232232+233233+/* This tests a noop operator for Start signals from the source.
234234+ When the operator's sink is started by the source it'll receive
235235+ a Start event. As a response it should never send more than one
236236+ Start signals to the sink. */
237237+export const passesSingleStart = (operator: Operator<any, any>) => {
238238+ it('sends a single Start event to the incoming sink (spec)', () => {
239239+ let starts = 0;
240240+241241+ const source: Source<any> = sink => {
242242+ sink(start(() => {}));
243243+ };
244244+245245+ const sink: Sink<any> = signal => {
246246+ if (signal !== SignalKind.End && signal.tag === SignalKind.Start) {
247247+ starts++;
248248+ }
249249+ };
250250+251251+ // When starting the operator we expect a single start event on the sink
252252+ operator(source)(sink);
253253+ expect(starts).toBe(1);
254254+ });
255255+};
256256+257257+/* This tests a noop operator for silence after End signals from the source.
258258+ When the operator receives the End signal it shouldn't forward any other
259259+ signals to the sink anymore.
260260+ This isn't a strict requirement, but some operators should ensure that
261261+ all sources are well behaved. This is particularly true for operators
262262+ that either Close sources themselves or may operate on multiple sources. */
263263+export const passesStrictEnd = (operator: Operator<any, any>) => {
264264+ it('stops all signals after End has been received (spec: strict end)', () => {
265265+ let pulls = 0;
266266+ const signals: Signal<any>[] = [];
267267+268268+ const source: Source<any> = sink => {
269269+ sink(
270270+ start(signal => {
271271+ if (signal === TalkbackKind.Pull) {
272272+ pulls++;
273273+ sink(SignalKind.End);
274274+ sink(push(123));
275275+ }
276276+ })
277277+ );
278278+ };
279279+280280+ const sink: Sink<any> = signal => {
281281+ if (signal === SignalKind.End) {
282282+ signals.push(signal);
283283+ } else if (signal.tag === SignalKind.Push) {
284284+ signals.push(signal);
285285+ } else {
286286+ signal[0](TalkbackKind.Pull);
287287+ }
288288+ };
289289+290290+ operator(source)(sink);
291291+292292+ // The Push signal should've been dropped
293293+ vi.runAllTimers();
294294+ expect(signals).toEqual([SignalKind.End]);
295295+ expect(pulls).toBe(1);
296296+ });
297297+298298+ it('stops all signals after Close has been received (spec: strict close)', () => {
299299+ const signals: Signal<any>[] = [];
300300+301301+ const source: Source<any> = sink => {
302302+ sink(
303303+ start(signal => {
304304+ if (signal === TalkbackKind.Close) {
305305+ sink(push(123));
306306+ }
307307+ })
308308+ );
309309+ };
310310+311311+ const sink: Sink<any> = signal => {
312312+ if (signal === SignalKind.End) {
313313+ signals.push(signal);
314314+ } else if (signal.tag === SignalKind.Push) {
315315+ signals.push(signal);
316316+ } else {
317317+ signal[0](TalkbackKind.Close);
318318+ }
319319+ };
320320+321321+ operator(source)(sink);
322322+323323+ // The Push signal should've been dropped
324324+ vi.runAllTimers();
325325+ expect(signals).toEqual([]);
326326+ });
327327+};
328328+329329+/* This tests an immediately closing operator for End signals to
330330+ the sink and Close signals to the source.
331331+ When an operator closes immediately we expect to see a Close
332332+ signal at the source and an End signal to the sink, since the
333333+ closing operator is expected to end the entire chain. */
334334+export const passesCloseAndEnd = (closingOperator: Operator<any, any>) => {
335335+ it('closes the source and ends the sink correctly (spec: ending operator)', () => {
336336+ let closing = 0;
337337+ let ending = 0;
338338+339339+ const source: Source<any> = sink => {
340340+ sink(
341341+ start(signal => {
342342+ // For some operator tests we do need to send a single value
343343+ if (signal === TalkbackKind.Pull) {
344344+ sink(push(null));
345345+ } else {
346346+ closing++;
347347+ }
348348+ })
349349+ );
350350+ };
351351+352352+ const sink: Sink<any> = signal => {
353353+ if (signal === SignalKind.End) {
354354+ ending++;
355355+ } else if (signal.tag === SignalKind.Start) {
356356+ signal[0](TalkbackKind.Pull);
357357+ }
358358+ };
359359+360360+ // We expect the operator to immediately end and close
361361+ closingOperator(source)(sink);
362362+ expect(closing).toBe(1);
363363+ expect(ending).toBe(1);
364364+ });
365365+};
366366+367367+export const passesAsyncSequence = (operator: Operator<any, any>, result: any = 0) => {
368368+ it('passes an async push with an async end (spec)', () => {
369369+ let hasPushed = false;
370370+ const signals: Signal<any>[] = [];
371371+372372+ const source: Source<any> = sink => {
373373+ sink(
374374+ start(signal => {
375375+ if (signal === TalkbackKind.Pull && !hasPushed) {
376376+ hasPushed = true;
377377+ setTimeout(() => sink(push(0)), 10);
378378+ setTimeout(() => sink(SignalKind.End), 20);
379379+ }
380380+ })
381381+ );
382382+ };
383383+384384+ const sink: Sink<any> = signal => {
385385+ if (signal === SignalKind.End) {
386386+ signals.push(signal);
387387+ } else if (signal.tag === SignalKind.Push) {
388388+ signals.push(signal);
389389+ } else {
390390+ setTimeout(() => {
391391+ signal[0](TalkbackKind.Pull);
392392+ }, 5);
393393+ }
394394+ };
395395+396396+ // We initially expect to see the push signal
397397+ // Afterwards after all timers all other signals come in
398398+ operator(source)(sink);
399399+ expect(signals.length).toBe(0);
400400+ vi.advanceTimersByTime(5);
401401+ expect(hasPushed).toBeTruthy();
402402+ vi.runAllTimers();
403403+404404+ expect(signals).toEqual([push(result), SignalKind.End]);
405405+ });
406406+};
···11+import { describe, it, expect, beforeEach, vi } from 'vitest';
22+33+import { Source, Sink, Signal, SignalKind, TalkbackKind, TalkbackFn } from '../types';
44+import { push, start, talkbackPlaceholder } from '../helpers';
55+66+import * as sources from '../sources';
77+import * as operators from '../operators';
88+import * as callbag from '../callbag';
99+import * as observable from '../observable';
1010+1111+import callbagFromArray from 'callbag-from-iter';
1212+import Observable from 'zen-observable';
1313+1414+const collectSignals = (source: Source<any>, onStart?: (talkbackCb: TalkbackFn) => void) => {
1515+ let talkback = talkbackPlaceholder;
1616+ const signals: Signal<any>[] = [];
1717+ source(signal => {
1818+ signals.push(signal);
1919+ if (signal === SignalKind.End) {
2020+ /*noop*/
2121+ } else if (signal.tag === SignalKind.Start) {
2222+ talkback = signal[0];
2323+ if (onStart) onStart(talkback);
2424+ talkback(TalkbackKind.Pull);
2525+ } else {
2626+ talkback(TalkbackKind.Pull);
2727+ }
2828+ });
2929+3030+ return signals;
3131+};
3232+3333+/* When a Close talkback signal is sent the source should immediately end */
3434+const passesActiveClose = (source: Source<any>) => {
3535+ it('stops emitting when a Close talkback signal is received (spec)', () => {
3636+ let talkback: TalkbackFn | null = null;
3737+ const sink: Sink<any> = signal => {
3838+ expect(signal).not.toBe(SignalKind.End);
3939+ expect((signal as any).tag).not.toBe(SignalKind.Push);
4040+ if ((signal as any).tag === SignalKind.Start) {
4141+ (talkback = signal[0])(TalkbackKind.Close);
4242+ }
4343+ };
4444+ source(sink);
4545+ expect(talkback).not.toBe(null);
4646+ });
4747+};
4848+4949+/* All synchronous, cold sources won't send anything unless a Pull signal
5050+ has been received. */
5151+const passesColdPull = (source: Source<any>) => {
5252+ it('sends nothing when no Pull talkback signal has been sent (spec)', () => {
5353+ let talkback: TalkbackFn | null = null;
5454+ let pushes = 0;
5555+5656+ const sink: Sink<any> = signal => {
5757+ if (signal === SignalKind.End) {
5858+ /*noop*/
5959+ } else if (signal.tag === SignalKind.Push) {
6060+ pushes++;
6161+ } else {
6262+ talkback = signal[0];
6363+ }
6464+ };
6565+6666+ source(sink);
6767+ expect(talkback).not.toBe(null);
6868+ expect(pushes).toBe(0);
6969+7070+ setTimeout(() => {
7171+ expect(pushes).toBe(0);
7272+ talkback!(TalkbackKind.Pull);
7373+ }, 10);
7474+7575+ vi.runAllTimers();
7676+ expect(pushes).toBe(1);
7777+ });
7878+};
7979+8080+/* All synchronous, cold sources need to use trampoline scheduling to avoid
8181+ recursively sending more and more Push signals which would eventually lead
8282+ to a call stack overflow when too many values are emitted. */
8383+const passesTrampoline = (source: Source<any>) => {
8484+ it('uses trampoline scheduling instead of recursive push signals (spec)', () => {
8585+ let talkback: TalkbackFn | null = null;
8686+ let pushes = 0;
8787+8888+ const signals: Signal<any>[] = [];
8989+ const sink: Sink<any> = signal => {
9090+ if (signal === SignalKind.End) {
9191+ signals.push(signal);
9292+ expect(pushes).toBe(2);
9393+ } else if (signal.tag === SignalKind.Push) {
9494+ const lastPushes = ++pushes;
9595+ signals.push(signal);
9696+ talkback!(TalkbackKind.Pull);
9797+ expect(lastPushes).toBe(pushes);
9898+ } else if (signal.tag === SignalKind.Start) {
9999+ (talkback = signal[0])(TalkbackKind.Pull);
100100+ expect(pushes).toBe(2);
101101+ }
102102+ };
103103+104104+ source(sink);
105105+ expect(signals).toEqual([push(1), push(2), SignalKind.End]);
106106+ });
107107+};
108108+109109+beforeEach(() => {
110110+ vi.useFakeTimers();
111111+});
112112+113113+describe('fromArray', () => {
114114+ passesTrampoline(sources.fromArray([1, 2]));
115115+ passesColdPull(sources.fromArray([0]));
116116+ passesActiveClose(sources.fromArray([0]));
117117+});
118118+119119+describe('fromValue', () => {
120120+ passesColdPull(sources.fromValue(0));
121121+ passesActiveClose(sources.fromValue(0));
122122+123123+ it('sends a single value and ends', () => {
124124+ expect(collectSignals(sources.fromValue(1))).toEqual([
125125+ start(expect.any(Function)),
126126+ push(1),
127127+ SignalKind.End,
128128+ ]);
129129+ });
130130+});
131131+132132+describe('merge', () => {
133133+ const source = operators.merge<any>([sources.fromValue(0), sources.empty]);
134134+135135+ passesColdPull(source);
136136+ passesActiveClose(source);
137137+138138+ it('correctly merges two sources where the second is empty', () => {
139139+ const source = operators.merge<any>([sources.fromValue(0), sources.empty]);
140140+141141+ expect(collectSignals(source)).toEqual([start(expect.any(Function)), push(0), SignalKind.End]);
142142+ });
143143+144144+ it('correctly merges hot sources', () => {
145145+ const onStart = vi.fn();
146146+ const source = operators.merge<any>([
147147+ operators.onStart(onStart)(sources.never),
148148+ operators.onStart(onStart)(sources.fromArray([1, 2])),
149149+ ]);
150150+151151+ const signals = collectSignals(source);
152152+ expect(onStart).toHaveBeenCalledTimes(2);
153153+154154+ expect(signals).toEqual([start(expect.any(Function)), push(1), push(2)]);
155155+ });
156156+157157+ it('correctly merges asynchronous sources', () => {
158158+ vi.useFakeTimers();
159159+160160+ const onStart = vi.fn();
161161+ const source = operators.merge<any>([
162162+ operators.onStart(onStart)(sources.fromValue(-1)),
163163+ operators.onStart(onStart)(operators.take(2)(sources.interval(50))),
164164+ ]);
165165+166166+ const signals = collectSignals(source);
167167+ vi.advanceTimersByTime(100);
168168+ expect(onStart).toHaveBeenCalledTimes(2);
169169+170170+ expect(signals).toEqual([
171171+ start(expect.any(Function)),
172172+ push(-1),
173173+ push(0),
174174+ push(1),
175175+ SignalKind.End,
176176+ ]);
177177+ });
178178+});
179179+180180+describe('concat', () => {
181181+ const source = operators.concat<any>([sources.fromValue(0), sources.empty]);
182182+183183+ passesColdPull(source);
184184+ passesActiveClose(source);
185185+186186+ it('correctly concats two sources where the second is empty', () => {
187187+ const source = operators.concat<any>([sources.fromValue(0), sources.empty]);
188188+189189+ expect(collectSignals(source)).toEqual([start(expect.any(Function)), push(0), SignalKind.End]);
190190+ });
191191+});
192192+193193+describe('make', () => {
194194+ it('may be used to create async sources', () => {
195195+ const teardown = vi.fn();
196196+ const source = sources.make(observer => {
197197+ setTimeout(() => observer.next(1), 10);
198198+ setTimeout(() => observer.complete(), 20);
199199+ return teardown;
200200+ });
201201+202202+ const signals = collectSignals(source);
203203+ expect(signals).toEqual([start(expect.any(Function))]);
204204+ vi.runAllTimers();
205205+206206+ expect(signals).toEqual([start(expect.any(Function)), push(1), SignalKind.End]);
207207+ });
208208+209209+ it('supports active cancellation', () => {
210210+ const teardown = vi.fn();
211211+ const source = sources.make(() => teardown);
212212+213213+ const sink: Sink<any> = signal => {
214214+ expect(signal).not.toBe(SignalKind.End);
215215+ expect((signal as any).tag).not.toBe(SignalKind.Push);
216216+ setTimeout(() => signal[0](TalkbackKind.Close));
217217+ };
218218+219219+ source(sink);
220220+ expect(teardown).not.toHaveBeenCalled();
221221+ vi.runAllTimers();
222222+ expect(teardown).toHaveBeenCalled();
223223+ });
224224+});
225225+226226+describe('makeSubject', () => {
227227+ it('may be used to emit signals programmatically', () => {
228228+ const { source, next, complete } = sources.makeSubject();
229229+ const signals = collectSignals(source);
230230+231231+ expect(signals).toEqual([start(expect.any(Function))]);
232232+233233+ next(1);
234234+235235+ expect(signals).toEqual([start(expect.any(Function)), push(1)]);
236236+237237+ complete();
238238+239239+ expect(signals).toEqual([start(expect.any(Function)), push(1), SignalKind.End]);
240240+ });
241241+242242+ it('ignores signals after complete has been called', () => {
243243+ const { source, next, complete } = sources.makeSubject();
244244+ const signals = collectSignals(source);
245245+ complete();
246246+247247+ expect(signals).toEqual([start(expect.any(Function)), SignalKind.End]);
248248+249249+ next(1);
250250+ complete();
251251+ expect(signals.length).toBe(2);
252252+ });
253253+});
254254+255255+describe('never', () => {
256256+ it('emits nothing and ends immediately', () => {
257257+ const signals = collectSignals(sources.never);
258258+ expect(signals).toEqual([start(expect.any(Function))]);
259259+ });
260260+});
261261+262262+describe('empty', () => {
263263+ it('emits nothing and ends immediately', () => {
264264+ const signals = collectSignals(sources.empty);
265265+266266+ expect(signals).toEqual([start(expect.any(Function)), SignalKind.End]);
267267+ });
268268+});
269269+270270+describe('fromPromise', () => {
271271+ passesActiveClose(sources.fromPromise(Promise.resolve(null)));
272272+273273+ it('emits a value when the promise resolves', async () => {
274274+ const promise = Promise.resolve(1);
275275+ const signals = collectSignals(sources.fromPromise(promise));
276276+277277+ expect(signals).toEqual([start(expect.any(Function))]);
278278+279279+ await Promise.resolve();
280280+ await promise;
281281+ await Promise.resolve();
282282+283283+ expect(signals).toEqual([start(expect.any(Function)), push(1), SignalKind.End]);
284284+ });
285285+});
286286+287287+describe('fromObservable', () => {
288288+ beforeEach(() => {
289289+ vi.useRealTimers();
290290+ });
291291+292292+ it('converts an Observable to a Wonka source', async () => {
293293+ const source = observable.fromObservable(Observable.from([1, 2]));
294294+ const signals = collectSignals(source);
295295+296296+ await new Promise(resolve => setTimeout(resolve));
297297+298298+ expect(signals).toEqual([start(expect.any(Function)), push(1), push(2), SignalKind.End]);
299299+ });
300300+301301+ it('supports cancellation on converted Observables', async () => {
302302+ const source = observable.fromObservable(Observable.from([1, 2]));
303303+ const signals = collectSignals(source, talkback => {
304304+ talkback(TalkbackKind.Close);
305305+ });
306306+307307+ await new Promise(resolve => setTimeout(resolve));
308308+309309+ expect(signals).toEqual([start(expect.any(Function))]);
310310+ });
311311+});
312312+313313+describe('fromCallbag', () => {
314314+ it('converts a Callbag to a Wonka source', () => {
315315+ const source = callbag.fromCallbag(callbagFromArray([1, 2]) as any);
316316+ const signals = collectSignals(source);
317317+318318+ expect(signals).toEqual([start(expect.any(Function)), push(1), push(2), SignalKind.End]);
319319+ });
320320+321321+ it('supports cancellation on converted Observables', () => {
322322+ const source = callbag.fromCallbag(callbagFromArray([1, 2]) as any);
323323+ const signals = collectSignals(source, talkback => {
324324+ talkback(TalkbackKind.Close);
325325+ });
326326+327327+ expect(signals).toEqual([start(expect.any(Function))]);
328328+ });
329329+});
330330+331331+describe('interval', () => {
332332+ it('emits Push signals until Cancel is sent', () => {
333333+ let pushes = 0;
334334+ let talkback: TalkbackFn | null = null;
335335+336336+ const sink: Sink<any> = signal => {
337337+ if (signal === SignalKind.End) {
338338+ /*noop*/
339339+ } else if (signal.tag === SignalKind.Push) {
340340+ pushes++;
341341+ } else {
342342+ talkback = signal[0];
343343+ }
344344+ };
345345+346346+ sources.interval(100)(sink);
347347+ expect(talkback).not.toBe(null);
348348+ expect(pushes).toBe(0);
349349+350350+ vi.advanceTimersByTime(100);
351351+ expect(pushes).toBe(1);
352352+ vi.advanceTimersByTime(100);
353353+ expect(pushes).toBe(2);
354354+355355+ talkback!(TalkbackKind.Close);
356356+ vi.advanceTimersByTime(100);
357357+ expect(pushes).toBe(2);
358358+ });
359359+});
360360+361361+describe('fromDomEvent', () => {
362362+ it('emits Push signals for events on a DOM element', () => {
363363+ let talkback: TalkbackFn | null = null;
364364+365365+ const element = {
366366+ addEventListener: vi.fn(),
367367+ removeEventListener: vi.fn(),
368368+ };
369369+370370+ const sink: Sink<any> = signal => {
371371+ expect(signal).not.toBe(SignalKind.End);
372372+ if ((signal as any).tag === SignalKind.Start) talkback = signal[0];
373373+ };
374374+375375+ sources.fromDomEvent(element as any, 'click')(sink);
376376+377377+ expect(element.addEventListener).toHaveBeenCalledWith('click', expect.any(Function));
378378+ expect(element.removeEventListener).not.toHaveBeenCalled();
379379+ const listener = element.addEventListener.mock.calls[0][1];
380380+381381+ listener(1);
382382+ listener(2);
383383+ talkback!(TalkbackKind.Close);
384384+ expect(element.removeEventListener).toHaveBeenCalledWith('click', listener);
385385+ });
386386+});
+64
src/callbag.ts
···11+import { Source, SignalKind } from './types';
22+import { push, start } from './helpers';
33+44+/** A definition of the Callbag type as per its specification.
55+ * @see {@link https://github.com/callbag/callbag} for the Callbag specification.
66+ */
77+interface Callbag<I, O> {
88+ (t: 0, d: Callbag<O, I>): void;
99+ (t: 1, d: I): void;
1010+ (t: 2, d?: any): void;
1111+}
1212+1313+/** Converts a Callbag to a {@link Source}.
1414+ * @param callbag - The {@link Callbag} object that will be converted.
1515+ * @returns A {@link Source} wrapping the passed Callbag.
1616+ *
1717+ * @remarks
1818+ * This converts a Callbag to a {@link Source}. When this Source receives a {@link Sink} and
1919+ * the subscription starts, internally, it'll subscribe to the passed Callbag, passing through
2020+ * all of its emitted values.
2121+ */
2222+export function fromCallbag<T>(callbag: Callbag<any, T>): Source<T> {
2323+ return sink => {
2424+ callbag(0, (signal: number, data: any) => {
2525+ if (signal === 0) {
2626+ sink(
2727+ start(signal => {
2828+ data(signal + 1);
2929+ })
3030+ );
3131+ } else if (signal === 1) {
3232+ sink(push(data));
3333+ } else {
3434+ sink(SignalKind.End);
3535+ }
3636+ });
3737+ };
3838+}
3939+4040+/** Converts a {@link Source} to a Callbag.
4141+ * @param source - The {@link Source} that will be converted.
4242+ * @returns A {@link Callbag} wrapping the passed Source.
4343+ *
4444+ * @remarks
4545+ * This converts a {@link Source} to a {@link Callbag}. When this Callbag is subscribed to, it
4646+ * internally subscribes to the Wonka Source and pulls new values.
4747+ */
4848+export function toCallbag<T>(source: Source<T>): Callbag<any, T> {
4949+ return (signal: number, sink: any) => {
5050+ if (signal === 0) {
5151+ source(signal => {
5252+ if (signal === SignalKind.End) {
5353+ sink(2);
5454+ } else if (signal.tag === SignalKind.Start) {
5555+ sink(0, (num: number) => {
5656+ if (num < 3) signal[0](num - 1);
5757+ });
5858+ } else {
5959+ sink(1, signal[0]);
6060+ }
6161+ });
6262+ }
6363+ };
6464+}
+137
src/combine.ts
···11+import { Source, TypeOfSource, SignalKind, TalkbackKind, TalkbackFn } from './types';
22+import { push, start, talkbackPlaceholder } from './helpers';
33+44+type TypeOfSourceArray<T extends readonly [...any[]]> = T extends [infer Head, ...infer Tail]
55+ ? [TypeOfSource<Head>, ...TypeOfSourceArray<Tail>]
66+ : [];
77+88+/** Combines the latest values of several sources into a Source issuing either tuple or dictionary
99+ * values.
1010+ *
1111+ * @param sources - Either an array or dictionary object of Sources.
1212+ * @returns A {@link Source} issuing a zipped value whenever any input Source updates.
1313+ *
1414+ * @remarks
1515+ * `zip` combines several {@link Source | Sources}. The resulting Source will issue its first value
1616+ * once all input Sources have at least issued one value, and will subsequently issue a new value
1717+ * each time any of the Sources emits a new value.
1818+ *
1919+ * Depending on whether an array or dictionary object of Sources is passed to `zip`, its emitted
2020+ * values will be arrays or dictionary objects of the Sources' values.
2121+ *
2222+ * @example
2323+ * An example of passing a dictionary object to `zip`. If an array is passed, the resulting
2424+ * values will output arrays of the sources' values instead.
2525+ *
2626+ * ```ts
2727+ * pipe(
2828+ * zip({
2929+ * x: fromValue(1),
3030+ * y: fromArray([2, 3]),
3131+ * }),
3232+ * subscribe(result => {
3333+ * // logs { x: 1, y: 2 } then { x: 1, y: 3 }
3434+ * console.log(result);
3535+ * })
3636+ * );
3737+ * ```
3838+ */
3939+interface zip {
4040+ <Sources extends readonly [...Source<any>[]]>(sources: [...Sources]): Source<
4141+ TypeOfSourceArray<Sources>
4242+ >;
4343+4444+ <Sources extends { [prop: string]: Source<any> }>(sources: Sources): Source<{
4545+ [Property in keyof Sources]: TypeOfSource<Sources[Property]>;
4646+ }>;
4747+}
4848+4949+function zip<T>(sources: Source<T>[] | Record<string, Source<T>>): Source<T[] | Record<string, T>> {
5050+ const size = Object.keys(sources).length;
5151+ return sink => {
5252+ const filled: Set<string | number> = new Set();
5353+5454+ const talkbacks: TalkbackFn[] | Record<string, TalkbackFn | void> = Array.isArray(sources)
5555+ ? new Array(size).fill(talkbackPlaceholder)
5656+ : {};
5757+ const buffer: T[] | Record<string, T> = Array.isArray(sources) ? new Array(size) : {};
5858+5959+ let gotBuffer = false;
6060+ let gotSignal = false;
6161+ let ended = false;
6262+ let endCount = 0;
6363+6464+ for (const key in sources) {
6565+ (sources[key] as Source<T>)(signal => {
6666+ if (signal === SignalKind.End) {
6767+ if (endCount >= size - 1) {
6868+ ended = true;
6969+ sink(SignalKind.End);
7070+ } else {
7171+ endCount++;
7272+ }
7373+ } else if (signal.tag === SignalKind.Start) {
7474+ talkbacks[key] = signal[0];
7575+ } else if (!ended) {
7676+ buffer[key] = signal[0];
7777+ filled.add(key);
7878+ if (!gotBuffer && filled.size < size) {
7979+ if (!gotSignal) {
8080+ for (const key in sources)
8181+ if (!filled.has(key)) (talkbacks[key] || talkbackPlaceholder)(TalkbackKind.Pull);
8282+ } else {
8383+ gotSignal = false;
8484+ }
8585+ } else {
8686+ gotBuffer = true;
8787+ gotSignal = false;
8888+ sink(push(Array.isArray(buffer) ? buffer.slice() : { ...buffer }));
8989+ }
9090+ }
9191+ });
9292+ }
9393+ sink(
9494+ start(signal => {
9595+ if (ended) {
9696+ /*noop*/
9797+ } else if (signal === TalkbackKind.Close) {
9898+ ended = true;
9999+ for (const key in talkbacks) talkbacks[key](TalkbackKind.Close);
100100+ } else if (!gotSignal) {
101101+ gotSignal = true;
102102+ for (const key in talkbacks) talkbacks[key](TalkbackKind.Pull);
103103+ }
104104+ })
105105+ );
106106+ };
107107+}
108108+109109+export { zip };
110110+111111+/** Combines the latest values of all passed sources into a Source issuing tuple values.
112112+ *
113113+ * @see {@link zip | `zip`} which this helper wraps and uses.
114114+ * @param sources - A variadic list of {@link Source} parameters.
115115+ * @returns A {@link Source} issuing a zipped value whenever any input Source updates.
116116+ *
117117+ * @remarks
118118+ * `combine` takes one or more {@link Source | Sources} as arguments. Once all input Sources have at
119119+ * least issued one value it will issue an array of all of the Sources' values. Subsequently, it
120120+ * will issue a new array value whenever any of the Sources update.
121121+ *
122122+ * @example
123123+ *
124124+ * ```ts
125125+ * pipe(
126126+ * combine(fromValue(1), fromValue(2)),
127127+ * subscribe(result => {
128128+ * console.log(result); // logs [1, 2]
129129+ * })
130130+ * );
131131+ * ```
132132+ */
133133+export function combine<Sources extends Source<any>[]>(
134134+ ...sources: Sources
135135+): Source<TypeOfSourceArray<Sources>> {
136136+ return zip(sources) as Source<any>;
137137+}
+62
src/helpers.ts
···11+import { TalkbackFn, TeardownFn, Start, Push, SignalKind } from './types';
22+33+/** Placeholder {@link TeardownFn | teardown functions} that's a no-op.
44+ * @see {@link TeardownFn} for the definition and usage of teardowns.
55+ * @internal
66+ */
77+export const teardownPlaceholder: TeardownFn = () => {
88+ /*noop*/
99+};
1010+1111+/** Placeholder {@link TalkbackFn | talkback function} that's a no-op.
1212+ * @privateRemarks
1313+ * This is frequently used in the codebase as a no-op initializer value for talkback functions in
1414+ * the implementation of {@link Operator | Operators}. This is cheaper than initializing the
1515+ * variables of talkbacks to `undefined` or `null` and performing an extra check before calling
1616+ * them. Since the {@link Start | Start signal} is assumed to come first and carry a talkback, we can
1717+ * use this to our advantage and use a no-op placeholder before {@link Start} is received.
1818+ *
1919+ * @internal
2020+ */
2121+export const talkbackPlaceholder: TalkbackFn = teardownPlaceholder;
2222+2323+/** Wraps the passed {@link TalkbackFn | talkback function} in a {@link Start | Start signal}.
2424+ * @internal
2525+ */
2626+export function start<T>(talkback: TalkbackFn): Start<T> {
2727+ return {
2828+ tag: SignalKind.Start,
2929+ 0: talkback,
3030+ } as Start<T>;
3131+}
3232+3333+/** Wraps the passed value in a {@link Push | Push signal}.
3434+ * @internal
3535+ */
3636+export function push<T>(value: T): Push<T> {
3737+ return {
3838+ tag: SignalKind.Push,
3939+ 0: value,
4040+ } as Push<T>;
4141+}
4242+4343+/** Returns the well-known symbol specifying the default AsyncIterator.
4444+ * @internal
4545+ */
4646+export const asyncIteratorSymbol = (): typeof Symbol.asyncIterator =>
4747+ (typeof Symbol === 'function' && Symbol.asyncIterator) || ('@@asyncIterator' as any);
4848+4949+/** Returns the well-known symbol specifying the default ES Observable.
5050+ * @privateRemarks
5151+ * This symbol is used to mark an object as a default ES Observable. By the specification, an object
5252+ * that abides by the default Observable implementation must carry a method set to this well-known
5353+ * symbol that returns the Observable implementation. It's common for this object to be an
5454+ * Observable itself and return itself on this method.
5555+ *
5656+ * @see {@link https://github.com/0no-co/wonka/issues/122} for notes on the intercompatibility
5757+ * between Observable implementations.
5858+ *
5959+ * @internal
6060+ */
6161+export const observableSymbol = (): typeof Symbol.observable =>
6262+ (typeof Symbol === 'function' && Symbol.observable) || ('@@observable' as any);
-4
src/index.d.ts
···11-export * from './pipe';
22-export * from './wonka_types';
33-export * from './wonka';
44-export * from './web/wonkaJs';
-2
src/index.js
···11-export * from './pipe';
22-export * from './wonka';
+31
src/index.ts
···11+/**
22+ * A tiny but capable push & pull stream library for TypeScript and Flow.
33+ *
44+ * @remarks
55+ * Wonka is a lightweight iterable and observable library and exposes a set of helpers to create
66+ * streams, which are sources emitting multiple values, which allow you to create, transform, and
77+ * consume event streams or iterable sets of data.
88+ *
99+ * It's loosely based on the Callbag spec: {@link https://github.com/callbag/callbag}
1010+ * @packageDocumentation
1111+ */
1212+1313+export type {
1414+ TeardownFn,
1515+ Signal,
1616+ Sink,
1717+ Source,
1818+ Operator,
1919+ TypeOfSource,
2020+ Subscription,
2121+ Observer,
2222+ Subject,
2323+} from './types';
2424+2525+export * from './sources';
2626+export * from './operators';
2727+export * from './sinks';
2828+export * from './combine';
2929+export * from './observable';
3030+export * from './callbag';
3131+export * from './pipe';
+207
src/observable.ts
···11+import { Source, SignalKind, TalkbackKind } from './types';
22+import { push, start, talkbackPlaceholder, observableSymbol } from './helpers';
33+44+// NOTE: This must be placed in an exported file for `rollup-plugin-dts`
55+// to include it in output typings files
66+declare global {
77+ interface SymbolConstructor {
88+ readonly observable: symbol;
99+ }
1010+}
1111+1212+/** A definition of the ES Observable Subscription type that is returned by
1313+ * {@link Observable.subscribe}
1414+ *
1515+ * @remarks
1616+ * The Subscription in ES Observables is a handle that is held while the Observable is actively
1717+ * streaming values. As such, it's used to indicate with {@link ObservableSubscription.closed}
1818+ * whether it's active, and {@link ObservableSubscription.unsubscribe} may be used to cancel the
1919+ * ongoing subscription and end the {@link Observable} early.
2020+ *
2121+ * @see {@link https://github.com/tc39/proposal-observable} for the ES Observable specification.
2222+ */
2323+interface ObservableSubscription {
2424+ /** A boolean flag indicating whether the subscription is closed.
2525+ * @remarks
2626+ * When `true`, the subscription will not issue new values to the {@link ObservableObserver} and
2727+ * has terminated. No new values are expected.
2828+ *
2929+ * @readonly
3030+ */
3131+ closed: boolean;
3232+ /** Cancels the subscription.
3333+ * @remarks
3434+ * This cancels the ongoing subscription and the {@link ObservableObserver}'s callbacks will
3535+ * subsequently not be called at all. The subscription will be terminated and become inactive.
3636+ */
3737+ unsubscribe(): void;
3838+}
3939+4040+/** A definition of the ES Observable Observer type that is used to receive data from an
4141+ * {@link Observable}.
4242+ *
4343+ * @remarks
4444+ * The Observer in ES Observables is supplied to {@link Observable.subscribe} to receive events from
4545+ * an {@link Observable} as it issues them.
4646+ *
4747+ * @see {@link https://github.com/tc39/proposal-observable#observer} for the ES Observable
4848+ * specification of an Observer.
4949+ */
5050+interface ObservableObserver<T> {
5151+ /** Callback for the Observable issuing new values.
5252+ * @param value - The value that the {@link Observable} is sending.
5353+ */
5454+ next(value: T): void;
5555+ /** Callback for the Observable encountering an error, terminating it.
5656+ * @param error - The error that the {@link Observable} has encountered.
5757+ */
5858+ error?(error: any): void;
5959+ /** Callback for the Observable ending, after all values have been issued. */
6060+ complete?(): void;
6161+}
6262+6363+/** A looser definition of ES Observable-like types that is used for interoperability.
6464+ * @remarks
6565+ * The Observable is often used by multiple libraries supporting or creating streams to provide
6666+ * interoperability for push-based streams. When converting from an Observable to a {@link Source},
6767+ * this looser type is accepted as an input.
6868+ *
6969+ * @see {@link https://github.com/tc39/proposal-observable} for the ES Observable specification.
7070+ * @see {@link Observable} for the full ES Observable type.
7171+ */
7272+interface ObservableLike<T> {
7373+ /**
7474+ * Subscribes to new signals from an {@link Observable} via callbacks.
7575+ * @param observer - An object containing callbacks for the various events of an Observable.
7676+ * @returns Subscription handle of type {@link ObservableSubscription}.
7777+ *
7878+ * @see {@link ObservableObserver} for the callbacks in an object that are called as Observables
7979+ * issue events.
8080+ */
8181+ subscribe(observer: ObservableObserver<T>): { unsubscribe(): void };
8282+8383+ /** The well-known symbol specifying the default ES Observable for an object. */
8484+ [Symbol.observable]?(): Observable<T>;
8585+}
8686+8787+/** An ES Observable type that is a de-facto standard for push-based data sources across the JS
8888+ * ecosystem.
8989+ *
9090+ * @remarks
9191+ * The Observable is often used by multiple libraries supporting or creating streams to provide
9292+ * interoperability for push-based streams. As Wonka's {@link Source | Sources} are similar in
9393+ * functionality to Observables, it provides utilities to cleanly convert to and from Observables.
9494+ *
9595+ * @see {@link https://github.com/tc39/proposal-observable} for the ES Observable specification.
9696+ */
9797+interface Observable<T> {
9898+ /** Subscribes to new signals from an {@link Observable} via callbacks.
9999+ * @param observer - An object containing callbacks for the various events of an Observable.
100100+ * @returns Subscription handle of type {@link ObservableSubscription}.
101101+ *
102102+ * @see {@link ObservableObserver} for the callbacks in an object that are called as Observables
103103+ * issue events.
104104+ */
105105+ subscribe(observer: ObservableObserver<T>): ObservableSubscription;
106106+107107+ /** Subscribes to new signals from an {@link Observable} via callbacks.
108108+ * @param onNext - Callback for the Observable issuing new values.
109109+ * @param onError - Callback for the Observable encountering an error, terminating it.
110110+ * @param onComplete - Callback for the Observable ending, after all values have been issued.
111111+ * @returns Subscription handle of type {@link ObservableSubscription}.
112112+ */
113113+ subscribe(
114114+ onNext: (value: T) => any,
115115+ onError?: (error: any) => any,
116116+ onComplete?: () => any
117117+ ): ObservableSubscription;
118118+119119+ /** The well-known symbol specifying the default ES Observable for an object. */
120120+ [Symbol.observable](): Observable<T>;
121121+}
122122+123123+/** Converts an ES Observable to a {@link Source}.
124124+ * @param input - The {@link ObservableLike} object that will be converted.
125125+ * @returns A {@link Source} wrapping the passed Observable.
126126+ *
127127+ * @remarks
128128+ * This converts an ES Observable to a {@link Source}. When this Source receives a {@link Sink} and
129129+ * the subscription starts, internally, it'll subscribe to the passed Observable, passing through
130130+ * all of the Observable's values. As such, this utility provides intercompatibility converting from
131131+ * standard Observables to Wonka Sources.
132132+ *
133133+ * @throws
134134+ * When the passed ES Observable throws, the error is simply re-thrown as {@link Source} does
135135+ * not support or expect errors to be handled by streams.
136136+ */
137137+export function fromObservable<T>(input: ObservableLike<T>): Source<T> {
138138+ return sink => {
139139+ const subscription = (
140140+ input[observableSymbol()] ? input[observableSymbol()]!() : input
141141+ ).subscribe({
142142+ next(value: T) {
143143+ sink(push(value));
144144+ },
145145+ complete() {
146146+ sink(SignalKind.End);
147147+ },
148148+ error(error) {
149149+ throw error;
150150+ },
151151+ });
152152+ sink(
153153+ start(signal => {
154154+ if (signal === TalkbackKind.Close) subscription.unsubscribe();
155155+ })
156156+ );
157157+ };
158158+}
159159+160160+/** Converts a {@link Source} to an ES Observable.
161161+ * @param source - The {@link Source} that will be converted.
162162+ * @returns An {@link Observable} wrapping the passed Source.
163163+ *
164164+ * @remarks
165165+ * This converts a {@link Source} to an {@link Observable}. When this Observable is subscribed to, it
166166+ * internally subscribes to the Wonka Source and pulls new values. As such, this utility provides
167167+ * intercompatibility converting from Wonka Sources to standard ES Observables.
168168+ */
169169+export function toObservable<T>(source: Source<T>): Observable<T> {
170170+ return {
171171+ subscribe(
172172+ next: ObservableObserver<T> | ((value: T) => any),
173173+ error?: (error: any) => any | undefined,
174174+ complete?: () => any | undefined
175175+ ) {
176176+ const observer: ObservableObserver<T> =
177177+ typeof next == 'object' ? next : { next, error, complete };
178178+ let talkback = talkbackPlaceholder;
179179+ let ended = false;
180180+ source(signal => {
181181+ if (ended) {
182182+ /*noop*/
183183+ } else if (signal === SignalKind.End) {
184184+ ended = true;
185185+ if (observer.complete) observer.complete();
186186+ } else if (signal.tag === SignalKind.Start) {
187187+ (talkback = signal[0])(TalkbackKind.Pull);
188188+ } else {
189189+ observer.next(signal[0]);
190190+ talkback(TalkbackKind.Pull);
191191+ }
192192+ });
193193+ const subscription = {
194194+ closed: false,
195195+ unsubscribe() {
196196+ subscription.closed = true;
197197+ ended = true;
198198+ talkback(TalkbackKind.Close);
199199+ },
200200+ };
201201+ return subscription;
202202+ },
203203+ [observableSymbol()]() {
204204+ return this;
205205+ },
206206+ };
207207+}
···11-export function pipe(source) {
22- const args = arguments;
33- const len = args.length;
44- let x = source;
55-66- for (let i = 1; i < len; i++) {
77- x = args[i](x);
88- }
99-1010- return x;
1111-}
+185
src/pipe.ts
···11+import { Source, Sink, Operator } from './types';
22+33+interface UnaryFn<T, R> {
44+ (source: T): R;
55+}
66+77+/** Chain calls operators on a given source and returns the last result.
88+ * @param args - A source, then a variable number of transform functions
99+ *
1010+ * @remarks
1111+ * The `pipe` utility can be called with a {@link Source} then one or more unary transform functions.
1212+ * Each transform function will be called in turn with the last function's return value, starting
1313+ * with the source passed as the first argument to `pipe`.
1414+ *
1515+ * It's used to transform a source with a list of {@link Operator | Operators}. The last argument may
1616+ * also be a {@link Sink} that returns something else than a Source.
1717+ *
1818+ * @example
1919+ *
2020+ * ```ts
2121+ * pipe(
2222+ * fromArray([1, 2, 3]),
2323+ * map(x => x * 2),
2424+ * subscribe(console.log)
2525+ * );
2626+ * ```
2727+ *
2828+ * @see {@link https://github.com/tc39/proposal-pipeline-operator} for the JS Pipeline Operator spec, for which this is a replacement utility for.
2929+ */
3030+interface pipe {
3131+ /* pipe definitions for source + operators composition */
3232+3333+ <T, A>(source: Source<T>, op1: UnaryFn<Source<T>, Source<A>>): Source<A>;
3434+3535+ <T, A, B>(
3636+ source: Source<T>,
3737+ op1: UnaryFn<Source<T>, Source<A>>,
3838+ op2: UnaryFn<Source<A>, Source<B>>
3939+ ): Source<B>;
4040+4141+ <T, A, B, C>(
4242+ source: Source<T>,
4343+ op1: UnaryFn<Source<T>, Source<A>>,
4444+ op2: UnaryFn<Source<A>, Source<B>>,
4545+ op3: UnaryFn<Source<B>, Source<C>>
4646+ ): Source<C>;
4747+4848+ <T, A, B, C, D>(
4949+ source: Source<T>,
5050+ op1: UnaryFn<Source<T>, Source<A>>,
5151+ op2: UnaryFn<Source<A>, Source<B>>,
5252+ op3: UnaryFn<Source<B>, Source<C>>,
5353+ op4: UnaryFn<Source<C>, Source<D>>
5454+ ): Source<D>;
5555+5656+ <T, A, B, C, D, E>(
5757+ source: Source<T>,
5858+ op1: UnaryFn<Source<T>, Source<A>>,
5959+ op2: UnaryFn<Source<A>, Source<B>>,
6060+ op3: UnaryFn<Source<B>, Source<C>>,
6161+ op4: UnaryFn<Source<C>, Source<D>>,
6262+ op5: UnaryFn<Source<D>, Source<E>>
6363+ ): Source<E>;
6464+6565+ <T, A, B, C, D, E, F>(
6666+ source: Source<T>,
6767+ op1: UnaryFn<Source<T>, Source<A>>,
6868+ op2: UnaryFn<Source<A>, Source<B>>,
6969+ op3: UnaryFn<Source<B>, Source<C>>,
7070+ op4: UnaryFn<Source<C>, Source<D>>,
7171+ op5: UnaryFn<Source<D>, Source<E>>,
7272+ op6: UnaryFn<Source<E>, Source<F>>
7373+ ): Source<F>;
7474+7575+ <T, A, B, C, D, E, F, G>(
7676+ source: Source<T>,
7777+ op1: UnaryFn<Source<T>, Source<A>>,
7878+ op2: UnaryFn<Source<A>, Source<B>>,
7979+ op3: UnaryFn<Source<B>, Source<C>>,
8080+ op4: UnaryFn<Source<C>, Source<D>>,
8181+ op5: UnaryFn<Source<D>, Source<E>>,
8282+ op6: UnaryFn<Source<E>, Source<F>>,
8383+ op7: UnaryFn<Source<F>, Source<G>>
8484+ ): Source<G>;
8585+8686+ <T, A, B, C, D, E, F, G, H>(
8787+ source: Source<T>,
8888+ op1: UnaryFn<Source<T>, Source<A>>,
8989+ op2: UnaryFn<Source<A>, Source<B>>,
9090+ op3: UnaryFn<Source<B>, Source<C>>,
9191+ op4: UnaryFn<Source<C>, Source<D>>,
9292+ op5: UnaryFn<Source<D>, Source<E>>,
9393+ op6: UnaryFn<Source<E>, Source<F>>,
9494+ op7: UnaryFn<Source<F>, Source<G>>,
9595+ op8: UnaryFn<Source<G>, Source<H>>
9696+ ): Source<H>;
9797+9898+ /* pipe definitions for source + operators + consumer composition */
9999+100100+ <T, R>(source: Source<T>, consumer: UnaryFn<Source<T>, R>): R;
101101+102102+ <T, A, R>(
103103+ source: Source<T>,
104104+ op1: UnaryFn<Source<T>, Source<A>>,
105105+ consumer: UnaryFn<Source<A>, R>
106106+ ): R;
107107+108108+ <T, A, B, R>(
109109+ source: Source<T>,
110110+ op1: UnaryFn<Source<T>, Source<A>>,
111111+ op2: UnaryFn<Source<A>, Source<B>>,
112112+ consumer: UnaryFn<Source<B>, R>
113113+ ): R;
114114+115115+ <T, A, B, C, R>(
116116+ source: Source<T>,
117117+ op1: UnaryFn<Source<T>, Source<A>>,
118118+ op2: UnaryFn<Source<A>, Source<B>>,
119119+ op3: UnaryFn<Source<B>, Source<C>>,
120120+ consumer: UnaryFn<Source<C>, R>
121121+ ): R;
122122+123123+ <T, A, B, C, D, R>(
124124+ source: Source<T>,
125125+ op1: UnaryFn<Source<T>, Source<A>>,
126126+ op2: UnaryFn<Source<A>, Source<B>>,
127127+ op3: UnaryFn<Source<B>, Source<C>>,
128128+ op4: UnaryFn<Source<C>, Source<D>>,
129129+ consumer: UnaryFn<Source<D>, R>
130130+ ): R;
131131+132132+ <T, A, B, C, D, E, R>(
133133+ source: Source<T>,
134134+ op1: UnaryFn<Source<T>, Source<A>>,
135135+ op2: UnaryFn<Source<A>, Source<B>>,
136136+ op3: UnaryFn<Source<B>, Source<C>>,
137137+ op4: UnaryFn<Source<C>, Source<D>>,
138138+ op5: UnaryFn<Source<D>, Source<E>>,
139139+ consumer: UnaryFn<Source<E>, R>
140140+ ): R;
141141+142142+ <T, A, B, C, D, E, F, R>(
143143+ source: Source<T>,
144144+ op1: UnaryFn<Source<T>, Source<A>>,
145145+ op2: UnaryFn<Source<A>, Source<B>>,
146146+ op3: UnaryFn<Source<B>, Source<C>>,
147147+ op4: UnaryFn<Source<C>, Source<D>>,
148148+ op5: UnaryFn<Source<D>, Source<E>>,
149149+ op6: UnaryFn<Source<E>, Source<F>>,
150150+ consumer: UnaryFn<Source<F>, R>
151151+ ): R;
152152+153153+ <T, A, B, C, D, E, F, G, R>(
154154+ source: Source<T>,
155155+ op1: UnaryFn<Source<T>, Source<A>>,
156156+ op2: UnaryFn<Source<A>, Source<B>>,
157157+ op3: UnaryFn<Source<B>, Source<C>>,
158158+ op4: UnaryFn<Source<C>, Source<D>>,
159159+ op5: UnaryFn<Source<D>, Source<E>>,
160160+ op6: UnaryFn<Source<E>, Source<F>>,
161161+ op7: UnaryFn<Source<F>, Source<G>>,
162162+ consumer: UnaryFn<Source<G>, R>
163163+ ): R;
164164+165165+ <T, A, B, C, D, E, F, G, H, R>(
166166+ source: Source<T>,
167167+ op1: UnaryFn<Source<T>, Source<A>>,
168168+ op2: UnaryFn<Source<A>, Source<B>>,
169169+ op3: UnaryFn<Source<B>, Source<C>>,
170170+ op4: UnaryFn<Source<C>, Source<D>>,
171171+ op5: UnaryFn<Source<D>, Source<E>>,
172172+ op6: UnaryFn<Source<E>, Source<F>>,
173173+ op7: UnaryFn<Source<F>, Source<G>>,
174174+ op8: UnaryFn<Source<G>, Source<H>>,
175175+ consumer: UnaryFn<Source<H>, R>
176176+ ): R;
177177+}
178178+179179+const pipe: pipe = (...args: Function[]): any => {
180180+ let x = args[0];
181181+ for (let i = 1, l = args.length; i < l; i++) x = args[i](x);
182182+ return x;
183183+};
184184+185185+export { pipe };
···11-open Wonka_types;
22-33-let subscribe: ((. 'a) => unit, sourceT('a)) => subscriptionT;
44-let forEach: ((. 'a) => unit, sourceT('a)) => unit;
+253
src/sinks.ts
···11+import { Source, Subscription, TalkbackKind, SignalKind, SourceIterable } from './types';
22+import { talkbackPlaceholder, asyncIteratorSymbol } from './helpers';
33+44+/** Creates a subscription to a given source and invokes a `subscriber` callback for each value.
55+ * @param subscriber - A callback function called for each issued value.
66+ * @returns A function accepting a {@link Source} and returning a {@link Subscription}.
77+ *
88+ * @remarks
99+ * `subscribe` accepts a `subscriber` callback and returns a function accepting a {@link Source}.
1010+ * When a source is passed to the returned funtion, the subscription will start and `subscriber`
1111+ * will be called for each new value the Source issues. This will also return a {@link Subscription}
1212+ * object that can cancel the ongoing {@link Source} early.
1313+ *
1414+ * @example
1515+ * ```ts
1616+ * const subscription = pipe(
1717+ * fromValue('test'),
1818+ * subscribe(text => {
1919+ * console.log(text); // 'test'
2020+ * })
2121+ * );
2222+ * ```
2323+ */
2424+export function subscribe<T>(subscriber: (value: T) => void) {
2525+ return (source: Source<T>): Subscription => {
2626+ let talkback = talkbackPlaceholder;
2727+ let ended = false;
2828+ source(signal => {
2929+ if (signal === SignalKind.End) {
3030+ ended = true;
3131+ } else if (signal.tag === SignalKind.Start) {
3232+ (talkback = signal[0])(TalkbackKind.Pull);
3333+ } else if (!ended) {
3434+ subscriber(signal[0]);
3535+ talkback(TalkbackKind.Pull);
3636+ }
3737+ });
3838+ return {
3939+ unsubscribe() {
4040+ if (!ended) {
4141+ ended = true;
4242+ talkback(TalkbackKind.Close);
4343+ }
4444+ },
4545+ };
4646+ };
4747+}
4848+4949+/** Creates a subscription to a given source and invokes a `subscriber` callback for each value.
5050+ * @see {@link subscribe} which this helper aliases without returnin a {@link Subscription}.
5151+ * @param subscriber - A callback function called for each issued value.
5252+ * @returns A function accepting a {@link Source}.
5353+ *
5454+ * @remarks
5555+ * `forEach` accepts a `subscriber` callback and returns a function accepting a {@link Source}.
5656+ * When a source is passed to the returned funtion, the subscription will start and `subscriber`
5757+ * will be called for each new value the Source issues. Unlike `subscribe` it will not return a
5858+ * Subscription object and can't be cancelled early.
5959+ *
6060+ * @example
6161+ * ```ts
6262+ * pipe(
6363+ * fromValue('test'),
6464+ * forEach(text => {
6565+ * console.log(text); // 'test'
6666+ * })
6767+ * ); // undefined
6868+ * ```
6969+ */
7070+export function forEach<T>(subscriber: (value: T) => void) {
7171+ return (source: Source<T>): void => {
7272+ subscribe(subscriber)(source);
7373+ };
7474+}
7575+7676+/** Creates a subscription to a given source and invokes a `subscriber` callback for each value.
7777+ * @see {@link subscribe} which this helper aliases without accepting parameters or returning a
7878+ * {@link Subscription | Subscription}.
7979+ *
8080+ * @param source - A {@link Source}.
8181+ *
8282+ * @remarks
8383+ * `publish` accepts a {@link Source} and subscribes to it, starting its values. The resulting
8484+ * values cannot be observed and the subscription can't be cancelled, as this helper is purely
8585+ * intended to start side-effects.
8686+ *
8787+ * @example
8888+ * ```ts
8989+ * pipe(
9090+ * lazy(() => {
9191+ * console.log('test'); // this is called
9292+ * return fromValue(123); // this is never used
9393+ * }),
9494+ * publish
9595+ * ); // undefined
9696+ * ```
9797+ */
9898+export function publish<T>(source: Source<T>): void {
9999+ subscribe(_value => {
100100+ /*noop*/
101101+ })(source);
102102+}
103103+104104+const doneResult = { done: true } as IteratorReturnResult<void>;
105105+106106+/** Converts a Source to an AsyncIterable that pulls and issues values from the Source.
107107+ *
108108+ * @param source - A {@link Source}.
109109+ * @returns An {@link AsyncIterable | `AsyncIterable`} issuing values from the Source.
110110+ *
111111+ * @remarks
112112+ * `toAsyncIterable` will create an {@link AsyncIterable} that pulls and issues values from a given
113113+ * {@link Source}. This can be used in many interoperability situations, to provide an iterable when
114114+ * a consumer requires it.
115115+ *
116116+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_async_iterator_and_async_iterable_protocols}
117117+ * for the JS Iterable protocol.
118118+ *
119119+ * @example
120120+ * ```ts
121121+ * const iterable = toAsyncIterable(fromArray([1, 2, 3]));
122122+ * for await (const value of iterable) {
123123+ * console.log(value); // outputs: 1, 2, 3
124124+ * }
125125+ * ```
126126+ */
127127+export const toAsyncIterable = <T>(source: Source<T>): SourceIterable<T> => {
128128+ const buffer: T[] = [];
129129+130130+ let ended = false;
131131+ let started = false;
132132+ let pulled = false;
133133+ let talkback = talkbackPlaceholder;
134134+ let next: ((value: IteratorResult<T>) => void) | void;
135135+136136+ return {
137137+ async next(): Promise<IteratorResult<T>> {
138138+ if (!started) {
139139+ started = true;
140140+ source(signal => {
141141+ if (ended) {
142142+ /*noop*/
143143+ } else if (signal === SignalKind.End) {
144144+ if (next) next = next(doneResult);
145145+ ended = true;
146146+ } else if (signal.tag === SignalKind.Start) {
147147+ pulled = true;
148148+ (talkback = signal[0])(TalkbackKind.Pull);
149149+ } else {
150150+ pulled = false;
151151+ if (next) {
152152+ next = next({ value: signal[0], done: false });
153153+ } else {
154154+ buffer.push(signal[0]);
155155+ }
156156+ }
157157+ });
158158+ }
159159+160160+ if (ended && !buffer.length) {
161161+ return doneResult;
162162+ } else if (!ended && !pulled && buffer.length <= 1) {
163163+ pulled = true;
164164+ talkback(TalkbackKind.Pull);
165165+ }
166166+167167+ return buffer.length
168168+ ? { value: buffer.shift()!, done: false }
169169+ : new Promise(resolve => (next = resolve));
170170+ },
171171+ async return(): Promise<IteratorReturnResult<void>> {
172172+ if (!ended) next = talkback(TalkbackKind.Close);
173173+ ended = true;
174174+ return doneResult;
175175+ },
176176+ [asyncIteratorSymbol()](): SourceIterable<T> {
177177+ return this;
178178+ },
179179+ };
180180+};
181181+182182+/** Subscribes to a given source and collects all synchronous values into an array.
183183+ * @param source - A {@link Source}.
184184+ * @returns An array of values collected from the {@link Source}.
185185+ *
186186+ * @remarks
187187+ * `toArray` accepts a {@link Source} and returns an array of all synchronously issued values from
188188+ * this Source. It will issue {@link TalkbackKind.Pull | Pull signals} after every value it receives
189189+ * and expects the Source to recursively issue values.
190190+ *
191191+ * Any asynchronously issued values will not be
192192+ * added to the array and a {@link TalkbackKind.Close | Close signal} is issued by the sink before
193193+ * returning the array.
194194+ *
195195+ * @example
196196+ * ```ts
197197+ * toArray(fromArray([1, 2, 3])); // [1, 2, 3]
198198+ * ```
199199+ */
200200+export function toArray<T>(source: Source<T>): T[] {
201201+ const values: T[] = [];
202202+ let talkback = talkbackPlaceholder;
203203+ let ended = false;
204204+ source(signal => {
205205+ if (signal === SignalKind.End) {
206206+ ended = true;
207207+ } else if (signal.tag === SignalKind.Start) {
208208+ (talkback = signal[0])(TalkbackKind.Pull);
209209+ } else {
210210+ values.push(signal[0]);
211211+ talkback(TalkbackKind.Pull);
212212+ }
213213+ });
214214+ if (!ended) talkback(TalkbackKind.Close);
215215+ return values;
216216+}
217217+218218+/** Subscribes to a given source and returns a Promise that will resolve with the last value the
219219+ * source issues.
220220+ *
221221+ * @param source - A {@link Source}.
222222+ * @returns A {@link Promise} resolving to the last value of the {@link Source}.
223223+ *
224224+ * @remarks
225225+ * `toPromise` will subscribe to the passed {@link Source} and resolve to the last value of it once
226226+ * it receives the last value, as signaled by the {@link SignalKind.End | End signal}.
227227+ *
228228+ * To keep its implementation simple, padding sources that don't issue any values to `toPromise` is
229229+ * undefined behaviour and `toPromise` will issue `undefined` in that case.
230230+ *
231231+ * The returned {@link Promise} delays its value by a microtick, using `Promise.resolve`.
232232+ *
233233+ * @example
234234+ * ```ts
235235+ * toPromise(fromValue('test')); // resolves: 'test'
236236+ * ```
237237+ */
238238+export function toPromise<T>(source: Source<T>): Promise<T> {
239239+ return new Promise(resolve => {
240240+ let talkback = talkbackPlaceholder;
241241+ let value: T | void;
242242+ source(signal => {
243243+ if (signal === SignalKind.End) {
244244+ Promise.resolve(value!).then(resolve);
245245+ } else if (signal.tag === SignalKind.Start) {
246246+ (talkback = signal[0])(TalkbackKind.Pull);
247247+ } else {
248248+ value = signal[0];
249249+ talkback(TalkbackKind.Pull);
250250+ }
251251+ });
252252+ });
253253+}
···11+import { Source, Sink, SignalKind, TalkbackKind, Observer, Subject, TeardownFn } from './types';
22+import {
33+ push,
44+ start,
55+ talkbackPlaceholder,
66+ teardownPlaceholder,
77+ asyncIteratorSymbol,
88+} from './helpers';
99+import { share } from './operators';
1010+1111+/** Helper creating a Source from a factory function when it's subscribed to.
1212+ * @param produce - A factory function returning a {@link Source}.
1313+ * @returns A {@link Source} lazyily subscribing to the Source returned by the given factory
1414+ * function.
1515+ *
1616+ * @remarks
1717+ * At times it's necessary to create a {@link Source} lazily. The time of a {@link Source} being
1818+ * created could be different from when it's subscribed to, and hence we may want to split the
1919+ * creation and subscription time. This is especially useful when the Source we wrap is "hot" and
2020+ * issues values as soon as it's created, which we may then not receive in a subscriber.
2121+ *
2222+ * @example An example of creating a {@link Source} that issues the timestamp of subscription. Here
2323+ * we effectively use `lazy` with the simple {@link fromValue | `fromValue`} source, to quickly
2424+ * create a Source that issues the time of its subscription, rather than the time of its creation
2525+ * that it would otherwise issue without `lazy`.
2626+ *
2727+ * ```ts
2828+ * lazy(() => fromValue(Date.now()));
2929+ * ```
3030+ */
3131+export function lazy<T>(produce: () => Source<T>): Source<T> {
3232+ return sink => produce()(sink);
3333+}
3434+3535+/** Converts an AsyncIterable to a Source that pulls and issues values from it as requested.
3636+ *
3737+ * @see {@link fromIterable | `fromIterable`} for the non-async Iterable version of this helper,
3838+ * which calls this helper automatically as needed.
3939+ *
4040+ * @param iterable - An {@link AsyncIterable | `AsyncIterable`}.
4141+ * @returns A {@link Source} issuing values sourced from the Iterable.
4242+ *
4343+ * @remarks
4444+ * `fromAsyncIterable` will create a {@link Source} that pulls and issues values from a given
4545+ * {@link AsyncIterable}. This can be used in many interoperability situations, including to consume
4646+ * an async generator function.
4747+ *
4848+ * When the {@link Sink} throws an exception when a new value is pushed, this helper will rethrow it
4949+ * using {@link AsyncIterator.throw}, which allows an async generator to recover from the exception.
5050+ *
5151+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_async_iterator_and_async_iterable_protocols}
5252+ * for the JS Iterable protocol.
5353+ */
5454+export function fromAsyncIterable<T>(iterable: AsyncIterable<T> | AsyncIterator<T>): Source<T> {
5555+ return sink => {
5656+ const iterator: AsyncIterator<T> =
5757+ (iterable[asyncIteratorSymbol()] && iterable[asyncIteratorSymbol()]()) || iterable;
5858+5959+ let ended = false;
6060+ let looping = false;
6161+ let pulled = false;
6262+ let next: IteratorResult<T>;
6363+ sink(
6464+ start(async signal => {
6565+ if (signal === TalkbackKind.Close) {
6666+ ended = true;
6767+ if (iterator.return) iterator.return();
6868+ } else if (looping) {
6969+ pulled = true;
7070+ } else {
7171+ for (pulled = looping = true; pulled && !ended; ) {
7272+ if ((next = await iterator.next()).done) {
7373+ ended = true;
7474+ if (iterator.return) await iterator.return();
7575+ sink(SignalKind.End);
7676+ } else {
7777+ try {
7878+ pulled = false;
7979+ sink(push(next.value));
8080+ } catch (error) {
8181+ if (iterator.throw) {
8282+ if ((ended = !!(await iterator.throw(error)).done)) sink(SignalKind.End);
8383+ } else {
8484+ throw error;
8585+ }
8686+ }
8787+ }
8888+ }
8989+ looping = false;
9090+ }
9191+ })
9292+ );
9393+ };
9494+}
9595+9696+/** Converts an Iterable to a Source that pulls and issues values from it as requested.
9797+ * @see {@link fromAsyncIterable | `fromAsyncIterable`} for the AsyncIterable version of this helper.
9898+ * @param iterable - An {@link Iterable | `Iterable`} or an `AsyncIterable`
9999+ * @returns A {@link Source} issuing values sourced from the Iterable.
100100+ *
101101+ * @remarks
102102+ * `fromIterable` will create a {@link Source} that pulls and issues values from a given
103103+ * {@link Iterable | JS Iterable}. As iterables are the common standard for any lazily iterated list
104104+ * of values in JS it can be applied to many different JS data types, including a JS Generator
105105+ * function.
106106+ *
107107+ * This Source will only call {@link Iterator.next} on the iterator when the subscribing {@link Sink}
108108+ * has pulled a new value with the {@link TalkbackKind.Pull | Pull signal}. `fromIterable` can
109109+ * therefore also be applied to "infinite" iterables, without a predefined end.
110110+ *
111111+ * This helper will call {@link fromAsyncIterable | `fromAsyncIterable`} automatically when the
112112+ * passed object also implements the async iterator protocol.
113113+ *
114114+ * When the {@link Sink} throws an exception when a new value is pushed, this helper will rethrow it
115115+ * using {@link Iterator.throw}, which allows a generator to recover from the exception.
116116+ *
117117+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterable_protocol}
118118+ * for the JS Iterable protocol.
119119+ */
120120+export function fromIterable<T>(iterable: Iterable<T> | AsyncIterable<T>): Source<T> {
121121+ if (iterable[asyncIteratorSymbol()]) return fromAsyncIterable(iterable as AsyncIterable<T>);
122122+ return sink => {
123123+ const iterator = iterable[Symbol.iterator]();
124124+ let ended = false;
125125+ let looping = false;
126126+ let pulled = false;
127127+ let next: IteratorResult<T>;
128128+ sink(
129129+ start(signal => {
130130+ if (signal === TalkbackKind.Close) {
131131+ ended = true;
132132+ if (iterator.return) iterator.return();
133133+ } else if (looping) {
134134+ pulled = true;
135135+ } else {
136136+ for (pulled = looping = true; pulled && !ended; ) {
137137+ if ((next = iterator.next()).done) {
138138+ ended = true;
139139+ if (iterator.return) iterator.return();
140140+ sink(SignalKind.End);
141141+ } else {
142142+ try {
143143+ pulled = false;
144144+ sink(push(next.value));
145145+ } catch (error) {
146146+ if (iterator.throw) {
147147+ if ((ended = !!iterator.throw(error).done)) sink(SignalKind.End);
148148+ } else {
149149+ throw error;
150150+ }
151151+ }
152152+ }
153153+ }
154154+ looping = false;
155155+ }
156156+ })
157157+ );
158158+ };
159159+}
160160+161161+/** Creates a Source that issues a each value of a given array synchronously.
162162+ * @see {@link fromIterable} which `fromArray` aliases.
163163+ * @param array - The array whose values will be issued one by one.
164164+ * @returns A {@link Source} issuing the array's values.
165165+ *
166166+ * @remarks
167167+ * `fromArray` will create a {@link Source} that issues the values of a given JS array one by one. It
168168+ * will issue values as they're pulled and is hence a "cold" source, not eagerly emitting values. It
169169+ * will end and issue the {@link SignalKind.End | End signal} when the array is exhausted of values.
170170+ *
171171+ * @example
172172+ * ```ts
173173+ * fromArray([1, 2, 3]);
174174+ * ```
175175+ */
176176+export const fromArray: <T>(array: T[]) => Source<T> = fromIterable;
177177+178178+/** Creates a Source that issues a single value and ends immediately after.
179179+ * @param value - The value that will be issued.
180180+ * @returns A {@link Source} issuing the single value.
181181+ *
182182+ * @example
183183+ * ```ts
184184+ * fromValue('test');
185185+ * ```
186186+ */
187187+export function fromValue<T>(value: T): Source<T> {
188188+ return sink => {
189189+ let ended = false;
190190+ sink(
191191+ start(signal => {
192192+ if (signal === TalkbackKind.Close) {
193193+ ended = true;
194194+ } else if (!ended) {
195195+ ended = true;
196196+ sink(push(value));
197197+ sink(SignalKind.End);
198198+ }
199199+ })
200200+ );
201201+ };
202202+}
203203+204204+/** Creates a new Source from scratch from a passed `subscriber` function.
205205+ * @param subscriber - A callback that is called when the {@link Source} is subscribed to.
206206+ * @returns A {@link Source} created from the `subscriber` parameter.
207207+ *
208208+ * @remarks
209209+ * `make` is used to create a new, arbitrary {@link Source} from scratch. It calls the passed
210210+ * `subscriber` function when it's subscribed to.
211211+ *
212212+ * The `subscriber` function receives an {@link Observer}. You may call {@link Observer.next} to
213213+ * issue values on the Source, and {@link Observer.complete} to end the Source.
214214+ *
215215+ * Your `subscribr` function must return a {@link TeardownFn | teardown function} which is only
216216+ * called when your source is cancelled — not when you invoke `complete` yourself. As this creates a
217217+ * "cold" source, every time this source is subscribed to, it will invoke the `subscriber` function
218218+ * again and create a new source.
219219+ *
220220+ * @example
221221+ *
222222+ * ```ts
223223+ * make(observer => {
224224+ * const frame = requestAnimationFrame(() => {
225225+ * observer.next('animate!');
226226+ * });
227227+ * return () => {
228228+ * cancelAnimationFrame(frame);
229229+ * };
230230+ * });
231231+ * ```
232232+ */
233233+export function make<T>(subscriber: (observer: Observer<T>) => TeardownFn): Source<T> {
234234+ return sink => {
235235+ let ended = false;
236236+ const teardown = subscriber({
237237+ next(value: T) {
238238+ if (!ended) sink(push(value));
239239+ },
240240+ complete() {
241241+ if (!ended) {
242242+ ended = true;
243243+ sink(SignalKind.End);
244244+ }
245245+ },
246246+ });
247247+ sink(
248248+ start(signal => {
249249+ if (signal === TalkbackKind.Close && !ended) {
250250+ ended = true;
251251+ teardown();
252252+ }
253253+ })
254254+ );
255255+ };
256256+}
257257+258258+/** Creates a new Subject which can be used as an IO event hub.
259259+ * @returns A new {@link Subject}.
260260+ *
261261+ * @remarks
262262+ * `makeSubject` creates a new {@link Subject}. A Subject is a {@link Source} and an {@link Observer}
263263+ * combined in one interface, as the Observer is used to send new signals to the Source. This means
264264+ * that it's "hot" and hence all subscriptions to {@link Subject.source} share the same underlying
265265+ * signals coming from {@link Subject.next} and {@link Subject.complete}.
266266+ *
267267+ * @example
268268+ * ```ts
269269+ * const subject = makeSubject();
270270+ * pipe(subject.source, subscribe(console.log));
271271+ * // This will log the string on the above subscription
272272+ * subject.next('hello subject!');
273273+ * ```
274274+ */
275275+export function makeSubject<T>(): Subject<T> {
276276+ let next: Subject<T>['next'] | void;
277277+ let complete: Subject<T>['complete'] | void;
278278+ return {
279279+ source: share(
280280+ make(observer => {
281281+ next = observer.next;
282282+ complete = observer.complete;
283283+ return teardownPlaceholder;
284284+ })
285285+ ),
286286+ next(value: T) {
287287+ if (next) next(value);
288288+ },
289289+ complete() {
290290+ if (complete) complete();
291291+ },
292292+ };
293293+}
294294+295295+/** A {@link Source} that immediately ends.
296296+ * @remarks
297297+ * `empty` is a {@link Source} that immediately issues an {@link SignalKind.End | End signal} when
298298+ * it's subscribed to, ending immediately.
299299+ *
300300+ * @see {@link never | `never`} for a source that instead never ends.
301301+ */
302302+export const empty: Source<any> = (sink: Sink<any>): void => {
303303+ let ended = false;
304304+ sink(
305305+ start(signal => {
306306+ if (signal === TalkbackKind.Close) {
307307+ ended = true;
308308+ } else if (!ended) {
309309+ ended = true;
310310+ sink(SignalKind.End);
311311+ }
312312+ })
313313+ );
314314+};
315315+316316+/** A {@link Source} without values that never ends.
317317+ * @remarks
318318+ * `never` is a {@link Source} that never issues any signals and neither sends values nor ends.
319319+ *
320320+ * @see {@link empty | `empty`} for a source that instead ends immediately.
321321+ */
322322+export const never: Source<any> = (sink: Sink<any>): void => {
323323+ sink(start(talkbackPlaceholder));
324324+};
325325+326326+/** Creates a Source that issues an incrementing integer in intervals.
327327+ * @param ms - The interval in milliseconds.
328328+ * @returns A {@link Source} issuing an incrementing count on each interval.
329329+ *
330330+ * @remarks
331331+ * `interval` will create a {@link Source} that issues an incrementing counter each time the `ms`
332332+ * interval expires.
333333+ *
334334+ * It'll only stop when it's cancelled by a {@link TalkbackKind.Close | Close signal}.
335335+ *
336336+ * @example
337337+ * An example printing `0`, then `1`, and so on, in intervals of 50ms.
338338+ *
339339+ * ```ts
340340+ * pipe(interval(50), subscribe(console.log));
341341+ * ```
342342+ */
343343+export function interval(ms: number): Source<number> {
344344+ return make(observer => {
345345+ let i = 0;
346346+ const id = setInterval(() => observer.next(i++), ms);
347347+ return () => clearInterval(id);
348348+ });
349349+}
350350+351351+/** Converts DOM Events to a Source given an `HTMLElement` and an event's name.
352352+ * @param element - The {@link HTMLElement} to listen to.
353353+ * @param event - The DOM Event name to listen to.
354354+ * @returns A {@link Source} issuing the {@link Event | DOM Events} as they're issued by the DOM.
355355+ *
356356+ * @remarks
357357+ * `fromDomEvent` will create a {@link Source} that listens to the given element's events and issues
358358+ * them as values on the source. This source will only stop when it's cancelled by a
359359+ * {@link TalkbackKind.Close | Close signal}.
360360+ *
361361+ * @example
362362+ * An example printing `'clicked!'` when the given `#root` element is clicked.
363363+ *
364364+ * ```ts
365365+ * const element = document.getElementById('root');
366366+ * pipe(
367367+ * fromDomEvent(element, 'click'),
368368+ * subscribe(() => console.log('clicked!'))
369369+ * );
370370+ * ```
371371+ */
372372+export function fromDomEvent(element: HTMLElement, event: string): Source<Event> {
373373+ return make(observer => {
374374+ element.addEventListener(event, observer.next);
375375+ return () => element.removeEventListener(event, observer.next);
376376+ });
377377+}
378378+379379+/** Converts a Promise to a Source that issues the resolving Promise's value and then ends.
380380+ * @param promise - The promise that will be wrapped.
381381+ * @returns A {@link Source} issuing the promise's value when it resolves.
382382+ *
383383+ * @remarks
384384+ * `fromPromise` will create a {@link Source} that issues the {@link Promise}'s resolving value
385385+ * asynchronously and ends immediately after resolving.
386386+ *
387387+ * This helper will not handle the promise's exceptions, and will cause uncaught errors if the
388388+ * promise rejects without a value.
389389+ *
390390+ * @example
391391+ * An example printing `'resolved!'` when the given promise resolves after a tick.
392392+ *
393393+ * ```ts
394394+ * pipe(fromPromise(Promise.resolve('resolved!')), subscribe(console.log));
395395+ * ```
396396+ */
397397+export function fromPromise<T>(promise: Promise<T>): Source<T> {
398398+ return make(observer => {
399399+ promise.then(value => {
400400+ Promise.resolve(value).then(() => {
401401+ observer.next(value);
402402+ observer.complete();
403403+ });
404404+ });
405405+ return teardownPlaceholder;
406406+ });
407407+}
+207
src/types.d.ts
···11+/**
22+ * Talkback signal that sends instructions from a sink to a source.
33+ *
44+ * @remarks
55+ * This signal is issued via {@link TalkbackFn | talkback functions} that a {@link Sink} receives via
66+ * the {@link Start} signal, to tell a {@link Source} to either send a new value (pulling) or stop
77+ * sending values altogether (cancellation).
88+ */
99+export declare enum TalkbackKind {
1010+ /** Instructs the {@link Source} to send the next value. */
1111+ Pull = 0,
1212+ /** Instructs the {@link Source} to stop sending values and cancels it. */
1313+ Close = 1,
1414+}
1515+1616+/**
1717+ * Talkback callback that sends instructions to a source.
1818+ *
1919+ * @remarks
2020+ * This function sends a {@link TalkbackKind} signal to the source to instruct it to send a new value
2121+ * (pulling) or to be cancelled and stop sending values altogether.
2222+ */
2323+export type TalkbackFn = (signal: TalkbackKind) => void;
2424+2525+/**
2626+ * Callback that is called when a source is cancelled.
2727+ *
2828+ * @remarks
2929+ * This is used, in particular, in the {@link make | make Source} and is a returned function that is
3030+ * called when the {@link TalkbackKind.Close} signal is received by the source.
3131+ */
3232+export type TeardownFn = () => void;
3333+3434+/**
3535+ * Tag enum that is used to on signals that are sent from a source to a sink.
3636+ *
3737+ * @remarks
3838+ * This signal is issued by a {@link Source} and {@link Sink | Sinks} are called with it. The signals
3939+ * carrying values ({@link Start} and {@link Push}) are sent as a unary `[T]` tuple tagged with
4040+ * {@link Tag}. The {@link End} signal carries no value and is sent as a raw `0` value.
4141+ * @see {@link Start} for the data structure of the start signal.
4242+ * @see {@link Push} for the data structure of the push signal, carrying values.
4343+ */
4444+export declare enum SignalKind {
4545+ /**
4646+ * Informs the {@link Sink} that it's being called by a {@link Source}.
4747+ *
4848+ * @remarks
4949+ * This starts the stream of values and carries a {@link TalkbackFn | talkback function} with it
5050+ * that is used by the {@link Sink} to communicate back to the {@link Source}.
5151+ * @see {@link Start} for the data structure of the signal.
5252+ */
5353+ Start = 0,
5454+ /**
5555+ * Informs the {@link Sink} of a new values that's incoming from the {@link Source}.
5656+ *
5757+ * @remarks
5858+ * This informs the {@link Sink} of new values that are sent by the {@link Source}.
5959+ * @see {@link Push} for the data structure of the signal.
6060+ */
6161+ Push = 1,
6262+ /**
6363+ * Informs the {@link Sink} that the {@link Source} has ended and that it won't send more values.
6464+ *
6565+ * @remarks
6666+ * This signal signifies that the stream has stopped and that no more values are expected. Some
6767+ * sources don't have a set end or limit on how many values will be sent. This signal is not sent
6868+ * when the {@link Source} is cancelled with a {@link TalkbackKind.Close | Close talkback signal}.
6969+ */
7070+ End = 0,
7171+}
7272+7373+/**
7474+ * The tag property that's put on unary `[T]` tuple to turn them into signals carrying values.
7575+ *
7676+ * @internal
7777+ */
7878+export interface Tag<T> {
7979+ tag: T;
8080+}
8181+8282+/**
8383+ * Indicates the start of a stream to a {@link Sink}.
8484+ *
8585+ * @remarks
8686+ * This signal is sent from a {@link Source} to a {@link Sink} at the start of a stream to inform it
8787+ * that values can be pulled and/or will be sent. This signal carries a
8888+ * {@link TalkbackFn | talkback function} that is used by the {@link Sink} to communicate back to the
8989+ * {@link Source} as a callback. The talkback accepts {@link TalkbackKind.Pull | Pull} and
9090+ * {@link TalkbackKind.Close | Close} signals.
9191+ */
9292+export type Start<_T> = Tag<SignalKind.Start> & [TalkbackFn];
9393+9494+/**
9595+ * Sends a new value to a {@link Sink}.
9696+ *
9797+ * @remarks
9898+ * This signal is sent from a {@link Source} to a {@link Sink} to send a new value to it. This is
9999+ * essentially the signal that wraps new values coming in, like an event. Values are carried on
100100+ * unary tuples and can be accessed using `signal[0]`.
101101+ */
102102+export type Push<T> = Tag<SignalKind.Push> & [T];
103103+104104+/**
105105+ * Signals are sent from {@link Source | Sources} to {@link Sink | Sinks} to inform them of changes.
106106+ *
107107+ * @remarks
108108+ * A {@link Source}, when consumed, sends a sequence of events to {@link Sink | Sinks}. In order, a
109109+ * {@link SignalKind.Start | Start} signal will always be sent first, followed optionally by one or
110110+ * more {@link SignalKind.Push | Push signals}, carrying values and representing the stream. A
111111+ * {@link Source} will send the {@link SignalKind.End | End signal} when it runs out of values. The
112112+ * End signal will be omitted if the Source is cancelled by a
113113+ * {@link TalkbackKind.Close | Close signal}, sent back from the {@link Sink}.
114114+ * @see {@link SignalKind} for the kinds signals sent by {@link Source | Sources}.
115115+ * @see {@link Start} for the data structure of the start signal.
116116+ * @see {@link Push} for the data structure of the push signal.
117117+ */
118118+export type Signal<T> = Start<T> | Push<T> | SignalKind.End;
119119+120120+/**
121121+ * Callback function that is called by a {@link Source} with {@link Signal | Signals}.
122122+ *
123123+ * @remarks
124124+ * A Sink is a function that is called repeatedly with signals from a {@link Source}. It represents
125125+ * the receiver of the stream of signals/events coming from a {@link Source}.
126126+ * @see {@link Signal} for the data structure of signals.
127127+ */
128128+export type Sink<T> = (signal: Signal<T>) => void;
129129+130130+/** Factory function that calls {@link Sink | Sinks} with {@link Signal | Signals} when invoked.
131131+ * @remarks
132132+ * A Source is a factory function that when invoked with a {@link Sink}, calls it with
133133+ * {@link Signal | Signals} to create a stream of events, informing it of new values and the
134134+ * potential end of the stream of values. The first signal a Source sends is always a
135135+ * {@link Start | Start signal} that sends a talkback function to the {@link Sink}, so it may request
136136+ * new values or cancel the source.
137137+ *
138138+ * @see {@link Signal} for the data structure of signals.
139139+ * @see {@link Sink} for the data structure of sinks.
140140+ */
141141+export type Source<T> = (sink: Sink<T>) => void;
142142+143143+/** Transform function that accepts a {@link Source} and returns a new one.
144144+ * @remarks
145145+ * Wonka comes with several helper operators that transform a given {@link Source} into a new one,
146146+ * potentially changing its outputs, or the outputs' timing. An "operator" in Wonka typically
147147+ * accepts arguments and then returns this kind of function, so they can be chained and composed.
148148+ *
149149+ * @see {@link pipe | `pipe`} for the helper used to compose operators.
150150+ */
151151+export type Operator<In, Out> = (a: Source<In>) => Source<Out>;
152152+153153+/** Type utility to determine the type of a {@link Source}. */
154154+export type TypeOfSource<T> = T extends Source<infer U> ? U : never;
155155+156156+/** Subscription object that can be used to cancel a {@link Source}.
157157+ * @see {@link subscribe | subscribe sink} for a helper that returns this structure.
158158+ */
159159+export interface Subscription {
160160+ /**
161161+ * Cancels a {@link Source} to stop the subscription from receiving new values.
162162+ *
163163+ * @see {@link TalkbackKind.Close | Close signal} This uses the {@link TalkbackFn | talkback function} to send a {@link TalkbackKind.Close | Close signal}
164164+ * to the subscribed-to {@link Source} to stop it from sending new values. This cleans up the subscription
165165+ * and ends it immediately.
166166+ */
167167+ unsubscribe(): void;
168168+}
169169+170170+/** An Observer represents sending signals manually to a {@link Sink}.
171171+ * @remarks
172172+ * The Observer is used whenever a utility allows for signals to be sent manually as a {@link Source}
173173+ * would send them.
174174+ *
175175+ * @see {@link make | `make` source} for a helper that uses this structure.
176176+ */
177177+export interface Observer<T> {
178178+ /** Sends a new value to the receiving Sink.
179179+ * @remarks
180180+ * This creates a {@link Push | Push signal} that is sent to a {@link Sink}.
181181+ */
182182+ next(value: T): void;
183183+ /** Indicates to the receiving Sink that no more values will be sent.
184184+ * @remarks
185185+ * This creates an {@link SignalKind.End | End signal} that is sent to a {@link Sink}. The Observer
186186+ * will accept no more values via {@link Observer.next | `next` calls} once this method has been
187187+ * invoked.
188188+ */
189189+ complete(): void;
190190+}
191191+192192+/** Subjects combine a {@link Source} with the {@link Observer} that is used to send values on said Source.
193193+ * @remarks
194194+ * A Subject is used whenever an event hub-like structure is needed, as it both provides the
195195+ * {@link Observer}'s methods to send signals, as well as the `source` to receive said signals.
196196+ *
197197+ * @see {@link makeSubject | `makeSubject` source} for a helper that creates this structure.
198198+ */
199199+export interface Subject<T> extends Observer<T> {
200200+ /** The {@link Source} that issues the signals as the {@link Observer} methods are called. */
201201+ source: Source<T>;
202202+}
203203+204204+/** Async Iterable/Iterator after having converted a {@link Source}.
205205+ * @see {@link toAsyncIterable} for a helper that creates this structure.
206206+ */
207207+export interface SourceIterable<T> extends AsyncIterator<T>, AsyncIterable<T> {}
···11-/* operators */
22-export * from './wonka_operator_debounce';
33-export * from './wonka_operator_delay';
44-export * from './wonka_operator_interval';
55-export * from './wonka_operator_sample';
66-export * from './wonka_operator_throttle';
77-88-/* sinks */
99-export * from './wonka_sink_toPromise';
1010-1111-/* sources */
1212-export * from './wonka_source_fromDomEvent';
1313-export * from './wonka_source_fromListener';
1414-export * from './wonka_source_fromPromise';
···11-/* sources */
22-export * from './sources/wonka_source_fromArray';
33-export * from './sources/wonka_source_fromList';
44-export * from './sources/wonka_source_fromValue';
55-export * from './sources/wonka_source_make';
66-export * from './sources/wonka_source_makeSubject';
77-export * from './sources/wonka_source_primitives';
88-99-/* operators */
1010-export * from './operators/wonka_operator_combine';
1111-export * from './operators/wonka_operator_concatMap';
1212-export * from './operators/wonka_operator_filter';
1313-export * from './operators/wonka_operator_map';
1414-export * from './operators/wonka_operator_mergeMap';
1515-export * from './operators/wonka_operator_onEnd';
1616-export * from './operators/wonka_operator_onPush';
1717-export * from './operators/wonka_operator_onStart';
1818-export * from './operators/wonka_operator_scan';
1919-export * from './operators/wonka_operator_share';
2020-export * from './operators/wonka_operator_skip';
2121-export * from './operators/wonka_operator_skipUntil';
2222-export * from './operators/wonka_operator_skipWhile';
2323-export * from './operators/wonka_operator_switchMap';
2424-export * from './operators/wonka_operator_take';
2525-export * from './operators/wonka_operator_takeLast';
2626-export * from './operators/wonka_operator_takeUntil';
2727-export * from './operators/wonka_operator_takeWhile';
2828-2929-/* sinks */
3030-export * from './sinks/wonka_sink_publish';
3131-export * from './sinks/wonka_sink_subscribe';
3232-3333-export * from './web/wonkaJs';
···11-/* A sink has the signature: `signalT('a) => unit`
22- * A source thus has the signature: `sink => unit`, or `(signalT('a) => unit) => unit`
33- *
44- * Effectively a sink is a callback receiving signals as its first argument.
55- * - Start(talkback) will be carrying a talkback using which the sink can attempt
66- * to pull values (Pull) or request the source to end its stream (End)
77- * - Push(payload) carries a value that the source sends to the sink.
88- * This can happen at any time, since a source can be both pullable or
99- * merely listenable.
1010- * - End signifies the end of the source stream, be it because of a talkback (End)
1111- * or because the source is exhausted.
1212- *
1313- * In detail, a talkback is simply a callback that receives a talkback signal as
1414- * its first argument. It's thus typically anonymously created by the source.
1515- *
1616- * A source is a factory that accepts a sink. Calling a source with a sink will
1717- * instantiate and initiate the source's stream, after which the source sends the sink
1818- * a talkback (Start(talkback)). This is called the "handshake".
1919- *
2020- * Typically an operator factory won't call the source with a sink it receives
2121- * immediately—because this would cause the operator to simply be a noop—but instead
2222- * it will create an intermediate sink with the same signature to perform its own
2323- * logic.
2424- *
2525- * At that point the operator can for instance intercept the talkback for its own
2626- * purposes, or call the actual sink as it sees fit.
2727- */
2828-2929-type talkbackT =
3030- | Pull
3131- | Close;
3232-3333-type signalT('a) =
3434- | Start((. talkbackT) => unit)
3535- | Push('a)
3636- | End;
3737-3838-type sinkT('a) = (. signalT('a)) => unit;
3939-type sourceT('a) = sinkT('a) => unit;
4040-4141-type teardownT = (. unit) => unit;
4242-4343-type subscriptionT = {unsubscribe: unit => unit};
4444-4545-type observerT('a) = {
4646- next: 'a => unit,
4747- complete: unit => unit,
4848-};
4949-5050-type subjectT('a) = {
5151- source: sourceT('a),
5252- next: 'a => unit,
5353- complete: unit => unit,
5454-};
5555-5656-/* Sinks and sources need to explicitly be their own callbacks;
5757- * This means that currying needs to be forced for Bucklescript
5858- * not to optimise them away
5959- */
6060-external curry: 'a => 'a = "%identity";