jrx 0.3.0-alpha.1 → 0.3.0-alpha.2
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/README.md +70 -56
- package/index.d.ts +13 -7
- package/index.js +29 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,12 +28,12 @@ npm i jrx
|
|
|
28
28
|
- [`makeReset()`](#makereset) - Create a resettable `DisposableStack`
|
|
29
29
|
- [`makeAsyncReset()`](#makeasyncreset) - Create a resettable `AsyncDisposableStack`
|
|
30
30
|
- [`makeRenderLoop()`](#makerenderloop) - Render loops with automatic cleanup
|
|
31
|
-
- [`
|
|
32
|
-
- [`
|
|
33
|
-
- [`
|
|
34
|
-
- [`
|
|
35
|
-
- [`
|
|
36
|
-
- [`
|
|
31
|
+
- [`makeInterval(cb, ms)`](#makeintervalcb-ms) - Repeating intervals with cleanup
|
|
32
|
+
- [`makeIntervalAsync(cb, ms)`](#makeintervalasynccb-ms) - Async intervals with cancellation
|
|
33
|
+
- [`makeAnimationFrame(cb)`](#makeanimationframecb) - Single animation frame with cleanup
|
|
34
|
+
- [`makeAnimationFrameLoop(cb)`](#makeanimationframeloopcb) - Animation frame loops
|
|
35
|
+
- [`makeTimeout(cb, ms)`](#maketimeoutcb-ms) - Timeouts with cleanup
|
|
36
|
+
- [`makeTransition(cb, durationMs)`](#maketransitioncb-durationms) - Progress-based animations
|
|
37
37
|
- [`computed(fn, getDeps?)`](#computedfn-getdeps) - Memoized computed values
|
|
38
38
|
- [`retry(cb, backoffSec?)`](#retrycb-backoffsec) - Retry with exponential backoff
|
|
39
39
|
|
|
@@ -76,13 +76,15 @@ import {makeRenderLoop} from 'jrx'
|
|
|
76
76
|
|
|
77
77
|
const {loop, setLoop} = makeRenderLoop()
|
|
78
78
|
|
|
79
|
-
// Set the loop function
|
|
80
|
-
const
|
|
79
|
+
// Set the loop function - returns a Disposable
|
|
80
|
+
const handle = setLoop((time) => {
|
|
81
81
|
console.log('Frame time:', time)
|
|
82
82
|
|
|
83
|
-
// Optional: return cleanup
|
|
84
|
-
return
|
|
85
|
-
|
|
83
|
+
// Optional: return a Disposable for cleanup
|
|
84
|
+
return {
|
|
85
|
+
[Symbol.dispose]() {
|
|
86
|
+
console.log('Cleanup previous frame')
|
|
87
|
+
},
|
|
86
88
|
}
|
|
87
89
|
})
|
|
88
90
|
|
|
@@ -90,41 +92,43 @@ const dispose = setLoop((time) => {
|
|
|
90
92
|
requestAnimationFrame(loop)
|
|
91
93
|
|
|
92
94
|
// Cleanup
|
|
93
|
-
dispose()
|
|
95
|
+
handle[Symbol.dispose]()
|
|
94
96
|
```
|
|
95
97
|
|
|
96
|
-
### `
|
|
98
|
+
### `makeInterval(cb, ms)`
|
|
97
99
|
|
|
98
|
-
Creates a repeating interval with cleanup. The callback can optionally return a
|
|
100
|
+
Creates a repeating interval with cleanup. The callback can optionally return a `Disposable` that is disposed before the next invocation. Returns a `Disposable`.
|
|
99
101
|
|
|
100
102
|
**Note:** The callback fires **immediately** on first call, then waits `ms` milliseconds **after** the previous callback completes. This is not a fixed-rate timer.
|
|
101
103
|
|
|
102
104
|
```typescript
|
|
103
|
-
import {
|
|
105
|
+
import {makeInterval} from 'jrx'
|
|
104
106
|
|
|
105
|
-
const
|
|
107
|
+
const handle = makeInterval(() => {
|
|
106
108
|
console.log('Tick') // Called immediately, then every 1000ms after completion
|
|
107
109
|
|
|
108
|
-
// Optional: return cleanup
|
|
109
|
-
return
|
|
110
|
-
|
|
110
|
+
// Optional: return a Disposable for cleanup
|
|
111
|
+
return {
|
|
112
|
+
[Symbol.dispose]() {
|
|
113
|
+
console.log('Cleanup')
|
|
114
|
+
},
|
|
111
115
|
}
|
|
112
116
|
}, 1000)
|
|
113
117
|
|
|
114
118
|
// Stop the interval
|
|
115
|
-
dispose()
|
|
119
|
+
handle[Symbol.dispose]()
|
|
116
120
|
```
|
|
117
121
|
|
|
118
|
-
### `
|
|
122
|
+
### `makeIntervalAsync(cb, ms)`
|
|
119
123
|
|
|
120
|
-
Async version of `
|
|
124
|
+
Async version of `makeInterval`. Waits for the callback to complete before scheduling the next invocation.
|
|
121
125
|
|
|
122
126
|
**Note:** The callback fires **immediately** on first call, then waits `ms` milliseconds **after** the previous async callback completes.
|
|
123
127
|
|
|
124
128
|
```typescript
|
|
125
|
-
import {
|
|
129
|
+
import {makeIntervalAsync} from 'jrx'
|
|
126
130
|
|
|
127
|
-
const dispose =
|
|
131
|
+
const dispose = makeIntervalAsync(async () => {
|
|
128
132
|
// Called immediately, then 5000ms after each completion
|
|
129
133
|
await fetchData()
|
|
130
134
|
processData()
|
|
@@ -133,19 +137,21 @@ const dispose = addIntervalAsync(async () => {
|
|
|
133
137
|
dispose()
|
|
134
138
|
```
|
|
135
139
|
|
|
136
|
-
### `
|
|
140
|
+
### `makeAnimationFrame(cb)`
|
|
137
141
|
|
|
138
142
|
Executes a callback on the next animation frame with cleanup.
|
|
139
143
|
|
|
140
144
|
```typescript
|
|
141
|
-
import {
|
|
145
|
+
import {makeAnimationFrame} from 'jrx'
|
|
142
146
|
|
|
143
|
-
const dispose =
|
|
147
|
+
const dispose = makeAnimationFrame((now) => {
|
|
144
148
|
updateAnimation(now)
|
|
145
149
|
|
|
146
|
-
// Optional: return cleanup
|
|
147
|
-
return
|
|
148
|
-
|
|
150
|
+
// Optional: return a Disposable for cleanup
|
|
151
|
+
return {
|
|
152
|
+
[Symbol.dispose]() {
|
|
153
|
+
cleanupAnimation()
|
|
154
|
+
},
|
|
149
155
|
}
|
|
150
156
|
})
|
|
151
157
|
|
|
@@ -153,19 +159,21 @@ const dispose = addRequestAnimationFrame((now) => {
|
|
|
153
159
|
dispose()
|
|
154
160
|
```
|
|
155
161
|
|
|
156
|
-
### `
|
|
162
|
+
### `makeAnimationFrameLoop(cb)`
|
|
157
163
|
|
|
158
164
|
Creates a continuous `requestAnimationFrame` loop with cleanup.
|
|
159
165
|
|
|
160
166
|
```typescript
|
|
161
|
-
import {
|
|
167
|
+
import {makeAnimationFrameLoop} from 'jrx'
|
|
162
168
|
|
|
163
|
-
const dispose =
|
|
169
|
+
const dispose = makeAnimationFrameLoop((now) => {
|
|
164
170
|
updateAnimation(now)
|
|
165
171
|
|
|
166
|
-
// Optional: return cleanup
|
|
167
|
-
return
|
|
168
|
-
|
|
172
|
+
// Optional: return a Disposable for cleanup
|
|
173
|
+
return {
|
|
174
|
+
[Symbol.dispose]() {
|
|
175
|
+
cleanupAnimation()
|
|
176
|
+
},
|
|
169
177
|
}
|
|
170
178
|
})
|
|
171
179
|
|
|
@@ -173,14 +181,14 @@ const dispose = addRequestAnimationFrameLoop((now) => {
|
|
|
173
181
|
dispose()
|
|
174
182
|
```
|
|
175
183
|
|
|
176
|
-
### `
|
|
184
|
+
### `makeTimeout(cb, ms)`
|
|
177
185
|
|
|
178
186
|
Creates a timeout with cleanup.
|
|
179
187
|
|
|
180
188
|
```typescript
|
|
181
|
-
import {
|
|
189
|
+
import {makeTimeout} from 'jrx'
|
|
182
190
|
|
|
183
|
-
const cancel =
|
|
191
|
+
const cancel = makeTimeout(() => {
|
|
184
192
|
console.log('Timeout fired')
|
|
185
193
|
}, 1000)
|
|
186
194
|
|
|
@@ -188,23 +196,25 @@ const cancel = addTimeout(() => {
|
|
|
188
196
|
cancel()
|
|
189
197
|
```
|
|
190
198
|
|
|
191
|
-
### `
|
|
199
|
+
### `makeTransition(cb, durationMs)`
|
|
192
200
|
|
|
193
|
-
Creates an animation transition with progress tracking (0 to 1).
|
|
201
|
+
Creates an animation transition with progress tracking (0 to 1). Returns a `Disposable`.
|
|
194
202
|
|
|
195
203
|
```typescript
|
|
196
|
-
import {
|
|
204
|
+
import {makeTransition} from 'jrx'
|
|
197
205
|
|
|
198
|
-
const
|
|
206
|
+
const handle = makeTransition((progress) => {
|
|
199
207
|
element.style.opacity = progress.toString()
|
|
200
208
|
|
|
201
|
-
// Optional: return cleanup
|
|
202
|
-
return
|
|
203
|
-
|
|
209
|
+
// Optional: return a Disposable for cleanup
|
|
210
|
+
return {
|
|
211
|
+
[Symbol.dispose]() {
|
|
212
|
+
console.log('Frame cleanup')
|
|
213
|
+
},
|
|
204
214
|
}
|
|
205
215
|
}, 1000)
|
|
206
216
|
|
|
207
|
-
dispose()
|
|
217
|
+
handle[Symbol.dispose]()
|
|
208
218
|
```
|
|
209
219
|
|
|
210
220
|
### `computed(fn, getDeps?)`
|
|
@@ -212,7 +222,7 @@ dispose()
|
|
|
212
222
|
Creates a memoized computed value with optional dependency tracking.
|
|
213
223
|
|
|
214
224
|
```typescript
|
|
215
|
-
import {
|
|
225
|
+
import {computed} from 'jrx'
|
|
216
226
|
|
|
217
227
|
// Without dependencies - always recomputes
|
|
218
228
|
const value1 = computed(() => expensiveCalculation())
|
|
@@ -279,18 +289,22 @@ const data = await r // undefined
|
|
|
279
289
|
|
|
280
290
|
## Cleanup Pattern
|
|
281
291
|
|
|
282
|
-
|
|
292
|
+
Functions that manage ongoing effects return either a dispose function or a `Disposable` object:
|
|
283
293
|
|
|
284
294
|
```typescript
|
|
285
|
-
import {
|
|
295
|
+
import {makeInterval, makeTimeout, makeAnimationFrame, makeTransition} from 'jrx'
|
|
296
|
+
|
|
297
|
+
// Functions returning Disposable objects (use with `using` or call [Symbol.dispose]())
|
|
298
|
+
const interval = makeInterval(() => console.log('tick'), 1000)
|
|
299
|
+
const transition = makeTransition((p) => console.log(p), 1000)
|
|
300
|
+
|
|
301
|
+
interval[Symbol.dispose]()
|
|
302
|
+
transition[Symbol.dispose]()
|
|
286
303
|
|
|
287
|
-
//
|
|
288
|
-
const
|
|
289
|
-
const
|
|
290
|
-
const disposeRaf = addRequestAnimationFrame((now) => render(now))
|
|
304
|
+
// Functions returning dispose functions (call directly)
|
|
305
|
+
const disposeTimeout = makeTimeout(() => console.log('timeout'), 5000)
|
|
306
|
+
const disposeRaf = makeAnimationFrame((now) => render(now))
|
|
291
307
|
|
|
292
|
-
// Call the dispose function to stop the effect
|
|
293
|
-
disposeInterval()
|
|
294
308
|
disposeTimeout()
|
|
295
309
|
disposeRaf()
|
|
296
310
|
```
|
package/index.d.ts
CHANGED
|
@@ -5,11 +5,17 @@ export declare function makeReset(): () => DisposableStack;
|
|
|
5
5
|
export declare function makeAsyncReset(): () => Promise<AsyncDisposableStack>;
|
|
6
6
|
export declare function makeRenderLoop(): {
|
|
7
7
|
loop(this: void, time: DOMHighResTimeStamp): void;
|
|
8
|
-
setLoop(this: void, loop: (time: DOMHighResTimeStamp) => undefined |
|
|
8
|
+
setLoop(this: void, loop: (time: DOMHighResTimeStamp) => undefined | Disposable): {
|
|
9
|
+
[Symbol.dispose](): void;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
export declare function makeInterval(cb: () => undefined | Disposable, ms: number): {
|
|
13
|
+
[Symbol.dispose](): void;
|
|
14
|
+
};
|
|
15
|
+
export declare function makeIntervalAsync(cb: () => void | Disposable | Promise<void>, ms: number): () => void;
|
|
16
|
+
export declare function makeAnimationFrame(cb: (now: DOMHighResTimeStamp) => undefined | Disposable): () => void;
|
|
17
|
+
export declare function makeAnimationFrameLoop(cb: (now: DOMHighResTimeStamp) => undefined | Disposable): () => void;
|
|
18
|
+
export declare function makeTimeout(cb: () => void, ms: number): () => void;
|
|
19
|
+
export declare function makeTransition(cb: (progress: number) => undefined | Disposable, durationMs: number): {
|
|
20
|
+
[Symbol.dispose](): void;
|
|
9
21
|
};
|
|
10
|
-
export declare function addInterval(cb: () => undefined | (() => any), ms: number): () => void;
|
|
11
|
-
export declare function addIntervalAsync(cb: () => (void | Disposable) & (void | (() => any) | Promise<void> | Promise<() => any>), ms: number): () => void;
|
|
12
|
-
export declare function addRequestAnimationFrame(cb: (now: DOMHighResTimeStamp) => undefined | (() => any)): () => void;
|
|
13
|
-
export declare function addRequestAnimationFrameLoop(cb: (now: DOMHighResTimeStamp) => undefined | (() => any)): () => void;
|
|
14
|
-
export declare function addTimeout(cb: () => void, ms: number): () => void;
|
|
15
|
-
export declare function addTransition(cb: (progress: number) => undefined | (() => void), durationMs: number): () => void;
|
package/index.js
CHANGED
|
@@ -20,31 +20,35 @@ export function makeRenderLoop() {
|
|
|
20
20
|
const reset = makeReset();
|
|
21
21
|
return {
|
|
22
22
|
loop(time) {
|
|
23
|
-
reset().
|
|
23
|
+
reset().use(loop_?.(time));
|
|
24
24
|
},
|
|
25
25
|
setLoop(loop) {
|
|
26
26
|
loop_ = loop;
|
|
27
|
-
return
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
return {
|
|
28
|
+
[Symbol.dispose]() {
|
|
29
|
+
reset();
|
|
30
|
+
loop_ = undefined;
|
|
31
|
+
},
|
|
30
32
|
};
|
|
31
33
|
},
|
|
32
34
|
};
|
|
33
35
|
}
|
|
34
|
-
export function
|
|
36
|
+
export function makeInterval(cb, ms) {
|
|
35
37
|
const reset = makeReset();
|
|
36
38
|
let timeout;
|
|
37
39
|
wrapper();
|
|
38
|
-
return
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
return {
|
|
41
|
+
[Symbol.dispose]() {
|
|
42
|
+
reset();
|
|
43
|
+
clearTimeout(timeout);
|
|
44
|
+
},
|
|
41
45
|
};
|
|
42
46
|
function wrapper() {
|
|
43
|
-
reset().
|
|
47
|
+
reset().use(cb());
|
|
44
48
|
timeout = setTimeout(wrapper, ms);
|
|
45
49
|
}
|
|
46
50
|
}
|
|
47
|
-
export function
|
|
51
|
+
export function makeIntervalAsync(cb, ms) {
|
|
48
52
|
const reset = makeReset();
|
|
49
53
|
let timeout;
|
|
50
54
|
void wrapper();
|
|
@@ -54,24 +58,24 @@ export function addIntervalAsync(cb, ms) {
|
|
|
54
58
|
};
|
|
55
59
|
async function wrapper() {
|
|
56
60
|
const stack = reset();
|
|
57
|
-
await stack.adopt(cb(), v => v?.[Symbol.dispose]?.());
|
|
61
|
+
await stack.adopt(cb(), (v) => v?.[Symbol.dispose]?.());
|
|
58
62
|
if (!stack.disposed)
|
|
59
63
|
timeout = setTimeout(wrapper, ms);
|
|
60
64
|
}
|
|
61
65
|
}
|
|
62
|
-
export function
|
|
66
|
+
export function makeAnimationFrame(cb) {
|
|
63
67
|
const stack = new DisposableStack();
|
|
64
68
|
const raf = requestAnimationFrame(now => {
|
|
65
69
|
if (stack.disposed)
|
|
66
70
|
return;
|
|
67
|
-
stack.
|
|
71
|
+
stack.use(cb(now));
|
|
68
72
|
});
|
|
69
73
|
return () => {
|
|
70
74
|
stack.dispose();
|
|
71
75
|
cancelAnimationFrame(raf);
|
|
72
76
|
};
|
|
73
77
|
}
|
|
74
|
-
export function
|
|
78
|
+
export function makeAnimationFrameLoop(cb) {
|
|
75
79
|
const reset = makeReset();
|
|
76
80
|
let raf = requestAnimationFrame(wrapper);
|
|
77
81
|
return () => {
|
|
@@ -79,34 +83,36 @@ export function addRequestAnimationFrameLoop(cb) {
|
|
|
79
83
|
cancelAnimationFrame(raf);
|
|
80
84
|
};
|
|
81
85
|
function wrapper(now) {
|
|
82
|
-
reset().
|
|
86
|
+
reset().use(cb(now));
|
|
83
87
|
raf = requestAnimationFrame(wrapper);
|
|
84
88
|
}
|
|
85
89
|
}
|
|
86
|
-
export function
|
|
90
|
+
export function makeTimeout(cb, ms) {
|
|
87
91
|
const timeout = setTimeout(cb, ms);
|
|
88
92
|
return () => clearTimeout(timeout);
|
|
89
93
|
}
|
|
90
|
-
export function
|
|
94
|
+
export function makeTransition(cb, durationMs) {
|
|
91
95
|
const reset = makeReset();
|
|
92
96
|
let start;
|
|
93
97
|
let raf = requestAnimationFrame(wrapper);
|
|
94
|
-
return
|
|
95
|
-
|
|
96
|
-
|
|
98
|
+
return {
|
|
99
|
+
[Symbol.dispose]() {
|
|
100
|
+
reset();
|
|
101
|
+
cancelAnimationFrame(raf);
|
|
102
|
+
},
|
|
97
103
|
};
|
|
98
104
|
function wrapper(now) {
|
|
99
105
|
if (start === undefined) {
|
|
100
106
|
start = now;
|
|
101
|
-
reset().
|
|
107
|
+
reset().use(cb(0));
|
|
102
108
|
raf = requestAnimationFrame(wrapper);
|
|
103
109
|
}
|
|
104
110
|
else {
|
|
105
111
|
const progress = (now - start) / durationMs;
|
|
106
112
|
if (progress >= 1)
|
|
107
|
-
reset().
|
|
113
|
+
reset().use(cb(1));
|
|
108
114
|
else {
|
|
109
|
-
reset().
|
|
115
|
+
reset().use(cb(progress));
|
|
110
116
|
raf = requestAnimationFrame(wrapper);
|
|
111
117
|
}
|
|
112
118
|
}
|