jrx 0.0.2 → 0.2.0
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 +71 -6
- package/addEvtListener.d.ts +15 -0
- package/addEvtListener.js +6 -0
- package/index.d.ts +5 -0
- package/index.js +12 -0
- package/package.json +5 -11
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ A lightweight TypeScript library for managing side effects, subscriptions, and a
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm
|
|
8
|
+
npm i jrx
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
## Features
|
|
@@ -17,6 +17,20 @@ npm install jrx
|
|
|
17
17
|
- Composable reactive utilities
|
|
18
18
|
- Browser and Node.js compatible
|
|
19
19
|
|
|
20
|
+
## API Overview
|
|
21
|
+
|
|
22
|
+
- [`makeRenderLoop()`](#makerenderloop) - Render loops with automatic cleanup
|
|
23
|
+
- [`addEvtListener(target, event, handler, option?)`](#addevtlistenertarget-event-handler-option) - Event listeners with cleanup
|
|
24
|
+
- [`addInterval(cb, ms)`](#addintervalcb-ms) - Repeating intervals with cleanup
|
|
25
|
+
- [`addIntervalAsync(cb, ms)`](#addintervalasynccb-ms) - Async intervals with cancellation
|
|
26
|
+
- [`addRequestAnimationFrame(cb)`](#addrequestanimationframecb) - Single animation frame with cleanup
|
|
27
|
+
- [`addRequestAnimationFrameLoop(cb)`](#addrequestanimationframeloopcb) - Animation frame loops
|
|
28
|
+
- [`addSubs(subs, cb, options?)`](#addsubssubs-cb-options) - Multiple subscription management
|
|
29
|
+
- [`addTimeout(cb, ms)`](#addtimeoutcb-ms) - Timeouts with cleanup
|
|
30
|
+
- [`addTransition(cb, durationMs)`](#addtransitioncb-durationms) - Progress-based animations
|
|
31
|
+
- [`computed(fn, getDeps?)`](#computedfn-getdeps) - Memoized computed values
|
|
32
|
+
- [`retry(cb, options?)`](#retrycb-options) - Async retry with exponential backoff
|
|
33
|
+
|
|
20
34
|
## API
|
|
21
35
|
|
|
22
36
|
### `makeRenderLoop()`
|
|
@@ -45,15 +59,39 @@ requestAnimationFrame(loop)
|
|
|
45
59
|
dispose()
|
|
46
60
|
```
|
|
47
61
|
|
|
62
|
+
### `addEvtListener(target, event, handler, option?)`
|
|
63
|
+
|
|
64
|
+
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.).
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import { addEvtListener } from 'jrx'
|
|
68
|
+
|
|
69
|
+
// Basic usage
|
|
70
|
+
const dispose = addEvtListener(window, 'resize', (e) => {
|
|
71
|
+
console.log('Window resized', e)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
// With options
|
|
75
|
+
const dispose2 = addEvtListener(element, 'click', (e) => {
|
|
76
|
+
console.log('Clicked', e)
|
|
77
|
+
}, { capture: true })
|
|
78
|
+
|
|
79
|
+
// Cleanup
|
|
80
|
+
dispose()
|
|
81
|
+
dispose2()
|
|
82
|
+
```
|
|
83
|
+
|
|
48
84
|
### `addInterval(cb, ms)`
|
|
49
85
|
|
|
50
86
|
Creates a repeating interval with cleanup. The callback can optionally return a cleanup function that runs before the next invocation.
|
|
51
87
|
|
|
88
|
+
**Note:** The callback fires **immediately** on first call, then waits `ms` milliseconds **after** the previous callback completes. This is not a fixed-rate timer.
|
|
89
|
+
|
|
52
90
|
```typescript
|
|
53
91
|
import { addInterval } from 'jrx'
|
|
54
92
|
|
|
55
93
|
const dispose = addInterval(() => {
|
|
56
|
-
console.log('Tick')
|
|
94
|
+
console.log('Tick') // Called immediately, then every 1000ms after completion
|
|
57
95
|
|
|
58
96
|
// Optional: return cleanup function
|
|
59
97
|
return () => {
|
|
@@ -69,10 +107,13 @@ dispose()
|
|
|
69
107
|
|
|
70
108
|
Async version of `addInterval`. Waits for the callback to complete before scheduling the next invocation.
|
|
71
109
|
|
|
110
|
+
**Note:** The callback fires **immediately** on first call, then waits `ms` milliseconds **after** the previous async callback completes.
|
|
111
|
+
|
|
72
112
|
```typescript
|
|
73
113
|
import { addIntervalAsync } from 'jrx'
|
|
74
114
|
|
|
75
115
|
const dispose = addIntervalAsync(async (disposer) => {
|
|
116
|
+
// Called immediately, then 5000ms after each completion
|
|
76
117
|
await fetchData()
|
|
77
118
|
|
|
78
119
|
// Check if disposed during async operation
|
|
@@ -86,7 +127,7 @@ dispose()
|
|
|
86
127
|
|
|
87
128
|
### `addRequestAnimationFrame(cb)`
|
|
88
129
|
|
|
89
|
-
|
|
130
|
+
Executes a callback on the next animation frame with cleanup.
|
|
90
131
|
|
|
91
132
|
```typescript
|
|
92
133
|
import { addRequestAnimationFrame } from 'jrx'
|
|
@@ -100,6 +141,27 @@ const dispose = addRequestAnimationFrame((now) => {
|
|
|
100
141
|
}
|
|
101
142
|
})
|
|
102
143
|
|
|
144
|
+
// Cancel if needed before the frame fires
|
|
145
|
+
dispose()
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### `addRequestAnimationFrameLoop(cb)`
|
|
149
|
+
|
|
150
|
+
Creates a continuous `requestAnimationFrame` loop with cleanup.
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
import { addRequestAnimationFrameLoop } from 'jrx'
|
|
154
|
+
|
|
155
|
+
const dispose = addRequestAnimationFrameLoop((now) => {
|
|
156
|
+
updateAnimation(now)
|
|
157
|
+
|
|
158
|
+
// Optional: return cleanup function
|
|
159
|
+
return () => {
|
|
160
|
+
cleanupAnimation()
|
|
161
|
+
}
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
// Stop the loop
|
|
103
165
|
dispose()
|
|
104
166
|
```
|
|
105
167
|
|
|
@@ -196,8 +258,10 @@ console.log(value2.value) // Recomputed: 12
|
|
|
196
258
|
|
|
197
259
|
Retries an async operation with exponential backoff on failure.
|
|
198
260
|
|
|
261
|
+
**Default backoff:** `[5, 5, 10, 10, 20, 20, 40, 40, 60, -1]` seconds (where `-1` means retry forever with 60s delay)
|
|
262
|
+
|
|
199
263
|
```typescript
|
|
200
|
-
import retry from 'jrx
|
|
264
|
+
import {retry} from 'jrx'
|
|
201
265
|
|
|
202
266
|
// Basic usage - retries with default backoff
|
|
203
267
|
const result = await retry(async (disposer, { resetBackoff }) => {
|
|
@@ -249,7 +313,8 @@ console.log(data) // T | undefined
|
|
|
249
313
|
```
|
|
250
314
|
|
|
251
315
|
**Options:**
|
|
252
|
-
- `backoffSec`: Array of retry delays in seconds. Use `-1` for infinite retries with the last delay.
|
|
316
|
+
- `backoffSec`: Array of retry delays in seconds. Use `-1` for infinite retries with the last delay.
|
|
317
|
+
- Default: `[5, 5, 10, 10, 20, 20, 40, 40, 60, -1]`
|
|
253
318
|
- `disposer`: Optional disposer for cancellation. When provided, the return type is `T | undefined`. Otherwise, the return type is `T`.
|
|
254
319
|
|
|
255
320
|
**Callback parameters:**
|
|
@@ -269,7 +334,7 @@ const disposer = makeDisposer()
|
|
|
269
334
|
// Collect disposers
|
|
270
335
|
disposer.add(addInterval(() => console.log('tick'), 1000))
|
|
271
336
|
disposer.add(addTimeout(() => console.log('timeout'), 5000))
|
|
272
|
-
disposer.add(
|
|
337
|
+
disposer.add(addRequestAnimationFrameLoop((now) => render(now)))
|
|
273
338
|
|
|
274
339
|
// Cleanup all at once
|
|
275
340
|
disposer.dispose()
|
|
@@ -0,0 +1,15 @@
|
|
|
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 {};
|
package/index.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { type Disposer } from 'jdisposer';
|
|
2
|
+
import retry from './retry.js';
|
|
3
|
+
import computed from './computed.js';
|
|
4
|
+
import addEvtListener from './addEvtListener.js';
|
|
5
|
+
export { retry, computed, addEvtListener };
|
|
2
6
|
export declare function makeRenderLoop(): {
|
|
3
7
|
loop(this: void, time: DOMHighResTimeStamp): void;
|
|
4
8
|
setLoop(this: void, loop: (time: DOMHighResTimeStamp) => void | (() => void)): () => void;
|
|
@@ -6,6 +10,7 @@ export declare function makeRenderLoop(): {
|
|
|
6
10
|
export declare function addInterval(cb: () => void | (() => any), ms: number): () => void;
|
|
7
11
|
export declare function addIntervalAsync(cb: (disposer: Disposer) => void | (() => any) | Promise<void> | Promise<() => any>, ms: number): () => void;
|
|
8
12
|
export declare function addRequestAnimationFrame(cb: (now: DOMHighResTimeStamp) => void | (() => any)): () => void;
|
|
13
|
+
export declare function addRequestAnimationFrameLoop(cb: (now: DOMHighResTimeStamp) => void | (() => any)): () => void;
|
|
9
14
|
export declare function addSubs<Subs extends any[]>(subs: Subs, cb: () => void | (() => void), { now }?: {
|
|
10
15
|
now?: boolean;
|
|
11
16
|
}): (this: void) => void;
|
package/index.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { makeDisposer, makeReset } from 'jdisposer';
|
|
2
|
+
import retry from './retry.js';
|
|
3
|
+
import computed from './computed.js';
|
|
4
|
+
import addEvtListener from './addEvtListener.js';
|
|
5
|
+
export { retry, computed, addEvtListener };
|
|
2
6
|
export function makeRenderLoop() {
|
|
3
7
|
let loop_;
|
|
4
8
|
const reset = makeReset();
|
|
@@ -44,6 +48,14 @@ export function addIntervalAsync(cb, ms) {
|
|
|
44
48
|
}
|
|
45
49
|
}
|
|
46
50
|
export function addRequestAnimationFrame(cb) {
|
|
51
|
+
const disposer = makeDisposer();
|
|
52
|
+
const raf = requestAnimationFrame(now => disposer.add(cb(now)));
|
|
53
|
+
return () => {
|
|
54
|
+
disposer.dispose();
|
|
55
|
+
cancelAnimationFrame(raf);
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
export function addRequestAnimationFrameLoop(cb) {
|
|
47
59
|
const reset = makeReset();
|
|
48
60
|
let raf = requestAnimationFrame(wrapper);
|
|
49
61
|
return () => {
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jrx",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
|
-
"prepublishOnly": "npx tsc
|
|
6
|
+
"prepublishOnly": "npx tsc",
|
|
7
7
|
"test": "node --test tests/*.test.ts",
|
|
8
8
|
"test:coverage": "node --test --experimental-test-coverage tests/*.test.ts"
|
|
9
9
|
},
|
|
@@ -11,14 +11,6 @@
|
|
|
11
11
|
".": {
|
|
12
12
|
"types": "./index.d.ts",
|
|
13
13
|
"import": "./index.js"
|
|
14
|
-
},
|
|
15
|
-
"./retry": {
|
|
16
|
-
"types": "./retry.d.ts",
|
|
17
|
-
"import": "./retry.js"
|
|
18
|
-
},
|
|
19
|
-
"./computed": {
|
|
20
|
-
"types": "./computed.d.ts",
|
|
21
|
-
"import": "./computed.js"
|
|
22
14
|
}
|
|
23
15
|
},
|
|
24
16
|
"repository": {
|
|
@@ -31,7 +23,9 @@
|
|
|
31
23
|
"retry.js",
|
|
32
24
|
"retry.d.ts",
|
|
33
25
|
"computed.js",
|
|
34
|
-
"computed.d.ts"
|
|
26
|
+
"computed.d.ts",
|
|
27
|
+
"addEvtListener.js",
|
|
28
|
+
"addEvtListener.d.ts"
|
|
35
29
|
],
|
|
36
30
|
"devDependencies": {
|
|
37
31
|
"@types/node": "^25.2.1",
|