jrx 0.3.0-alpha.1 → 0.3.0-alpha.3
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 +79 -67
- package/index.d.ts +22 -8
- package/index.js +49 -33
- 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,121 +92,129 @@ 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. Returns a `Disposable`.
|
|
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
|
|
131
|
+
const handle = makeIntervalAsync(async () => {
|
|
128
132
|
// Called immediately, then 5000ms after each completion
|
|
129
133
|
await fetchData()
|
|
130
134
|
processData()
|
|
131
135
|
}, 5000)
|
|
132
136
|
|
|
133
|
-
dispose()
|
|
137
|
+
handle[Symbol.dispose]()
|
|
134
138
|
```
|
|
135
139
|
|
|
136
|
-
### `
|
|
140
|
+
### `makeAnimationFrame(cb)`
|
|
137
141
|
|
|
138
|
-
Executes a callback on the next animation frame with cleanup.
|
|
142
|
+
Executes a callback on the next animation frame with cleanup. Returns a `Disposable`.
|
|
139
143
|
|
|
140
144
|
```typescript
|
|
141
|
-
import {
|
|
145
|
+
import {makeAnimationFrame} from 'jrx'
|
|
142
146
|
|
|
143
|
-
const
|
|
147
|
+
const handle = 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
|
|
|
152
158
|
// Cancel if needed before the frame fires
|
|
153
|
-
dispose()
|
|
159
|
+
handle[Symbol.dispose]()
|
|
154
160
|
```
|
|
155
161
|
|
|
156
|
-
### `
|
|
162
|
+
### `makeAnimationFrameLoop(cb)`
|
|
157
163
|
|
|
158
|
-
Creates a continuous `requestAnimationFrame` loop with cleanup.
|
|
164
|
+
Creates a continuous `requestAnimationFrame` loop with cleanup. Returns a `Disposable`.
|
|
159
165
|
|
|
160
166
|
```typescript
|
|
161
|
-
import {
|
|
167
|
+
import {makeAnimationFrameLoop} from 'jrx'
|
|
162
168
|
|
|
163
|
-
const
|
|
169
|
+
const handle = 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
|
|
|
172
180
|
// Stop the loop
|
|
173
|
-
dispose()
|
|
181
|
+
handle[Symbol.dispose]()
|
|
174
182
|
```
|
|
175
183
|
|
|
176
|
-
### `
|
|
184
|
+
### `makeTimeout(cb, ms)`
|
|
177
185
|
|
|
178
|
-
Creates a timeout with cleanup.
|
|
186
|
+
Creates a timeout with cleanup. Returns a `Disposable`.
|
|
179
187
|
|
|
180
188
|
```typescript
|
|
181
|
-
import {
|
|
189
|
+
import {makeTimeout} from 'jrx'
|
|
182
190
|
|
|
183
|
-
const
|
|
191
|
+
const handle = makeTimeout(() => {
|
|
184
192
|
console.log('Timeout fired')
|
|
185
193
|
}, 1000)
|
|
186
194
|
|
|
187
195
|
// Cancel if needed
|
|
188
|
-
|
|
196
|
+
handle[Symbol.dispose]()
|
|
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,20 +289,22 @@ const data = await r // undefined
|
|
|
279
289
|
|
|
280
290
|
## Cleanup Pattern
|
|
281
291
|
|
|
282
|
-
All effect functions return a
|
|
292
|
+
All effect functions return a `Disposable` object that stops the effect and runs any pending cleanup:
|
|
283
293
|
|
|
284
294
|
```typescript
|
|
285
|
-
import {
|
|
286
|
-
|
|
287
|
-
// Each function returns a
|
|
288
|
-
const
|
|
289
|
-
const
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
295
|
+
import {makeInterval, makeTimeout, makeAnimationFrame, makeTransition} from 'jrx'
|
|
296
|
+
|
|
297
|
+
// Each function returns a Disposable
|
|
298
|
+
const interval = makeInterval(() => console.log('tick'), 1000)
|
|
299
|
+
const timeout = makeTimeout(() => console.log('timeout'), 5000)
|
|
300
|
+
const raf = makeAnimationFrame((now) => render(now))
|
|
301
|
+
const transition = makeTransition((p) => console.log(p), 1000)
|
|
302
|
+
|
|
303
|
+
// Call [Symbol.dispose]() to stop the effect
|
|
304
|
+
interval[Symbol.dispose]()
|
|
305
|
+
timeout[Symbol.dispose]()
|
|
306
|
+
raf[Symbol.dispose]()
|
|
307
|
+
transition[Symbol.dispose]()
|
|
296
308
|
```
|
|
297
309
|
|
|
298
310
|
## TypeScript
|
package/index.d.ts
CHANGED
|
@@ -5,11 +5,25 @@ 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 |
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
export declare function
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
export declare function
|
|
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): {
|
|
16
|
+
[Symbol.dispose](): void;
|
|
17
|
+
};
|
|
18
|
+
export declare function makeAnimationFrame(cb: (now: DOMHighResTimeStamp) => undefined | Disposable): {
|
|
19
|
+
[Symbol.dispose](): void;
|
|
20
|
+
};
|
|
21
|
+
export declare function makeAnimationFrameLoop(cb: (now: DOMHighResTimeStamp) => undefined | Disposable): {
|
|
22
|
+
[Symbol.dispose](): void;
|
|
23
|
+
};
|
|
24
|
+
export declare function makeTimeout(cb: () => void, ms: number): {
|
|
25
|
+
[Symbol.dispose](): void;
|
|
26
|
+
};
|
|
27
|
+
export declare function makeTransition(cb: (progress: number) => undefined | Disposable, durationMs: number): {
|
|
28
|
+
[Symbol.dispose](): void;
|
|
29
|
+
};
|
package/index.js
CHANGED
|
@@ -20,93 +20,109 @@ 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();
|
|
51
|
-
return
|
|
52
|
-
|
|
53
|
-
|
|
55
|
+
return {
|
|
56
|
+
[Symbol.dispose]() {
|
|
57
|
+
reset();
|
|
58
|
+
clearTimeout(timeout);
|
|
59
|
+
},
|
|
54
60
|
};
|
|
55
61
|
async function wrapper() {
|
|
56
62
|
const stack = reset();
|
|
57
|
-
await stack.adopt(cb(), v => v?.[Symbol.dispose]?.());
|
|
63
|
+
await stack.adopt(cb(), (v) => v?.[Symbol.dispose]?.());
|
|
58
64
|
if (!stack.disposed)
|
|
59
65
|
timeout = setTimeout(wrapper, ms);
|
|
60
66
|
}
|
|
61
67
|
}
|
|
62
|
-
export function
|
|
68
|
+
export function makeAnimationFrame(cb) {
|
|
63
69
|
const stack = new DisposableStack();
|
|
64
70
|
const raf = requestAnimationFrame(now => {
|
|
65
71
|
if (stack.disposed)
|
|
66
72
|
return;
|
|
67
|
-
stack.
|
|
73
|
+
stack.use(cb(now));
|
|
68
74
|
});
|
|
69
|
-
return
|
|
70
|
-
|
|
71
|
-
|
|
75
|
+
return {
|
|
76
|
+
[Symbol.dispose]() {
|
|
77
|
+
stack.dispose();
|
|
78
|
+
cancelAnimationFrame(raf);
|
|
79
|
+
},
|
|
72
80
|
};
|
|
73
81
|
}
|
|
74
|
-
export function
|
|
82
|
+
export function makeAnimationFrameLoop(cb) {
|
|
75
83
|
const reset = makeReset();
|
|
76
84
|
let raf = requestAnimationFrame(wrapper);
|
|
77
|
-
return
|
|
78
|
-
|
|
79
|
-
|
|
85
|
+
return {
|
|
86
|
+
[Symbol.dispose]() {
|
|
87
|
+
reset();
|
|
88
|
+
cancelAnimationFrame(raf);
|
|
89
|
+
},
|
|
80
90
|
};
|
|
81
91
|
function wrapper(now) {
|
|
82
|
-
reset().
|
|
92
|
+
reset().use(cb(now));
|
|
83
93
|
raf = requestAnimationFrame(wrapper);
|
|
84
94
|
}
|
|
85
95
|
}
|
|
86
|
-
export function
|
|
96
|
+
export function makeTimeout(cb, ms) {
|
|
87
97
|
const timeout = setTimeout(cb, ms);
|
|
88
|
-
return
|
|
98
|
+
return {
|
|
99
|
+
[Symbol.dispose]() {
|
|
100
|
+
clearTimeout(timeout);
|
|
101
|
+
},
|
|
102
|
+
};
|
|
89
103
|
}
|
|
90
|
-
export function
|
|
104
|
+
export function makeTransition(cb, durationMs) {
|
|
91
105
|
const reset = makeReset();
|
|
92
106
|
let start;
|
|
93
107
|
let raf = requestAnimationFrame(wrapper);
|
|
94
|
-
return
|
|
95
|
-
|
|
96
|
-
|
|
108
|
+
return {
|
|
109
|
+
[Symbol.dispose]() {
|
|
110
|
+
reset();
|
|
111
|
+
cancelAnimationFrame(raf);
|
|
112
|
+
},
|
|
97
113
|
};
|
|
98
114
|
function wrapper(now) {
|
|
99
115
|
if (start === undefined) {
|
|
100
116
|
start = now;
|
|
101
|
-
reset().
|
|
117
|
+
reset().use(cb(0));
|
|
102
118
|
raf = requestAnimationFrame(wrapper);
|
|
103
119
|
}
|
|
104
120
|
else {
|
|
105
121
|
const progress = (now - start) / durationMs;
|
|
106
122
|
if (progress >= 1)
|
|
107
|
-
reset().
|
|
123
|
+
reset().use(cb(1));
|
|
108
124
|
else {
|
|
109
|
-
reset().
|
|
125
|
+
reset().use(cb(progress));
|
|
110
126
|
raf = requestAnimationFrame(wrapper);
|
|
111
127
|
}
|
|
112
128
|
}
|