npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

super-effects

v0.0.0-next.9

Published

> ⚠️ This is very unstable, please do not use this in production. Contributions welcome!

Downloads

10

Readme

super-effects

⚠️ This is very unstable, please do not use this in production. Contributions welcome!

This is the core engine for a reactive effects system that makes use of generators.

It is not intended to be used directly as it is abstract enough to support different observable formats.

What does it do?

It works a bit like useEffect in React, except instead of having a dependencies array you yield things you want to depend on.

If any of your dependencies change the effect restarts.

There's various meta hooks you can use to listen to the different of the effect's lifecycle. You can also make anything a reactive dependency using a simple observable API.

Why is it so abstract?

The core principle could be applied in many different contexts, in different view frameworks, or even server-side. We wanted to know the basic logic of the effects system was rock solid so we've put it in its own repo and written a lot of tests to verify different corner cases work as expected.

Why generators?

Generators allow us to do alot of things that aren't possible otherwise. E.g.

  • Synchronously tracking references to dependencies
  • Early cancellation of an execution context (for e.g. restarts, component unmounts)
  • Add custom handlers for custom data types that are yielded

API

fx.createEffect(...)

let api = createEffect<T>(options: Options): API<T, Initial>
type Options = {
	handlers: Handler[]
	, visitor: (ctx: Initial) => Generator<any, U, any> 
	, interceptNext?: (it: Iterator<any, U, any>, next: any) => any
}

Create an effect by providiing a visitor GeneratorFunction and a list of handlers.

Handlers let you control how yielded values are handled. E.g. if you want to add support for a particular stream library you might add a handler like so:

import * as fx from 'super-mithril-effects'

let effectApi = createEffect({
	visitor: function * (){
		let a = yield myStream
		let b = yield otherStream
	},
	handlers: [streamHandler]
})

Where streamHandler looks like this:

const streamHandler = (x) => {
	if (!isStream(x)) {
		return fx.SKIP
	}

	return (onchange) => {
		let mapper = x.map(onchange)
		return () => {
			mapper.end(true)
		}
	}
}

interceptNext allows you to control the execution of iterator.next(nextValue)

We provide this API so we can track creation and references of streams and stores, but you may also use it just for simple logging / debuging.

let effectApi = createEffect({
	...,
	interceptNext(it, next){
		console.log('injecting', next)
		const value = it.next(next)
		console.log('got back', value)
		return value
	}
})

effectApi

export type API<U = any, Initial = U> = {
	start(initial?: Initial): void
	addDependency(x: any): void
	stop(): void
	resume(): void
	context: EffectContext<U>
	on(cb: (x: U) => void): (x: U) => void
	off(cb: (x: U) => void): void
	running: Promise<void>
	onReplace(fn: onReplace): void
	onReturn(fn: onReturn): void
	onThrows(fn: onThrow): void
	onFinally(fn: onFinally): void
}

This is what createEffect returns.

effectApi.start(...)

Starts the generator function and handler. Calling this function when the generator is already started does nothing.

effectApi.stop(...)

Stops the generator function and handler and cleans up all listeners. Calling this function when the generator is already stopped does nothing.

effectApi.resume()

Will resume any blocking yield ctx.pause() effects.

effectApi.on

Subscribe to the return value of a generator.

const cb = x => console.log(x)
effectApi.on( cb )

effectApi.off

Unsubscribe to the return value of a generator.

const cb = x => console.log(x)
effectApi.off( cb )

effectApi.running

A promise that resolves when the effect is no longer running.

effectApi.addDependency

Manually add a dependency that will restart the generator when it emits 2 times. Just like a normal yield the first emit is assumed to be the current value and is valid in the current iterator session. The second emit invalidates the current session and triggers a restart.

addDependency uses the same handler list that you pass in on initialization.

effectApi.context

The effect context is designed to be accessible to the framework user. You may want to pass it in as the initial value to effectApi.start(...) along with other framework or application specific context.

The context allows the user to subscribe to specific lifecycle events of the effect and use special utils like yield ctx.pause() and yield ctx.sleep(5000).

export type EffectContext<U> = {
	onFinally: (f: () => any) => void
	onReplace: (f: () => any) => void
	onReturn: (f: (data: U) => any) => void
	onThrow: (f: (error: any) => any) => void
	sleep: (ms: number) => SleepEffect
	pause: () => PauseEffect
	resume: () => void
	encased<T>(fn: () => T): EncasedEffect<T>
}

EffectContext.onFinally

Called whenever the generator function is exiting. This could be due to any of the following:

  • A restart of the generator due to a dependency changing
  • An exception was thrown
  • The generator completed and returned a value
  • The effect is being teardown after effectApi.stop() was called

EffectContext.onReplace

Called whenever the generator is restarting and the current instance is being replaced.

This is called immediately before starting the new instance so you will still have access to the closure of the old instance.

EffectContext.onReturn

Called only after the generator has exited without error.

EffectContext.onThrow

Called only after the generator has exited via an exception being thrown.

EffectContext.sleep(ms)

Will pause the generator for the specified amount of ms.

EffectContext.pause()

Will pause the generator from continuing execution. Useful if you want to wait for a dependency change to restart the effect.

Can be resumed via a dependency emit or calling api.resume()

Cancellation

When a dependency observable changes the current iterator is cancelled and restarted. The cancellation works by calling it.throw(new Cancellation())

If you have a try / catch in your generator you can check if the error is a cancellation via err instanceof zed.Cancellation. Note you shouldn't try to guard against cancellation, this library will stop iterating generators that have been cancelled, but you may want to rule out Cancellation exceptions for logging / debugging purposes.

Typescript

Typescript doesn't really support generators very well. The good news is there are various open issues on the Typescript repo and it looks the core team takes the gap in support very seriously.

The best we can do in the meantime is manually cast our yields and pray for a better future.