jrx 0.2.1 → 0.3.0-alpha.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/README.md +73 -157
- package/index.d.ts +10 -13
- package/index.js +32 -26
- package/package.json +5 -10
- package/retry.d.ts +2 -16
- package/retry.js +30 -32
- package/addEvtListener.d.ts +0 -15
- package/addEvtListener.js +0 -6
package/README.md
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
# jrx
|
|
2
2
|
|
|
3
|
-
A lightweight TypeScript library for managing side effects, subscriptions, and animations with automatic cleanup
|
|
3
|
+
A lightweight TypeScript library for managing side effects, subscriptions, and animations with automatic cleanup using the [Explicit Resource Management](https://github.com/tc39/proposal-explicit-resource-management) API.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
This library requires the [Explicit Resource Management](https://github.com/tc39/proposal-explicit-resource-management) globals (`DisposableStack`, `AsyncDisposableStack`, `Symbol.dispose`, `Symbol.asyncDispose`). If your environment does not support them natively, you must load a polyfill before importing jrx (e.g. [`core-js`](https://github.com/nicolo-ribaudo/tc39-proposal-explicit-resource-management-polyfill)).
|
|
8
|
+
|
|
9
|
+
The `using` keyword is **not** required — this library only uses the API objects directly, so no transpiler support for `using` declarations is needed.
|
|
4
10
|
|
|
5
11
|
## Installation
|
|
6
12
|
|
|
@@ -11,37 +17,64 @@ npm i jrx
|
|
|
11
17
|
## Features
|
|
12
18
|
|
|
13
19
|
- Automatic cleanup for all effects and subscriptions
|
|
14
|
-
-
|
|
20
|
+
- Built on the native `DisposableStack` / `AsyncDisposableStack` API
|
|
15
21
|
- Retry logic with exponential backoff and cancellation
|
|
16
|
-
-
|
|
22
|
+
- Zero dependencies
|
|
17
23
|
- Composable reactive utilities
|
|
18
24
|
- Browser and Node.js compatible
|
|
19
25
|
|
|
20
26
|
## API Overview
|
|
21
27
|
|
|
28
|
+
- [`makeReset()`](#makereset) - Create a resettable `DisposableStack`
|
|
29
|
+
- [`makeAsyncReset()`](#makeasyncreset) - Create a resettable `AsyncDisposableStack`
|
|
22
30
|
- [`makeRenderLoop()`](#makerenderloop) - Render loops with automatic cleanup
|
|
23
|
-
- [`addEvtListener(target, event, handler, option?)`](#addevtlistenertarget-event-handler-option) - Event listeners with cleanup
|
|
24
31
|
- [`addInterval(cb, ms)`](#addintervalcb-ms) - Repeating intervals with cleanup
|
|
25
32
|
- [`addIntervalAsync(cb, ms)`](#addintervalasynccb-ms) - Async intervals with cancellation
|
|
26
33
|
- [`addRequestAnimationFrame(cb)`](#addrequestanimationframecb) - Single animation frame with cleanup
|
|
27
34
|
- [`addRequestAnimationFrameLoop(cb)`](#addrequestanimationframeloopcb) - Animation frame loops
|
|
28
|
-
- [`addSubs(subs, cb, options?)`](#addsubssubs-cb-options) - Multiple subscription management
|
|
29
35
|
- [`addTimeout(cb, ms)`](#addtimeoutcb-ms) - Timeouts with cleanup
|
|
30
36
|
- [`addTransition(cb, durationMs)`](#addtransitioncb-durationms) - Progress-based animations
|
|
31
37
|
- [`computed(fn, getDeps?)`](#computedfn-getdeps) - Memoized computed values
|
|
32
|
-
- [`retry(cb,
|
|
33
|
-
- [`addRetry(cb, options?)`](#addretrycb-options) - Fire-and-forget retry with disposal
|
|
38
|
+
- [`retry(cb, backoffSec?)`](#retrycb-backoffsec) - Retry with exponential backoff
|
|
34
39
|
|
|
35
40
|
## API
|
|
36
41
|
|
|
42
|
+
### `makeReset()`
|
|
43
|
+
|
|
44
|
+
Creates a resettable `DisposableStack`. Each call disposes the previous stack and returns a new one.
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import {makeReset} from 'jrx'
|
|
48
|
+
|
|
49
|
+
const reset = makeReset()
|
|
50
|
+
const stack = reset() // Get a fresh DisposableStack
|
|
51
|
+
|
|
52
|
+
// Add disposables
|
|
53
|
+
stack.use(someDisposable)
|
|
54
|
+
|
|
55
|
+
// Reset - disposes previous stack, returns new one
|
|
56
|
+
const newStack = reset()
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### `makeAsyncReset()`
|
|
60
|
+
|
|
61
|
+
Async version of `makeReset` using `AsyncDisposableStack`.
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
import {makeAsyncReset} from 'jrx'
|
|
65
|
+
|
|
66
|
+
const reset = makeAsyncReset()
|
|
67
|
+
const stack = await reset() // Get a fresh AsyncDisposableStack
|
|
68
|
+
```
|
|
69
|
+
|
|
37
70
|
### `makeRenderLoop()`
|
|
38
71
|
|
|
39
72
|
Creates a render loop with automatic cleanup management.
|
|
40
73
|
|
|
41
74
|
```typescript
|
|
42
|
-
import {
|
|
75
|
+
import {makeRenderLoop} from 'jrx'
|
|
43
76
|
|
|
44
|
-
const {
|
|
77
|
+
const {loop, setLoop} = makeRenderLoop()
|
|
45
78
|
|
|
46
79
|
// Set the loop function
|
|
47
80
|
const dispose = setLoop((time) => {
|
|
@@ -60,28 +93,6 @@ requestAnimationFrame(loop)
|
|
|
60
93
|
dispose()
|
|
61
94
|
```
|
|
62
95
|
|
|
63
|
-
### `addEvtListener(target, event, handler, option?)`
|
|
64
|
-
|
|
65
|
-
Adds an event listener to a target and returns a disposer that removes it. Works with any object that implements `addEventListener`/`removeEventListener` (DOM elements, `window`, `document`, etc.).
|
|
66
|
-
|
|
67
|
-
```typescript
|
|
68
|
-
import { addEvtListener } from 'jrx'
|
|
69
|
-
|
|
70
|
-
// Basic usage
|
|
71
|
-
const dispose = addEvtListener(window, 'resize', (e) => {
|
|
72
|
-
console.log('Window resized', e)
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
// With options
|
|
76
|
-
const dispose2 = addEvtListener(element, 'click', (e) => {
|
|
77
|
-
console.log('Clicked', e)
|
|
78
|
-
}, { capture: true })
|
|
79
|
-
|
|
80
|
-
// Cleanup
|
|
81
|
-
dispose()
|
|
82
|
-
dispose2()
|
|
83
|
-
```
|
|
84
|
-
|
|
85
96
|
### `addInterval(cb, ms)`
|
|
86
97
|
|
|
87
98
|
Creates a repeating interval with cleanup. The callback can optionally return a cleanup function that runs before the next invocation.
|
|
@@ -113,13 +124,9 @@ Async version of `addInterval`. Waits for the callback to complete before schedu
|
|
|
113
124
|
```typescript
|
|
114
125
|
import { addIntervalAsync } from 'jrx'
|
|
115
126
|
|
|
116
|
-
const dispose = addIntervalAsync(async (
|
|
127
|
+
const dispose = addIntervalAsync(async () => {
|
|
117
128
|
// Called immediately, then 5000ms after each completion
|
|
118
129
|
await fetchData()
|
|
119
|
-
|
|
120
|
-
// Check if disposed during async operation
|
|
121
|
-
if (disposer.signal.aborted) return
|
|
122
|
-
|
|
123
130
|
processData()
|
|
124
131
|
}, 5000)
|
|
125
132
|
|
|
@@ -166,35 +173,6 @@ const dispose = addRequestAnimationFrameLoop((now) => {
|
|
|
166
173
|
dispose()
|
|
167
174
|
```
|
|
168
175
|
|
|
169
|
-
### `addSubs(subs, cb, options?)`
|
|
170
|
-
|
|
171
|
-
Manages multiple subscriptions with a single callback.
|
|
172
|
-
|
|
173
|
-
```typescript
|
|
174
|
-
import { addSubs } from 'jrx'
|
|
175
|
-
|
|
176
|
-
const sub1 = (listener) => {
|
|
177
|
-
eventEmitter.on('event1', listener)
|
|
178
|
-
return () => eventEmitter.off('event1', listener)
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const sub2 = (listener) => {
|
|
182
|
-
eventEmitter.on('event2', listener)
|
|
183
|
-
return () => eventEmitter.off('event2', listener)
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const dispose = addSubs([sub1, sub2], () => {
|
|
187
|
-
console.log('Any event fired')
|
|
188
|
-
|
|
189
|
-
// Optional: return cleanup function
|
|
190
|
-
return () => {
|
|
191
|
-
console.log('Cleanup')
|
|
192
|
-
}
|
|
193
|
-
}, { now: true }) // Call immediately with now: true
|
|
194
|
-
|
|
195
|
-
dispose()
|
|
196
|
-
```
|
|
197
|
-
|
|
198
176
|
### `addTimeout(cb, ms)`
|
|
199
177
|
|
|
200
178
|
Creates a timeout with cleanup.
|
|
@@ -255,9 +233,9 @@ a = 10
|
|
|
255
233
|
console.log(value2.value) // Recomputed: 12
|
|
256
234
|
```
|
|
257
235
|
|
|
258
|
-
### `retry(cb,
|
|
236
|
+
### `retry(cb, backoffSec?)`
|
|
259
237
|
|
|
260
|
-
Retries an
|
|
238
|
+
Retries an operation with exponential backoff on failure. Returns `Disposable & Promise<T>`.
|
|
261
239
|
|
|
262
240
|
**Default backoff:** `[5, 5, 10, 10, 20, 20, 40, 40, 60, -1]` seconds (where `-1` means retry forever with 60s delay)
|
|
263
241
|
|
|
@@ -265,123 +243,61 @@ Retries an async operation with exponential backoff on failure.
|
|
|
265
243
|
import {retry} from 'jrx'
|
|
266
244
|
|
|
267
245
|
// Basic usage - retries with default backoff
|
|
268
|
-
const result = await retry(
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
return response.json()
|
|
246
|
+
const result = await retry(({resetBackoff}) => {
|
|
247
|
+
const promise = fetch('/api/data').then((r) => r.json())
|
|
248
|
+
return Object.assign(promise, {[Symbol.dispose]() {}})
|
|
272
249
|
})
|
|
273
250
|
|
|
274
251
|
// Custom backoff schedule (in seconds)
|
|
275
252
|
await retry(
|
|
276
|
-
|
|
277
|
-
|
|
253
|
+
() => {
|
|
254
|
+
const promise = fetchData()
|
|
255
|
+
return Object.assign(promise, {[Symbol.dispose]() {}})
|
|
278
256
|
},
|
|
279
|
-
|
|
280
|
-
backoffSec: [1, 2, 5, 10, -1] // -1 means retry forever with last delay
|
|
281
|
-
}
|
|
257
|
+
[1, 2, 5, 10, -1], // -1 means retry forever with last delay
|
|
282
258
|
)
|
|
283
259
|
|
|
284
|
-
//
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
const data = await retry(
|
|
290
|
-
async (loopDisposer, { resetBackoff }) => {
|
|
291
|
-
// Check if aborted
|
|
292
|
-
if (loopDisposer.signal.aborted) return
|
|
293
|
-
|
|
294
|
-
const result = await fetchData()
|
|
295
|
-
|
|
296
|
-
// Reset backoff on successful partial progress
|
|
297
|
-
if (result.isPartialSuccess) {
|
|
298
|
-
resetBackoff()
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
return result
|
|
260
|
+
// Cancellation via Disposable
|
|
261
|
+
const r = retry(
|
|
262
|
+
({resetBackoff}) => {
|
|
263
|
+
const promise = fetchData()
|
|
264
|
+
return Object.assign(promise, {[Symbol.dispose]() { /* cancel */ }})
|
|
302
265
|
},
|
|
303
|
-
|
|
304
|
-
disposer,
|
|
305
|
-
backoffSec: [5, 10, 20, 40, -1]
|
|
306
|
-
}
|
|
266
|
+
[5, 10, 20, 40, -1],
|
|
307
267
|
)
|
|
308
268
|
|
|
309
269
|
// Cancel the retry loop
|
|
310
|
-
|
|
270
|
+
r[Symbol.dispose]()
|
|
311
271
|
|
|
312
272
|
// Returns undefined when disposed
|
|
313
|
-
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
**Options:**
|
|
317
|
-
- `backoffSec`: Array of retry delays in seconds. Use `-1` for infinite retries with the last delay.
|
|
318
|
-
- Default: `[5, 5, 10, 10, 20, 20, 40, 40, 60, -1]`
|
|
319
|
-
- `disposer`: Optional disposer for cancellation. When provided, the return type is `T | undefined`. Otherwise, the return type is `T`.
|
|
320
|
-
|
|
321
|
-
**Callback parameters:**
|
|
322
|
-
- `disposer`: A disposer for the current retry attempt. Check `disposer.signal.aborted` to handle cancellation
|
|
323
|
-
- `info.resetBackoff()`: Call this to reset the backoff counter to the beginning (useful when making partial progress)
|
|
324
|
-
|
|
325
|
-
### `addRetry(cb, options?)`
|
|
326
|
-
|
|
327
|
-
Fire-and-forget version of `retry`. Starts the retry loop in the background and returns a dispose function to cancel it.
|
|
328
|
-
|
|
329
|
-
```typescript
|
|
330
|
-
import {addRetry} from 'jrx'
|
|
331
|
-
|
|
332
|
-
// Start a retry loop in the background
|
|
333
|
-
const dispose = addRetry(async (disposer, { resetBackoff }) => {
|
|
334
|
-
const response = await fetch('/api/data')
|
|
335
|
-
if (!response.ok) throw new Error('Failed')
|
|
336
|
-
processData(await response.json())
|
|
337
|
-
})
|
|
338
|
-
|
|
339
|
-
// Cancel the retry loop
|
|
340
|
-
dispose()
|
|
341
|
-
|
|
342
|
-
// With custom backoff
|
|
343
|
-
const dispose2 = addRetry(
|
|
344
|
-
async (disposer) => {
|
|
345
|
-
await connectWebSocket()
|
|
346
|
-
},
|
|
347
|
-
{ backoffSec: [1, 2, 5, -1] }
|
|
348
|
-
)
|
|
349
|
-
|
|
350
|
-
dispose2()
|
|
273
|
+
const data = await r // undefined
|
|
351
274
|
```
|
|
352
275
|
|
|
353
|
-
**
|
|
354
|
-
- `
|
|
276
|
+
**Parameters:**
|
|
277
|
+
- `cb`: Callback that returns `Disposable & (T | Promise<T>)`. Receives `{ resetBackoff() }` to reset the backoff counter.
|
|
278
|
+
- `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]`
|
|
355
279
|
|
|
356
280
|
## Cleanup Pattern
|
|
357
281
|
|
|
358
|
-
All functions return
|
|
282
|
+
All effect functions return a dispose function that stops the effect and runs any pending cleanup:
|
|
359
283
|
|
|
360
284
|
```typescript
|
|
361
285
|
import {addInterval, addTimeout, addRequestAnimationFrame} from 'jrx'
|
|
362
|
-
import {makeDisposer} from 'jdisposer'
|
|
363
|
-
|
|
364
|
-
const disposer = makeDisposer()
|
|
365
286
|
|
|
366
|
-
//
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
287
|
+
// Each function returns a dispose function
|
|
288
|
+
const disposeInterval = addInterval(() => console.log('tick'), 1000)
|
|
289
|
+
const disposeTimeout = addTimeout(() => console.log('timeout'), 5000)
|
|
290
|
+
const disposeRaf = addRequestAnimationFrame((now) => render(now))
|
|
370
291
|
|
|
371
|
-
//
|
|
372
|
-
|
|
292
|
+
// Call the dispose function to stop the effect
|
|
293
|
+
disposeInterval()
|
|
294
|
+
disposeTimeout()
|
|
295
|
+
disposeRaf()
|
|
373
296
|
```
|
|
374
297
|
|
|
375
298
|
## TypeScript
|
|
376
299
|
|
|
377
|
-
This library is written in TypeScript and
|
|
378
|
-
|
|
379
|
-
```typescript
|
|
380
|
-
import type { Disposer } from 'jdisposer'
|
|
381
|
-
|
|
382
|
-
// All disposer functions follow this pattern
|
|
383
|
-
type DisposerFunction = () => void
|
|
384
|
-
```
|
|
300
|
+
This library is written in TypeScript and uses the [Explicit Resource Management](https://github.com/tc39/proposal-explicit-resource-management) types (`Disposable`, `DisposableStack`, `AsyncDisposableStack`).
|
|
385
301
|
|
|
386
302
|
## License
|
|
387
303
|
|
package/index.d.ts
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
|
-
import
|
|
2
|
-
import retry, { addRetry } from './retry.js';
|
|
1
|
+
import retry from './retry.js';
|
|
3
2
|
import computed from './computed.js';
|
|
4
|
-
|
|
5
|
-
export
|
|
3
|
+
export { retry, computed };
|
|
4
|
+
export declare function makeReset(): () => DisposableStack;
|
|
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) =>
|
|
8
|
+
setLoop(this: void, loop: (time: DOMHighResTimeStamp) => undefined | (() => void)): () => void;
|
|
9
9
|
};
|
|
10
|
-
export declare function addInterval(cb: () =>
|
|
11
|
-
export declare function addIntervalAsync(cb: (
|
|
12
|
-
export declare function addRequestAnimationFrame(cb: (now: DOMHighResTimeStamp) =>
|
|
13
|
-
export declare function addRequestAnimationFrameLoop(cb: (now: DOMHighResTimeStamp) =>
|
|
14
|
-
export declare function addSubs<Subs extends any[]>(subs: Subs, cb: () => void | (() => void), { now }?: {
|
|
15
|
-
now?: boolean;
|
|
16
|
-
}): (this: void) => void;
|
|
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;
|
|
17
14
|
export declare function addTimeout(cb: () => void, ms: number): () => void;
|
|
18
|
-
export declare function addTransition(cb: (progress: number) =>
|
|
15
|
+
export declare function addTransition(cb: (progress: number) => undefined | (() => void), durationMs: number): () => void;
|
package/index.js
CHANGED
|
@@ -1,14 +1,26 @@
|
|
|
1
|
-
import
|
|
2
|
-
import retry, { addRetry } from './retry.js';
|
|
1
|
+
import retry from './retry.js';
|
|
3
2
|
import computed from './computed.js';
|
|
4
|
-
|
|
5
|
-
export
|
|
3
|
+
export { retry, computed };
|
|
4
|
+
export function makeReset() {
|
|
5
|
+
let stack = new DisposableStack();
|
|
6
|
+
return () => {
|
|
7
|
+
stack.dispose();
|
|
8
|
+
return (stack = new DisposableStack());
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export function makeAsyncReset() {
|
|
12
|
+
let stack = new AsyncDisposableStack();
|
|
13
|
+
return async () => {
|
|
14
|
+
await stack.disposeAsync();
|
|
15
|
+
return (stack = new AsyncDisposableStack());
|
|
16
|
+
};
|
|
17
|
+
}
|
|
6
18
|
export function makeRenderLoop() {
|
|
7
19
|
let loop_;
|
|
8
20
|
const reset = makeReset();
|
|
9
21
|
return {
|
|
10
22
|
loop(time) {
|
|
11
|
-
reset().
|
|
23
|
+
reset().adopt(loop_?.(time), v => v?.());
|
|
12
24
|
},
|
|
13
25
|
setLoop(loop) {
|
|
14
26
|
loop_ = loop;
|
|
@@ -28,7 +40,7 @@ export function addInterval(cb, ms) {
|
|
|
28
40
|
clearTimeout(timeout);
|
|
29
41
|
};
|
|
30
42
|
function wrapper() {
|
|
31
|
-
reset().
|
|
43
|
+
reset().adopt(cb(), v => v?.());
|
|
32
44
|
timeout = setTimeout(wrapper, ms);
|
|
33
45
|
}
|
|
34
46
|
}
|
|
@@ -41,17 +53,21 @@ export function addIntervalAsync(cb, ms) {
|
|
|
41
53
|
clearTimeout(timeout);
|
|
42
54
|
};
|
|
43
55
|
async function wrapper() {
|
|
44
|
-
const
|
|
45
|
-
await cb(
|
|
46
|
-
if (!
|
|
56
|
+
const stack = reset();
|
|
57
|
+
await stack.adopt(cb(), v => v?.[Symbol.dispose]?.());
|
|
58
|
+
if (!stack.disposed)
|
|
47
59
|
timeout = setTimeout(wrapper, ms);
|
|
48
60
|
}
|
|
49
61
|
}
|
|
50
62
|
export function addRequestAnimationFrame(cb) {
|
|
51
|
-
const
|
|
52
|
-
const raf = requestAnimationFrame(now =>
|
|
63
|
+
const stack = new DisposableStack();
|
|
64
|
+
const raf = requestAnimationFrame(now => {
|
|
65
|
+
if (stack.disposed)
|
|
66
|
+
return;
|
|
67
|
+
stack.adopt(cb(now), v => v?.());
|
|
68
|
+
});
|
|
53
69
|
return () => {
|
|
54
|
-
|
|
70
|
+
stack.dispose();
|
|
55
71
|
cancelAnimationFrame(raf);
|
|
56
72
|
};
|
|
57
73
|
}
|
|
@@ -63,20 +79,10 @@ export function addRequestAnimationFrameLoop(cb) {
|
|
|
63
79
|
cancelAnimationFrame(raf);
|
|
64
80
|
};
|
|
65
81
|
function wrapper(now) {
|
|
66
|
-
reset().
|
|
82
|
+
reset().adopt(cb(now), v => v?.());
|
|
67
83
|
raf = requestAnimationFrame(wrapper);
|
|
68
84
|
}
|
|
69
85
|
}
|
|
70
|
-
export function addSubs(subs, cb, { now } = {}) {
|
|
71
|
-
const disposer = makeDisposer();
|
|
72
|
-
const reset = makeReset();
|
|
73
|
-
disposer.add(reset);
|
|
74
|
-
for (const sub of subs)
|
|
75
|
-
disposer.add(sub(() => reset().add(cb())));
|
|
76
|
-
if (now)
|
|
77
|
-
reset().add(cb());
|
|
78
|
-
return disposer.dispose;
|
|
79
|
-
}
|
|
80
86
|
export function addTimeout(cb, ms) {
|
|
81
87
|
const timeout = setTimeout(cb, ms);
|
|
82
88
|
return () => clearTimeout(timeout);
|
|
@@ -92,15 +98,15 @@ export function addTransition(cb, durationMs) {
|
|
|
92
98
|
function wrapper(now) {
|
|
93
99
|
if (start === undefined) {
|
|
94
100
|
start = now;
|
|
95
|
-
reset().
|
|
101
|
+
reset().adopt(cb(0), v => v?.());
|
|
96
102
|
raf = requestAnimationFrame(wrapper);
|
|
97
103
|
}
|
|
98
104
|
else {
|
|
99
105
|
const progress = (now - start) / durationMs;
|
|
100
106
|
if (progress >= 1)
|
|
101
|
-
reset().
|
|
107
|
+
reset().adopt(cb(1), v => v?.());
|
|
102
108
|
else {
|
|
103
|
-
reset().
|
|
109
|
+
reset().adopt(cb(progress), v => v?.());
|
|
104
110
|
raf = requestAnimationFrame(wrapper);
|
|
105
111
|
}
|
|
106
112
|
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jrx",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0-alpha.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"prepublishOnly": "npx tsc",
|
|
7
|
-
"test": "node --test tests/*.test.ts",
|
|
8
|
-
"test:coverage": "node --test --experimental-test-coverage tests/*.test.ts"
|
|
7
|
+
"test": "npm run prepublishOnly && node --test tests/*.test.ts",
|
|
8
|
+
"test:coverage": "npm run prepublishOnly && node --test --experimental-test-coverage tests/*.test.ts"
|
|
9
9
|
},
|
|
10
10
|
"exports": {
|
|
11
11
|
".": {
|
|
@@ -23,15 +23,10 @@
|
|
|
23
23
|
"retry.js",
|
|
24
24
|
"retry.d.ts",
|
|
25
25
|
"computed.js",
|
|
26
|
-
"computed.d.ts"
|
|
27
|
-
"addEvtListener.js",
|
|
28
|
-
"addEvtListener.d.ts"
|
|
26
|
+
"computed.d.ts"
|
|
29
27
|
],
|
|
30
28
|
"devDependencies": {
|
|
31
29
|
"@types/node": "^25.2.1",
|
|
32
|
-
"typescript": "^
|
|
33
|
-
},
|
|
34
|
-
"dependencies": {
|
|
35
|
-
"jdisposer": "^0.0.1"
|
|
30
|
+
"typescript": "^6.0.2"
|
|
36
31
|
}
|
|
37
32
|
}
|
package/retry.d.ts
CHANGED
|
@@ -1,17 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
export default function retry<T>(cb: (disposer: Disposer, info: {
|
|
1
|
+
export default function retry<T>(cb: (info: {
|
|
3
2
|
resetBackoff(): void;
|
|
4
|
-
}) => T | Promise<T
|
|
5
|
-
backoffSec?: number[];
|
|
6
|
-
}): Promise<T>;
|
|
7
|
-
export default function retry<T>(cb: (disposer: Disposer, info: {
|
|
8
|
-
resetBackoff(): void;
|
|
9
|
-
}) => T | Promise<T>, options?: {
|
|
10
|
-
backoffSec?: number[];
|
|
11
|
-
disposer: Disposer;
|
|
12
|
-
}): Promise<T | undefined>;
|
|
13
|
-
export declare function addRetry<T>(cb: (disposer: Disposer, info: {
|
|
14
|
-
resetBackoff(): void;
|
|
15
|
-
}) => T | Promise<T>, options?: {
|
|
16
|
-
backoffSec?: number[];
|
|
17
|
-
}): (this: void) => void;
|
|
3
|
+
}) => (Disposable | undefined) & (T | Promise<T>), backoffSec?: number[]): Disposable & Promise<T | undefined>;
|
package/retry.js
CHANGED
|
@@ -1,37 +1,35 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export default
|
|
3
|
-
|
|
1
|
+
import { makeReset } from './index.js';
|
|
2
|
+
export default function retry(cb, backoffSec = [5, 5, 10, 10, 20, 20, 40, 40, 60, -1]) {
|
|
3
|
+
const stack = new DisposableStack();
|
|
4
4
|
const reset = makeReset();
|
|
5
|
-
|
|
5
|
+
stack.defer(reset);
|
|
6
6
|
let count = 0;
|
|
7
|
-
let
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
count
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
7
|
+
let loopStack = reset();
|
|
8
|
+
return Object.assign((async () => {
|
|
9
|
+
while (true) {
|
|
10
|
+
if (backoffSec[count] !== -1)
|
|
11
|
+
count++;
|
|
12
|
+
try {
|
|
13
|
+
if (loopStack.disposed)
|
|
14
|
+
return;
|
|
15
|
+
const value = cb({
|
|
16
|
+
resetBackoff() {
|
|
17
|
+
count = 1;
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
return value?.[Symbol.dispose] ? loopStack.use(value) : value;
|
|
21
|
+
}
|
|
22
|
+
catch (e) {
|
|
23
|
+
if (stack.disposed)
|
|
24
|
+
return;
|
|
25
|
+
if (count > backoffSec.length) {
|
|
26
|
+
console.error('max retries reached:', e);
|
|
27
|
+
throw e;
|
|
28
|
+
}
|
|
29
|
+
console.warn('Retrying due to error:', e);
|
|
30
|
+
loopStack = reset();
|
|
31
|
+
await new Promise(resolve => setTimeout(resolve, backoffSec[count - 1] * 1000));
|
|
26
32
|
}
|
|
27
|
-
console.warn('Retrying due to error:', e);
|
|
28
|
-
loopDisposer = reset();
|
|
29
|
-
await new Promise(resolve => setTimeout(resolve, backoffSec[count - 1] * 1000));
|
|
30
33
|
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
export function addRetry(cb, options) {
|
|
34
|
-
const disposer = makeDisposer();
|
|
35
|
-
void retry(cb, { ...options, disposer });
|
|
36
|
-
return disposer.dispose;
|
|
34
|
+
})(), { [Symbol.dispose]: stack[Symbol.dispose].bind(stack) });
|
|
37
35
|
}
|
package/addEvtListener.d.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
type IEventHandler<Target extends {
|
|
2
|
-
addEventListener(event: string, handler: any, option?: any): any;
|
|
3
|
-
}, EventName> = Target['addEventListener'] extends (event: EventName, handler: infer Handler, ...args: any[]) => any ? Handler extends (...params: any[]) => any ? Handler : never : never;
|
|
4
|
-
type IEventOption<Target extends {
|
|
5
|
-
addEventListener(event: string, handler: any, option?: any): any;
|
|
6
|
-
}, EventName, Handler> = Target['addEventListener'] extends (event: EventName, handler: Handler, option: infer Option) => any ? Option : never;
|
|
7
|
-
export default function addEvtListener<Target extends {
|
|
8
|
-
addEventListener(event: string, handler: any, option?: any): any;
|
|
9
|
-
removeEventListener(event: string, handler: any, option?: any): any;
|
|
10
|
-
}, EventName extends Parameters<Target['addEventListener']>[0], Handler extends IEventHandler<Target, EventName>>(target: Target, event: EventName, handler: Handler, option?: IEventOption<Target, EventName, Handler>): () => void;
|
|
11
|
-
export default function addEvtListener<Target extends {
|
|
12
|
-
addEventListener(event: string, handler: any, option?: any): any;
|
|
13
|
-
removeEventListener(event: string, handler: any, option?: any): any;
|
|
14
|
-
}, EventName extends Parameters<Target['addEventListener']>[0]>(target: Target, event: EventName, handler: (...args: any[]) => any, option?: IEventOption<Target, EventName, IEventHandler<Target, EventName>>): () => void;
|
|
15
|
-
export {};
|