jrx 0.1.0 → 0.2.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 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
@@ -20,6 +20,7 @@ npm install jrx
20
20
  ## API Overview
21
21
 
22
22
  - [`makeRenderLoop()`](#makerenderloop) - Render loops with automatic cleanup
23
+ - [`addEvtListener(target, event, handler, option?)`](#addevtlistenertarget-event-handler-option) - Event listeners with cleanup
23
24
  - [`addInterval(cb, ms)`](#addintervalcb-ms) - Repeating intervals with cleanup
24
25
  - [`addIntervalAsync(cb, ms)`](#addintervalasynccb-ms) - Async intervals with cancellation
25
26
  - [`addRequestAnimationFrame(cb)`](#addrequestanimationframecb) - Single animation frame with cleanup
@@ -29,6 +30,7 @@ npm install jrx
29
30
  - [`addTransition(cb, durationMs)`](#addtransitioncb-durationms) - Progress-based animations
30
31
  - [`computed(fn, getDeps?)`](#computedfn-getdeps) - Memoized computed values
31
32
  - [`retry(cb, options?)`](#retrycb-options) - Async retry with exponential backoff
33
+ - [`addRetry(cb, options?)`](#addretrycb-options) - Fire-and-forget retry with disposal
32
34
 
33
35
  ## API
34
36
 
@@ -58,6 +60,28 @@ requestAnimationFrame(loop)
58
60
  dispose()
59
61
  ```
60
62
 
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
+
61
85
  ### `addInterval(cb, ms)`
62
86
 
63
87
  Creates a repeating interval with cleanup. The callback can optionally return a cleanup function that runs before the next invocation.
@@ -238,7 +262,7 @@ Retries an async operation with exponential backoff on failure.
238
262
  **Default backoff:** `[5, 5, 10, 10, 20, 20, 40, 40, 60, -1]` seconds (where `-1` means retry forever with 60s delay)
239
263
 
240
264
  ```typescript
241
- import retry from 'jrx/retry'
265
+ import {retry} from 'jrx'
242
266
 
243
267
  // Basic usage - retries with default backoff
244
268
  const result = await retry(async (disposer, { resetBackoff }) => {
@@ -298,6 +322,37 @@ console.log(data) // T | undefined
298
322
  - `disposer`: A disposer for the current retry attempt. Check `disposer.signal.aborted` to handle cancellation
299
323
  - `info.resetBackoff()`: Call this to reset the backoff counter to the beginning (useful when making partial progress)
300
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()
351
+ ```
352
+
353
+ **Options:**
354
+ - `backoffSec`: Array of retry delays in seconds (same as `retry`)
355
+
301
356
  ## Cleanup Pattern
302
357
 
303
358
  All functions return disposer functions that clean up resources:
@@ -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, { addRetry } from './retry.js';
3
+ import computed from './computed.js';
4
+ import addEvtListener from './addEvtListener.js';
5
+ export { retry, computed, addEvtListener, addRetry };
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;
package/index.js CHANGED
@@ -1,4 +1,8 @@
1
1
  import { makeDisposer, makeReset } from 'jdisposer';
2
+ import retry, { addRetry } from './retry.js';
3
+ import computed from './computed.js';
4
+ import addEvtListener from './addEvtListener.js';
5
+ export { retry, computed, addEvtListener, addRetry };
2
6
  export function makeRenderLoop() {
3
7
  let loop_;
4
8
  const reset = makeReset();
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "jrx",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
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",
package/retry.d.ts CHANGED
@@ -10,3 +10,8 @@ export default function retry<T>(cb: (disposer: Disposer, info: {
10
10
  backoffSec?: number[];
11
11
  disposer: Disposer;
12
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;
package/retry.js CHANGED
@@ -1,4 +1,4 @@
1
- import { makeReset } from 'jdisposer';
1
+ import { makeDisposer, makeReset } from 'jdisposer';
2
2
  export default async function retry(cb, { backoffSec = [5, 5, 10, 10, 20, 20, 40, 40, 60, -1], // -1: retry forever with the last backoff . first element must not be -1
3
3
  disposer, } = {}) {
4
4
  const reset = makeReset();
@@ -30,3 +30,8 @@ disposer, } = {}) {
30
30
  }
31
31
  }
32
32
  }
33
+ export function addRetry(cb, options) {
34
+ const disposer = makeDisposer();
35
+ void retry(cb, { ...options, disposer });
36
+ return disposer.dispose;
37
+ }