@tanstack/angular-pacer 0.20.0 → 0.21.0

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 (94) hide show
  1. package/LICENSE +21 -0
  2. package/dist/async-batcher/index.d.cts +2 -2
  3. package/dist/async-batcher/index.d.ts +2 -2
  4. package/dist/async-batcher/injectAsyncBatcher.cjs +27 -3
  5. package/dist/async-batcher/injectAsyncBatcher.cjs.map +1 -1
  6. package/dist/async-batcher/injectAsyncBatcher.d.cts +25 -3
  7. package/dist/async-batcher/injectAsyncBatcher.d.ts +25 -3
  8. package/dist/async-batcher/injectAsyncBatcher.js +27 -3
  9. package/dist/async-batcher/injectAsyncBatcher.js.map +1 -1
  10. package/dist/async-debouncer/index.d.cts +2 -2
  11. package/dist/async-debouncer/index.d.ts +2 -2
  12. package/dist/async-debouncer/injectAsyncDebouncer.cjs +27 -3
  13. package/dist/async-debouncer/injectAsyncDebouncer.cjs.map +1 -1
  14. package/dist/async-debouncer/injectAsyncDebouncer.d.cts +25 -3
  15. package/dist/async-debouncer/injectAsyncDebouncer.d.ts +25 -3
  16. package/dist/async-debouncer/injectAsyncDebouncer.js +27 -3
  17. package/dist/async-debouncer/injectAsyncDebouncer.js.map +1 -1
  18. package/dist/async-queuer/index.d.cts +2 -2
  19. package/dist/async-queuer/index.d.ts +2 -2
  20. package/dist/async-queuer/injectAsyncQueuer.cjs +27 -3
  21. package/dist/async-queuer/injectAsyncQueuer.cjs.map +1 -1
  22. package/dist/async-queuer/injectAsyncQueuer.d.cts +25 -3
  23. package/dist/async-queuer/injectAsyncQueuer.d.ts +25 -3
  24. package/dist/async-queuer/injectAsyncQueuer.js +27 -3
  25. package/dist/async-queuer/injectAsyncQueuer.js.map +1 -1
  26. package/dist/async-rate-limiter/index.d.cts +2 -2
  27. package/dist/async-rate-limiter/index.d.ts +2 -2
  28. package/dist/async-rate-limiter/injectAsyncRateLimiter.cjs +15 -3
  29. package/dist/async-rate-limiter/injectAsyncRateLimiter.cjs.map +1 -1
  30. package/dist/async-rate-limiter/injectAsyncRateLimiter.d.cts +15 -3
  31. package/dist/async-rate-limiter/injectAsyncRateLimiter.d.ts +15 -3
  32. package/dist/async-rate-limiter/injectAsyncRateLimiter.js +15 -3
  33. package/dist/async-rate-limiter/injectAsyncRateLimiter.js.map +1 -1
  34. package/dist/async-throttler/index.d.cts +2 -2
  35. package/dist/async-throttler/index.d.ts +2 -2
  36. package/dist/async-throttler/injectAsyncThrottler.cjs +27 -3
  37. package/dist/async-throttler/injectAsyncThrottler.cjs.map +1 -1
  38. package/dist/async-throttler/injectAsyncThrottler.d.cts +25 -3
  39. package/dist/async-throttler/injectAsyncThrottler.d.ts +25 -3
  40. package/dist/async-throttler/injectAsyncThrottler.js +27 -3
  41. package/dist/async-throttler/injectAsyncThrottler.js.map +1 -1
  42. package/dist/batcher/index.d.cts +2 -2
  43. package/dist/batcher/index.d.ts +2 -2
  44. package/dist/batcher/injectBatcher.cjs +22 -3
  45. package/dist/batcher/injectBatcher.cjs.map +1 -1
  46. package/dist/batcher/injectBatcher.d.cts +22 -3
  47. package/dist/batcher/injectBatcher.d.ts +22 -3
  48. package/dist/batcher/injectBatcher.js +22 -3
  49. package/dist/batcher/injectBatcher.js.map +1 -1
  50. package/dist/debouncer/index.d.cts +2 -2
  51. package/dist/debouncer/index.d.ts +2 -2
  52. package/dist/debouncer/injectDebouncer.cjs +22 -3
  53. package/dist/debouncer/injectDebouncer.cjs.map +1 -1
  54. package/dist/debouncer/injectDebouncer.d.cts +22 -3
  55. package/dist/debouncer/injectDebouncer.d.ts +22 -3
  56. package/dist/debouncer/injectDebouncer.js +22 -3
  57. package/dist/debouncer/injectDebouncer.js.map +1 -1
  58. package/dist/index.d.cts +11 -11
  59. package/dist/index.d.ts +11 -11
  60. package/dist/queuer/index.d.cts +2 -2
  61. package/dist/queuer/index.d.ts +2 -2
  62. package/dist/queuer/injectQueuer.cjs +22 -3
  63. package/dist/queuer/injectQueuer.cjs.map +1 -1
  64. package/dist/queuer/injectQueuer.d.cts +22 -3
  65. package/dist/queuer/injectQueuer.d.ts +22 -3
  66. package/dist/queuer/injectQueuer.js +22 -3
  67. package/dist/queuer/injectQueuer.js.map +1 -1
  68. package/dist/rate-limiter/index.d.cts +2 -2
  69. package/dist/rate-limiter/index.d.ts +2 -2
  70. package/dist/rate-limiter/injectRateLimiter.cjs +13 -3
  71. package/dist/rate-limiter/injectRateLimiter.cjs.map +1 -1
  72. package/dist/rate-limiter/injectRateLimiter.d.cts +13 -3
  73. package/dist/rate-limiter/injectRateLimiter.d.ts +13 -3
  74. package/dist/rate-limiter/injectRateLimiter.js +13 -3
  75. package/dist/rate-limiter/injectRateLimiter.js.map +1 -1
  76. package/dist/throttler/index.d.cts +2 -2
  77. package/dist/throttler/index.d.ts +2 -2
  78. package/dist/throttler/injectThrottler.cjs +22 -3
  79. package/dist/throttler/injectThrottler.cjs.map +1 -1
  80. package/dist/throttler/injectThrottler.d.cts +22 -3
  81. package/dist/throttler/injectThrottler.d.ts +22 -3
  82. package/dist/throttler/injectThrottler.js +22 -3
  83. package/dist/throttler/injectThrottler.js.map +1 -1
  84. package/package.json +15 -15
  85. package/src/async-batcher/injectAsyncBatcher.ts +43 -4
  86. package/src/async-debouncer/injectAsyncDebouncer.ts +43 -4
  87. package/src/async-queuer/injectAsyncQueuer.ts +43 -4
  88. package/src/async-rate-limiter/injectAsyncRateLimiter.ts +32 -4
  89. package/src/async-throttler/injectAsyncThrottler.ts +43 -4
  90. package/src/batcher/injectBatcher.ts +39 -4
  91. package/src/debouncer/injectDebouncer.ts +39 -4
  92. package/src/queuer/injectQueuer.ts +39 -4
  93. package/src/rate-limiter/injectRateLimiter.ts +28 -4
  94. package/src/throttler/injectThrottler.ts +39 -4
