@kontsedal/olas-core 0.0.1 → 0.0.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.
@@ -5,15 +5,22 @@ import type { ReadSignal } from '../signals/types'
5
5
  * Lag a signal by `ms`. The returned signal updates only after the source has
6
6
  * been unchanged for `ms`. Each new write resets the timer.
7
7
  *
8
- * No lifecycle the internal effect runs for the lifetime of the program.
9
- * Use inside a controller closure so it dies with the closure.
8
+ * Pass `options.signal` (an `AbortSignal`) to tie the internal effect to a
9
+ * lifecycle when the signal aborts the effect disposes, the pending timer
10
+ * clears, and the subscriber chain on `source` drops. Without `signal`, the
11
+ * effect lives as long as `source` does; pass a signal whenever the source
12
+ * outlives the consumer.
10
13
  */
11
- export function debounced<T>(source: ReadSignal<T>, ms: number): ReadSignal<T> {
14
+ export function debounced<T>(
15
+ source: ReadSignal<T>,
16
+ ms: number,
17
+ options?: { signal?: AbortSignal },
18
+ ): ReadSignal<T> {
12
19
  const out = signal<T>(source.peek())
13
20
  let timer: ReturnType<typeof setTimeout> | null = null
14
21
  let initial = true
15
22
 
16
- effect(() => {
23
+ const dispose = effect(() => {
17
24
  const value = source.value
18
25
  if (initial) {
19
26
  // The first effect run reads the source for tracking; we already
@@ -28,5 +35,18 @@ export function debounced<T>(source: ReadSignal<T>, ms: number): ReadSignal<T> {
28
35
  }, ms)
29
36
  })
30
37
 
38
+ const sig = options?.signal
39
+ if (sig) {
40
+ const stop = () => {
41
+ if (timer != null) {
42
+ clearTimeout(timer)
43
+ timer = null
44
+ }
45
+ dispose()
46
+ }
47
+ if (sig.aborted) stop()
48
+ else sig.addEventListener('abort', stop, { once: true })
49
+ }
50
+
31
51
  return out
32
52
  }
@@ -6,16 +6,22 @@ import type { ReadSignal } from '../signals/types'
6
6
  * The first change passes through immediately. Subsequent changes within the
7
7
  * window are coalesced; the latest value is emitted when the window expires.
8
8
  *
9
- * No lifecycle — see debounced() note.
9
+ * Pass `options.signal` to tie the internal effect to a lifecycle — when the
10
+ * signal aborts the effect disposes and any pending trailing timer clears.
11
+ * Without `signal`, the effect lives as long as `source` does.
10
12
  */
11
- export function throttled<T>(source: ReadSignal<T>, ms: number): ReadSignal<T> {
13
+ export function throttled<T>(
14
+ source: ReadSignal<T>,
15
+ ms: number,
16
+ options?: { signal?: AbortSignal },
17
+ ): ReadSignal<T> {
12
18
  const out = signal<T>(source.peek())
13
19
  let lastEmit = Number.NEGATIVE_INFINITY
14
20
  let trailingTimer: ReturnType<typeof setTimeout> | null = null
15
21
  let trailingValue: T = source.peek()
16
22
  let initial = true
17
23
 
18
- effect(() => {
24
+ const dispose = effect(() => {
19
25
  const value = source.value
20
26
  if (initial) {
21
27
  initial = false
@@ -42,5 +48,18 @@ export function throttled<T>(source: ReadSignal<T>, ms: number): ReadSignal<T> {
42
48
  }
43
49
  })
44
50
 
51
+ const sig = options?.signal
52
+ if (sig) {
53
+ const stop = () => {
54
+ if (trailingTimer != null) {
55
+ clearTimeout(trailingTimer)
56
+ trailingTimer = null
57
+ }
58
+ dispose()
59
+ }
60
+ if (sig.aborted) stop()
61
+ else sig.addEventListener('abort', stop, { once: true })
62
+ }
63
+
45
64
  return out
46
65
  }
package/src/utils.ts CHANGED
@@ -1,14 +1,18 @@
1
1
  /**
2
- * True iff `err` is an AbortError. Used to filter superseded latest-wins
3
- * mutations and aborted fetches from genuine failures.
2
+ * True iff `err` looks like an AbortError. Matches the standard `DOMException`
3
+ * shape thrown by `AbortController` AND any object whose `name === 'AbortError'`
4
+ * — that covers axios / msw / user-thrown plain Errors that signal abort.
4
5
  *
5
- * Spec: §20.12 checks `err instanceof DOMException && err.name === 'AbortError'`.
6
- * Node 17+ exposes a global DOMException, so this works server-side too.
6
+ * Spec: §20.12. Node 17+ exposes a global DOMException, so the instanceof
7
+ * branch works server-side; the name-based branch is the portable fallback.
7
8
  */
8
9
  export function isAbortError(err: unknown): boolean {
9
10
  if (typeof DOMException !== 'undefined' && err instanceof DOMException) {
10
11
  return err.name === 'AbortError'
11
12
  }
13
+ if (err != null && typeof err === 'object' && 'name' in err) {
14
+ return (err as { name: unknown }).name === 'AbortError'
15
+ }
12
16
  return false
13
17
  }
14
18