jrx 0.3.0-alpha.5 → 0.3.0-alpha.7

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
@@ -28,14 +28,33 @@ 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
- - [`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
31
+ - [`createInterval(cb, ms)`](#createIntervalcb-ms) - Repeating intervals with cleanup
32
+ - [`createIntervalAsync(cb, ms)`](#createIntervalasynccb-ms) - Async intervals with cancellation
33
+ - [`createAnimationFrame(cb)`](#createAnimationFramecb) - Single animation frame with cleanup
34
+ - [`createAnimationFrameLoop(cb)`](#createAnimationFrameloopcb) - Animation frame loops
35
+ - [`createTimeout(cb, ms)`](#createTimeoutcb-ms) - Timeouts with cleanup
36
+ - [`createTransition(cb, durationMs)`](#createTransitioncb-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
+ - [`assignDispose(value, disposable)`](#assigndisposevalue-disposable) - Attach a `Symbol.dispose` to any value
40
+
41
+ ## Naming convention
42
+
43
+ Functions prefixed with `create*` return a `Disposable` object. The returned object can be passed to [`DisposableStack.use()`](https://github.com/tc39/proposal-explicit-resource-management) or bound with the `using` keyword for automatic cleanup.
44
+
45
+ ```typescript
46
+ import {createInterval, createTimeout} from 'jrx'
47
+
48
+ // With DisposableStack.use()
49
+ using stack = new DisposableStack()
50
+ stack.use(createInterval(() => console.log('tick'), 1000))
51
+ stack.use(createTimeout(() => console.log('done'), 5000))
52
+ // Both are disposed when `stack` goes out of scope
53
+
54
+ // Or directly with `using`
55
+ using interval = createInterval(() => console.log('tick'), 1000)
56
+ // Disposed when the enclosing block exits
57
+ ```
39
58
 
40
59
  ## API
41
60
 
@@ -95,16 +114,16 @@ requestAnimationFrame(loop)
95
114
  handle[Symbol.dispose]()
96
115
  ```
97
116
 
98
- ### `makeInterval(cb, ms)`
117
+ ### `createInterval(cb, ms)`
99
118
 
100
119
  Creates a repeating interval with cleanup. The callback can optionally return a `Disposable` that is disposed before the next invocation. Returns a `Disposable`.
101
120
 
102
121
  **Note:** The callback fires **immediately** on first call, then waits `ms` milliseconds **after** the previous callback completes. This is not a fixed-rate timer.
103
122
 
104
123
  ```typescript
105
- import {makeInterval} from 'jrx'
124
+ import {createInterval} from 'jrx'
106
125
 
107
- const handle = makeInterval(() => {
126
+ const handle = createInterval(() => {
108
127
  console.log('Tick') // Called immediately, then every 1000ms after completion
109
128
 
110
129
  // Optional: return a Disposable for cleanup
@@ -119,16 +138,16 @@ const handle = makeInterval(() => {
119
138
  handle[Symbol.dispose]()
120
139
  ```
121
140
 
122
- ### `makeIntervalAsync(cb, ms)`
141
+ ### `createIntervalAsync(cb, ms)`
123
142
 
124
- Async version of `makeInterval`. Waits for the callback to complete before scheduling the next invocation. Returns a `Disposable`.
143
+ Async version of `createInterval`. Waits for the callback to complete before scheduling the next invocation. Returns a `Disposable`.
125
144
 
126
145
  **Note:** The callback fires **immediately** on first call, then waits `ms` milliseconds **after** the previous async callback completes.
127
146
 
128
147
  ```typescript
129
- import {makeIntervalAsync} from 'jrx'
148
+ import {createIntervalAsync} from 'jrx'
130
149
 
131
- const handle = makeIntervalAsync(async () => {
150
+ const handle = createIntervalAsync(async () => {
132
151
  // Called immediately, then 5000ms after each completion
133
152
  await fetchData()
134
153
  processData()
@@ -137,14 +156,14 @@ const handle = makeIntervalAsync(async () => {
137
156
  handle[Symbol.dispose]()
138
157
  ```
139
158
 
140
- ### `makeAnimationFrame(cb)`
159
+ ### `createAnimationFrame(cb)`
141
160
 
142
161
  Executes a callback on the next animation frame with cleanup. Returns a `Disposable`.
143
162
 
144
163
  ```typescript
145
- import {makeAnimationFrame} from 'jrx'
164
+ import {createAnimationFrame} from 'jrx'
146
165
 
147
- const handle = makeAnimationFrame((now) => {
166
+ const handle = createAnimationFrame((now) => {
148
167
  updateAnimation(now)
149
168
 
150
169
  // Optional: return a Disposable for cleanup
@@ -159,14 +178,14 @@ const handle = makeAnimationFrame((now) => {
159
178
  handle[Symbol.dispose]()
160
179
  ```
161
180
 
162
- ### `makeAnimationFrameLoop(cb)`
181
+ ### `createAnimationFrameLoop(cb)`
163
182
 
164
183
  Creates a continuous `requestAnimationFrame` loop with cleanup. Returns a `Disposable`.
165
184
 
166
185
  ```typescript
167
- import {makeAnimationFrameLoop} from 'jrx'
186
+ import {createAnimationFrameLoop} from 'jrx'
168
187
 
169
- const handle = makeAnimationFrameLoop((now) => {
188
+ const handle = createAnimationFrameLoop((now) => {
170
189
  updateAnimation(now)
171
190
 
172
191
  // Optional: return a Disposable for cleanup
@@ -181,14 +200,14 @@ const handle = makeAnimationFrameLoop((now) => {
181
200
  handle[Symbol.dispose]()
182
201
  ```
183
202
 
184
- ### `makeTimeout(cb, ms)`
203
+ ### `createTimeout(cb, ms)`
185
204
 
186
205
  Creates a timeout with cleanup. Returns a `Disposable`.
187
206
 
188
207
  ```typescript
189
- import {makeTimeout} from 'jrx'
208
+ import {createTimeout} from 'jrx'
190
209
 
191
- const handle = makeTimeout(() => {
210
+ const handle = createTimeout(() => {
192
211
  console.log('Timeout fired')
193
212
  }, 1000)
194
213
 
@@ -196,14 +215,14 @@ const handle = makeTimeout(() => {
196
215
  handle[Symbol.dispose]()
197
216
  ```
198
217
 
199
- ### `makeTransition(cb, durationMs)`
218
+ ### `createTransition(cb, durationMs)`
200
219
 
201
220
  Creates an animation transition with progress tracking (0 to 1). Returns a `Disposable`.
202
221
 
203
222
  ```typescript
204
- import {makeTransition} from 'jrx'
223
+ import {createTransition} from 'jrx'
205
224
 
206
- const handle = makeTransition((progress) => {
225
+ const handle = createTransition((progress) => {
207
226
  element.style.opacity = progress.toString()
208
227
 
209
228
  // Optional: return a Disposable for cleanup
@@ -287,18 +306,53 @@ const data = await r // undefined
287
306
  - `cb`: Callback that returns `Disposable & (T | Promise<T>)`. Receives `{ resetBackoff() }` to reset the backoff counter.
288
307
  - `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]`
289
308
 
309
+ ### `assignDispose(value, disposable)`
310
+
311
+ Attaches a `Symbol.dispose` to an existing value (object, function, array, promise, etc.) that delegates to a given `Disposable`. Returns the same value, now also typed as `Disposable`.
312
+
313
+ This is useful when a function needs to return a meaningful value **and** be disposable — for example, returning a promise, a function, or a tuple from a factory while still allowing the caller to clean up the underlying resources.
314
+
315
+ ```typescript
316
+ import {assignDispose} from 'jrx'
317
+
318
+ function makeThing() {
319
+ using stack = new DisposableStack()
320
+ stack.defer(() => console.log('cleanup'))
321
+
322
+ const obj = {value: 42}
323
+ return assignDispose(obj, stack.move())
324
+ }
325
+ ```
326
+
327
+ Async factory — wrap an in-flight promise so the caller can dispose the underlying resources mid-flight:
328
+
329
+ ```typescript
330
+ import {assignDispose} from 'jrx'
331
+
332
+ function loadThing() {
333
+ const stack = new DisposableStack()
334
+ return assignDispose(
335
+ (async () => {
336
+ const res = await fetch('/api/data', {signal: stack.adopt(new AbortController(), c => c.abort()).signal})
337
+ return await res.json()
338
+ })(),
339
+ stack,
340
+ )
341
+ }
342
+ ```
343
+
290
344
  ## Cleanup Pattern
291
345
 
292
346
  All effect functions return a `Disposable` object that stops the effect and runs any pending cleanup:
293
347
 
294
348
  ```typescript