@@ -1,14 +1,26 @@
1
+ import { DestroyRef, inject } from '@angular/core'
1
2
  import { injectStore } from '@tanstack/angular-store'
2
3
  import { AsyncRateLimiter } from '@tanstack/pacer/async-rate-limiter'
3
4
  import { injectPacerOptions } from '../provider/pacer-context'
4
5
  import type { Signal } from '@angular/core'
5
- import type { Store } from '@tanstack/store'
6
+ import type { Store } from '@tanstack/angular-store'
6
7
  import type { AnyAsyncFunction } from '@tanstack/pacer/types'
7
8
  import type {
8
9
  AsyncRateLimiterOptions,
9
10
  AsyncRateLimiterState,
10
11
  } from '@tanstack/pacer/async-rate-limiter'
11
12
 
13
+ export interface AngularAsyncRateLimiterOptions<
14
+ TFn extends AnyAsyncFunction,
15
+ TSelected = {},
16
+ > extends AsyncRateLimiterOptions<TFn> {
17
+ /**
18
+ * Optional callback invoked when the component is destroyed. Receives the rate limiter instance.
19
+ * When provided, replaces the default cleanup (abort).
20
+ */
21
+ onUnmount?: (rateLimiter: AngularAsyncRateLimiter<TFn, TSelected>) => void
22
+ }
23
+
12
24
  export interface AngularAsyncRateLimiter<
