Skip to content

Commit

Permalink
+ autosubscribe,Autosubscribe,autosubscriber,BoxAutosubscribe,UnboxAu…
Browse files Browse the repository at this point in the history
…tosubscribe (nanostores#188)

atom: is a function which takes an optional autosubscribe function & returns .get()

	+ .a: active autosubscribe when atom was instantiated

computed:

	+ autosubscribe:
		(): returns computed store's value
		(store: AnyStore): computed store autosubscribes to the given store
		(cb: () => any): runs the cb & autosubscribes to any called store functions within the cb
		.off(cb): hook which runs callback onUnmount & before the computed cb being called
		.on(cb): hook which runs callback onMount
		.save(): sets computed & sets undoValue
		.set(newValue): sets computed & does not set undoValue
		.stale(): returns true if computed store's cb was called again after call associated with the autosubscribe
		.undo: sets computed to undoValue
		a stale autosubscribe has some of it's methods neutered:
			.off
			.on
			.save
			.set
			.undo
	+ async support

Thanks to @dtinth for autosubscribe function type inference which he implemented in https://github.com/dtinth/nanostores-computed-dynamic

size-limit:

	Atom: - 4 B
	All: + 288 B
  • Loading branch information
btakita committed Jul 8, 2023
1 parent 8bd06ad commit dd0c0f2
Show file tree
Hide file tree
Showing 11 changed files with 1,655 additions and 158 deletions.
61 changes: 61 additions & 0 deletions atom/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,65 @@ export interface WritableAtom<Value = any> extends ReadableAtom<Value> {

export type Atom<Value = any> = ReadableAtom<Value> | WritableAtom<Value>

export interface Autosubscribe<Value = any> {
/**
* @return The computed store's value
*/
(): Value
/**
* Causes atom function calls to autosubscribe to the associated computed store.
*
* @param cb
*/
<V>(cb: <T>() => T): V
<V>(atom: ReadableAtom<V>): V
/**
* Runs fn when the computed store is unmounted or when the computed store's cb is next run
*
* When .stale() is true, saving fn callback is deactivated.
*
* @param fn
*/
off(fn: () => any): Autosubscribe<Value>
/**
* Immediately runs fn & calls fn when the computed store is mounted.
*
* When .stale() is true, calling fn & saving fn callback is deactivated.
*
* @param fn
*/
on(fn: () => any): Autosubscribe<Value>
/**
* Sets the computed store's value & undoValue.
* A subsequent call to .undo() will reset any intermediate values from `.set` to the given `newValue`.
*
* When .stale() is true, setting computed store's value & undoValue is deactivated.
*
* @param newValue
*/
save(newValue: Value): Autosubscribe<Value>
/**
* Sets an intermediate value on the computed store. Sets computed store's value & does not set the undoValue.
* Calling .undo() will reset the value back to the `undoValue`.
*
* When .stale() is true, setting the computed store's value is deactivated.
*
* @param intermediateValue
*/
set(intermediateValue: Value): Autosubscribe<Value>
/**
* Returns true when the computed store's cb is run after the run which created the Autosubscribe.
*/
stale(): boolean
/**
* An intermediate value from `.set(intermediateValue)` will be undone.
* Sets computed store's value to the undoValue, which is the last non-stale computed cb return value or .save() value.
*
* When .stale() is true, setting the computed store's value is deactivated.
*/
undo(): Autosubscribe<Value>
}

export declare let notifyId: number
/**
* Create store with atomic value. It could be a string or an object, which you
Expand Down Expand Up @@ -132,3 +191,5 @@ export declare let notifyId: number
export function atom<Value, StoreExt = {}>(
...args: undefined extends Value ? [] | [Value] : [Value]
): WritableAtom<Value> & StoreExt

export function autosubscriber<Value = any>(): Autosubscribe<Value>
53 changes: 26 additions & 27 deletions atom/index.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@
import { clean } from '../clean-stores/index.js'

let listenerQueue = []

export let atom = (initialValue, level) => {
export let autosubscriberStack = []
export let autosubscriber = cb =>
cb ? autosubscriberStack.at(-1)(cb) : autosubscriberStack.at(-1)
export let atom = initialValue => {
let listeners = []
let store = {
a: autosubscriberStack.at(-1),
get() {
if (!store.lc) {
store.listen(() => {})()
}
return store.value
},
l: level || 0,
l: 0,
lc: 0,
listen(listener, listenerLevel) {
store.lc = listeners.push(listener, listenerLevel || store.l) / 2
listen(listener, listenerStore) {
store.lc = listeners.push(listener, listenerStore || store) / 2

return () => {
let index = listeners.indexOf(listener)
if (~index) {
listeners.splice(index, 2)
store.lc--
if (!store.lc) store.off()
if (!--store.lc) store.off()
}
}
},
Expand All @@ -30,47 +32,44 @@ export let atom = (initialValue, level) => {
for (let i = 0; i < listeners.length; i += 2) {
listenerQueue.push(
listeners[i],
listeners[i + 1],
store.value,
changedKey,
listeners[i + 1]
)
}

if (runListenerQueue) {
for (let i = 0; i < listenerQueue.length; i += 4) {
let skip = false
for (let j = i + 7; j < listenerQueue.length; j += 4) {
if (listenerQueue[j] < listenerQueue[i + 3]) {
skip = true
break
let skip
for (let j = i + 1; !skip && (j += 4) < listenerQueue.length;) {
if (listenerQueue[j].l < listenerQueue[i + 1].l) {
skip = listenerQueue.push(
listenerQueue[i],
listenerQueue[i + 1],
listenerQueue[i + 2],
listenerQueue[i + 3]
)
}
}

if (skip) {
listenerQueue.push(
listenerQueue[i],
listenerQueue[i + 1],
listenerQueue[i + 2],
listenerQueue[i + 3]
)
} else {
listenerQueue[i](listenerQueue[i + 1], listenerQueue[i + 2])
if (!skip) {
listenerQueue[i](listenerQueue[i + 2], listenerQueue[i + 3])
}
}
listenerQueue.length = 0
}
},
off() {}, /* It will be called on last listener unsubscribing.
We will redefine it in onMount and onStop. */
off: () => {}, /* It will be called on last listener unsubscribing.
We will redefine it in onMount and onStop. */
set(data) {
if (store.value !== data) {
store.value = data
store.notify()
}
},
subscribe(cb, listenerLevel) {
let unbind = store.listen(cb, listenerLevel)
cb(store.value)
subscribe(listener, listenerStore) {
let unbind = store.listen(listener, listenerStore)
listener(store.value)
return unbind
},
value: initialValue
Expand Down
Loading

0 comments on commit dd0c0f2

Please sign in to comment.