295
- import {makeInterval, makeTimeout, makeAnimationFrame, makeTransition} from 'jrx'
349
+ import {createInterval, createTimeout, createAnimationFrame, createTransition} from 'jrx'
296
350
 
297
351
  // 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)
352
+ const interval = createInterval(() => console.log('tick'), 1000)
353
+ const timeout = createTimeout(() => console.log('timeout'), 5000)
354
+ const raf = createAnimationFrame((now) => render(now))
355
+ const transition = createTransition((p) => console.log(p), 1000)
302
356
 
303
357
  // Call [Symbol.dispose]() to stop the effect
304
358
  interval[Symbol.dispose]()
package/index.d.ts CHANGED
@@ -9,21 +9,24 @@ export declare function makeRenderLoop(): {
9
9
  [Symbol.dispose](): void;
10
10
  };
11
11
  };
12
- export declare function makeInterval(cb: () => undefined | Disposable, ms: number): {
12
+ export declare function createInterval(cb: () => undefined | Disposable, ms: number): {
13
13
  [Symbol.dispose](): void;
14
14
  };
15
- export declare function makeIntervalAsync(cb: () => void | Disposable | Promise<void>, ms: number): {
15
+ export declare function createIntervalAsync(cb: () => void | Disposable | Promise<void>, ms: number): {
16
16
  [Symbol.dispose](): void;
17
17
  };
18
- export declare function makeAnimationFrame(cb: (now: DOMHighResTimeStamp) => undefined | Disposable): {
18
+ export declare function createAnimationFrame(cb: (now: DOMHighResTimeStamp) => undefined | Disposable): {
19
19
  [Symbol.dispose](): void;
20
20
  };
21
- export declare function makeAnimationFrameLoop(cb: (now: DOMHighResTimeStamp) => undefined | Disposable): {
21
+ export declare function createAnimationFrameLoop(cb: (now: DOMHighResTimeStamp) => undefined | Disposable): {
22
22
  [Symbol.dispose](): void;
23
23
  };
24
- export declare function makeTimeout(cb: () => void, ms: number): {
24
+ export declare function createTimeout(cb: () => void, ms: number): {
25
25
  [Symbol.dispose](): void;
26
26
  };
27
- export declare function makeTransition(cb: (progress: number) => undefined | Disposable, durationMs: number): {
27
+ export declare function createTransition(cb: (progress: number) => undefined | Disposable, durationMs: number): {
28
28
  [Symbol.dispose](): void;
29
29
  };
30
+ export declare function assignDispose<T extends object>(value: T, disposable: Disposable): T & {
31
+ [Symbol.dispose]: () => void;
32
+ };
package/index.js CHANGED
@@ -33,7 +33,7 @@ export function makeRenderLoop() {
33
33
  },
34
34
  };
35
35
  }
36
- export function makeInterval(cb, ms) {
36
+ export function createInterval(cb, ms) {
37
37
  const reset = makeReset();
38
38
  let timeout;
39
39
  wrapper();
@@ -48,7 +48,7 @@ export function makeInterval(cb, ms) {
48
48
  timeout = setTimeout(wrapper, ms);
49
49
  }
50
50
  }
51
- export function makeIntervalAsync(cb, ms) {
51
+ export function createIntervalAsync(cb, ms) {
52
52
  const reset = makeReset();
53
53
  let timeout;
54
54
  void wrapper();
@@ -65,7 +65,7 @@ export function makeIntervalAsync(cb, ms) {
65
65
  timeout = setTimeout(wrapper, ms);
66
66
  }
67
67
  }
68
- export function makeAnimationFrame(cb) {
68
+ export function createAnimationFrame(cb) {
69
69
  const stack = new DisposableStack();
70
70
  const raf = requestAnimationFrame(now => {
71
71
  if (stack.disposed)
@@ -79,7 +79,7 @@ export function makeAnimationFrame(cb) {
79
79
  },
80
80
  };
81
81
  }
82
- export function makeAnimationFrameLoop(cb) {
82
+ export function createAnimationFrameLoop(cb) {
83
83
  const reset = makeReset();
84
84
  let raf = requestAnimationFrame(wrapper);
85
85
  return {
@@ -93,7 +93,7 @@ export function makeAnimationFrameLoop(cb) {
93
93
  raf = requestAnimationFrame(wrapper);
94
94
  }
95
95
  }
96
- export function makeTimeout(cb, ms) {
96
+ export function createTimeout(cb, ms) {
97
97
  const timeout = setTimeout(cb, ms);
98
98
  return {
99
99
  [Symbol.dispose]() {
@@ -101,7 +101,7 @@ export function makeTimeout(cb, ms) {
101
101
  },
102
102
  };
103
103
  }
104
- export function makeTransition(cb, durationMs) {
104
+ export function createTransition(cb, durationMs) {
105
105
  const reset = makeReset();
106
106
  let start;
107
107
  let raf = requestAnimationFrame(wrapper);
@@ -128,3 +128,8 @@ export function makeTransition(cb, durationMs) {
128
128
  }
129
129
  }
130
130
  }
131
+ export function assignDispose(value, disposable) {
132
+ return Object.assign(value, {
133
+ [Symbol.dispose]: disposable[Symbol.dispose].bind(disposable),
134
+ });
135
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jrx",
3
- "version": "0.3.0-alpha.5",
3
+ "version": "0.3.0-alpha.7",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "prepublishOnly": "npx tsc",