13
25
  TFn extends AnyAsyncFunction,
14
26
  TSelected = {},
@@ -45,6 +57,11 @@ export interface AngularAsyncRateLimiter<
45
57
  * tracking by providing a selector function. This prevents unnecessary updates and gives you
46
58
  * full control over when your component tracks state changes.
47
59
  *
60
+ * ## Cleanup on Destroy
61
+ *
62
+ * By default, the function aborts in-flight work when the component is destroyed.
63
+ * Use the `onUnmount` option to customize this.
64
+ *
48
65
  * @example
49
66
  * ```ts
50
67
  * // Default behavior - no reactive state subscriptions
@@ -68,20 +85,31 @@ export function injectAsyncRateLimiter<
68
85
  TSelected = {},
69
86
  >(
70
87
  fn: TFn,
71
- options: AsyncRateLimiterOptions<TFn>,
88
+ options: AngularAsyncRateLimiterOptions<TFn, TSelected>,
72
89
  selector: (state: AsyncRateLimiterState<TFn>) => TSelected = () =>
73
90
  ({}) as TSelected,
74
91
  ): AngularAsyncRateLimiter<TFn, TSelected> {
75
92
  const mergedOptions = {
76
93
  ...injectPacerOptions().asyncRateLimiter,
77
94
  ...options,
78
- } as AsyncRateLimiterOptions<TFn>
95
+ } as AngularAsyncRateLimiterOptions<TFn, TSelected>
79
96
 
80
97
  const rateLimiter = new AsyncRateLimiter<TFn>(fn, mergedOptions)
81
98
  const state = injectStore(rateLimiter.store, selector)
82
99
 
83
- return {
100
+ const result = {
84
101
  ...rateLimiter,
85
102
  state,
86
103
  } as AngularAsyncRateLimiter<TFn, TSelected>
104
+
105
+ const destroyRef = inject(DestroyRef, { optional: true })
106
+ destroyRef?.onDestroy(() => {
107
+ if (mergedOptions.onUnmount) {
108
+ mergedOptions.onUnmount(result)
109
+ } else {
110
+ rateLimiter.abort()
111
+ }
112
+ })
113
+
114
+ return result
87
115
  }
@@ -1,14 +1,27 @@
1
+ import { DestroyRef, inject } from '@angular/core'
1
2
  import { injectStore } from '@tanstack/angular-store'
2
3
  import { AsyncThrottler } from '@tanstack/pacer/async-throttler'
3
4
  import { injectPacerOptions } from '../provider/pacer-context'
4
5
  import type { Signal } from '@angular/core'
5
- import type { Store } from '@tanstack/store'
6
+ import type { Store } from '@tanstack/angular-store'
6
7
  import type { AnyAsyncFunction } from '@tanstack/pacer/types'
7
8
  import type {
8
9
  AsyncThrottlerOptions,
9
10
  AsyncThrottlerState,
10
11
  } from '@tanstack/pacer/async-throttler'
11
12
 
13
+ export interface AngularAsyncThrottlerOptions<
14
+ TFn extends AnyAsyncFunction,
15
+ TSelected = {},
16
+ > extends AsyncThrottlerOptions<TFn> {
17
+ /**
18
+ * Optional callback invoked when the component is destroyed. Receives the throttler instance.
19
+ * When provided, replaces the default cleanup (cancel + abort); use it to call flush(), cancel(), add logging, etc.
20
+ * When using onUnmount with flush, guard your callbacks since the component may already be destroyed.
21
+ */
22
+ onUnmount?: (throttler: AngularAsyncThrottler<TFn, TSelected>) => void
23
+ }
24
+
12
25
  export interface AngularAsyncThrottler<
13
26
  TFn extends AnyAsyncFunction,
14
27
  TSelected = {},
@@ -47,6 +60,20 @@ export interface AngularAsyncThrottler<
47
60
  * tracking by providing a selector function. This prevents unnecessary updates and gives you
48
61
  * full control over when your component tracks state changes.
49
62
  *
63
+ * ## Cleanup on Destroy
64
+ *
65
+ * By default, the function cancels any pending execution and aborts in-flight work when the component is destroyed.
66
+ * Use the `onUnmount` option to customize this. For example, to flush pending work instead:
67
+ *
68
+ * ```ts
69
+ * const throttler = injectAsyncThrottler(fn, {
70
+ * wait: 1000,
71
+ * onUnmount: (t) => t.flush()
72
+ * });
73
+ * ```
74
+ *
75
+ * When using onUnmount with flush, guard your callbacks since the component may already be destroyed.
76
+ *
50
77
  * @example
51
78
  * ```ts
