jrx 0.3.0-alpha.5 → 0.3.0-alpha.7
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 +84 -30
- package/index.d.ts +9 -6
- package/index.js +11 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,14 +28,33 @@ 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
|
+
- [`createInterval(cb, ms)`](#createIntervalcb-ms) - Repeating intervals with cleanup
|
|
32
|
+
- [`createIntervalAsync(cb, ms)`](#createIntervalasynccb-ms) - Async intervals with cancellation
|
|
33
|
+
- [`createAnimationFrame(cb)`](#createAnimationFramecb) - Single animation frame with cleanup
|
|
34
|
+
- [`createAnimationFrameLoop(cb)`](#createAnimationFrameloopcb) - Animation frame loops
|
|
35
|
+
- [`createTimeout(cb, ms)`](#createTimeoutcb-ms) - Timeouts with cleanup
|
|
36
|
+
- [`createTransition(cb, durationMs)`](#createTransitioncb-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
|
+
- [`assignDispose(value, disposable)`](#assigndisposevalue-disposable) - Attach a `Symbol.dispose` to any value
|
|
40
|
+
|
|
41
|
+
## Naming convention
|
|
42
|
+
|
|
43
|
+
Functions prefixed with `create*` return a `Disposable` object. The returned object can be passed to [`DisposableStack.use()`](https://github.com/tc39/proposal-explicit-resource-management) or bound with the `using` keyword for automatic cleanup.
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
import {createInterval, createTimeout} from 'jrx'
|
|
47
|
+
|
|
48
|
+
// With DisposableStack.use()
|
|
49
|
+
using stack = new DisposableStack()
|
|
50
|
+
stack.use(createInterval(() => console.log('tick'), 1000))
|
|
51
|
+
stack.use(createTimeout(() => console.log('done'), 5000))
|
|
52
|
+
// Both are disposed when `stack` goes out of scope
|
|
53
|
+
|
|
54
|
+
// Or directly with `using`
|
|
55
|
+
using interval = createInterval(() => console.log('tick'), 1000)
|
|
56
|
+
// Disposed when the enclosing block exits
|
|
57
|
+
```
|
|
39
58
|
|
|
40
59
|
## API
|
|
41
60
|
|
|
@@ -95,16 +114,16 @@ requestAnimationFrame(loop)
|
|
|
95
114
|
handle[Symbol.dispose]()
|
|
96
115
|
```
|
|
97
116
|
|
|
98
|
-
### `
|
|
117
|
+
### `createInterval(cb, ms)`
|
|
99
118
|
|
|
100
119
|
Creates a repeating interval with cleanup. The callback can optionally return a `Disposable` that is disposed before the next invocation. Returns a `Disposable`.
|
|
101
120
|
|
|
102
121
|
**Note:** The callback fires **immediately** on first call, then waits `ms` milliseconds **after** the previous callback completes. This is not a fixed-rate timer.
|
|
103
122
|
|
|
104
123
|
```typescript
|
|
105
|
-
import {
|
|
124
|
+
import {createInterval} from 'jrx'
|
|
106
125
|
|
|
107
|
-
const handle =
|
|
126
|
+
const handle = createInterval(() => {
|
|
108
127
|
console.log('Tick') // Called immediately, then every 1000ms after completion
|
|
109
128
|
|
|
110
129
|
// Optional: return a Disposable for cleanup
|
|
@@ -119,16 +138,16 @@ const handle = makeInterval(() => {
|
|
|
119
138
|
handle[Symbol.dispose]()
|
|
120
139
|
```
|
|
121
140
|
|
|
122
|
-
### `
|
|
141
|
+
### `createIntervalAsync(cb, ms)`
|
|
123
142
|
|
|
124
|
-
Async version of `
|
|
143
|
+
Async version of `createInterval`. Waits for the callback to complete before scheduling the next invocation. Returns a `Disposable`.
|
|
125
144
|
|
|
126
145
|
**Note:** The callback fires **immediately** on first call, then waits `ms` milliseconds **after** the previous async callback completes.
|
|
127
146
|
|
|
128
147
|
```typescript
|
|
129
|
-
import {
|
|
148
|
+
import {createIntervalAsync} from 'jrx'
|
|
130
149
|
|
|
131
|
-
const handle =
|
|
150
|
+
const handle = createIntervalAsync(async () => {
|
|
132
151
|
// Called immediately, then 5000ms after each completion
|
|
133
152
|
await fetchData()
|
|
134
153
|
processData()
|
|
@@ -137,14 +156,14 @@ const handle = makeIntervalAsync(async () => {
|
|
|
137
156
|
handle[Symbol.dispose]()
|
|
138
157
|
```
|
|
139
158
|
|
|
140
|
-
### `
|
|
159
|
+
### `createAnimationFrame(cb)`
|
|
141
160
|
|
|
142
161
|
Executes a callback on the next animation frame with cleanup. Returns a `Disposable`.
|
|
143
162
|
|
|
144
163
|
```typescript
|
|
145
|
-
import {
|
|
164
|
+
import {createAnimationFrame} from 'jrx'
|
|
146
165
|
|
|
147
|
-
const handle =
|
|
166
|
+
const handle = createAnimationFrame((now) => {
|
|
148
167
|
updateAnimation(now)
|
|
149
168
|
|
|
150
169
|
// Optional: return a Disposable for cleanup
|
|
@@ -159,14 +178,14 @@ const handle = makeAnimationFrame((now) => {
|
|
|
159
178
|
handle[Symbol.dispose]()
|
|
160
179
|
```
|
|
161
180
|
|
|
162
|
-
### `
|
|
181
|
+
### `createAnimationFrameLoop(cb)`
|
|
163
182
|
|
|
164
183
|
Creates a continuous `requestAnimationFrame` loop with cleanup. Returns a `Disposable`.
|
|
165
184
|
|
|
166
185
|
```typescript
|
|
167
|
-
import {
|
|
186
|
+
import {createAnimationFrameLoop} from 'jrx'
|
|
168
187
|
|
|
169
|
-
const handle =
|
|
188
|
+
const handle = createAnimationFrameLoop((now) => {
|
|
170
189
|
updateAnimation(now)
|
|
171
190
|
|
|
172
191
|
// Optional: return a Disposable for cleanup
|
|
@@ -181,14 +200,14 @@ const handle = makeAnimationFrameLoop((now) => {
|
|
|
181
200
|
handle[Symbol.dispose]()
|
|
182
201
|
```
|
|
183
202
|
|
|
184
|
-
### `
|
|
203
|
+
### `createTimeout(cb, ms)`
|
|
185
204
|
|
|
186
205
|
Creates a timeout with cleanup. Returns a `Disposable`.
|
|
187
206
|
|
|
188
207
|
```typescript
|
|
189
|
-
import {
|
|
208
|
+
import {createTimeout} from 'jrx'
|
|
190
209
|
|
|
191
|
-
const handle =
|
|
210
|
+
const handle = createTimeout(() => {
|
|
192
211
|
console.log('Timeout fired')
|
|
193
212
|
}, 1000)
|
|
194
213
|
|
|
@@ -196,14 +215,14 @@ const handle = makeTimeout(() => {
|
|
|
196
215
|
handle[Symbol.dispose]()
|
|
197
216
|
```
|
|
198
217
|
|
|
199
|
-
### `
|
|
218
|
+
### `createTransition(cb, durationMs)`
|
|
200
219
|
|
|
201
220
|
Creates an animation transition with progress tracking (0 to 1). Returns a `Disposable`.
|
|
202
221
|
|
|
203
222
|
```typescript
|
|
204
|
-
import {
|
|
223
|
+
import {createTransition} from 'jrx'
|
|
205
224
|
|
|
206
|
-
const handle =
|
|
225
|
+
const handle = createTransition((progress) => {
|
|
207
226
|
element.style.opacity = progress.toString()
|
|
208
227
|
|
|
209
228
|
// Optional: return a Disposable for cleanup
|
|
@@ -287,18 +306,53 @@ const data = await r // undefined
|
|
|
287
306
|
- `cb`: Callback that returns `Disposable & (T | Promise<T>)`. Receives `{ resetBackoff() }` to reset the backoff counter.
|
|
288
307
|
- `backoffSec`: Array of retry delays in seconds. Use `-1` for infinite retries with the last delay. Default: `[5, 5, 10, 10, 20, 20, 40, 40, 60, -1]`
|
|
289
308
|
|
|
309
|
+
### `assignDispose(value, disposable)`
|
|
310
|
+
|
|
311
|
+
Attaches a `Symbol.dispose` to an existing value (object, function, array, promise, etc.) that delegates to a given `Disposable`. Returns the same value, now also typed as `Disposable`.
|
|
312
|
+
|
|
313
|
+
This is useful when a function needs to return a meaningful value **and** be disposable — for example, returning a promise, a function, or a tuple from a factory while still allowing the caller to clean up the underlying resources.
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
import {assignDispose} from 'jrx'
|
|
317
|
+
|
|
318
|
+
function makeThing() {
|
|
319
|
+
using stack = new DisposableStack()
|
|
320
|
+
stack.defer(() => console.log('cleanup'))
|
|
321
|
+
|
|
322
|
+
const obj = {value: 42}
|
|
323
|
+
return assignDispose(obj, stack.move())
|
|
324
|
+
}
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Async factory — wrap an in-flight promise so the caller can dispose the underlying resources mid-flight:
|
|
328
|
+
|
|
329
|
+
```typescript
|
|
330
|
+
import {assignDispose} from 'jrx'
|
|
331
|
+
|
|
332
|
+
function loadThing() {
|
|
333
|
+
const stack = new DisposableStack()
|
|
334
|
+
return assignDispose(
|
|
335
|
+
(async () => {
|
|
336
|
+
const res = await fetch('/api/data', {signal: stack.adopt(new AbortController(), c => c.abort()).signal})
|
|
337
|
+
return await res.json()
|
|
338
|
+
})(),
|
|
339
|
+
stack,
|
|
340
|
+
)
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
290
344
|
## Cleanup Pattern
|
|
291
345
|
|
|
292
346
|
All effect functions return a `Disposable` object that stops the effect and runs any pending cleanup:
|
|
293
347
|
|
|
294
348
|
```typescript
|
|
295
|
-
import {
|
|
349
|
+
import {createInterval, createTimeout, createAnimationFrame, createTransition} from 'jrx'
|
|
296
350
|
|
|
297
351
|
// Each function returns a Disposable
|
|
298
|
-
const interval =
|
|
299
|
-
const timeout =
|
|
300
|
-
const raf =
|
|
301
|
-
const transition =
|
|
352
|
+
const interval = createInterval(() => console.log('tick'), 1000)
|
|
353
|
+
const timeout = createTimeout(() => console.log('timeout'), 5000)
|
|
354
|
+
const raf = createAnimationFrame((now) => render(now))
|
|
355
|
+
const transition = createTransition((p) => console.log(p), 1000)
|
|
302
356
|
|
|
303
357
|
// Call [Symbol.dispose]() to stop the effect
|
|
304
358
|
interval[Symbol.dispose]()
|
package/index.d.ts
CHANGED
|
@@ -9,21 +9,24 @@ export declare function makeRenderLoop(): {
|
|
|
9
9
|
[Symbol.dispose](): void;
|
|
10
10
|
};
|
|
11
11
|
};
|
|
12
|
-
export declare function
|
|
12
|
+
export declare function createInterval(cb: () => undefined | Disposable, ms: number): {
|
|
13
13
|
[Symbol.dispose](): void;
|
|
14
14
|
};
|
|
15
|
-
export declare function
|
|
15
|
+
export declare function createIntervalAsync(cb: () => void | Disposable | Promise<void>, ms: number): {
|
|
16
16
|
[Symbol.dispose](): void;
|
|
17
17
|
};
|
|
18
|
-
export declare function
|
|
18
|
+
export declare function createAnimationFrame(cb: (now: DOMHighResTimeStamp) => undefined | Disposable): {
|
|
19
19
|
[Symbol.dispose](): void;
|
|
20
20
|
};
|
|
21
|
-
export declare function
|
|
21
|
+
export declare function createAnimationFrameLoop(cb: (now: DOMHighResTimeStamp) => undefined | Disposable): {
|
|
22
22
|
[Symbol.dispose](): void;
|
|
23
23
|
};
|
|
24
|
-
export declare function
|
|
24
|
+
export declare function createTimeout(cb: () => void, ms: number): {
|
|
25
25
|
[Symbol.dispose](): void;
|
|
26
26
|
};
|
|
27
|
-
export declare function
|
|
27
|
+
export declare function createTransition(cb: (progress: number) => undefined | Disposable, durationMs: number): {
|
|
28
28
|
[Symbol.dispose](): void;
|
|
29
29
|
};
|
|
30
|
+
export declare function assignDispose<T extends object>(value: T, disposable: Disposable): T & {
|
|
31
|
+
[Symbol.dispose]: () => void;
|
|
32
|
+
};
|
package/index.js
CHANGED
|
@@ -33,7 +33,7 @@ export function makeRenderLoop() {
|
|
|
33
33
|
},
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
|
-
export function
|
|
36
|
+
export function createInterval(cb, ms) {
|
|
37
37
|
const reset = makeReset();
|
|
38
38
|
let timeout;
|
|
39
39
|
wrapper();
|
|
@@ -48,7 +48,7 @@ export function makeInterval(cb, ms) {
|
|
|
48
48
|
timeout = setTimeout(wrapper, ms);
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
|
-
export function
|
|
51
|
+
export function createIntervalAsync(cb, ms) {
|
|
52
52
|
const reset = makeReset();
|
|
53
53
|
let timeout;
|
|
54
54
|
void wrapper();
|
|
@@ -65,7 +65,7 @@ export function makeIntervalAsync(cb, ms) {
|
|
|
65
65
|
timeout = setTimeout(wrapper, ms);
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
|
-
export function
|
|
68
|
+
export function createAnimationFrame(cb) {
|
|
69
69
|
const stack = new DisposableStack();
|
|
70
70
|
const raf = requestAnimationFrame(now => {
|
|
71
71
|
if (stack.disposed)
|
|
@@ -79,7 +79,7 @@ export function makeAnimationFrame(cb) {
|
|
|
79
79
|
},
|
|
80
80
|
};
|
|
81
81
|
}
|
|
82
|
-
export function
|
|
82
|
+
export function createAnimationFrameLoop(cb) {
|
|
83
83
|
const reset = makeReset();
|
|
84
84
|
let raf = requestAnimationFrame(wrapper);
|
|
85
85
|
return {
|
|
@@ -93,7 +93,7 @@ export function makeAnimationFrameLoop(cb) {
|
|
|
93
93
|
raf = requestAnimationFrame(wrapper);
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
|
-
export function
|
|
96
|
+
export function createTimeout(cb, ms) {
|
|
97
97
|
const timeout = setTimeout(cb, ms);
|
|
98
98
|
return {
|
|
99
99
|
[Symbol.dispose]() {
|
|
@@ -101,7 +101,7 @@ export function makeTimeout(cb, ms) {
|
|
|
101
101
|
},
|
|
102
102
|
};
|
|
103
103
|
}
|
|
104
|
-
export function
|
|
104
|
+
export function createTransition(cb, durationMs) {
|
|
105
105
|
const reset = makeReset();
|
|
106
106
|
let start;
|
|
107
107
|
let raf = requestAnimationFrame(wrapper);
|
|
@@ -128,3 +128,8 @@ export function makeTransition(cb, durationMs) {
|
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
|
+
export function assignDispose(value, disposable) {
|
|
132
|
+
return Object.assign(value, {
|
|
133
|
+
[Symbol.dispose]: disposable[Symbol.dispose].bind(disposable),
|
|
134
|
+
});
|
|
135
|
+
}
|