jrx 0.3.0-alpha.7 → 0.3.0-alpha.9
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 +2 -73
- package/index.d.ts +1 -4
- package/index.js +5 -4
- package/package.json +2 -6
- package/computed.d.ts +0 -3
- package/computed.js +0 -17
- package/retry.d.ts +0 -3
- package/retry.js +0 -35
package/README.md
CHANGED
|
@@ -18,7 +18,6 @@ npm i jrx
|
|
|
18
18
|
|
|
19
19
|
- Automatic cleanup for all effects and subscriptions
|
|
20
20
|
- Built on the native `DisposableStack` / `AsyncDisposableStack` API
|
|
21
|
-
- Retry logic with exponential backoff and cancellation
|
|
22
21
|
- Zero dependencies
|
|
23
22
|
- Composable reactive utilities
|
|
24
23
|
- Browser and Node.js compatible
|
|
@@ -34,8 +33,6 @@ npm i jrx
|
|
|
34
33
|
- [`createAnimationFrameLoop(cb)`](#createAnimationFrameloopcb) - Animation frame loops
|
|
35
34
|
- [`createTimeout(cb, ms)`](#createTimeoutcb-ms) - Timeouts with cleanup
|
|
36
35
|
- [`createTransition(cb, durationMs)`](#createTransitioncb-durationms) - Progress-based animations
|
|
37
|
-
- [`computed(fn, getDeps?)`](#computedfn-getdeps) - Memoized computed values
|
|
38
|
-
- [`retry(cb, backoffSec?)`](#retrycb-backoffsec) - Retry with exponential backoff
|
|
39
36
|
- [`assignDispose(value, disposable)`](#assigndisposevalue-disposable) - Attach a `Symbol.dispose` to any value
|
|
40
37
|
|
|
41
38
|
## Naming convention
|
|
@@ -236,76 +233,6 @@ const handle = createTransition((progress) => {
|
|
|
236
233
|
handle[Symbol.dispose]()
|
|
237
234
|
```
|
|
238
235
|
|
|
239
|
-
### `computed(fn, getDeps?)`
|
|
240
|
-
|
|
241
|
-
Creates a memoized computed value with optional dependency tracking.
|
|
242
|
-
|
|
243
|
-
```typescript
|
|
244
|
-
import {computed} from 'jrx'
|
|
245
|
-
|
|
246
|
-
// Without dependencies - always recomputes
|
|
247
|
-
const value1 = computed(() => expensiveCalculation())
|
|
248
|
-
console.log(value1.value) // Computed
|
|
249
|
-
console.log(value1.value) // Computed again
|
|
250
|
-
|
|
251
|
-
// With dependencies - memoizes when deps unchanged
|
|
252
|
-
let a = 1, b = 2
|
|
253
|
-
const value2 = computed(
|
|
254
|
-
() => a + b,
|
|
255
|
-
() => [a, b] // Dependencies
|
|
256
|
-
)
|
|
257
|
-
|
|
258
|
-
console.log(value2.value) // Computed: 3
|
|
259
|
-
console.log(value2.value) // Cached: 3
|
|
260
|
-
|
|
261
|
-
a = 10
|
|
262
|
-
console.log(value2.value) // Recomputed: 12
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
### `retry(cb, backoffSec?)`
|
|
266
|
-
|
|
267
|
-
Retries an operation with exponential backoff on failure. Returns `Disposable & Promise<T>`.
|
|
268
|
-
|
|
269
|
-
**Default backoff:** `[5, 5, 10, 10, 20, 20, 40, 40, 60, -1]` seconds (where `-1` means retry forever with 60s delay)
|
|
270
|
-
|
|
271
|
-
```typescript
|
|
272
|
-
import {retry} from 'jrx'
|
|
273
|
-
|
|
274
|
-
// Basic usage - retries with default backoff
|
|
275
|
-
const result = await retry(({resetBackoff}) => {
|
|
276
|
-
const promise = fetch('/api/data').then((r) => r.json())
|
|
277
|
-
return Object.assign(promise, {[Symbol.dispose]() {}})
|
|
278
|
-
})
|
|
279
|
-
|
|
280
|
-
// Custom backoff schedule (in seconds)
|
|
281
|
-
await retry(
|
|
282
|
-
() => {
|
|
283
|
-
const promise = fetchData()
|
|
284
|
-
return Object.assign(promise, {[Symbol.dispose]() {}})
|
|
285
|
-
},
|
|
286
|
-
[1, 2, 5, 10, -1], // -1 means retry forever with last delay
|
|
287
|
-
)
|
|
288
|
-
|
|
289
|
-
// Cancellation via Disposable
|
|
290
|
-
const r = retry(
|
|
291
|
-
({resetBackoff}) => {
|
|
292
|
-
const promise = fetchData()
|
|
293
|
-
return Object.assign(promise, {[Symbol.dispose]() { /* cancel */ }})
|
|
294
|
-
},
|
|
295
|
-
[5, 10, 20, 40, -1],
|
|
296
|
-
)
|
|
297
|
-
|
|
298
|
-
// Cancel the retry loop
|
|
299
|
-
r[Symbol.dispose]()
|
|
300
|
-
|
|
301
|
-
// Returns undefined when disposed
|
|
302
|
-
const data = await r // undefined
|
|
303
|
-
```
|
|
304
|
-
|
|
305
|
-
**Parameters:**
|
|
306
|
-
- `cb`: Callback that returns `Disposable & (T | Promise<T>)`. Receives `{ resetBackoff() }` to reset the backoff counter.
|
|
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]`
|
|
308
|
-
|
|
309
236
|
### `assignDispose(value, disposable)`
|
|
310
237
|
|
|
311
238
|
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`.
|
|
@@ -341,6 +268,8 @@ function loadThing() {
|
|
|
341
268
|
}
|
|
342
269
|
```
|
|
343
270
|
|
|
271
|
+
If `value` is itself a `Disposable`, disposing the returned value disposes `value` first, then `disposable`.
|
|
272
|
+
|
|
344
273
|
## Cleanup Pattern
|
|
345
274
|
|
|
346
275
|
All effect functions return a `Disposable` object that stops the effect and runs any pending cleanup:
|
package/index.d.ts
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import retry from './retry.js';
|
|
2
|
-
import computed from './computed.js';
|
|
3
|
-
export { retry, computed };
|
|
4
1
|
export declare function makeReset(): () => DisposableStack;
|
|
5
2
|
export declare function makeAsyncReset(): () => Promise<AsyncDisposableStack>;
|
|
6
3
|
export declare function makeRenderLoop(): {
|
|
@@ -28,5 +25,5 @@ export declare function createTransition(cb: (progress: number) => undefined | D
|
|
|
28
25
|
[Symbol.dispose](): void;
|
|
29
26
|
};
|
|
30
27
|
export declare function assignDispose<T extends object>(value: T, disposable: Disposable): T & {
|
|
31
|
-
[Symbol.dispose]
|
|
28
|
+
[Symbol.dispose](): void;
|
|
32
29
|
};
|
package/index.js
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import retry from './retry.js';
|
|
2
|
-
import computed from './computed.js';
|
|
3
|
-
export { retry, computed };
|
|
4
1
|
export function makeReset() {
|
|
5
2
|
let stack = new DisposableStack();
|
|
6
3
|
return () => {
|
|
@@ -129,7 +126,11 @@ export function createTransition(cb, durationMs) {
|
|
|
129
126
|
}
|
|
130
127
|
}
|
|
131
128
|
export function assignDispose(value, disposable) {
|
|
129
|
+
const valueDispose = value?.[Symbol.dispose]?.bind(value);
|
|
132
130
|
return Object.assign(value, {
|
|
133
|
-
[Symbol.dispose]
|
|
131
|
+
[Symbol.dispose]() {
|
|
132
|
+
valueDispose?.();
|
|
133
|
+
disposable[Symbol.dispose]();
|
|
134
|
+
}
|
|
134
135
|
});
|
|
135
136
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jrx",
|
|
3
|
-
"version": "0.3.0-alpha.
|
|
3
|
+
"version": "0.3.0-alpha.9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"prepublishOnly": "npx tsc",
|
|
@@ -19,11 +19,7 @@
|
|
|
19
19
|
},
|
|
20
20
|
"files": [
|
|
21
21
|
"index.js",
|
|
22
|
-
"index.d.ts"
|
|
23
|
-
"retry.js",
|
|
24
|
-
"retry.d.ts",
|
|
25
|
-
"computed.js",
|
|
26
|
-
"computed.d.ts"
|
|
22
|
+
"index.d.ts"
|
|
27
23
|
],
|
|
28
24
|
"devDependencies": {
|
|
29
25
|
"@types/node": "^25.2.1",
|
package/computed.d.ts
DELETED
package/computed.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
export default function computed(fn, getDeps) {
|
|
2
|
-
let cached;
|
|
3
|
-
let lastDeps;
|
|
4
|
-
let first = true;
|
|
5
|
-
return {
|
|
6
|
-
get value() {
|
|
7
|
-
if (!getDeps)
|
|
8
|
-
return fn();
|
|
9
|
-
const deps = getDeps();
|
|
10
|
-
if (!first && deps.length === lastDeps.length && deps.every((dep, idx) => Object.is(dep, lastDeps[idx])))
|
|
11
|
-
return cached;
|
|
12
|
-
first = false;
|
|
13
|
-
lastDeps = deps;
|
|
14
|
-
return (cached = fn());
|
|
15
|
-
},
|
|
16
|
-
};
|
|
17
|
-
}
|
package/retry.d.ts
DELETED
package/retry.js
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
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
|
-
const reset = makeReset();
|
|
5
|
-
stack.defer(reset);
|
|
6
|
-
let count = 0;
|
|
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));
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
})(), { [Symbol.dispose]: stack[Symbol.dispose].bind(stack) });
|
|
35
|
-
}
|