52
79
  * // Default behavior - no reactive state subscriptions
@@ -73,20 +100,32 @@ export function injectAsyncThrottler<
73
100
  TSelected = {},
74
101
  >(
75
102
  fn: TFn,
76
- options: AsyncThrottlerOptions<TFn>,
103
+ options: AngularAsyncThrottlerOptions<TFn, TSelected>,
77
104
  selector: (state: AsyncThrottlerState<TFn>) => TSelected = () =>
78
105
  ({}) as TSelected,
79
106
  ): AngularAsyncThrottler<TFn, TSelected> {
80
107
  const mergedOptions = {
81
108
  ...injectPacerOptions().asyncThrottler,
82
109
  ...options,
83
- } as AsyncThrottlerOptions<TFn>
110
+ } as AngularAsyncThrottlerOptions<TFn, TSelected>
84
111
 
85
112
  const throttler = new AsyncThrottler<TFn>(fn, mergedOptions)
86
113
  const state = injectStore(throttler.store, selector)
87
114
 
88
- return {
115
+ const result = {
89
116
  ...throttler,
90
117
  state,
91
118
  } as AngularAsyncThrottler<TFn, TSelected>
119
+
120
+ const destroyRef = inject(DestroyRef, { optional: true })
121
+ destroyRef?.onDestroy(() => {
122
+ if (mergedOptions.onUnmount) {
123
+ mergedOptions.onUnmount(result)
124
+ } else {
125
+ throttler.cancel()
126
+ throttler.abort()
127
+ }
128
+ })
129
+
130
+ return result
92
131
  }
@@ -1,10 +1,22 @@
1
+ import { DestroyRef, inject } from '@angular/core'
1
2
  import { injectStore } from '@tanstack/angular-store'
2
3
  import { Batcher } from '@tanstack/pacer/batcher'
3
4
  import { injectPacerOptions } from '../provider/pacer-context'
4
5
  import type { Signal } from '@angular/core'
5
- import type { Store } from '@tanstack/store'
6
+ import type { Store } from '@tanstack/angular-store'
6
7
  import type { BatcherOptions, BatcherState } from '@tanstack/pacer/batcher'
7
8
 
9
+ export interface AngularBatcherOptions<
10
+ TValue,
11
+ TSelected = {},
12
+ > extends BatcherOptions<TValue> {
13
+ /**
14
+ * Optional callback invoked when the component is destroyed. Receives the batcher instance.
15
+ * When provided, replaces the default cleanup (cancel); use it to call flush(), cancel(), add logging, etc.
16
+ */
17
+ onUnmount?: (batcher: AngularBatcher<TValue, TSelected>) => void
18
+ }
19
+
8
20
  export interface AngularBatcher<TValue, TSelected = {}> extends Omit<
9
21
  Batcher<TValue>,
10
22
  'store'
