uneventful 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +13 -0
- package/README.md +191 -0
- package/dist/mod.cjs +2 -0
- package/dist/mod.cjs.map +1 -0
- package/dist/mod.d.ts +1895 -0
- package/dist/mod.mjs +2 -0
- package/dist/mod.mjs.map +1 -0
- package/package.json +61 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Copyright 2023 PJ Eby
|
|
2
|
+
|
|
3
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
4
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
5
|
+
copyright notice and this permission notice appear in all copies.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
8
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
9
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
10
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
11
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
12
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
13
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
## uneventful: signals plus streams, minus the seams
|
|
2
|
+
|
|
3
|
+
#### The Problem
|
|
4
|
+
Event-driven programming creates a lot of *garbage*. Whether you're using raw event handlers or some kind of functional abstraction (like streams or signals or channels), the big issue with creating complex interactivity is that at some point, you have to *clean it all up*.
|
|
5
|
+
|
|
6
|
+
Handlers need to be removed, requests need to be canceled, streams unsusbcribed or channels closed, and a whole bunch more. And if you don't do it *just right*, you get **bugs**, hiding in your leftover garbage.
|
|
7
|
+
|
|
8
|
+
Worse, the need to keep track of *what* garbage to get rid of and *when* to do it breaks functional composition and information hiding. You can't just write functions that *do* things, because they need to either return disposal information or have it passed into them.
|
|
9
|
+
|
|
10
|
+
Sure, reactive stream and signal libraries help with this some, by giving you fewer things to dispose of, or giving you some tools to dispose of them with. But both paradigms have their limits: when you start doing more complex interactions, you usually end up needing ever-more complex stream operators, or signal-based state machines.
|
|
11
|
+
|
|
12
|
+
And so, while your code *is* a bit cleaner, the complexity and clutter hasn't really gone away: it's just moved to the mind of the person *reading* your code. (Like you, six months later!)
|
|
13
|
+
|
|
14
|
+
#### The Solution
|
|
15
|
+
Enter Uneventful: a seamless, *declarative*, and **composable** blend of signals, streams, and CSP-like, cancelable asynchronous jobs (aka structured concurrency) with automatic resource management.
|
|
16
|
+
|
|
17
|
+
Uneventful does for event-driven interaction what async functions did for promises: it lets you build things out of *functions*, instead of spaghetti and garbage. It's a system for *composable interactivity*, unifying and composing all of the current reactive paradigms in a way that hides the seams and keeps garbage collection where it belongs: hidden in utility functions, not cluttering up your code and your brain.
|
|
18
|
+
|
|
19
|
+
And it does all this by letting your program structure *reflect its interactivity:*
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { start, pipe, into, fromDomEvent, must, Job } from "uneventful";
|
|
23
|
+
|
|
24
|
+
function drag(node: HTMLElement): Job<HTMLElement> {
|
|
25
|
+
return start(job => {
|
|
26
|
+
// The dragged item needs a dragging class during the operation
|
|
27
|
+
addDragClass(node);
|
|
28
|
+
|
|
29
|
+
// The item position needs to track the mouse movement
|
|
30
|
+
trackMousePosition(node);
|
|
31
|
+
|
|
32
|
+
// The job ends when the mouse button goes up,
|
|
33
|
+
// returning the DOM node it happens over
|
|
34
|
+
pipe(fromDomEvent(document, "mouseup"), into(e => {
|
|
35
|
+
// Exit the job, removing all the listeners (and the .dragging class)
|
|
36
|
+
job.return(e.target);
|
|
37
|
+
}));
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function addDragClass(node: HTMlElement) {
|
|
42
|
+
node.classList.add("dragging"); // Add a class now
|
|
43
|
+
must(() => node.classList.remove("dragging")); // Remove it when the job is over
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function trackMousePosition(node: HTMLElement) {
|
|
47
|
+
pipe(fromDomEvent(document, "mousemove"), into(e => {
|
|
48
|
+
// ... assign node.style.x/.y from event
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The example above is a sketch of a drag-and-drop operation that can be *called as a function*. It returns a Job, which is basically a cancellable Promise. (With a bunch of extra superpowers we'll get to later.) Other jobs can wait for it to complete, or you can `await` it in a regular async function if you want.
|
|
54
|
+
|
|
55
|
+
You've probably noticed that there isn't any code here that unsubscribes from anything, and that the only explicit "cleanup" code present is the `must()` call in `addDragClass()`. That's because Uneventful keeps track of the "active" job, and has APIs like `must()` to register cleanup code that will run when that job is finished or canceled. This lets you move the garbage collection to *precisely* where it belongs in your code: **the place where it's created**.
|
|
56
|
+
|
|
57
|
+
If you're familiar with [statecharts](https://statecharts.dev/what-is-a-statechart.html), you might notice that this code sample can easily be translated to one, and the same is true in reverse: if you use statecharts for design and uneventful for implementation, you can pretty much *write down the chart as code*. (A job definition function is a state, and each job instance at runtime represents one "run" of that state, from entry to exit. And of course job definitions can nest like states, and be named and abstracted away like states.)
|
|
58
|
+
|
|
59
|
+
But Uneventful is actually *better* than statecharts, even for design purposes: instead of following boxes and lines, your code is a straightforward list of substates, event handlers, or even *sequential activities*:
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import { each } from "uneventful";
|
|
63
|
+
|
|
64
|
+
function supportDragDrop(parentNode: HTMLElement) {
|
|
65
|
+
return start(function*(job) {
|
|
66
|
+
const mouseDown = fromDomEvent(parentNode, "mousedown");
|
|
67
|
+
for (const {item: event, next} of yield *each(mouseDown)) {
|
|
68
|
+
if (event.target.matches(".drag-handle") {
|
|
69
|
+
const dropTarget = yield *drag(event.target.closest(".draggable"));
|
|
70
|
+
// do something with the dropTarget here
|
|
71
|
+
}
|
|
72
|
+
yield next; // wait for next mousedown
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Where our previous job did a bunch of things in parallel, this one is *serial*. If the previous job was akin to a Promise constructor, this one is more like an async function. It loops over an event like it was an async iterator, but it does so semi-synchronously. (Specifically, each pass of the loop starts *during* the event being responded to, not in a later microtask!)
|
|
79
|
+
|
|
80
|
+
Then it starts a drag job, and waits for its completion, receiving the return value in much the same way as an `await` does -- but again, semi-synchronously, during the mouseup event that ends the `drag()` call. (Note: this pseudo-synchronous return-from-a-job is specific to using `yield` in another job function: if you `await` a job or call its `.then()` method to obtain the result, it'll happen in a later microtask as is normal for promise-based APIs.)
|
|
81
|
+
|
|
82
|
+
And though we haven't shown any details here of what's being *done* with the drop, it's possible that we'll kick off some additional jobs to do an animation or contact a server or something of that sort, and wait for those to finish before enabling drag again. (Unless of course we *want* them to be able to overlap with additional dragging, in which case we can spin off detached jobs.)
|
|
83
|
+
|
|
84
|
+
#### Context, Cancellation, and Cleanup
|
|
85
|
+
If you look closely, you might notice that our last example is an *infinite loop*. `fromDomEvent` returns a stream that will never end on its own, so we could in fact declare this function as returning `Job<never>` -- i.e. a promise that will never return a value. (But it can still throw an error, or be canceled.)
|
|
86
|
+
|
|
87
|
+
So how does it *exit*? When do the event handlers get cleaned up?
|
|
88
|
+
|
|
89
|
+
Well, that's up to the *caller*. If the calling job exits, then any unfinished jobs "inside" it are automatically canceled. (It can also explicitly cancel the job, of course.)
|
|
90
|
+
|
|
91
|
+
For jobs implemented via a setup function (like `drag()`) this just means that all `must()` callbacks registered with that job will be invoked, in reverse order. For a job implemented as a generator (like `supportDragDrop()`), it also means that the most recent `yield` will be resumed as if it had been a `return` instead, allowing any enclosing `try` /`finally` blocks to run.
|
|
92
|
+
|
|
93
|
+
In order for all this to work, of course, Uneventful has to keep track of the "active" job, so that `must()` callbacks and nested jobs can be linked to the correct owner. (You can also do this linking explicitly, e.g. by directly calling a specific job's `.start()` or `.must()` methods instead of the standalone versions.) The way it works is this:
|
|
94
|
+
|
|
95
|
+
- If you're in the body of a `start()` function, that job is active
|
|
96
|
+
- if you're in the body of a `start()`-ed *generator* function, the same applies, but also any generator functions you `yield *` to in the generator function will still have the job active.
|
|
97
|
+
- Callbacks **must** be wrapped with `restarting()` or a job's `.bind()` method (or invoked via a job's `.run()` method) in order to have a job active.
|
|
98
|
+
- If you're in a function directly called from an any place where there's an active job, that job is still active.
|
|
99
|
+
|
|
100
|
+
Early versions of Uneventful also tried to automatically wrap event handlers to run in their owning jobs, but it turned out that this is fairly wasteful in practice! Most event handlers are defined inside of jobs, and so have easy access to their job instance in a variable (as provided by `start()`). So they can explicitly target `job.start()` or `job.must()` to create subjobs or register cleanups, etc., without needing an implicit current job.
|
|
101
|
+
|
|
102
|
+
(Also, as in our `supportDragDrop()` example, you can just loop over `yield *each()` and avoid callbacks entirely!)
|
|
103
|
+
|
|
104
|
+
So the main place where you're likely to want to wrap an event handler is when you want events to start an operation that might be superseded by a *later* event of the same kind. For example, if you want to make a folder open in your UI when a drag hovers over it for a certain amount of time:
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
import {restarting, sleep} from "uneventful";
|
|
108
|
+
|
|
109
|
+
start(job => {
|
|
110
|
+
pipe(currentlyHoveredFolder, into(restarting(folder => {
|
|
111
|
+
if (folder && !folder.isOpen()) start(function *(job) {
|
|
112
|
+
yield *sleep(300);
|
|
113
|
+
// ... open the folder here
|
|
114
|
+
});
|
|
115
|
+
})));
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Let's say that `currentlyHoveredFolder` is a stream (or signal!) that sends events as the hover state changes: either a folder object or `null` if no hovering is happening. The `restarting()` API wraps the event handler with a "temp" job that is canceled and restarted each time the function is called.
|
|
120
|
+
|
|
121
|
+
With this setup, the "open the folder here" code will only be reached if the hover time on a given folder exceeds 300ms. Otherwise, the next change in the hovered folder will cancel the sleeping job (incidentally clearing the timeout allocated by the `sleep()` as it does so).
|
|
122
|
+
|
|
123
|
+
Now, in this simple example you *could* just directly do the debouncing by manipulating the stream. And for a lot of simple things, that might even be the best way to do it. Some event driven libraries might even have lots of handy built-in ways to do things like canceling your in-flight ajax requests when the user types in a search field.
|
|
124
|
+
|
|
125
|
+
But the key benefit to how Uneventful works is that you're not *limited* to whatever bag of tricks the framework itself provides: you can just **write out what you want** and it's easily cancellable by *default*, without you needing to try to twist your use case to fit a specific trick or tool.
|
|
126
|
+
|
|
127
|
+
#### Signals and Streams, Minus The Seams
|
|
128
|
+
So far our examples haven't really used anything "fancy": we've only imported eight functions and a type! But Uneventful also provides a collection of reactive stream operators roughly on par with Wonka.js, and a reactive signals API comparable to that of Maverick Signals. So you can `pipe()`, `take()`, `skip()`, `map()`, `filter()` or even `switchMap()` streams to your heart's content. (See the Stream Operators section of the docs for the full list.)
|
|
129
|
+
|
|
130
|
+
Uneventful's signals and effects are named and work slightly differently from most other frameworks, though. In particular, what other framework APIs usually call a "signal", we call a *value*. What others call "computed", we call a *cached function*. And what they call an "effect", we call a *rule*. (With the respective APIs being named `value()`, `cached()`, and `rule()`. We still call them "signals" as a category, though.)
|
|
131
|
+
|
|
132
|
+
Why the differences? Uneventful is all about *making clear what your code is doing*. A "signal" is just an **observable value** that you can change. A "computed" value is just a function whose value you don't *want* to recompute unless its dependencies change: that is, it's a **cached function**. And when you write an "effect" you're really defining a **rule for synchronizing state**.
|
|
133
|
+
|
|
134
|
+
(But of course, if you're migrating from another signal framework, or are just really attached to the more obscure terminology, you can still rename them in your code with `import as`!)
|
|
135
|
+
|
|
136
|
+
Beyond these superficial differences, though, there are some deeper ones.
|
|
137
|
+
|
|
138
|
+
First off, in Uneventful, *signals are also streams*. When signals are used in APIs that expect streams (including `each()`), they send their current value on the initial subscription, followed by new values when their values change.
|
|
139
|
+
|
|
140
|
+
And they also support *backpressure*: if you iterate over a signal's values with `each()`, then the changes are based on sampling the value when the loop isn't busy (i.e. during the `yield next`). This makes it really easy to (for example) loop over the various value of an input field doing remote searches with them, while maintaining a desired search frequency or level connection saturation using `sleep()` delays in the loop.
|
|
141
|
+
|
|
142
|
+
Second, you can also *turn streams into signals*, by passing them to `cached()`. So if you want a signal that tracks the current mouse position or modifier keys' state, just use `cached(fromDomEvent(...))` or `pipe(fromDomEvent(...), map(...), cached)`, and off you go! As long as the resulting signal is observed by a rule (directly or indirectly) it subscribes to the stream and returns the most recent value. And as soon as all its observers go away, the underlying source is unsubscribed, so there are no dangling event listeners.
|
|
143
|
+
|
|
144
|
+
But wait, there's more: Unlike most libraries' "effects", Uneventful's rules *start asynchronously* and can be *independently scheduled*. This means, for example, that it's easy to make rules that run only in, say, animation frames:
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
import { RuleScheduler } from "uneventful";
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* An alternate version of rule() that runs in animation frames
|
|
151
|
+
* instead of microticks
|
|
152
|
+
*/
|
|
153
|
+
const animate = RuleScheduler.for(requestAnimationFrame).rule;
|
|
154
|
+
|
|
155
|
+
animate(() => {
|
|
156
|
+
// Code here will not run until the next animation frame.
|
|
157
|
+
// After that, though, it'll be *rerun* in another animation frame,
|
|
158
|
+
// any time there's a change to a `value()` or `cached()` it read
|
|
159
|
+
// in its previous run.
|
|
160
|
+
//
|
|
161
|
+
// It's also run in a `restarting()` job, allowing it to register
|
|
162
|
+
// must() functions that will be called on the next run, or when
|
|
163
|
+
// the enclosing job ends. (It can also define other rules or
|
|
164
|
+
// start jobs, which will be similarly canceled and restarted if
|
|
165
|
+
// dependencies change, or if the jobs/rules/etc. containing this
|
|
166
|
+
// rule are finished, canceled, or restarted!)
|
|
167
|
+
});
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
As in most of the better signal frameworks, Uneventful rules can be nested inside of other rules. But they can *also* be nested in jobs, and vice versa: if a rule starts a job, it's contained in the rule's restarting job, and canceled/started over if any of the rule's dependencies change.
|
|
171
|
+
|
|
172
|
+
Also unlike other frameworks, you can have rules that run on different schedules, and nest and combine them to your heart's content. For example, you can use a default, microtask-based `rule()` that decides *whether* an animation rule inside it should be active, or does some of the heavier computation first so the actual animation rule has less to do during the animation frame.
|
|
173
|
+
|
|
174
|
+
Schedulers also let you appropriately debounce or sample changes for some of your rules so you can avoid unnecessary updates. Instead of requiring an immediate response to every change of an observable value, or explicit batching declarations, Uneventful just marks dependencies dirty, and queues affected rules to be run by their corresponding scheduler(s).
|
|
175
|
+
|
|
176
|
+
(This means, for example, that you can have rules that update data models immediately, other rules that update visible UI in the next animation frame, and still others that update a server or database every few seconds, without needing anything more complicated than using rule functions tied to different schedulers when creating them.)
|
|
177
|
+
|
|
178
|
+
#### What's Next
|
|
179
|
+
So far, we've highlighted just a handful of Uneventful's coolest and most impactful features, showing how you can:
|
|
180
|
+
|
|
181
|
+
- Use the best-fit tools from every major reactive paradigm, from signals, streams, and CSP, to cancelable async processes and structured concurrency -- while still being interoperable with standard APIs like promises, async functions, and abort signals
|
|
182
|
+
- Make your code's interactivity *visible* and *composable*, such that serial and parallel job flows are obvious in your code, or hidden away within functions, as required, while easily expressing interactions that would be challenging in other paradigms
|
|
183
|
+
- Play well with state charts, or ignore the charts and just express interactivity directly in code!
|
|
184
|
+
- Easily control the *timing* of operations, building advanced debouncing and sampling with basic async operators like `sleep()` or by defining rules tied to a scheduler
|
|
185
|
+
|
|
186
|
+
And at the same time, this has actually been a pretty superficial tour: we haven't gotten into a lot of things like how to actually *use* signals or abort jobs or any other details, really. For those, you'll currently have to dig through the API Reference, but there should be more tutorials and guides as time goes on.
|
|
187
|
+
|
|
188
|
+
(Also, at some point you'll be able to use the "Calibre Connect" Obsidian plugin I'm working on as an example of how to use these things to create responsive search and tame Electron WebFrames while keeping track of whether a connection to a remote server is available, handling logins and background processes and integrating a plugin to a larger application, not to mention controlling lots of features via settings.)
|
|
189
|
+
|
|
190
|
+
In the meantime, this library should now be available for installation and experimentation via npm. Enjoy!
|
|
191
|
+
|
package/dist/mod.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";let g=typeof queueMicrotask=="function"?queueMicrotask:(n=>t=>n.then(t))(Promise.resolve());function W(n,t){n("next",t)}function O(n,t){n("throw",void 0,t)}function it(n){return n.bind(null,"next")}function st(n){return n.bind(null,"throw",void 0)}function P(){}function ut(n,t,e){return{op:n,val:t,err:e}}const Q=Object.freeze(ut("cancel"));function vt(n){return ut("next",n)}function mt(n){return ut("throw",void 0,n)}function q(n){return n===Q}function k(n){return n?n.op==="next":!1}function C(n){return n?n.op==="throw":!1}function L(n){return C(n)&&n.val===void 0}function Bt(n){return C(n)&&n.val===null}function V(n){return n.val=null,n.err}function gt(n){if(k(n))return n.val;n.op,J(P,t=>{throw t},n)}function J(n,t,e){C(e)?t(V(e)):q(e)?t(new wt("Job canceled")):n(e.val)}function yt(n,t){n.result()||J(n.return.bind(n),n.throw.bind(n),t)}class wt extends Error{}var p=_();function h(n){const t=p;return p=n,t}var B=[];function _(n,t){if(B&&B.length){const e=B.pop();return e.job=n,e.cell=t,e}return{job:n,cell:t}}function G(n){n.job=n.cell=null,B.push(n)}const z=new WeakMap,St=n=>{Promise.reject(n)};function bt(){return j(0,void 0,void 0)}function Gt(n){for(;n.v;)F(n,n.n);n.u=void 0,F(n,n)}function Yt(n,t){++n.v,j(t,n.n,n)}function jt(n,t){++n.v,j(t,n,n.p)}function Ft(n,t){return++n.v,Kt(n,j(t,n,n.p))}function Xt(n){return!n||n.v===0}function kt(n){return n?n.v:0}function Zt(n){if(kt(n))return F(n,n.p)}class $t{constructor(){this.n=this,this.p=this,this.v=0,this.u=void 0}}var Y;function j(n,t,e){let r=Y;return r?(Y=r.n,r.n=t||r,r.p=e||r):(r=new $t,t&&(r.n=t),e&&(r.p=e)),r.v=n,r.n.p=r,r.p.n=r,r}function F(n,t){--n.v;var e=t.v,r=t.u;return t.n&&(t.n.p=t.p),t.p&&(t.p.n=t.n),t.u=t.v=t.p=void 0,t.n=Y,Y=t,r&&r(),e}function Kt(n,t){let e=t.u||(t.u=()=>{e&&(e===t.u&&F(n,t),e=n=t=void 0)});return e}function w(n){return typeof n=="function"}function d(){const{job:n}=p;if(n)return n;throw new Error("No job is currently active")}const te=_();function ee(n){return t=>{p.job.must(n.release(t))}}const _t=new WeakMap;function Rt(n,t){for(;kt(t);)try{Zt(t)(n)}catch(e){y.asyncThrow(e)}t&&Gt(t)}var R=new Set;class H{constructor(){this.end=()=>{const t=this._done||(this._done=Q),e=this._cbs;if(!e&&!L(t))return;const r=R.size,i=h(te);if(r||R.add(null),e&&e.u&&(e.u=Rt(t,e.u)),R.add(this),r){h(i);return}R.delete(null);for(const s of R)s._cbs&&(s._cbs=Rt(s._done,s._cbs)),R.delete(s),L(s._done)&&s.throw(V(s._done));h(i)},this._done=void 0,this._cbs=void 0}static create(t,e){const r=new H;return(t||e)&&(r.must((t||(t=d())).release(e||r.end)),_t.set(r,t)),r}do(t){return Yt(this._chain(),t),this}onError(t){return this.do(e=>{C(e)&&t(V(e))})}onValue(t){return this.do(e=>{k(e)&&t(e.val)})}onCancel(t){return this.do(e=>{q(e)&&t()})}result(){return this._done||p.cell?.recalcWhen(this,ee)||void 0}get[Symbol.toStringTag](){return"Job"}restart(){if(!this._done&&R.size){const t=R;R=new Set,this.end(),R=t}else this._end(Q);return this._done=void 0,X.delete(this),this}_end(t){if(this._done)throw new Error("Job already ended");return this!==y&&(this._done=t),this.end(),this}throw(t){return this._done?((_t.get(this)||y).asyncThrow(t),this):this._end(mt(t))}return(t){return this._end(vt(t))}then(t,e){return Z(this).then(t,e)}catch(t){return Z(this).catch(t)}finally(t){return Z(this).finally(t)}*[Symbol.iterator](){return this._done?gt(this._done):yield t=>{this.do(e=>J(it(t),st(t),e))}}start(t,e){if(!t)return E(this);let r,i;if(w(e))r=e.bind(t);else if(w(t))r=t;else{if(t instanceof H)return t;i=t}const s=E(this);try{if(r&&(i=s.run(r,s)),i!=null)if(i instanceof H)i!==s&&i.do(u=>yt(s,u));else{if(w(i.then))return i.then(u=>{s.result()||s.return(u)},u=>{s.result()||s.throw(u)}),s;if(w(i[Symbol.iterator])&&typeof i!="string")s.run(ne,i,s);else throw new TypeError("Invalid value/return for start()")}return s}catch(u){throw s.end(),u}}connect(t,e,r){return this.start(i=>void t(e,i,r))}run(t,...e){const r=h(_(this));try{return t.apply(null,e)}finally{G(h(r))}}bind(t){const e=this;return function(){const r=h(_(e));try{return t.apply(this,arguments)}finally{G(h(r))}}}must(t){return w(t)&&jt(this._chain(),t),this}release(t){if(this===y)return P;let e=this._chain();return(!this._done||e.u)&&(e=e.u||(e.u=bt())),Ft(e,t)}asyncThrow(t){try{(z.get(this)||this.throw).call(this,t)}catch(e){this===y?z.set(this,St):z.delete(this);const r=z.get(this)||this.throw;r.call(this,t),r.call(this,e)}return this}asyncCatch(t){return w(t)?z.set(this,t):t===null&&z.delete(this),this}_chain(){return this===y&&this.end(),this._done&&Xt(this._cbs)&&g(this.end),this._cbs||(this._cbs=bt())}}const X=new WeakMap;function Z(n=d()){return X.has(n)||X.set(n,new Promise((t,e)=>{const r=J.bind(null,t,e);n.result()?r(n.result()):n.do(r)})),X.get(n)}const E=H.create,y=E();y.end=()=>{throw new Error("Can't do that with the detached job")},y.asyncCatch(St);function ne(n,t){let e=n[Symbol.iterator](),r=!0,i=_(t),s=0,u=i.job.release(()=>{t=void 0,++s,o("return",void 0)});g(()=>{r=!1,o("next",void 0)});function o(c,l){if(!e)return;if(r)return g(o.bind(null,c,l));const x=h(i);try{r=!0;try{for(;;){++s;const{done:f,value:b}=e[c](l);if(f){t&&t.return(b),t=void 0;break}else if(w(b)){let m=!1,N=!1,Ot=s;if(b((rt,Qt,Lt)=>{m||(m=!0,c=rt,l=rt==="next"?Qt:Lt,N&&Ot===s&&o(rt,l))}),N=!0,!m)return}else{c="throw",l=new TypeError("Jobs must yield functions (or yield* Yielding<T>s)");continue}}}catch(f){e=t=void 0,i.job.throw(f)}e=void 0,u?.(),u=void 0}finally{h(x),r=!1}}}function*Tt(n){return yield t=>Promise.resolve(n).then(it(t),st(t))}function*re(n){try{var t;yield e=>{t=setTimeout(()=>{t=void 0,W(e,void 0)},n)}}finally{t&&clearTimeout(t)}}class xt{constructor(t,e){this.sched=t,this.reap=e,this._flags=0,this.q=new Set,this._run=()=>{this._flags&=-3,this.flush()},this.flush=()=>{if(this._flags&1)return;const{q:r}=this;if(r.size){this._flags|=1;try{this.reap(r)}finally{this._flags&=-2,!r.size||this._flags&2||this._sched()}}}}isRunning(){return!!(this._flags&1)}isEmpty(){return!this.q.size}add(t){this.q.size||this._flags&3||this._sched(),this.q.add(t)}delete(t){this.q.delete(t)}_sched(){this._flags|=2,this.sched(this._run)}}const Ct=new xt(g,n=>{for(const t of n)n.delete(t),t.doPull()});Ct.flush;function A(n=se){const t=d();return e=>!t.result()&&n.isOpen()?(e&&n.onReady(e,t),n.isReady()):!1}const a="uneventful/is-stream";function Et(n,t,e){return d().connect(n,t,e)}function I(n=p.job){return new ie(n)}class ie{constructor(t){this._job=t,this._callbacks=void 0,this._isReady=!0,this._isPulling=!1}isOpen(){return!this._job?.result()}isReady(){return this.isOpen()&&this._isReady}onReady(t,e){if(!this.isOpen())return this;const r=this._callbacks||(this._callbacks=new Map),i=e.release(()=>r.delete(t));return this.isReady()&&this&&!r.size&&Ct.add(this),r.set(t,i),this}pause(){return this._isReady=!1,this}doPull(){if(this._isPulling)return;const{_callbacks:t}=this;if(t?.size){this._isPulling=!0;try{for(let[e,r]of t){if(!this.isReady())break;r(),t.delete(e),e()}}finally{this._isPulling=!1}}}resume(){this.isOpen()&&(this._isReady=!0,this.doPull())}}const se=I();function Mt(){for(var n=arguments[0],t=1;t<arguments.length;t++)n=arguments[t](n);return n}function ue(...n){return t=>Mt(t,...n)}function oe(...n){return t=>t(...n)}function le(n,t,e){return n.set(t,e),e}class ot extends Error{}class lt extends Error{}var v=1,U,ct;class ${constructor(t=g){this.rule=e=>T.mkRule(e,this.q),this.q=new xt(t,e=>{if(!ct){ct=this;try{for(U of e)U.catchUp(),e.delete(U)}finally{ct=U=void 0}}}),this.flush=this.q.flush}static for(t=g){return this.cache.has(t)||this.cache.set(t,new this(t)),this.cache.get(t)}}$.cache=new WeakMap;const Wt=$.for(g),at=Wt.rule,ce=Wt.flush,ae=new WeakMap,Pt=new WeakMap,Vt=[];function he(n){const t=n.lastChanged=n.latestSource=v;for(;n;n=Vt.pop())for(let e=n.subscribers;e;e=e.nT){const r=e.tgt;r.latestSource>=t||r.latestSource===0||(r.latestSource=t,r.flags&1&&r.value.add(r),r.subscribers&&Vt.push(r))}}var K;function fe(n,t){let e=K;e?(K=e.old,e.src=n,e.nS=void 0,e.pS=t.sources,e.tgt=t,e.nT=e.pT=void 0,e.ts=n.lastChanged,e.old=n.adding):e={src:n,nS:void 0,pS:t.sources,tgt:t,nT:void 0,pT:void 0,ts:n.lastChanged,old:n.adding},t.sources&&(t.sources.nS=e),t.sources=e,n.adding=e,t.latestSource===0||n.subscribe(e)}function zt(n){n.src.unsubscribe(n),n.nS&&(n.nS.pS=n.pS),n.pS&&(n.pS.nS=n.nS),n.src=n.tgt=n.nS=n.pS=n.nT=n.pT=void 0,n.old=K,K=n}const de={};class T{constructor(){this.value=void 0,this.validThrough=0,this.lastChanged=0,this.latestSource=v,this.flags=0,this.ctx=void 0,this.adding=void 0,this.sources=void 0,this.subscribers=void 0,this.compute=void 0}stream(t,e,r){let i=de;return(r?$.for(A(r)).rule:at)(()=>{const s=this.getValue();s!==i&&t(i=s)}),a}getValue(){if(arguments.length)return this.stream.apply(this,arguments);this.catchUp();const t=p.cell;if(t){if(this.flags&32)throw new lt("Cached function dependency cycle");let e=this.adding;!e||e.tgt!==t?fe(this,t):e.ts===-1&&(e.ts=this.lastChanged,e.nS&&(e.nS.pS=e.pS,e.pS&&(e.pS.nS=e.nS),e.nS=void 0,e.pS=t.sources,t.sources.nS=e,t.sources=e))}if(this.flags&16)throw this.value;return this.value}setValue(t){const e=p.cell||U;if(e){if(e.flags&4)throw new ot("Side-effects not allowed in cached functions");if(this.adding&&this.adding.tgt===e)throw new lt("Can't update direct dependency");if(this.validThrough===v)throw new ot("Value already used")}else{if(t===this.value)return;this.validThrough===v&&++v}this.lastChanged===v||he(this),this.value=t}catchUp(){const{validThrough:t}=this;if(t!==v&&(this.validThrough=v,!(this.latestSource!==0&&this.latestSource<=t||!(this.flags&5))))if(this.sources)for(let e=this.sources;e;e=e.nS){const r=e.src;if(!(r.latestSource!==0&&r.latestSource<=t)){if(e.ts!==r.lastChanged)return this.doRecalc();if(r.catchUp(),r.lastChanged>t)return this.doRecalc()}}else return this.doRecalc()}doRecalc(){const t=h(this.ctx);for(let e=this.sources;e;e=e.nS)e.ts=-1,e.old=e.src.adding,e.src.adding=e,this.sources=e;this.flags|=32;try{if(this.flags&4){this.flags&=-17;try{const e=this.compute();(e!==this.value||!this.lastChanged)&&(this.value=e,this.lastChanged=v)}catch(e){this.flags|=16,this.value=e,this.lastChanged=v}}else{const{job:e}=this.ctx;e.restart();try{e.must(this.compute()),this.lastChanged=v}catch(r){throw e.end(),this.disposeRule(),r}}}finally{this.flags&=-33,h(t);let e;for(let r=this.sources;r;){const i=r.pS;r.src.adding=r.old,r.old=void 0,r.ts===-1?zt(r):e=r,r=i}this.sources=e,this.flags&8&&this.disposeRule()}}disposeRule(){if(this.flags|=8,this.value.delete(this),p!==this.ctx){for(let t=this.sources;t;){let e=t.nS;zt(t),t=e}this.sources=void 0,this.ctx.job.end()}}subscribe(t){if(!this.subscribers){if(this.flags&4){this.latestSource=v;for(let e=this.sources;e;e=e.nS)e.src.subscribe(e)}this.flags&64&&this.compute()}this.subscribers!==t&&!t.pT&&(t.nT=this.subscribers,this.subscribers&&(this.subscribers.pT=t),this.subscribers=t)}unsubscribe(t){if(t.nT&&(t.nT.pT=t.pT),t.pT&&(t.pT.nT=t.nT),this.subscribers===t&&(this.subscribers=t.nT),!this.subscribers){if(this.flags&4){this.latestSource=0;for(let e=this.sources;e;e=e.nS)e.src.unsubscribe(e)}this.flags&64&&this.ctx.job?.restart()}}static mkValue(t){const e=new T;return e.value=t,e.lastChanged=v,e}static mkStream(t,e){const r=this.mkValue(e);r.flags|=64,r.ctx=_();const i=r.setValue.bind(r);return r.compute=()=>{var s;(s=r.ctx).job||(s.job=E().asyncCatch(o=>y.asyncThrow(o)).must(o=>{r.value=e,q(o)||(r.ctx.job=void 0)}));const u=h(r.ctx);try{t(i)}catch(o){y.asyncThrow(o),r.ctx.job.end(),r.ctx.job=void 0}finally{h(u)}},r.getValue.bind(r)}recalcWhen(t,e){let r=e?Pt.get(e)||le(Pt,e,new WeakMap):ae,i=r.get(t);if(!i){const s=e?e(t):t;let u=0;i=T.mkStream(o=>(s(()=>o(++u)),a),u),r.set(t,i)}i()}static mkCached(t){const e=new T;return e.compute=t,e.ctx=_(null,e),e.flags=4,e.latestSource=0,e.getValue.bind(e)}static mkRule(t,e){var r=d().release(u),i=new T,s=E();return i.value=e,i.compute=t.bind(null,u),i.ctx=_(s,i),i.flags=1,e.add(i),u;function u(){r(),i&&(i.disposeRule(),i=void 0)}}}class tt extends Function{get value(){return this()}valueOf(){return this()}toString(){return""+this()}toJSON(){return this()}peek(){return It(this)}readonly(){return this}withSet(t){const e=this;return et(function(){return e.apply(null,arguments)},t)}*"uneventful.until"(){return yield t=>{try{let e=this.peek();e?W(t,e):at(()=>{try{(e=this())&&W(t,e)}catch(r){O(t,r)}})}catch(e){O(t,e)}}}constructor(){super()}}class At extends tt{get value(){return this()}set value(t){this.set(t)}readonly(){const t=this;return et(function(){return t.apply(null,arguments)})}}function pe(n){const t=T.mkValue(n);return et(t.getValue.bind(t),t.setValue.bind(t))}function ve(n,t){return n instanceof tt?n:et(n.length?T.mkStream(n,t):T.mkCached(n))}function It(n,...t){if(!p.cell)return n(...t);const e=h(_(p.job));try{return n.apply(null,t)}finally{G(h(e))}}function me(n,t){p.cell?.recalcWhen(n,t)}function et(n,t){return t&&(n.set=t),Object.setPrototypeOf(n,(t?At:tt).prototype)}function M(n){return d().must(n)}function S(n,t){return d().start(n,t)}function ge(){return!!p.job}const D=new WeakMap;function qt(n=0,t=d()){let e=D.get(t);return e?clearTimeout(e):e===void 0&&!t.result()&&t.must(qt.bind(null,0,t)),t.result()?D.delete(t):n?D.set(t,setTimeout(()=>{D.set(t,null),t.end()},n)):D.set(t,null),t}const ht=new WeakMap;function ye(n=d()){let t=ht.get(n);if(!t){const e=new AbortController;t=e.signal,n.do(()=>{ht.set(n,null),e.abort()}),ht.set(n,t),n.result()&&e.abort()}return t}function we(n){const t=d(),e=E(t),{end:r}=e;return n||(n=i=>{e.must(i())}),e.asyncCatch(i=>t.asyncThrow(i)),function(){e.restart().must(t.release(r));const i=h(_(e));try{return n.apply(this,arguments)}catch(s){throw e.restart(),s}finally{G(h(i))}}}function Se(){const n=Jt();return n.source=Ut(n.source),n}function be(){return(n,t)=>(t?.return(),a)}function ke(n){return(t,e=S(),r)=>{const i=A(r),s=n[Symbol.asyncIterator]();return s.return&&M(()=>s.return()),i(u),a;function u(){s.next().then(({value:o,done:c})=>{c?e.return():i(()=>{t(o),i()?u():i(u)})},o=>e.throw(o))}}}function _e(n,t,e){return r=>{function i(s){r(s)}return n.addEventListener(t,i,e),M(()=>n.removeEventListener(t,i,e)),a}}function ft(n){return(t,e=S(),r)=>{const i=A(r),s=n[Symbol.iterator]();return s.return&&M(()=>s.return()),i(u),a;function u(){try{for(;;){const{value:o,done:c}=s.next();if(c)return e.return();if(t(o),!i())return i(u)}}catch(o){e.throw(o)}}}}function Re(n){return(t,e)=>{const r=d();return Promise.resolve(n).then(i=>void(r.result()||(t(i),e?.return())),i=>void(r.result()||e?.throw(i))),a}}function Te(n){return t=>{const e=d().must(()=>t=P);return g(()=>e.must(n(r=>{t(r)}))),a}}function xe(n){return(t,e)=>(M(()=>{t=P,e=void 0}),g(()=>{t(n),e?.return()}),a)}function Ce(n){return t=>{let e=0,r=setInterval(()=>t(e++),n);return M(()=>clearInterval(r)),a}}function Ee(n){return(t,e)=>n()(t,e)}function Jt(){let n,t,e;const r=i=>{n&&n(i)};return r.source=(i,s,u)=>(n=i,t=s,e=A(u),M(()=>n=t=e=void 0),a),r.end=()=>t?.return(),r.throw=i=>t?.throw(i),r.ready=i=>e(i),r}function Me(){return()=>a}function Ut(n){let t;const e=new Set,r=new Map,i=I(),s={isOpen(){return!t?.result()},isReady(){if(this.isOpen()){for(const[o]of r)if(!o.isReady())return i.pause(),!1;return!0}return i.pause(),!1},onReady(o,c){if(this.isOpen()){i.onReady(o,c);for(const[l]of r)l.isReady()||l.onReady(u,c)}return this}};function u(){s.isReady()&&i.resume()}return(o,c=S(),l)=>{const x=[o,c];return e.add(x),l&&r.set(l,1+(r.get(l)||0)),c.must(()=>{e.delete(x),l&&(r.set(l,r.get(l)-1),r.get(l)||r.delete(l)),e.size?s.isReady()&&!i.isReady()&&g(u):t?.end()}),e.size===1&&(t=y.connect(n,f=>{for(const[b,m]of e)try{b(f)}catch(N){m.throw(N)}},s).do(f=>{if(t=void 0,!q(f)){L(f)&&V(f);for(const[b,m]of e)C(f)?m.throw(f.err):m.return()}})),a}}function*We(n){let t=!1,e;const r={value:{item:void 0,next:u},done:!1},i=I(),s=d().connect(n,o=>{i.pause(),!(!e||s.result())&&(r.value.item=o,W(e,e=void 0))},i).do(o=>{C(o)&&V(o),e&&(g(u.bind(null,e)),e=void 0)});return i.pause(),yield u,{[Symbol.iterator](){return this},next(){if(!t)throw new Error("Must `yield next` in loop");return t=!1,r},return(){return s.end(),{value:void 0,done:!0}}};function u(o){if(e)throw new Error("Multiple `yield next` in loop");t=!0,s.result()?(r.value=void 0,r.done=!0,C(s.result())?O(o,s.result().err):W(o,void 0)):(e=o,i.resume())}}function Pe(n){if(w(n["uneventful.until"]))return n["uneventful.until"]();if(w(n.then))return Tt(n);if(w(n))return S(t=>{Et(n,e=>t.return(e)).onError(e=>t.throw(e)).onValue(()=>t.throw(new Error("Stream ended")))});throw new TypeError("until(): must be signal, source, or then-able")}function Ve(n){return dt(ft(n))}function dt(n){return(t,e=S(),r)=>{let i;const s=[],u=I();let o=e.connect(n,l=>{s.push(l),c(),u.pause()},u).do(l=>{o=void 0,s.length||i||!k(l)||e.return()});function c(){i||(i=e.connect(s.shift(),t,r).do(l=>{i=void 0,s.length?c():o?u.resume():!k(l)||e.return()}))}return a}}function ze(n){return t=>dt(nt(n)(t))}function Ae(n){return t=>(e,r,i)=>{let s=0;return t(u=>n(u,s++)&&e(u),r,i)}}function nt(n){return t=>(e,r,i)=>{let s=0;return t(u=>e(n(u,s++)),r,i)}}function Ie(n){return pt(ft(n))}function pt(n){return(t,e=S(),r)=>{const i=new Set;let s=e.connect(n,u=>{const o=e.connect(u,t,r).do(c=>{i.delete(o),i.size||s||!k(c)||e.return()});i.add(o)}).do(u=>{s=void 0,i.size||!k(u)||e.return()});return a}}function qe(n){return t=>pt(nt(n)(t))}function Je(n){return Dt((t,e)=>e<n)}function Ue(n){return t=>(e,r=S(),i)=>{let s=!1;const u=r.connect(n,()=>{s=!0,u.end()});return t(o=>s&&e(o),r,i)}}function Dt(n){return t=>(e,r,i)=>{let s=0,u=!1;return t(o=>(u||(u=!n(o,s++)))&&e(o),r,i)}}function De(n,t=P){const e=Math.abs(n);return r=>(i,s=S(),u)=>{const o=[],c=A(u);let l=!1,x=!1;const f=I();s.connect(r,m=>{if(o.push(m),!x&&c())return b();for(;o.length>e;)t(n<0?o.pop():o.shift());o.length===e&&(f.pause(),l=!0),o.length&&c(b)},f).do(m=>{k(m)&&s.return()});function b(){x=!0;try{for(;o.length;)if(i(o.shift()),l&&c()&&(l=!1,f.resume()),o.length&&!c())return c(b)}finally{x=!1}}return a}}function Ht(n){return(t,e=S(),r)=>{let i,s=e.connect(n,u=>{i?.end(),i=e.connect(u,t,r).do(o=>{i=void 0,s||!k(o)||e.return()})}).do(u=>{s=void 0,i||!k(u)||e.return()});return a}}function He(n){return t=>Ht(nt(n)(t))}function Ne(n){return Nt((t,e)=>e<n)}function Oe(n){return t=>(e,r=S(),i)=>(r.connect(n,()=>r.return()),t(e,r,i))}function Nt(n){return t=>(e,r,i)=>{let s=0;return t(u=>n(u,s++)?e(u):r?.return(),r,i)}}exports.CancelError=wt,exports.CancelResult=Q,exports.CircularDependency=lt,exports.ErrorResult=mt,exports.IsStream=a,exports.RuleScheduler=$,exports.Signal=tt,exports.ValueResult=vt,exports.Writable=At,exports.WriteConflict=ot,exports.abortSignal=ye,exports.backpressure=A,exports.cached=ve,exports.compose=ue,exports.concat=Ve,exports.concatAll=dt,exports.concatMap=ze,exports.connect=Et,exports.defer=g,exports.detached=y,exports.each=We,exports.emitter=Se,exports.empty=be,exports.filter=Ae,exports.fromAsyncIterable=ke,exports.fromDomEvent=_e,exports.fromIterable=ft,exports.fromPromise=Re,exports.fromSubscribe=Te,exports.fromValue=xe,exports.fulfillPromise=J,exports.getJob=d,exports.getResult=gt,exports.interval=Ce,exports.into=oe,exports.isCancel=q,exports.isError=C,exports.isFunction=w,exports.isHandled=Bt,exports.isJobActive=ge,exports.isUnhandled=L,exports.isValue=k,exports.lazy=Ee,exports.makeJob=E,exports.map=nt,exports.markHandled=V,exports.merge=Ie,exports.mergeAll=pt,exports.mergeMap=qe,exports.mockSource=Jt,exports.must=M,exports.nativePromise=Z,exports.never=Me,exports.noDeps=It,exports.noop=P,exports.pipe=Mt,exports.propagateResult=yt,exports.recalcWhen=me,exports.reject=O,exports.rejecter=st,exports.resolve=W,exports.resolver=it,exports.restarting=we,exports.rule=at,exports.runRules=ce,exports.share=Ut,exports.skip=Je,exports.skipUntil=Ue,exports.skipWhile=Dt,exports.slack=De,exports.sleep=re,exports.start=S,exports.switchAll=Ht,exports.switchMap=He,exports.take=Ne,exports.takeUntil=Oe,exports.takeWhile=Nt,exports.throttle=I,exports.timeout=qt,exports.to=Tt,exports.until=Pe,exports.value=pe;
|
|
2
|
+
//# sourceMappingURL=mod.cjs.map
|