Giter Club home page Giter Club logo

callbag-basics's Introduction

Callbag basics πŸ‘œ

Basic callbag factories and operators to get started with. Callbag is just a spec, but callbag-basics is a real library you can use.

Highlights:

  • Supports reactive stream programming
  • Supports iterable programming (also!)
  • Same operator works for both of the above
  • Tiny! Weighs just 7kB
  • Fast! Faster than xstream and RxJS
  • Extensible: no core library! Everything is a utility function

Imagine a hybrid between an Observable and an (Async)Iterable, that's what callbags are all about. In addition, the internals are tiny because it's all done with a few simple callbacks, following the callbag spec. As a result, it's tiny and fast.

Usage

npm install callbag-basics

Import operators and factories:

const {forEach, fromIter, map, filter, pipe} = require('callbag-basics');

Try it online

Reactive programming examples

Log XY coordinates of click events on <button> elements:

const {forEach, fromEvent, map, filter, pipe} = require('callbag-basics');

pipe(
  fromEvent(document, 'click'),
  filter(ev => ev.target.tagName === 'BUTTON'),
  map(ev => ({x: ev.clientX, y: ev.clientY})),
  forEach(coords => console.log(coords))
);

// {x: 110, y: 581}
// {x: 295, y: 1128}
// ...

Pick the first 5 odd numbers from a clock that ticks every second, then start observing them:

const {forEach, interval, map, filter, take, pipe} = require('callbag-basics');

pipe(
  interval(1000),
  map(x => x + 1),
  filter(x => x % 2),
  take(5),
  forEach(x => console.log(x))
);

// 1
// 3
// 5
// 7
// 9

Iterable programming examples

From a range of numbers, pick 5 of them and divide them by 4, then start pulling those one by one:

const {forEach, fromIter, take, map, pipe} = require('callbag-basics');

function* range(from, to) {
  let i = from;
  while (i <= to) {
    yield i;
    i++;
  }
}

pipe(
  fromIter(range(40, 99)), // 40, 41, 42, 43, 44, 45, 46, ...
  take(5), // 40, 41, 42, 43, 44
  map(x => x / 4), // 10, 10.25, 10.5, 10.75, 11
  forEach(x => console.log(x))
);

// 10
// 10.25
// 10.5
// 10.75
// 11

API

The list below shows what's included.

Source factories

Sink factories

Transformation operators

Filtering operators

Combination operators

Utilities

More

Terminology

  • source: a callbag that delivers data
  • sink: a callbag that receives data
  • puller sink: a sink that actively requests data from the source
  • pullable source: a source that delivers data only on demand (on receiving a request)
  • listener sink: a sink that passively receives data from the source
  • listenable source: source which sends data to the sink without waiting for requests
  • operator: a callbag based on another callbag which applies some operation

Contributing

The Callbag philosophy is: build it yourself. :) You can send pull requests, but even better, don't depend on the repo owner accepting it. Just fork the project, customize it as you wish, and publish your fork on npm. As long as it follows the callbag spec, everything will be interoperable! :)

callbag-basics's People

Contributors

bmingles avatar dependabot[bot] avatar ds82 avatar environmentset avatar johnlindquist avatar loreanvictor avatar pablopunk avatar staltz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

callbag-basics's Issues

Utility for building callbags

As I read through the source code, I see a lot of the following

(t, d) => {
  if (t == 0){
    ...
  } else if (t == 1){
    ...
  } else {
    ...
  }
}

I've typed up a quick helper to create these callbags and was wondering if you think that it would be useful more widely.

const build = cbs => (t, d) => {
  const arr = cbs instanceof Array ? cbs : [ cbs.start, cbs.data, cbs.end ];
  arr[t] && arr[t](d)
}

This allows you to create a callbag passing either an object with {start, data, end} functions or an array of functions.

build({
  start(d){
    ...
  },
  data(d){
    ...
  },
  end(d){
    ...
  }
});

build([
  d => ...,
  d => ...,
  d => ...
])

const interval = period => build({
  start(sink){
    let i = 0;
    const id = setInterval(() => sink(1, i++), period);
    sink(0, build({ end: () => clearInterval(id) }));
  }
})

I'd be interested to hear what feedback you have. Thank you for your time and for your novel thoughts on Observables and AsyncIterables!

takeUntil?

If I was using a callbag pipeline in React, say, in my componentDidMount, how would I tell the pipeline to stop listening for events on a componentWillUnmount?

Packaging for Multiple Targets

Howdy! Would you be open to a pull request that changes your build tooling to output distributable packages for multiple targets?

As is, it's problematic to include this module in a project targeted for the browser due to use of ES6+ features (arrow functions, spread,...). I think it's not uncommon for build tooling targeting the browser to exclude ./node_modules/ from transpilation (Babel). Of course... the user could always adjust their build, but.... Also, the module built here doesn't support tree shaking to reduce bundle size.

Debounce

A debounce callbag function would be useful.

callbag-flatten exports default

Following the build it yourself philosophy of this library, I made my own lib of callbags.
Just a package.json with the dependencies I need and an index.js exporting them like the one in this project.

However I noticed callbag-flatten failed to work, because the new version doesn't export the function like the rest of the callbags. Now it needs to be
flatten: require('callbag-flatten').default,
instead of the previous
flatten: require('callbag-flatten')

I wonder, shouldn't all calbags export the same way?