@@ -43,6 +55,18 @@ export interface AngularBatcher<TValue, TSelected = {}> extends Omit<
43
55
  * tracking by providing a selector function. This prevents unnecessary updates and gives you
44
56
  * full control over when your component tracks state changes.
45
57
  *
58
+ * ## Cleanup on Destroy
59
+ *
60
+ * By default, the function cancels any pending batch when the component is destroyed.
61
+ * Use the `onUnmount` option to customize this. For example, to flush pending work instead:
62
+ *
63
+ * ```ts
64
+ * const batcher = injectBatcher(fn, {
65
+ * maxSize: 5,
66
+ * onUnmount: (b) => b.flush()
67
+ * });
68
+ * ```
69
+ *
46
70
  * @example
47
71
  * ```ts
48
72
  * // Default behavior - no reactive state subscriptions
@@ -60,20 +84,31 @@ export interface AngularBatcher<TValue, TSelected = {}> extends Omit<
60
84
  */
61
85
  export function injectBatcher<TValue, TSelected = {}>(
62
86
  fn: (items: Array<TValue>) => void,
63
- options: BatcherOptions<TValue> = {},
87
+ options: AngularBatcherOptions<TValue, TSelected> = {},
64
88
  selector: (state: BatcherState<TValue>) => TSelected = () =>
65
89
  ({}) as TSelected,
66
90
  ): AngularBatcher<TValue, TSelected> {
67
91
  const mergedOptions = {
68
92
  ...injectPacerOptions().batcher,
69
93
  ...options,
70
- } as BatcherOptions<TValue>
94
+ } as AngularBatcherOptions<TValue, TSelected>
71
95
 
72
96
  const batcher = new Batcher<TValue>(fn, mergedOptions)
73
97
  const state = injectStore(batcher.store, selector)
74
98
 
75
- return {
99
+ const result = {
76
100
  ...batcher,
77
101
  state,
78
102
  } as AngularBatcher<TValue, TSelected>
103
+
104
+ const destroyRef = inject(DestroyRef, { optional: true })
105
+ destroyRef?.onDestroy(() => {
106
+ if (mergedOptions.onUnmount) {
107
+ mergedOptions.onUnmount(result)
108
+ } else {
109
+ batcher.cancel()
110
+ }
111
+ })
112
+
113
+ return result
79
114
  }
@@ -1,14 +1,26 @@
1
+ import { DestroyRef, inject } from '@angular/core'
1
2
  import { injectStore } from '@tanstack/angular-store'
2
3
  import { Debouncer } from '@tanstack/pacer/debouncer'
3
4
  import { injectPacerOptions } from '../provider/pacer-context'
4
5
  import type { Signal } from '@angular/core'
5
- import type { Store } from '@tanstack/store'
6
+ import type { Store } from '@tanstack/angular-store'
6
7
  import type { AnyFunction } from '@tanstack/pacer/types'
7
8
  import type {
8
9
  DebouncerOptions,
9
10
  DebouncerState,
10
11
  } from '@tanstack/pacer/debouncer'
11
12
 
13
+ export interface AngularDebouncerOptions<
14
+ TFn extends AnyFunction,
15
+ TSelected = {},
16
+ > extends DebouncerOptions<TFn> {
17
+ /**
18
+ * Optional callback invoked when the component is destroyed. Receives the debouncer instance.
19
+ * When provided, replaces the default cleanup (cancel); use it to call flush(), cancel(), add logging, etc.
20
+ */
21
+ onUnmount?: (debouncer: AngularDebouncer<TFn, TSelected>) => void
22
+ }
23
+
12
24
  export interface AngularDebouncer<
13
25
  TFn extends AnyFunction,
14
26
  TSelected = {},
@@ -57,6 +69,18 @@ export interface AngularDebouncer<
57
69
  * - `lastArgs`: The arguments from the most recent call to maybeExecute
58
70
  * - `status`: Current execution status ('disabled' | 'idle' | 'pending')
59
71
  *
72
+ * ## Cleanup on Destroy
73
+ *
74
+ * By default, the function cancels any pending execution when the component is destroyed.
75
+ * Use the `onUnmount` option to customize this. For example, to flush pending work instead:
76
+ *
77
+ * ```ts
78
+ * const debouncer = injectDebouncer(fn, {
79
+ * wait: 500,
80
+ * onUnmount: (d) => d.flush()
81
+ * });
82
+ * ```
83
+ *
60
84
  * @example
61
85
  * ```ts
