@naturalcycles/js-lib 15.83.0 → 15.84.1

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.
@@ -1,5 +1,6 @@
1
1
  import { comparators } from '../array/sort.js'
2
2
  import { _assert } from '../error/assert.js'
3
+ import { Intl2 } from '../intl/intl.js'
3
4
  import type {
4
5
  IANATimezone,
5
6
  Inclusiveness,
@@ -108,15 +109,7 @@ export class LocalTime {
108
109
  * @experimental
109
110
  */
110
111
  inTimezone(tz: IANATimezone): WallTime {
111
- const d = new Date(this.$date.toLocaleString('en-US', { timeZone: tz }))
112
- return new WallTime({
113
- year: d.getFullYear(),
114
- month: d.getMonth() + 1,
115
- day: d.getDate(),
116
- hour: d.getHours(),
117
- minute: d.getMinutes(),
118
- second: d.getSeconds(),
119
- })
112
+ return new WallTime(getWallComponentsInTimezone(this.$date, tz))
120
113
  }
121
114
 
122
115
  /**
@@ -136,8 +129,13 @@ export class LocalTime {
136
129
  getUTCOffsetMinutes(tz?: IANATimezone): NumberOfMinutes {
137
130
  if (tz) {
138
131
  // based on: https://stackoverflow.com/a/53652131/4919972
132
+ // The wall clock in `tz` is interpreted as UTC (not as host-local time, like the
133
+ // stackoverflow answer does): `wall = utc + offset`, so `offset = Date.UTC(wall) - now`.
134
+ // This makes the result independent of the host timezone (the host-local interpretation
135
+ // returned offsets relative to the host, and was distorted by the host's DST gaps).
139
136
  const nowTime = this.$date.getTime()
140
- const tzTime = new Date(this.$date.toLocaleString('en-US', { timeZone: tz })).getTime()
137
+ const { year, month, day, hour, minute, second } = getWallComponentsInTimezone(this.$date, tz)
138
+ const tzTime = Date.UTC(year, month - 1, day, hour, minute, second)
141
139
  return Math.round((tzTime - nowTime) / 60000) || 0
142
140
  }
143
141
 
@@ -163,10 +161,12 @@ export class LocalTime {
163
161
  */
164
162
  getUTCOffsetString(tz: IANATimezone): string {
165
163
  const minutes = this.getUTCOffsetMinutes(tz)
166
- const hours = Math.trunc(minutes / 60)
167
- const sign = hours < 0 ? '-' : '+'
168
- const h = String(Math.abs(hours)).padStart(2, '0')
169
- const m = String(minutes % 60).padStart(2, '0')
164
+ // Sign is taken from minutes (not truncated hours), and minutes are abs-ed,
165
+ // so negative fractional offsets like Newfoundland -03:30 don't render as `-03:-30`
166
+ const sign = minutes < 0 ? '-' : '+'
167
+ const absMinutes = Math.abs(minutes)
168
+ const h = String(Math.trunc(absMinutes / 60)).padStart(2, '0')
169
+ const m = String(absMinutes % 60).padStart(2, '0')
170
170
  return `${sign}${h}:${m}`
171
171
  }
172
172
 
@@ -1165,6 +1165,41 @@ function differenceInMonths(a: Date, b: Date): number {
1165
1165
  return -(wholeMonthDiff + ((b.getTime() - anchor) / (anchor2 - anchor)) * sign)
1166
1166
  }
1167
1167
 
1168
+ const dateTimeObjectKeys = new Set<keyof DateTimeObject>([
1169
+ 'year',
1170
+ 'month',
1171
+ 'day',
1172
+ 'hour',
1173
+ 'minute',
1174
+ 'second',
1175
+ ])
1176
+
1177
+ // Intl.DateTimeFormat construction is expensive (~30µs), while a cached formatter
1178
+ // (via Intl2) converts in ~3µs. Unlike `date.toLocaleString(...)` + `new Date(string)`
1179
+ // round-trip, formatToParts is also immune to the host timezone's DST gaps.
1180
+ function getWallComponentsInTimezone(date: Date, tz: IANATimezone): DateTimeObject {
1181
+ const fmt = Intl2.DateTimeFormat('en-US', {
1182
+ timeZone: tz,
1183
+ year: 'numeric',
1184
+ month: 'numeric',
1185
+ day: 'numeric',
1186
+ hour: 'numeric',
1187
+ minute: 'numeric',
1188
+ second: 'numeric',
1189
+ hourCycle: 'h23',
1190
+ })
1191
+
1192
+ const dto = {} as DateTimeObject
1193
+
1194
+ for (const { type, value } of fmt.formatToParts(date)) {
1195
+ if (dateTimeObjectKeys.has(type as keyof DateTimeObject)) {
1196
+ dto[type as keyof DateTimeObject] = Number(value)
1197
+ }
1198
+ }
1199
+
1200
+ return dto
1201
+ }
1202
+
1168
1203
  interface LocalTimeFn extends LocalTimeFactory {
1169
1204
  (d: LocalTimeInput): LocalTime
1170
1205
  }
@@ -2,8 +2,9 @@ import { _assert, _assertTypeOf } from '../error/assert.js'
2
2
  import type { CommonLogger } from '../log/commonLogger.js'
3
3
  import type { AnyAsyncFunction, AnyFunction, AnyObject, MaybeParameters } from '../types.js'
4
4
  import { _objectAssign, MISS } from '../types.js'
5
+ import type { MethodDecorator } from './decorator.util.js'
5
6
  import { _getTargetMethodSignature } from './decorator.util.js'
6
- import type { AsyncMemoCache, MethodDecorator } from './memo.util.js'
7
+ import type { AsyncMemoCache } from './memo.util.js'
7
8
  import { jsonMemoSerializer } from './memo.util.js'
8
9
 
9
10
  export interface AsyncMemoOptions<FN> {
@@ -1,18 +1,69 @@
1
- import type { DebounceOptions, ThrottleOptions } from './debounce.js'
2
- import { _debounce, _throttle } from './debounce.js'
1
+ import type { AnyAsyncFunction, AnyFunction } from '../types.js'
2
+ import type {
3
+ AsyncDebounceOptions,
4
+ AsyncThrottleOptions,
5
+ DebounceOptions,
6
+ ThrottleOptions,
7
+ } from './debounce.js'
8
+ import { _asyncDebounce, _asyncThrottle, _debounce, _throttle } from './debounce.js'
9
+ import type { MethodDecorator } from './decorator.util.js'
3
10
 
4
- export function _Debounce(wait: number, opt: DebounceOptions = {}): MethodDecorator {
11
+ export function _Debounce<T extends AnyFunction>(
12
+ wait: number,
13
+ opt: DebounceOptions = {},
14
+ ): MethodDecorator<T> {
5
15
  return (_target, _key, descriptor) => {
6
- const originalFn = descriptor.value
7
- descriptor.value = _debounce(originalFn as any, wait, opt)
16
+ const originalFn = descriptor.value!
17
+ descriptor.value = _debounce<T>(originalFn, wait, opt)
8
18
  return descriptor
9
19
  }
10
20
  }
11
21
 
12
- export function _Throttle(wait: number, opt: ThrottleOptions = {}): MethodDecorator {
22
+ export function _Throttle<T extends AnyFunction>(
23
+ wait: number,
24
+ opt: ThrottleOptions = {},
25
+ ): MethodDecorator<T> {
13
26
  return (_target, _key, descriptor) => {
14
- const originalFn = descriptor.value
15
- descriptor.value = _throttle(originalFn as any, wait, opt)
27
+ const originalFn = descriptor.value!
28
+ descriptor.value = _throttle<T>(originalFn, wait, opt)
29
+ return descriptor
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Like `@_Debounce`, but for async methods: every call returns a real Promise resolving with the
35
+ * coalesced invocation's result, so `await`-ing never yields a stale value or a non-promise
36
+ *
37
+ * Be aware that the decorated method may resolve `undefined` if the config yields no invocation,
38
+ * which the method's `T` type can't express.
39
+ *
40
+ * @experimental
41
+ */
42
+ export function _AsyncDebounce<T extends AnyAsyncFunction>(
43
+ wait: number,
44
+ opt: AsyncDebounceOptions = {},
45
+ ): MethodDecorator<T> {
46
+ return (_target, _key, descriptor) => {
47
+ const originalFn = descriptor.value!
48
+ descriptor.value = _asyncDebounce<T>(originalFn, wait, opt) as any
49
+ return descriptor
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Like `@_Throttle`, but for async methods.
55
+ *
56
+ * @see {@link _AsyncDebounce}
57
+ *
58
+ * @experimental
59
+ */
60
+ export function _AsyncThrottle<T extends AnyAsyncFunction>(
61
+ wait: number,
62
+ opt: AsyncThrottleOptions = {},
63
+ ): MethodDecorator<T> {
64
+ return (_target, _key, descriptor) => {
65
+ const originalFn = descriptor.value!
66
+ descriptor.value = _asyncThrottle<T>(originalFn, wait, opt) as any
16
67
  return descriptor
17
68
  }
18
69
  }
@@ -1,116 +1,58 @@
1
- import type { AnyFunction } from '../types.js'
2
-
3
- export interface Cancelable {
4
- cancel: () => void
5
- flush: () => void
6
- }
7
-
8
- export interface ThrottleOptions {
9
- /**
10
- * @default true
11
- */
12
- leading?: boolean
13
-
14
- /**
15
- * @default true
16
- */
17
- trailing?: boolean
18
- }
19
-
20
- export interface DebounceOptions {
21
- /**
22
- * @default false
23
- */
24
- leading?: boolean
25
-
26
- /**
27
- * @default true
28
- */
29
- trailing?: boolean
30
-
31
- /**
32
- *
33
- */
34
- maxWait?: number
35
- }
1
+ import { _anyToError } from '../error/error.util.js'
2
+ import { pDefer } from '../promise/pDefer.js'
3
+ import type { DeferredPromise } from '../promise/pDefer.js'
4
+ import type { AnyAsyncFunction, AnyFunction } from '../types.js'
36
5
 
37
6
  export function _debounce<T extends AnyFunction>(
38
7
  func: T,
39
8
  wait: number,
40
9
  opt: DebounceOptions = {},
41
10
  ): T & Cancelable {
42
- let lastArgs: any
43
- let lastThis: any
44
- let result: any
11
+ let lastArgs: Parameters<T> | undefined
12
+ let lastThis: ThisParameterType<T> | undefined
13
+ let result: ReturnType<T>
45
14
  let timerId: number | undefined
46
- let lastCallTime: number | undefined
47
15
 
48
- let lastInvokeTime = 0
49
16
  const maxing = 'maxWait' in opt
50
-
51
17
  const { leading = false, trailing = true } = opt
52
- const maxWait = maxing ? Math.max(Number(opt.maxWait) || 0, wait) : opt.maxWait
18
+ const state: DebounceTimerState = {
19
+ lastCallTime: undefined,
20
+ lastInvokeTime: 0,
21
+ wait,
22
+ maxing,
23
+ maxWait: maxing ? Math.max(Number(opt.maxWait) || 0, wait) : opt.maxWait,
24
+ }
53
25
 
54
- function invokeFunc(time: number): any {
26
+ function invokeFunc(time: number): ReturnType<T> {
55
27
  const args = lastArgs
56
28
  const thisArg = lastThis
57
29
 
58
30
  lastArgs = lastThis = undefined
59
- lastInvokeTime = time
60
- result = func.apply(thisArg, args)
31
+ state.lastInvokeTime = time
32
+ result = func.apply(thisArg, args!)
61
33
  return result
62
34
  }
63
35
 
64
- function startTimer(pendingFunc: AnyFunction, wait: number): number {
65
- return setTimeout(pendingFunc, wait)
66
- }
67
-
68
- function cancelTimer(id: number): void {
69
- clearTimeout(id)
70
- }
71
-
72
- function leadingEdge(time: number): any {
36
+ function leadingEdge(time: number): ReturnType<T> {
73
37
  // Reset any `maxWait` timer.
74
- lastInvokeTime = time
38
+ state.lastInvokeTime = time
75
39
  // Start the timer for the trailing edge.
76
- timerId = startTimer(timerExpired, wait)
40
+ timerId = startTimer(timerExpired, state.wait)
77
41
  // Invoke the leading edge.
78
42
  return leading ? invokeFunc(time) : result
79
43
  }
80
44
 
81
- function remainingWait(time: number): number {
82
- const timeSinceLastCall = time - lastCallTime!
83
- const timeSinceLastInvoke = time - lastInvokeTime
84
- const timeWaiting = wait - timeSinceLastCall
85
-
86
- return maxing ? Math.min(timeWaiting, maxWait! - timeSinceLastInvoke) : timeWaiting
87
- }
88
-
89
- function shouldInvoke(time: number): boolean {
90
- const timeSinceLastCall = time - lastCallTime!
91
- const timeSinceLastInvoke = time - lastInvokeTime
92
-
93
- // Either this is the first call, activity has stopped and we're at the
94
- // trailing edge, the system time has gone backwards and we're treating
95
- // it as the trailing edge, or we've hit the `maxWait` limit.
96
- return (
97
- lastCallTime === undefined ||
98
- timeSinceLastCall >= wait ||
99
- timeSinceLastCall < 0 ||
100
- (maxing && timeSinceLastInvoke >= maxWait!)
101
- )
102
- }
103
-
104
- function timerExpired(): any {
45
+ function timerExpired(): void {
105
46
  const time = Date.now()
106
- if (shouldInvoke(time)) {
107
- return trailingEdge(time)
47
+ if (shouldInvoke(time, state)) {
48
+ trailingEdge(time)
49
+ return
108
50
  }
109
51
  // Restart the timer.
110
- timerId = startTimer(timerExpired, remainingWait(time))
52
+ timerId = startTimer(timerExpired, remainingWait(time, state))
111
53
  }
112
54
 
113
- function trailingEdge(time: number): any {
55
+ function trailingEdge(time: number): ReturnType<T> {
114
56
  timerId = undefined
115
57
 
116
58
  // Only invoke if we have `lastArgs` which means `func` has been
@@ -126,11 +68,12 @@ export function _debounce<T extends AnyFunction>(
126
68
  if (timerId !== undefined) {
127
69
  cancelTimer(timerId)
128
70
  }
129
- lastInvokeTime = 0
130
- lastArgs = lastCallTime = lastThis = timerId = undefined
71
+ state.lastInvokeTime = 0
72
+ state.lastCallTime = undefined
73
+ lastArgs = lastThis = timerId = undefined
131
74
  }
132
75
 
133
- function flush(): any {
76
+ function flush(): ReturnType<T> {
134
77
  return timerId === undefined ? result : trailingEdge(Date.now())
135
78
  }
136
79
 
@@ -138,26 +81,26 @@ export function _debounce<T extends AnyFunction>(
138
81
  return timerId !== undefined
139
82
  }
140
83
 
141
- function debounced(this: any, ...args: any[]): any {
84
+ function debounced(this: ThisParameterType<T>, ...args: Parameters<T>): ReturnType<T> {
142
85
  const time = Date.now()
143
- const isInvoking = shouldInvoke(time)
86
+ const isInvoking = shouldInvoke(time, state)
144
87
 
145
88
  lastArgs = args
146
89
  lastThis = this
147
- lastCallTime = time
90
+ state.lastCallTime = time
148
91
 
149
92
  if (isInvoking) {
150
93
  if (timerId === undefined) {
151
- return leadingEdge(lastCallTime)
94
+ return leadingEdge(state.lastCallTime)
152
95
  }
153
- if (maxing) {
96
+ if (state.maxing) {
154
97
  // Handle invocations in a tight loop.
155
- timerId = startTimer(timerExpired, wait)
156
- return invokeFunc(lastCallTime)
98
+ timerId = startTimer(timerExpired, state.wait)
99
+ return invokeFunc(state.lastCallTime)
157
100
  }
158
101
  }
159
102
  if (timerId === undefined) {
160
- timerId = startTimer(timerExpired, wait)
103
+ timerId = startTimer(timerExpired, state.wait)
161
104
  }
162
105
  return result
163
106
  }
@@ -180,3 +123,319 @@ export function _throttle<T extends AnyFunction>(
180
123
  maxWait: wait,
181
124
  })
182
125
  }
126
+
127
+ /**
128
+ * Like `_debounce`, but for async functions.
129
+ *
130
+ * Unlike `_debounce` (which returns a stale `result`/`undefined` for suppressed calls),
131
+ * `_asyncDebounce` always returns a real Promise, resolving with the coalesced invocation's result.
132
+ *
133
+ * @experimental
134
+ */
135
+ export function _asyncDebounce<T extends AnyAsyncFunction>(
136
+ func: T,
137
+ wait: number,
138
+ opt: AsyncDebounceOptions = {},
139
+ ): AsyncDebounced<T> {
140
+ let lastArgs: Parameters<T> | undefined
141
+ let lastThis: ThisParameterType<T> | undefined
142
+ let timerId: number | undefined
143
+ // Promise shared by all calls coalesced into the next invocation (undefined when none pending).
144
+ let deferred: DeferredPromise<Awaited<ReturnType<T>> | undefined> | undefined
145
+
146
+ const { leading = false, trailing = true } = opt
147
+
148
+ const maxing = 'maxWait' in opt
149
+ const maxWait = maxing ? Math.max(Number(opt.maxWait) || 0, wait) : opt.maxWait
150
+
151
+ const state: DebounceTimerState = {
152
+ lastCallTime: undefined,
153
+ lastInvokeTime: 0,
154
+ wait,
155
+ maxing,
156
+ maxWait,
157
+ }
158
+
159
+ async function invokeFunc(time: number): Promise<void> {
160
+ const args = lastArgs
161
+ const thisArg = lastThis
162
+
163
+ lastArgs = lastThis = undefined
164
+ state.lastInvokeTime = time
165
+
166
+ // Detach and settle the current batch.
167
+ const d = deferred!
168
+ deferred = undefined
169
+ try {
170
+ const result = await func.apply(thisArg, args!)
171
+ d.resolve(result)
172
+ } catch (err) {
173
+ d.reject(_anyToError(err))
174
+ }
175
+ }
176
+
177
+ async function leadingEdge(time: number): Promise<Awaited<ReturnType<T>> | undefined> {
178
+ // Reset any `maxWait` timer.
179
+ state.lastInvokeTime = time
180
+ // Start the timer for the trailing edge.
181
+ timerId = startTimer(timerExpired, state.wait)
182
+ // Capture the batch before `invokeFunc` detaches it.
183
+ const d = deferred!
184
+ // Invoke the leading edge.
185
+ if (leading) {
186
+ await invokeFunc(time)
187
+ }
188
+ return d
189
+ }
190
+
191
+ function timerExpired(): void {
192
+ const time = Date.now()
193
+ if (shouldInvoke(time, state)) {
194
+ void trailingEdge(time)
195
+ return
196
+ }
197
+ // Restart the timer.
198
+ timerId = startTimer(timerExpired, remainingWait(time, state))
199
+ }
200
+
201
+ async function trailingEdge(time: number): Promise<void> {
202
+ timerId = undefined
203
+
204
+ // Only invoke if we have `lastArgs` which means `func` has been
205
+ // debounced at least once.
206
+ if (trailing && lastArgs && deferred) {
207
+ await invokeFunc(time)
208
+ return
209
+ }
210
+ lastArgs = lastThis = undefined
211
+ // Dropped batch (only reachable with `trailing: false`): no invocation, so resolve `undefined`.
212
+ if (deferred) {
213
+ const d = deferred
214
+ deferred = undefined
215
+ d.resolve(undefined)
216
+ }
217
+ }
218
+
219
+ function cancel(): void {
220
+ if (timerId !== undefined) {
221
+ cancelTimer(timerId)
222
+ }
223
+ state.lastInvokeTime = 0
224
+ state.lastCallTime = undefined
225
+ lastArgs = lastThis = timerId = undefined
226
+ // Reject pending awaiters.
227
+ if (deferred) {
228
+ const d = deferred
229
+ deferred = undefined
230
+ d.reject(new Error('asyncDebounce cancelled'))
231
+ }
232
+ }
233
+
234
+ async function flush(): Promise<Awaited<ReturnType<T>> | undefined> {
235
+ if (timerId === undefined || !deferred) {
236
+ return undefined
237
+ }
238
+ const d = deferred
239
+ cancelTimer(timerId)
240
+ await trailingEdge(Date.now())
241
+ return d
242
+ }
243
+
244
+ function pending(): boolean {
245
+ return timerId !== undefined
246
+ }
247
+
248
+ async function debounced(
249
+ this: ThisParameterType<T>,
250
+ ...args: Parameters<T>
251
+ ): Promise<Awaited<ReturnType<T>> | undefined> {
252
+ const time = Date.now()
253
+ const isInvoking = shouldInvoke(time, state)
254
+
255
+ lastArgs = args
256
+ lastThis = this
257
+ state.lastCallTime = time
258
+
259
+ deferred ||= pDefer<Awaited<ReturnType<T>> | undefined>()
260
+ const currentDeferred = deferred
261
+
262
+ if (isInvoking) {
263
+ if (timerId === undefined) {
264
+ return leadingEdge(state.lastCallTime)
265
+ }
266
+ if (state.maxing) {
267
+ // Handle invocations in a tight loop.
268
+ timerId = startTimer(timerExpired, state.wait)
269
+ await invokeFunc(state.lastCallTime)
270
+ return currentDeferred
271
+ }
272
+ }
273
+ if (timerId === undefined) {
274
+ timerId = startTimer(timerExpired, state.wait)
275
+ }
276
+ return currentDeferred
277
+ }
278
+
279
+ debounced.cancel = cancel
280
+ debounced.flush = flush
281
+ debounced.pending = pending
282
+ return debounced as any
283
+ }
284
+
285
+ /**
286
+ * Like `_throttle`, but for async functions. See `_asyncDebounce` for the Promise semantics.
287
+ *
288
+ * @experimental
289
+ */
290
+ export function _asyncThrottle<T extends AnyAsyncFunction>(
291
+ func: T,
292
+ wait: number,
293
+ opt: AsyncThrottleOptions = {},
294
+ ): AsyncDebounced<T> {
295
+ return _asyncDebounce(func, wait, {
296
+ leading: true,
297
+ trailing: true,
298
+ ...opt,
299
+ maxWait: wait,
300
+ })
301
+ }
302
+
303
+ function remainingWait(time: number, state: DebounceTimerState): number {
304
+ const { lastCallTime, lastInvokeTime, wait, maxing, maxWait } = state
305
+ const timeSinceLastCall = time - lastCallTime!
306
+ const timeSinceLastInvoke = time - lastInvokeTime
307
+ const timeWaiting = wait - timeSinceLastCall
308
+
309
+ return maxing ? Math.min(timeWaiting, maxWait! - timeSinceLastInvoke) : timeWaiting
310
+ }
311
+
312
+ function shouldInvoke(time: number, state: DebounceTimerState): boolean {
313
+ const { lastCallTime, lastInvokeTime, wait, maxing, maxWait } = state
314
+ const timeSinceLastCall = time - lastCallTime!
315
+ const timeSinceLastInvoke = time - lastInvokeTime
316
+
317
+ // Either this is the first call, activity has stopped and we're at the
318
+ // trailing edge, the system time has gone backwards and we're treating
319
+ // it as the trailing edge, or we've hit the `maxWait` limit.
320
+ return (
321
+ lastCallTime === undefined ||
322
+ timeSinceLastCall >= wait ||
323
+ timeSinceLastCall < 0 ||
324
+ (maxing && timeSinceLastInvoke >= maxWait!)
325
+ )
326
+ }
327
+
328
+ function startTimer(pendingFunc: AnyFunction, wait: number): number {
329
+ return setTimeout(pendingFunc, wait)
330
+ }
331
+
332
+ function cancelTimer(id: number): void {
333
+ clearTimeout(id)
334
+ }
335
+
336
+ interface DebounceTimerState {
337
+ lastCallTime: number | undefined
338
+ lastInvokeTime: number
339
+ wait: number
340
+ maxing: boolean
341
+ maxWait: number | undefined
342
+ }
343
+
344
+ export interface Cancelable {
345
+ cancel: () => void
346
+ flush: () => void
347
+ }
348
+
349
+ /**
350
+ * Like `Cancelable`, but for the async debounced functions: `flush()` resolves with the flushed
351
+ * invocation's result (or `undefined` if nothing was pending), and `pending()` is exposed.
352
+ */
353
+ export interface AsyncCancelable<T> {
354
+ cancel: () => void
355
+ flush: () => Promise<T | undefined>
356
+ pending: () => boolean
357
+ }
358
+
359
+ export interface ThrottleOptions {
360
+ /**
361
+ * Invoke on the leading edge of the window (immediately, on the first call).
362
+ *
363
+ * @default true
364
+ */
365
+ leading?: boolean
366
+
367
+ /**
368
+ * Invoke on the trailing edge of the window (after `wait` has elapsed).
369
+ *
370
+ * @default true
371
+ */
372
+ trailing?: boolean
373
+ }
374
+
375
+ export interface DebounceOptions {
376
+ /**
377
+ * Invoke on the leading edge of the window (immediately, on the first call).
378
+ *
379
+ * @default false
380
+ */
381
+ leading?: boolean
382
+
383
+ /**
384
+ * Invoke on the trailing edge of the window (after `wait` has elapsed).
385
+ *
386
+ * @default true
387
+ */
388
+ trailing?: boolean
389
+
390
+ /**
391
+ * Maximum time `func` is allowed to be delayed before it's forcibly invoked.
392
+ */
393
+ maxWait?: number
394
+ }
395
+
396
+ export interface AsyncThrottleOptions {
397
+ /**
398
+ * Invoke on the leading edge of the window (immediately, on the first call).
399
+ *
400
+ * @default true
401
+ */
402
+ leading?: boolean
403
+
404
+ /**
405
+ * Invoke on the trailing edge of the window (after `wait` has elapsed).
406
+ *
407
+ * When `false`, calls dropped within the window (not served by a leading invocation) resolve
408
+ * with `undefined`. There's no invocation for them to await. This is why the returned function
409
+ * can resolve with `undefined` even though the original function's return type doesn't express that.
410
+ *
411
+ * @default true
412
+ */
413
+ trailing?: boolean
414
+ }
415
+
416
+ export interface AsyncDebounceOptions extends AsyncThrottleOptions {
417
+ /**
418
+ * Invoke on the leading edge of the window (immediately, on the first call).
419
+ *
420
+ * @default false
421
+ */
422
+ leading?: boolean
423
+
424
+ /**
425
+ * Maximum time `func` is allowed to be delayed before it's forcibly invoked.
426
+ */
427
+ maxWait?: number
428
+ }
429
+
430
+ /**
431
+ * The function returned by `_asyncDebounce`/`_asyncThrottle`. Same signature as `T`, but resolves
432
+ * `Awaited<ReturnType<T>> | undefined`
433
+ *
434
+ * This is because some configurations (e.g. `trailing: false`) may drop calls without invoking
435
+ * the original function, so there's no result to await.
436
+ */
437
+ export type AsyncDebounced<T extends AnyAsyncFunction> = ((
438
+ this: ThisParameterType<T>,
439
+ ...args: Parameters<T>
440
+ ) => Promise<Awaited<ReturnType<T>> | undefined>) &
441
+ AsyncCancelable<Awaited<ReturnType<T>>>