jrx 0.3.0-alpha.1 → 0.3.0-alpha.2

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.
Files changed (4) hide show
  1. package/README.md +70 -56
  2. package/index.d.ts +13 -7
  3. package/index.js +29 -23
  4. package/package.json +1 -1
package/README.md CHANGED
@@ -28,12 +28,12 @@ npm i jrx
28
28
  - [`makeReset()`](#makereset) - Create a resettable `DisposableStack`
29
29
  - [`makeAsyncReset()`](#makeasyncreset) - Create a resettable `AsyncDisposableStack`
30
30
  - [`makeRenderLoop()`](#makerenderloop) - Render loops with automatic cleanup
31
- - [`addInterval(cb, ms)`](#addintervalcb-ms) - Repeating intervals with cleanup
32
- - [`addIntervalAsync(cb, ms)`](#addintervalasynccb-ms) - Async intervals with cancellation
33
- - [`addRequestAnimationFrame(cb)`](#addrequestanimationframecb) - Single animation frame with cleanup
34
- - [`addRequestAnimationFrameLoop(cb)`](#addrequestanimationframeloopcb) - Animation frame loops
35
- - [`addTimeout(cb, ms)`](#addtimeoutcb-ms) - Timeouts with cleanup
36
- - [`addTransition(cb, durationMs)`](#addtransitioncb-durationms) - Progress-based animations
31
+ - [`makeInterval(cb, ms)`](#makeintervalcb-ms) - Repeating intervals with cleanup
32
+ - [`makeIntervalAsync(cb, ms)`](#makeintervalasynccb-ms) - Async intervals with cancellation
33
+ - [`makeAnimationFrame(cb)`](#makeanimationframecb) - Single animation frame with cleanup
34
+ - [`makeAnimationFrameLoop(cb)`](#makeanimationframeloopcb) - Animation frame loops
35
+ - [`makeTimeout(cb, ms)`](#maketimeoutcb-ms) - Timeouts with cleanup
36
+ - [`makeTransition(cb, durationMs)`](#maketransitioncb-durationms) - Progress-based animations
37
37
  - [`computed(fn, getDeps?)`](#computedfn-getdeps) - Memoized computed values
38
38
  - [`retry(cb, backoffSec?)`](#retrycb-backoffsec) - Retry with exponential backoff
39
39
 
@@ -76,13 +76,15 @@ import {makeRenderLoop} from 'jrx'
76
76
 
77
77
  const {loop, setLoop} = makeRenderLoop()
78
78
 
79
- // Set the loop function
80
- const dispose = setLoop((time) => {
79
+ // Set the loop function - returns a Disposable
80
+ const handle = setLoop((time) => {
81
81
  console.log('Frame time:', time)
82
82
 
83
- // Optional: return cleanup function
84
- return () => {
85
- console.log('Cleanup previous frame')
83
+ // Optional: return a Disposable for cleanup
84
+ return {
85
+ [Symbol.dispose]() {
86
+ console.log('Cleanup previous frame')
87
+ },
86
88
  }
87
89
  })
88
90
 
@@ -90,41 +92,43 @@ const dispose = setLoop((time) => {
90
92
  requestAnimationFrame(loop)
91
93
 
92
94
  // Cleanup
93
- dispose()
95
+ handle[Symbol.dispose]()
94
96
  ```
95
97
 
96
- ### `addInterval(cb, ms)`
98
+ ### `makeInterval(cb, ms)`
97
99
 
98
- Creates a repeating interval with cleanup. The callback can optionally return a cleanup function that runs before the next invocation.
100
+ Creates a repeating interval with cleanup. The callback can optionally return a `Disposable` that is disposed before the next invocation. Returns a `Disposable`.
99
101
 
100
102
  **Note:** The callback fires **immediately** on first call, then waits `ms` milliseconds **after** the previous callback completes. This is not a fixed-rate timer.
101
103
 
102
104
  ```typescript
103
- import { addInterval } from 'jrx'
105
+ import {makeInterval} from 'jrx'
104
106
 
105
- const dispose = addInterval(() => {
107
+ const handle = makeInterval(() => {
106
108
  console.log('Tick') // Called immediately, then every 1000ms after completion
107
109
 
108
- // Optional: return cleanup function
109
- return () => {
110
- console.log('Cleanup')
110
+ // Optional: return a Disposable for cleanup
111
+ return {
112
+ [Symbol.dispose]() {
113
+ console.log('Cleanup')
114
+ },
111
115
  }
112
116
  }, 1000)
113
117
 
114
118
  // Stop the interval
115
- dispose()
119
+ handle[Symbol.dispose]()
116
120
  ```
117
121
 
118
- ### `addIntervalAsync(cb, ms)`
122
+ ### `makeIntervalAsync(cb, ms)`
119
123
 
120
- Async version of `addInterval`. Waits for the callback to complete before scheduling the next invocation.
124
+ Async version of `makeInterval`. Waits for the callback to complete before scheduling the next invocation.
121
125
 
122
126
  **Note:** The callback fires **immediately** on first call, then waits `ms` milliseconds **after** the previous async callback completes.
123
127
 
124
128
  ```typescript
125
- import { addIntervalAsync } from 'jrx'
129
+ import {makeIntervalAsync} from 'jrx'
126
130
 
127
- const dispose = addIntervalAsync(async () => {
131
+ const dispose = makeIntervalAsync(async () => {
128
132
  // Called immediately, then 5000ms after each completion
129
133
  await fetchData()
130
134
  processData()
@@ -133,19 +137,21 @@ const dispose = addIntervalAsync(async () => {
133
137
  dispose()
134
138
  ```
135
139
 
136
- ### `addRequestAnimationFrame(cb)`
140
+ ### `makeAnimationFrame(cb)`
137
141
 
138
142
  Executes a callback on the next animation frame with cleanup.
139
143
 
140
144
  ```typescript
141
- import { addRequestAnimationFrame } from 'jrx'
145
+ import {makeAnimationFrame} from 'jrx'
142
146
 
143
- const dispose = addRequestAnimationFrame((now) => {
147
+ const dispose = makeAnimationFrame((now) => {
144
148
  updateAnimation(now)
145
149
 
146
- // Optional: return cleanup function
147
- return () => {
148
- cleanupAnimation()
150
+ // Optional: return a Disposable for cleanup
151
+ return {
152
+ [Symbol.dispose]() {
153
+ cleanupAnimation()
154
+ },
149
155
  }
150
156
  })
151
157
 
@@ -153,19 +159,21 @@ const dispose = addRequestAnimationFrame((now) => {
153
159
  dispose()
154
160
  ```
155
161
 
156
- ### `addRequestAnimationFrameLoop(cb)`
162
+ ### `makeAnimationFrameLoop(cb)`
157
163
 
158
164
  Creates a continuous `requestAnimationFrame` loop with cleanup.
159
165
 
160
166
  ```typescript
161
- import { addRequestAnimationFrameLoop } from 'jrx'
167
+ import {makeAnimationFrameLoop} from 'jrx'
162
168
 
163
- const dispose = addRequestAnimationFrameLoop((now) => {
169
+ const dispose = makeAnimationFrameLoop((now) => {
164
170
  updateAnimation(now)
165
171
 
166
- // Optional: return cleanup function
167
- return () => {
168
- cleanupAnimation()
172
+ // Optional: return a Disposable for cleanup
173
+ return {
174
+ [Symbol.dispose]() {
175
+ cleanupAnimation()
176
+ },
169
177
  }
170
178
  })
171
179
 
@@ -173,14 +181,14 @@ const dispose = addRequestAnimationFrameLoop((now) => {
173
181
  dispose()
174
182
  ```
175
183
 
176
- ### `addTimeout(cb, ms)`
184
+ ### `makeTimeout(cb, ms)`
177
185
 
178
186
  Creates a timeout with cleanup.
179
187
 
180
188
  ```typescript
181
- import { addTimeout } from 'jrx'
189
+ import {makeTimeout} from 'jrx'
182
190
 
183
- const cancel = addTimeout(() => {
191
+ const cancel = makeTimeout(() => {
184
192
  console.log('Timeout fired')
185
193
  }, 1000)
186
194
 
@@ -188,23 +196,25 @@ const cancel = addTimeout(() => {
188
196
  cancel()
189
197
  ```
190
198
 
191
- ### `addTransition(cb, durationMs)`
199
+ ### `makeTransition(cb, durationMs)`
192
200
 
193
- Creates an animation transition with progress tracking (0 to 1).
201
+ Creates an animation transition with progress tracking (0 to 1). Returns a `Disposable`.
194
202
 
195
203
  ```typescript
196
- import { addTransition } from 'jrx'
204
+ import {makeTransition} from 'jrx'
197
205
 
198
- const dispose = addTransition((progress) => {
206
+ const handle = makeTransition((progress) => {
199
207
  element.style.opacity = progress.toString()
200
208
 
201
- // Optional: return cleanup function
202
- return () => {
203
- console.log('Frame cleanup')
209
+ // Optional: return a Disposable for cleanup
210
+ return {
211
+ [Symbol.dispose]() {
212
+ console.log('Frame cleanup')
213
+ },
204
214
  }
205
215
  }, 1000)
206
216
 
207
- dispose()
217
+ handle[Symbol.dispose]()
208
218
  ```
209
219
 
210
220
  ### `computed(fn, getDeps?)`
@@ -212,7 +222,7 @@ dispose()
212
222
  Creates a memoized computed value with optional dependency tracking.
213
223
 
214
224
  ```typescript
215
- import { computed } from 'jrx'
225
+ import {computed} from 'jrx'
216
226
 
217
227
  // Without dependencies - always recomputes
218
228
  const value1 = computed(() => expensiveCalculation())
@@ -279,18 +289,22 @@ const data = await r // undefined
279
289
 
280
290
  ## Cleanup Pattern
281
291
 
282
- All effect functions return a dispose function that stops the effect and runs any pending cleanup:
292
+ Functions that manage ongoing effects return either a dispose function or a `Disposable` object:
283
293
 
284
294
  ```typescript
285
- import {addInterval, addTimeout, addRequestAnimationFrame} from 'jrx'
295
+ import {makeInterval, makeTimeout, makeAnimationFrame, makeTransition} from 'jrx'
296
+
297
+ // Functions returning Disposable objects (use with `using` or call [Symbol.dispose]())
298
+ const interval = makeInterval(() => console.log('tick'), 1000)
299
+ const transition = makeTransition((p) => console.log(p), 1000)
300
+
301
+ interval[Symbol.dispose]()
302
+ transition[Symbol.dispose]()
286
303
 
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))
304
+ // Functions returning dispose functions (call directly)
305
+ const disposeTimeout = makeTimeout(() => console.log('timeout'), 5000)
306
+ const disposeRaf = makeAnimationFrame((now) => render(now))
291
307
 
292
- // Call the dispose function to stop the effect
293
- disposeInterval()
294
308
  disposeTimeout()
295
309
  disposeRaf()
296
310
  ```
package/index.d.ts CHANGED
@@ -5,11 +5,17 @@ export declare function makeReset(): () => DisposableStack;
5
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) => undefined | (() => void)): () => void;
8
+ setLoop(this: void, loop: (time: DOMHighResTimeStamp) => undefined | Disposable): {
9
+ [Symbol.dispose](): void;
10
+ };
11
+ };
12
+ export declare function makeInterval(cb: () => undefined | Disposable, ms: number): {
13
+ [Symbol.dispose](): void;
14
+ };
15
+ export declare function makeIntervalAsync(cb: () => void | Disposable | Promise<void>, ms: number): () => void;
16
+ export declare function makeAnimationFrame(cb: (now: DOMHighResTimeStamp) => undefined | Disposable): () => void;
17
+ export declare function makeAnimationFrameLoop(cb: (now: DOMHighResTimeStamp) => undefined | Disposable): () => void;
18
+ export declare function makeTimeout(cb: () => void, ms: number): () => void;
19
+ export declare function makeTransition(cb: (progress: number) => undefined | Disposable, durationMs: number): {
20
+ [Symbol.dispose](): void;
9
21
  };
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;
14
- export declare function addTimeout(cb: () => void, ms: number): () => void;
15
- export declare function addTransition(cb: (progress: number) => undefined | (() => void), durationMs: number): () => void;
package/index.js CHANGED
@@ -20,31 +20,35 @@ export function makeRenderLoop() {
20
20
  const reset = makeReset();
21
21
  return {
22
22
  loop(time) {
23
- reset().adopt(loop_?.(time), v => v?.());
23
+ reset().use(loop_?.(time));
24
24
  },
25
25
  setLoop(loop) {
26
26
  loop_ = loop;
27
- return () => {
28
- reset();
29
- loop_ = undefined;
27
+ return {
28
+ [Symbol.dispose]() {
29
+ reset();
30
+ loop_ = undefined;
31
+ },
30
32
  };
31
33
  },
32
34
  };
33
35
  }
34
- export function addInterval(cb, ms) {
36
+ export function makeInterval(cb, ms) {
35
37
  const reset = makeReset();
36
38
  let timeout;
37
39
  wrapper();
38
- return () => {
39
- reset();
40
- clearTimeout(timeout);
40
+ return {
41
+ [Symbol.dispose]() {
42
+ reset();
43
+ clearTimeout(timeout);
44
+ },
41
45
  };
42
46
  function wrapper() {
43
- reset().adopt(cb(), v => v?.());
47
+ reset().use(cb());
44
48
  timeout = setTimeout(wrapper, ms);
45
49
  }
46
50
  }
47
- export function addIntervalAsync(cb, ms) {
51
+ export function makeIntervalAsync(cb, ms) {
48
52
  const reset = makeReset();
49
53
  let timeout;
50
54
  void wrapper();
@@ -54,24 +58,24 @@ export function addIntervalAsync(cb, ms) {
54
58
  };
55
59
  async function wrapper() {
56
60
  const stack = reset();
57
- await stack.adopt(cb(), v => v?.[Symbol.dispose]?.());
61
+ await stack.adopt(cb(), (v) => v?.[Symbol.dispose]?.());
58
62
  if (!stack.disposed)
59
63
  timeout = setTimeout(wrapper, ms);
60
64
  }
61
65
  }
62
- export function addRequestAnimationFrame(cb) {
66
+ export function makeAnimationFrame(cb) {
63
67
  const stack = new DisposableStack();
64
68
  const raf = requestAnimationFrame(now => {
65
69
  if (stack.disposed)
66
70
  return;
67
- stack.adopt(cb(now), v => v?.());
71
+ stack.use(cb(now));
68
72
  });
69
73
  return () => {
70
74
  stack.dispose();
71
75
  cancelAnimationFrame(raf);
72
76
  };
73
77
  }
74
- export function addRequestAnimationFrameLoop(cb) {
78
+ export function makeAnimationFrameLoop(cb) {
75
79
  const reset = makeReset();
76
80
  let raf = requestAnimationFrame(wrapper);
77
81
  return () => {
@@ -79,34 +83,36 @@ export function addRequestAnimationFrameLoop(cb) {
79
83
  cancelAnimationFrame(raf);
80
84
  };
81
85
  function wrapper(now) {
82
- reset().adopt(cb(now), v => v?.());
86
+ reset().use(cb(now));
83
87
  raf = requestAnimationFrame(wrapper);
84
88
  }
85
89
  }
86
- export function addTimeout(cb, ms) {
90
+ export function makeTimeout(cb, ms) {
87
91
  const timeout = setTimeout(cb, ms);
88
92
  return () => clearTimeout(timeout);
89
93
  }
90
- export function addTransition(cb, durationMs) {
94
+ export function makeTransition(cb, durationMs) {
91
95
  const reset = makeReset();
92
96
  let start;
93
97
  let raf = requestAnimationFrame(wrapper);
94
- return () => {
95
- reset();
96
- cancelAnimationFrame(raf);
98
+ return {
99
+ [Symbol.dispose]() {
100
+ reset();
101
+ cancelAnimationFrame(raf);
102
+ },
97
103
  };
98
104
  function wrapper(now) {
99
105
  if (start === undefined) {
100
106
  start = now;
101
- reset().adopt(cb(0), v => v?.());
107
+ reset().use(cb(0));
102
108
  raf = requestAnimationFrame(wrapper);
103
109
  }
104
110
  else {
105
111
  const progress = (now - start) / durationMs;
106
112
  if (progress >= 1)
107
- reset().adopt(cb(1), v => v?.());
113
+ reset().use(cb(1));
108
114
  else {
109
- reset().adopt(cb(progress), v => v?.());
115
+ reset().use(cb(progress));
110
116
  raf = requestAnimationFrame(wrapper);
111
117
  }
112
118
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jrx",
3
- "version": "0.3.0-alpha.1",
3
+ "version": "0.3.0-alpha.2",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "prepublishOnly": "npx tsc",