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 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 install jrx
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
- Creates a `requestAnimationFrame` loop with cleanup.
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/retry'
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. Default: `[5, 5, 10, 10, 20, 20, 40, 40, 60, -1]`
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(addRequestAnimationFrame((now) => render(now)))
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 {};
@@ -0,0 +1,6 @@
1
+ export default function addEvtListener(target, event, handler, option) {
2
+ target.addEventListener(event, handler, option);
3
+ return function removeEvtListener() {
4
+ return target.removeEventListener(event, handler, option);
5
+ };
6
+ }
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.2",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "scripts": {
6
- "prepublishOnly": "npx tsc index.ts retry.ts computed.ts --target esnext --declaration --moduleResolution node",
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",