jrx 0.3.0-alpha.1 → 0.3.0-alpha.3

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 +79 -67
  2. package/index.d.ts +22 -8
  3. package/index.js +49 -33
  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,121 +92,129 @@ 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. Returns a `Disposable`.
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 handle = makeIntervalAsync(async () => {
128
132
  // Called immediately, then 5000ms after each completion
129
133
  await fetchData()
130
134
  processData()
131
135
  }, 5000)
132
136
 
133
- dispose()
137
+ handle[Symbol.dispose]()
134
138
  ```
135
139
 
136
- ### `addRequestAnimationFrame(cb)`
140
+ ### `makeAnimationFrame(cb)`
137
141
 
138
- Executes a callback on the next animation frame with cleanup.
142
+ Executes a callback on the next animation frame with cleanup. Returns a `Disposable`.
139
143
 
140
144
  ```typescript
141
- import { addRequestAnimationFrame } from 'jrx'
145
+ import {makeAnimationFrame} from 'jrx'
142
146
 
143
- const dispose = addRequestAnimationFrame((now) => {
147
+ const handle = 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
 
152
158
  // Cancel if needed before the frame fires
153
- dispose()
159
+ handle[Symbol.dispose]()
154
160
  ```
155
161
 
156
- ### `addRequestAnimationFrameLoop(cb)`
162
+ ### `makeAnimationFrameLoop(cb)`
157
163
 
158
- Creates a continuous `requestAnimationFrame` loop with cleanup.
164
+ Creates a continuous `requestAnimationFrame` loop with cleanup. Returns a `Disposable`.
159
165
 
160
166
  ```typescript
161
- import { addRequestAnimationFrameLoop } from 'jrx'
167
+ import {makeAnimationFrameLoop} from 'jrx'
162
168
 
163
- const dispose = addRequestAnimationFrameLoop((now) => {
169
+ const handle = 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
 
172
180
  // Stop the loop
173
- dispose()
181
+ handle[Symbol.dispose]()
174
182
  ```
175
183
 
176
- ### `addTimeout(cb, ms)`
184
+ ### `makeTimeout(cb, ms)`
177
185
 
178
- Creates a timeout with cleanup.
186
+ Creates a timeout with cleanup. Returns a `Disposable`.
179
187
 
180
188
  ```typescript
181
- import { addTimeout } from 'jrx'
189
+ import {makeTimeout} from 'jrx'
182
190
 
183
- const cancel = addTimeout(() => {
191
+ const handle = makeTimeout(() => {
184
192
  console.log('Timeout fired')
185
193
  }, 1000)
186
194
 
187
195
  // Cancel if needed
188
- cancel()
196
+ handle[Symbol.dispose]()
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,20 +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
+ All effect functions return a `Disposable` object that stops the effect and runs any pending cleanup:
283
293
 
284
294
  ```typescript
285
- import {addInterval, addTimeout, addRequestAnimationFrame} from 'jrx'
286
-
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))
291
-
292
- // Call the dispose function to stop the effect
293
- disposeInterval()
294
- disposeTimeout()
295
- disposeRaf()
295
+ import {makeInterval, makeTimeout, makeAnimationFrame, makeTransition} from 'jrx'
296
+
297
+ // Each function returns a Disposable
298
+ const interval = makeInterval(() => console.log('tick'), 1000)
299
+ const timeout = makeTimeout(() => console.log('timeout'), 5000)
300
+ const raf = makeAnimationFrame((now) => render(now))
301
+ const transition = makeTransition((p) => console.log(p), 1000)
302
+
303
+ // Call [Symbol.dispose]() to stop the effect
304
+ interval[Symbol.dispose]()
305
+ timeout[Symbol.dispose]()
306
+ raf[Symbol.dispose]()
307
+ transition[Symbol.dispose]()
296
308
  ```
297
309
 
298
310
  ## TypeScript
package/index.d.ts CHANGED
@@ -5,11 +5,25 @@ 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;
9
- };
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;
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): {
16
+ [Symbol.dispose](): void;
17
+ };
18
+ export declare function makeAnimationFrame(cb: (now: DOMHighResTimeStamp) => undefined | Disposable): {
19
+ [Symbol.dispose](): void;
20
+ };
21
+ export declare function makeAnimationFrameLoop(cb: (now: DOMHighResTimeStamp) => undefined | Disposable): {
22
+ [Symbol.dispose](): void;
23
+ };
24
+ export declare function makeTimeout(cb: () => void, ms: number): {
25
+ [Symbol.dispose](): void;
26
+ };
27
+ export declare function makeTransition(cb: (progress: number) => undefined | Disposable, durationMs: number): {
28
+ [Symbol.dispose](): void;
29
+ };
package/index.js CHANGED
@@ -20,93 +20,109 @@ 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();
51
- return () => {
52
- reset();
53
- clearTimeout(timeout);
55
+ return {
56
+ [Symbol.dispose]() {
57
+ reset();
58
+ clearTimeout(timeout);
59
+ },
54
60
  };
55
61
  async function wrapper() {
56
62
  const stack = reset();
57
- await stack.adopt(cb(), v => v?.[Symbol.dispose]?.());
63
+ await stack.adopt(cb(), (v) => v?.[Symbol.dispose]?.());
58
64
  if (!stack.disposed)
59
65
  timeout = setTimeout(wrapper, ms);
60
66
  }
61
67
  }
62
- export function addRequestAnimationFrame(cb) {
68
+ export function makeAnimationFrame(cb) {
63
69
  const stack = new DisposableStack();
64
70
  const raf = requestAnimationFrame(now => {
65
71
  if (stack.disposed)
66
72
  return;
67
- stack.adopt(cb(now), v => v?.());
73
+ stack.use(cb(now));
68
74
  });
69
- return () => {
70
- stack.dispose();
71
- cancelAnimationFrame(raf);
75
+ return {
76
+ [Symbol.dispose]() {
77
+ stack.dispose();
78
+ cancelAnimationFrame(raf);
79
+ },
72
80
  };
73
81
  }
74
- export function addRequestAnimationFrameLoop(cb) {
82
+ export function makeAnimationFrameLoop(cb) {
75
83
  const reset = makeReset();
76
84
  let raf = requestAnimationFrame(wrapper);
77
- return () => {
78
- reset();
79
- cancelAnimationFrame(raf);
85
+ return {
86
+ [Symbol.dispose]() {
87
+ reset();
88
+ cancelAnimationFrame(raf);
89
+ },
80
90
  };
81
91
  function wrapper(now) {
82
- reset().adopt(cb(now), v => v?.());
92
+ reset().use(cb(now));
83
93
  raf = requestAnimationFrame(wrapper);
84
94
  }
85
95
  }
86
- export function addTimeout(cb, ms) {
96
+ export function makeTimeout(cb, ms) {
87
97
  const timeout = setTimeout(cb, ms);
88
- return () => clearTimeout(timeout);
98
+ return {
99
+ [Symbol.dispose]() {
100
+ clearTimeout(timeout);
101
+ },
102
+ };
89
103
  }
90
- export function addTransition(cb, durationMs) {
104
+ export function makeTransition(cb, durationMs) {
91
105
  const reset = makeReset();
92
106
  let start;
93
107
  let raf = requestAnimationFrame(wrapper);
94
- return () => {
95
- reset();
96
- cancelAnimationFrame(raf);
108
+ return {
109
+ [Symbol.dispose]() {
110
+ reset();
111
+ cancelAnimationFrame(raf);
112
+ },
97
113
  };
98
114
  function wrapper(now) {
99
115
  if (start === undefined) {
100
116
  start = now;
101
- reset().adopt(cb(0), v => v?.());
117
+ reset().use(cb(0));
102
118
  raf = requestAnimationFrame(wrapper);
103
119
  }
104
120
  else {
105
121
  const progress = (now - start) / durationMs;
106
122
  if (progress >= 1)
107
- reset().adopt(cb(1), v => v?.());
123
+ reset().use(cb(1));
108
124
  else {
109
- reset().adopt(cb(progress), v => v?.());
125
+ reset().use(cb(progress));
110
126
  raf = requestAnimationFrame(wrapper);
111
127
  }
112
128
  }
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.3",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "prepublishOnly": "npx tsc",