62
86
  * // Default behavior - no reactive state subscriptions
@@ -84,19 +108,30 @@ export interface AngularDebouncer<
84
108
  */
85
109
  export function injectDebouncer<TFn extends AnyFunction, TSelected = {}>(
86
110
  fn: TFn,
87
- options: DebouncerOptions<TFn>,
111
+ options: AngularDebouncerOptions<TFn, TSelected>,
88
112
  selector: (state: DebouncerState<TFn>) => TSelected = () => ({}) as TSelected,
89
113
  ): AngularDebouncer<TFn, TSelected> {
90
114
  const mergedOptions = {
91
115
  ...injectPacerOptions().debouncer,
92
116
  ...options,
93
- } as DebouncerOptions<TFn>
117
+ } as AngularDebouncerOptions<TFn, TSelected>
94
118
 
95
119
  const debouncer = new Debouncer<TFn>(fn, mergedOptions)
96
120
  const state = injectStore(debouncer.store, selector)
97
121
 
98
- return {
122
+ const result = {
99
123
  ...debouncer,
100
124
  state,
101
125
  } as AngularDebouncer<TFn, TSelected>
126
+
127
+ const destroyRef = inject(DestroyRef, { optional: true })
128
+ destroyRef?.onDestroy(() => {
129
+ if (mergedOptions.onUnmount) {
130
+ mergedOptions.onUnmount(result)
131
+ } else {
132
+ debouncer.cancel()
133
+ }
134
+ })
135
+
136
+ return result
102
137
  }
@@ -1,10 +1,22 @@
1
+ import { DestroyRef, inject } from '@angular/core'
1
2
  import { injectStore } from '@tanstack/angular-store'
2
3
  import { Queuer } from '@tanstack/pacer/queuer'
3
4
  import { injectPacerOptions } from '../provider/pacer-context'
4
5
  import type { Signal } from '@angular/core'
5
- import type { Store } from '@tanstack/store'
6
+ import type { Store } from '@tanstack/angular-store'
6
7
  import type { QueuerOptions, QueuerState } from '@tanstack/pacer/queuer'
7
8
 
9
+ export interface AngularQueuerOptions<
10
+ TValue,
11
+ TSelected = {},
12
+ > extends QueuerOptions<TValue> {
13
+ /**
14
+ * Optional callback invoked when the component is destroyed. Receives the queuer instance.
15
+ * When provided, replaces the default cleanup (stop); use it to call flush(), stop(), add logging, etc.
16
+ */
17
+ onUnmount?: (queuer: AngularQueuer<TValue, TSelected>) => void
18
+ }
19
+
8
20
  export interface AngularQueuer<TValue, TSelected = {}> extends Omit<
9
21
  Queuer<TValue>,
10
22
  'store'
@@ -62,22 +74,45 @@ export interface AngularQueuer<TValue, TSelected = {}> extends Omit<
62
74
  * // Access the selected state
63
75
  * const { items, isRunning } = queue.state();