Asynchronous forEach

The goal of one of my callbag streams is to insert each processed item into a database. How can my final sink (forEach) be asynchronous? Why can't any callbag-basics handler also be asynchronous?

Unsubscribe

Hi AndrΓ© !

I just found out about your spec. I find it very nice and I love the idea of a new angle on that topic. I have been playing around with it last night and built my own operators ( a partition mostly ). When I get the time I'll do a rewrite with tests and may well publish it.

Back on the topic. I have read your article about repos without maintainers. There as well I really like your philosophy and I tend to read more and more open source code. I have to say I've been reading the rxjs implementation of partition before I gave it a go. So, I don't want to bother you too much, but maybe you could give me a direction. I'd like to implement an unsubscribe operator. I know I should use the type 2 for that, but I'm not sure how to tackle it.

callbag-debounce

Love the idea! It's simple and elegant.

I wanted to write debounce to make sure I understood the philosophy of callbag. Before I published it, I was wondering if you could just take a quick look and make sure it's written correctly. My testing seems to work, but this is totally new to me.

const debounce = duration => source => (start, sink) => {
  if (start !== 0) return;
  let latest = 0;
  let talkback;
  source(0, (t, d) => {
    if (t === 0) {
      talkback = d;
      sink(t, d);
    } else if (t === 1) {
      const now = Date.now();
      if (now >= latest + duration) {
        latest = now;
        sink(t, d);
      } else {
        talkback(1);
      }
    } else {
      sink(t, d);
    }
  });
};

Pin callbag* deps to their major versions?

From what I observed you follow semantic versioning in all your callbag-* packages. Since patch and minor releases therefore should not contain any breaking changes, can we pin all deps in callbag-basics to their major versions?

    "callbag": "1.0.x",
    "callbag-for-each": "1.0.x",
    "callbag-from-obs": "1.1.x",
    ...

==>

    "callbag": "1",
    "callbag-for-each": "1",
    "callbag-from-obs": "1",
    ...

I can make a PR for this if you agree.

Stricter types?

Up for discussion, but would the Typescript community benefit from stricter types?
It saves a few headaches when your compiler can hold your hand a bit more :)

A few things I would be looking for:

  • Allow the error type to be specified
  • Stricter signal / parameters for each type of callbag

Here is something I have been using:

export enum Signal {
  START = 0,
  DATA = 1,
  END = 2,
}

export type TalkbackArgs = [signal: Signal.DATA] | [signal: Signal.END]
export type Talkback = (...op: TalkbackArgs) => void

export type SinkArgs<E, A> =
  | [signal: Signal.START, talkback: Talkback]
  | [signal: Signal.DATA, data: A]
  | [signal: Signal.END, error?: E]
export type Sink<E, A> = (...op: SinkArgs<E, A>) => void

export type SourceArgs<E, A> = [signal: Signal.START, sink: Sink<E, A>]
export type Source<E, A> = (...op: SourceArgs<E, A>) => void

export type Callbag<E, A> = Source<E, A> | Sink<E, A>

Core Callbag Basics Feature: callbag-emitter

callbag-basics ought to have a very simple technique to introduce/push values onto a stream. The ability to emit any value onto the stream manually is more important than fromEvent, fromPromise, fromIterator, and fromObservable. Consider that all of these "from" modules do not belong in callbag-basics. Consider that callbag-basics ought to have a single basic module for introducing values onto a stream.

Use a monorepo?

A centralized repo (from which all your packages are published) might be a better fit for this.

Example not working

The first example from Try it online section - Observe Events doesn't seem to work:

(0 , _callbagBasics.fromEvent) is not a function

  16 | const setText = event => (prev, curr) => event.target.value;
> 17 | const inputs = pipe(fromEvent(input, "input"), map(setText));
     |                              ^
  18 | 

alas, I don't know how to fix this.

Thanks!

Error handling

Hey @staltz.

I like the stuff you build, but I am wondering what will happen if there is an error in one of the source factories. Observables or similar libraries like xstream always have a value handler and an error handler for that reason.

Maybe you can elaborate that a bit in the README.

Cheers

callbag-flatten: "source", ""innerSource" is not a function

When I invoke callbag-flatten, I expect that it will flatten an array. But instead I see errors that source and innerSource "is not a function". How is callbag-flatten supposed to work? With the Node stream API, it's pretty simple to push new values into a stream.

pipe(
	interval(1),
	map((each) => ([each, each])),
	flatten,
	forEach(function(each) {
		console.log('each: ' + JSON.stringify(each));
	})
);

fromObject

Hi,

In RxJS we can write something like this : Observable.of({}).subscribe(...). I need to build a similar operator for callbags but I can't seem to get it to work.

I have tried a naive implementation which is :

export const fromObject = obj => (start, sink) => {
	if (start !== 0) return;

	sink(0, (t) => {});
	sink(1, obj);
	sink(2);
};

So the idea is, I greet the sink, I send him the object, and I terminate it. The problem arises with merge() which gives me a "talkback is not a function". After looking at the code of merge, it seems that the issue comes from that line : if (++startCount === n) sink(0, talkback);. When it's looped on, the talkbacks are not set yet at the time I send my sink(1, obj). I'm not saying there's something wrong with the merge operator, but I look for guidance on how to tackle the problem and build my source.

The goal is to have a synchronous source that emits once and completes. And that plays nicely with the current ecosystem.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.