64
76
  * ```
77
+ *
78
+ * ## Cleanup on Destroy
79
+ *
80
+ * By default, the function stops the queuer when the component is destroyed.
81
+ * Use the `onUnmount` option to customize this. For example, to flush pending items instead:
82
+ *
83
+ * ```ts
84
+ * const queue = injectQueuer(fn, {
85
+ * started: true,
86
+ * onUnmount: (q) => q.flush()
87
+ * });
88
+ * ```
65
89
  */
66
90
  export function injectQueuer<TValue, TSelected = {}>(
67
91
  fn: (item: TValue) => void,
68
- options: QueuerOptions<TValue> = {},
92
+ options: AngularQueuerOptions<TValue, TSelected> = {},
69
93
  selector: (state: QueuerState<TValue>) => TSelected = () => ({}) as TSelected,
70
94
  ): AngularQueuer<TValue, TSelected> {
71
95
  const mergedOptions = {
72
96
  ...injectPacerOptions().queuer,
73
97
  ...options,
74
- } as QueuerOptions<TValue>
98
+ } as AngularQueuerOptions<TValue, TSelected>
75
99
 
76
100
  const queuer = new Queuer<TValue>(fn, mergedOptions)
77
101
  const state = injectStore(queuer.store, selector)
78
102
 
79
- return {
103
+ const result = {
80
104
  ...queuer,
81
105
  state,
82
106
  } as AngularQueuer<TValue, TSelected>
107
+
108
+ const destroyRef = inject(DestroyRef, { optional: true })
109
+ destroyRef?.onDestroy(() => {
110
+ if (mergedOptions.onUnmount) {
111
+ mergedOptions.onUnmount(result)
112
+ } else {
113
+ queuer.stop()
114
+ }
115
+ })
116
+
117
+ return result
83
118
  }
@@ -1,14 +1,25 @@
1
+ import { DestroyRef, inject } from '@angular/core'
1
2
  import { injectStore } from '@tanstack/angular-store'
2
3
  import { RateLimiter } from '@tanstack/pacer/rate-limiter'
3
4
  import { injectPacerOptions } from '../provider/pacer-context'
4
5
  import type { Signal } from '@angular/core'
5
- import type { Store } from '@tanstack/store'
6
+ import type { Store } from '@tanstack/angular-store'
6
7
  import type { AnyFunction } from '@tanstack/pacer/types'
7
8
  import type {
8
9
  RateLimiterOptions,
9
10
  RateLimiterState,
10
11
  } from '@tanstack/pacer/rate-limiter'
11
12
 
13
+ export interface AngularRateLimiterOptions<
14
+ TFn extends AnyFunction,
15
+ TSelected = {},
16
+ > extends RateLimiterOptions<TFn> {
17
+ /**
18
+ * Optional callback invoked when the component is destroyed. Receives the rate limiter instance.
19
+ */
20
+ onUnmount?: (rateLimiter: AngularRateLimiter<TFn, TSelected>) => void
21
+ }
22
+
12
23
  export interface AngularRateLimiter<
13
24
  TFn extends AnyFunction,
14
25
  TSelected = {},
@@ -62,6 +73,10 @@ export interface AngularRateLimiter<
62
73
  * - `executionTimes`: Array of timestamps when executions occurred for rate limiting calculations
63
74
  * - `rejectionCount`: Number of function executions that have been rejected due to rate limiting
64
75
  *
76
+ * ## Cleanup on Destroy
77
+ *
78
+ * Use the `onUnmount` option to run a callback when the component is destroyed.
79
+ *
65
80
  * @example
66
81
  * ```ts
67
82
  * // Default behavior - no reactive state subscriptions
@@ -98,19 +113,28 @@ export interface AngularRateLimiter<
98
113
  */
99
114
  export function injectRateLimiter<TFn extends AnyFunction, TSelected = {}>(
100
115
  fn: TFn,
101
- options: RateLimiterOptions<TFn>,
116
+ options: AngularRateLimiterOptions<TFn, TSelected>,
102
117
  selector: (state: RateLimiterState) => TSelected = () => ({}) as TSelected,
103
118
  ): AngularRateLimiter<TFn, TSelected> {
104
119
  const mergedOptions = {
105
120
  ...injectPacerOptions().rateLimiter,
106
121
  ...options,
107
- } as RateLimiterOptions<TFn>
122
+ } as AngularRateLimiterOptions<TFn, TSelected>
108
123
 
109
124
  const rateLimiter = new RateLimiter<TFn>(fn, mergedOptions)
110
125
  const state = injectStore(rateLimiter.store, selector)
111
126
 
112
- return {
127
+ const result = {
113
128
  ...rateLimiter,
114
129
  state,
115
130
  } as AngularRateLimiter<TFn, TSelected>
131
+
132
+ const destroyRef = inject(DestroyRef, { optional: true })
133
+ destroyRef?.onDestroy(() => {
134
+ if (mergedOptions.onUnmount) {
135
+ mergedOptions.onUnmount(result)
136
+ }
137
+ })
138
+
139
+ return result
116
140
  }
@@ -1,14 +1,26 @@
1
+ import { DestroyRef, inject } from '@angular/core'
1
2
  import { injectStore } from '@tanstack/angular-store'
2
3
  import { Throttler } from '@tanstack/pacer/throttler'
3
4
  import { injectPacerOptions } from '../provider/pacer-context'
4
5
  import type { Signal } from '@angular/core'
5
- import type { Store } from '@tanstack/store'
6
+ import type { Store } from '@tanstack/angular-store'
6
7
  import type { AnyFunction } from '@tanstack/pacer/types'
7
8
  import type {
8
9
  ThrottlerOptions,
9
10
  ThrottlerState,
10
11
  } from '@tanstack/pacer/throttler'
11
12
 
13
+ export interface AngularThrottlerOptions<
14
+ TFn extends AnyFunction,
15
+ TSelected = {},
16
+ > extends ThrottlerOptions<TFn> {
17
+ /**
18
+ * Optional callback invoked when the component is destroyed. Receives the throttler instance.
19
+ * When provided, replaces the default cleanup (cancel); use it to call flush(), cancel(), add logging, etc.
20
+ */
21
+ onUnmount?: (throttler: AngularThrottler<TFn, TSelected>) => void
22
+ }
23
+
12
24
  export interface AngularThrottler<
13
25
  TFn extends AnyFunction,
14
26
  TSelected = {},
@@ -58,6 +70,18 @@ export interface AngularThrottler<
58
70
  * - `nextExecutionTime`: Timestamp of the next allowed execution
59
71
  * - `status`: Current execution status ('disabled' | 'idle' | 'pending')
60
72
  *
73
+ * ## Cleanup on Destroy
74
+ *
75
+ * By default, the function cancels any pending execution when the component is destroyed.
76
+ * Use the `onUnmount` option to customize this. For example, to flush pending work instead:
77
+ *
78
+ * ```ts
79
+ * const throttler = injectThrottler(fn, {
80
+ * wait: 100,
81
+ * onUnmount: (t) => t.flush()
82
+ * });
83
+ * ```
84
+ *
61
85
  * @example
62
86
  * ```ts
63
87
  * // Default behavior - no reactive state subscriptions
@@ -84,19 +108,30 @@ export interface AngularThrottler<
84
108
  */
85
109
  export function injectThrottler<TFn extends AnyFunction, TSelected = {}>(
86
110
  fn: TFn,
87
- options: ThrottlerOptions<TFn>,
111
+ options: AngularThrottlerOptions<TFn, TSelected>,
88
112
  selector: (state: ThrottlerState<TFn>) => TSelected = () => ({}) as TSelected,
89
113
  ): AngularThrottler<TFn, TSelected> {
90
114
  const mergedOptions = {
91
115
  ...injectPacerOptions().throttler,
92
116
  ...options,
93
- } as ThrottlerOptions<TFn>
117
+ } as AngularThrottlerOptions<TFn, TSelected>
94
118
 
95
119
  const throttler = new Throttler<TFn>(fn, mergedOptions)
96
120
  const state = injectStore(throttler.store, selector)
97
121
 
98
- return {
122
+ const result = {
99
123
  ...throttler,
100
124
  state,
101
125
  } as AngularThrottler<TFn, TSelected>
126
+
127
+ const destroyRef = inject(DestroyRef, { optional: true })
128
+ destroyRef?.onDestroy(() => {
129
+ if (mergedOptions.onUnmount) {
130
+ mergedOptions.onUnmount(result)
131
+ } else {
132
+ throttler.cancel()
133
+ }
134
+ })
135
+
136
+ return result
102
137
  }