@depup/remeda 2.40.0-depup.0 → 2.42.0-depup.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.
package/README.md CHANGED
@@ -13,8 +13,8 @@ npm install @depup/remeda
13
13
 
14
14
  | Field | Value |
15
15
  |-------|-------|
16
- | Original | [remeda](https://www.npmjs.com/package/remeda) @ 2.40.0 |
17
- | Processed | 2026-08-16 |
16
+ | Original | [remeda](https://www.npmjs.com/package/remeda) @ 2.42.0 |
17
+ | Processed | 2026-08-23 |
18
18
  | Smoke test | passed |
19
19
  | Deps updated | 0 |
20
20
 
package/changes.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "bumped": {},
3
- "timestamp": "2026-08-16T00:27:39.570Z",
3
+ "timestamp": "2026-08-23T00:28:26.892Z",
4
4
  "totalUpdated": 0
5
5
  }
@@ -1 +1 @@
1
- {"version":3,"file":"debounce.cjs","names":[],"sources":["../src/debounce.ts"],"sourcesContent":["import type { StrictFunction } from \"./internal/types/StrictFunction\";\n\ntype Debouncer<F extends StrictFunction, IsNullable extends boolean = true> = {\n /**\n * Invoke the debounced function.\n *\n * @param args - Same as the args for the debounced function.\n * @returns The last computed value of the debounced function with the\n * latest args provided to it. If `timing` does not include `leading` then the\n * the function would return `undefined` until the first cool-down period is\n * over, otherwise the function would always return the return type of the\n * debounced function.\n */\n readonly call: (\n ...args: Parameters<F>\n ) => ReturnType<F> | (true extends IsNullable ? undefined : never);\n\n /**\n * Cancels any debounced functions without calling them, effectively resetting\n * the debouncer to the same state it is when initially created.\n */\n readonly cancel: () => void;\n\n /**\n * Similar to `cancel`, but would also trigger the `trailing` invocation if\n * the debouncer would run one at the end of the cool-down period.\n */\n readonly flush: () => ReturnType<F> | undefined;\n\n /**\n * Is `true` when there is an active cool-down period currently debouncing\n * invocations.\n */\n readonly isPending: boolean;\n\n /**\n * The last computed value of the debounced function.\n */\n readonly cachedValue: ReturnType<F> | undefined;\n};\n\ntype DebounceOptions = {\n readonly waitMs?: number;\n readonly maxWaitMs?: number;\n};\n\n/**\n * Wraps `func` with a debouncer object that \"debounces\" (delays) invocations of the function during a defined cool-down period (`waitMs`). It can be configured to invoke the function either at the start of the cool-down period, the end of it, or at both ends (`timing`).\n * It can also be configured to allow invocations during the cool-down period (`maxWaitMs`).\n * It stores the latest call's arguments so they could be used at the end of the cool-down period when invoking `func` (if configured to invoke the function at the end of the cool-down period).\n * It stores the value returned by `func` whenever its invoked. This value is returned on every call, and is accessible via the `cachedValue` property of the debouncer. Its important to note that the value might be different from the value that would be returned from running `func` with the current arguments as it is a cached value from a previous invocation.\n * **Important**: The cool-down period defines the minimum between two invocations, and not the maximum. The period will be **extended** each time a call is made until a full cool-down period has elapsed without any additional calls.\n *\n *! **DEPRECATED**: This implementation of debounce is known to have issues and might not behave as expected. It should be replaced with the `funnel` utility instead. The test file [funnel.remeda-debounce.test.ts](https://github.com/remeda/remeda/blob/main/packages/remeda/src/funnel.remeda-debounce.test.ts) offers a reference implementation that replicates `debounce` via `funnel`!\n *\n * @param func - The function to debounce, the returned `call` function will have\n * the exact same signature.\n * @param options - An object allowing further customization of the debouncer:\n * - `timing?: 'leading' | 'trailing' |'both'`. The default is `'trailing'`.\n * `leading` would result in the function being invoked at the start of the\n * cool-down period; `trailing` would result in the function being invoked at\n * the end of the cool-down period (using the args from the last call to the\n * debouncer). When `both` is selected the `trailing` invocation would only\n * take place if there were more than one call to the debouncer during the\n * cool-down period. **DEFAULT: 'trailing'**\n * - `waitMs?: number`. The length of the cool-down period in milliseconds. The\n * debouncer would wait until this amount of time has passed without **any**\n * additional calls to the debouncer before triggering the end-of-cool-down-\n * period event. When this happens, the function would be invoked (if `timing`\n * isn't `'leading'`) and the debouncer state would be reset. **DEFAULT: 0**\n * - `maxWaitMs?: number`. The length of time since a debounced call (a call\n * that the debouncer prevented from being invoked) was made until it would be\n * invoked. Because the debouncer can be continually triggered and thus never\n * reach the end of the cool-down period, this allows the function to still\n * be invoked occasionally. IMPORTANT: This param is ignored when `timing` is\n * `'leading'`.\n * @returns A debouncer object. The main function is `call`. In addition to it\n * the debouncer comes with the following additional functions and properties:\n * - `cancel` method to cancel delayed `func` invocations\n * - `flush` method to end the cool-down period immediately.\n * - `cachedValue` the latest return value of an invocation (if one occurred).\n * - `isPending` flag to check if there is an inflight cool-down window.\n * @signature\n * debounce(func, options);\n * @example\n * const debouncer = debounce(identity(), { timing: 'trailing', waitMs: 1000 });\n * const result1 = debouncer.call(1); // => undefined\n * const result2 = debouncer.call(2); // => undefined\n * // after 1 second\n * const result3 = debouncer.call(3); // => 2\n * // after 1 second\n * debouncer.cachedValue; // => 3\n * @dataFirst\n * @category Function\n * @deprecated This implementation of debounce is known to have issues and might\n * not behave as expected. It should be replaced with the `funnel` utility\n * instead. The test file `funnel.remeda-debounce.test.ts` offers a reference\n * implementation that replicates `debounce` via `funnel`.\n * @see https://css-tricks.com/debouncing-throttling-explained-examples/\n */\nexport function debounce<F extends StrictFunction>(\n func: F,\n options: DebounceOptions & { readonly timing?: \"trailing\" },\n): Debouncer<F>;\nexport function debounce<F extends StrictFunction>(\n func: F,\n options:\n | (DebounceOptions & { readonly timing: \"both\" })\n | (Omit<DebounceOptions, \"maxWaitMs\"> & { readonly timing: \"leading\" }),\n): Debouncer<F, false /* call CAN'T return null */>;\n\nexport function debounce<F extends StrictFunction>(\n func: F,\n {\n waitMs,\n timing = \"trailing\",\n maxWaitMs,\n }: DebounceOptions & {\n readonly timing?: \"both\" | \"leading\" | \"trailing\";\n },\n): Debouncer<F> {\n if (maxWaitMs !== undefined && waitMs !== undefined && maxWaitMs < waitMs) {\n throw new Error(\n `debounce: maxWaitMs (${maxWaitMs.toString()}) cannot be less than waitMs (${waitMs.toString()})`,\n );\n }\n\n // All these are part of the debouncer runtime state:\n\n // The timeout is the main object we use to tell if there's an active cool-\n // down period or not.\n let coolDownTimeoutId: ReturnType<typeof setTimeout> | undefined;\n\n // We use an additional timeout to track how long the last debounced call is\n // waiting.\n let maxWaitTimeoutId: ReturnType<typeof setTimeout> | undefined;\n\n // For 'trailing' invocations we need to keep the args around until we\n // actually invoke the function.\n let latestCallArgs: Parameters<F> | undefined;\n\n // To make any value of the debounced function we need to be able to return a\n // value. For any invocation except the first one when 'leading' is enabled we\n // will return this cached value.\n let result: ReturnType<F> | undefined;\n\n const handleInvoke = (): void => {\n if (maxWaitTimeoutId !== undefined) {\n // We are invoking the function so the wait is over...\n const timeoutId = maxWaitTimeoutId;\n maxWaitTimeoutId = undefined;\n clearTimeout(timeoutId);\n }\n\n /* v8 ignore if -- This protects us against changes to the logic, there is no known flow we can simulate to reach this condition. It can only happen if a previous timeout isn't cleared (or faces a race condition clearing). @preserve */\n if (latestCallArgs === undefined) {\n // If you see this error pop up when using this function please report\n // it on the Remeda github page!\n throw new Error(\n \"REMEDA[debounce]: latestCallArgs was unexpectedly undefined.\",\n );\n }\n\n const args = latestCallArgs;\n // Make sure the args aren't accidentally used again, this is mainly\n // relevant for the check above where we'll fail a subsequent call to\n // 'trailingEdge'.\n latestCallArgs = undefined;\n\n // Invoke the function and store the results locally.\n // @ts-expect-error [ts2345, ts2322] -- TypeScript infers the generic sub-\n // types too eagerly, making itself blind to the fact that the types match\n // here.\n result = func(...args);\n };\n\n const handleCoolDownEnd = (): void => {\n if (coolDownTimeoutId === undefined) {\n // It's rare to get here, it should only happen when `flush` is called\n // when the cool-down window isn't active.\n return;\n }\n\n // Make sure there are no more timers running.\n const timeoutId = coolDownTimeoutId;\n coolDownTimeoutId = undefined;\n clearTimeout(timeoutId);\n // Then reset state so a new cool-down window can begin on the next call.\n\n if (latestCallArgs !== undefined) {\n // If we have a debounced call waiting to be invoked at the end of the\n // cool-down period we need to invoke it now.\n handleInvoke();\n }\n };\n\n const handleDebouncedCall = (args: Parameters<F>): void => {\n // We save the latest call args so that (if and) when we invoke the function\n // in the future, we have args to invoke it with.\n latestCallArgs = args;\n\n if (maxWaitMs !== undefined && maxWaitTimeoutId === undefined) {\n // We only need to start the maxWait timeout once, on the first debounced\n // call that is now being delayed.\n maxWaitTimeoutId = setTimeout(handleInvoke, maxWaitMs);\n }\n };\n\n return {\n call: (...args) => {\n if (coolDownTimeoutId === undefined) {\n // This call is starting a new cool-down window!\n\n if (timing === \"trailing\") {\n // Only when the timing is \"trailing\" is the first call \"debounced\".\n handleDebouncedCall(args);\n } else {\n // Otherwise for \"leading\" and \"both\" the first call is actually\n // called directly and not via a timeout.\n // @ts-expect-error [ts2345, ts2322] -- TypeScript infers the generic\n // sub-types too eagerly, making itself blind to the fact that the\n // types match here.\n result = func(...args);\n }\n } else {\n // There's an inflight cool-down window.\n\n if (timing !== \"leading\") {\n // When the timing is 'leading' all following calls are just ignored\n // until the cool-down period ends. But for the other timings the call\n // is \"debounced\".\n handleDebouncedCall(args);\n }\n\n // The current timeout is no longer relevant because we need to wait the\n // full `waitMs` time from this call.\n const timeoutId = coolDownTimeoutId;\n coolDownTimeoutId = undefined;\n clearTimeout(timeoutId);\n }\n\n coolDownTimeoutId = setTimeout(\n handleCoolDownEnd,\n // If waitMs is not defined but maxWaitMs *is* it means the user is only\n // interested in the leaky-bucket nature of the debouncer which is\n // achieved by setting waitMs === maxWaitMs. If both are not defined we\n // default to 0 which would wait until the end of the execution frame.\n waitMs ?? maxWaitMs ?? 0,\n );\n\n // Return the last computed result while we \"debounce\" further calls.\n return result;\n },\n\n cancel: () => {\n // Reset all \"in-flight\" state of the debouncer. Notice that we keep the\n // cached value!\n\n if (coolDownTimeoutId !== undefined) {\n const timeoutId = coolDownTimeoutId;\n coolDownTimeoutId = undefined;\n clearTimeout(timeoutId);\n }\n\n if (maxWaitTimeoutId !== undefined) {\n const timeoutId = maxWaitTimeoutId;\n maxWaitTimeoutId = undefined;\n clearTimeout(timeoutId);\n }\n\n latestCallArgs = undefined;\n },\n\n flush: () => {\n // Flush is just a manual way to trigger the end of the cool-down window.\n handleCoolDownEnd();\n return result;\n },\n\n get isPending() {\n return coolDownTimeoutId !== undefined;\n },\n\n get cachedValue() {\n return result;\n },\n };\n}\n"],"mappings":"mEA+GA,SAAgB,EACd,EACA,CACE,SACA,SAAS,WACT,aAIY,CACd,GAAI,IAAc,IAAA,IAAa,IAAW,IAAA,IAAa,EAAY,EACjE,MAAU,MACR,wBAAwB,EAAU,SAAS,EAAE,gCAAgC,EAAO,SAAS,EAAE,EACjG,EAOF,IAAI,EAIA,EAIA,EAKA,EAEE,MAA2B,CAC/B,GAAI,IAAqB,IAAA,GAAW,CAElC,IAAM,EAAY,EAClB,EAAmB,IAAA,GACnB,aAAa,CAAS,CACxB,CAGA,GAAI,IAAmB,IAAA,GAGrB,MAAU,MACR,8DACF,EAGF,IAAM,EAAO,EAIb,EAAiB,IAAA,GAMjB,EAAS,EAAK,GAAG,CAAI,CACvB,EAEM,MAAgC,CACpC,GAAI,IAAsB,IAAA,GAGxB,OAIF,IAAM,EAAY,EAClB,EAAoB,IAAA,GACpB,aAAa,CAAS,EAGlB,IAAmB,IAAA,IAGrB,EAAa,CAEjB,EAEM,EAAuB,GAA8B,CAGzD,EAAiB,EAEb,IAAc,IAAA,IAAa,IAAqB,IAAA,KAGlD,EAAmB,WAAW,EAAc,CAAS,EAEzD,EAEA,MAAO,CACL,MAAO,GAAG,IAAS,CACjB,GAAI,IAAsB,IAAA,GAGpB,IAAW,WAEb,EAAoB,CAAI,EAOxB,EAAS,EAAK,GAAG,CAAI,MAElB,CAGD,IAAW,WAIb,EAAoB,CAAI,EAK1B,IAAM,EAAY,EAClB,EAAoB,IAAA,GACpB,aAAa,CAAS,CACxB,CAYA,MAVA,GAAoB,WAClB,EAKA,GAAU,GAAa,CACzB,EAGO,CACT,EAEA,WAAc,CAIZ,GAAI,IAAsB,IAAA,GAAW,CACnC,IAAM,EAAY,EAClB,EAAoB,IAAA,GACpB,aAAa,CAAS,CACxB,CAEA,GAAI,IAAqB,IAAA,GAAW,CAClC,IAAM,EAAY,EAClB,EAAmB,IAAA,GACnB,aAAa,CAAS,CACxB,CAEA,EAAiB,IAAA,EACnB,EAEA,WAEE,EAAkB,EACX,GAGT,IAAI,WAAY,CACd,OAAO,IAAsB,IAAA,EAC/B,EAEA,IAAI,aAAc,CAChB,OAAO,CACT,CACF,CACF"}
1
+ {"version":3,"file":"debounce.cjs","names":[],"sources":["../src/debounce.ts"],"sourcesContent":["import type { StrictFunction } from \"./internal/types/StrictFunction\";\n\ntype Debouncer<F extends StrictFunction, IsNullable extends boolean = true> = {\n /**\n * Invoke the debounced function.\n *\n * @param args - Same as the args for the debounced function.\n * @returns The last computed value of the debounced function with the\n * latest args provided to it. If `timing` does not include `leading` then the\n * the function would return `undefined` until the first cool-down period is\n * over, otherwise the function would always return the return type of the\n * debounced function.\n */\n readonly call: (\n ...args: Parameters<F>\n ) => ReturnType<F> | (true extends IsNullable ? undefined : never);\n\n /**\n * Cancels any debounced functions without calling them, effectively resetting\n * the debouncer to the same state it is when initially created.\n */\n readonly cancel: () => void;\n\n /**\n * Similar to `cancel`, but would also trigger the `trailing` invocation if\n * the debouncer would run one at the end of the cool-down period.\n */\n readonly flush: () => ReturnType<F> | undefined;\n\n /**\n * Is `true` when there is an active cool-down period currently debouncing\n * invocations.\n */\n readonly isPending: boolean;\n\n /**\n * The last computed value of the debounced function.\n */\n readonly cachedValue: ReturnType<F> | undefined;\n};\n\ntype DebounceOptions = {\n readonly waitMs?: number;\n readonly maxWaitMs?: number;\n};\n\n/**\n * Wraps `func` with a debouncer object that \"debounces\" (delays) invocations of the function during a defined cool-down period (`waitMs`). It can be configured to invoke the function either at the start of the cool-down period, the end of it, or at both ends (`timing`).\n * It can also be configured to allow invocations during the cool-down period (`maxWaitMs`).\n * It stores the latest call's arguments so they could be used at the end of the cool-down period when invoking `func` (if configured to invoke the function at the end of the cool-down period).\n * It stores the value returned by `func` whenever its invoked. This value is returned on every call, and is accessible via the `cachedValue` property of the debouncer. Its important to note that the value might be different from the value that would be returned from running `func` with the current arguments as it is a cached value from a previous invocation.\n * **Important**: The cool-down period defines the minimum between two invocations, and not the maximum. The period will be **extended** each time a call is made until a full cool-down period has elapsed without any additional calls.\n *\n *! **DEPRECATED**: This implementation of debounce is known to have issues and might not behave as expected. It should be replaced with the `funnel` utility instead. The test file [funnel.remeda-debounce.test.ts](https://github.com/remeda/remeda/blob/main/packages/remeda/src/funnel.remeda-debounce.test.ts) offers a drop-in replacement implemented via `funnel`: copy everything between its REFERENCE START and REFERENCE END markers into your project!\n *\n * @param func - The function to debounce, the returned `call` function will have\n * the exact same signature.\n * @param options - An object allowing further customization of the debouncer:\n * - `timing?: 'leading' | 'trailing' |'both'`. The default is `'trailing'`.\n * `leading` would result in the function being invoked at the start of the\n * cool-down period; `trailing` would result in the function being invoked at\n * the end of the cool-down period (using the args from the last call to the\n * debouncer). When `both` is selected the `trailing` invocation would only\n * take place if there were more than one call to the debouncer during the\n * cool-down period. **DEFAULT: 'trailing'**\n * - `waitMs?: number`. The length of the cool-down period in milliseconds. The\n * debouncer would wait until this amount of time has passed without **any**\n * additional calls to the debouncer before triggering the end-of-cool-down-\n * period event. When this happens, the function would be invoked (if `timing`\n * isn't `'leading'`) and the debouncer state would be reset. **DEFAULT: 0**\n * - `maxWaitMs?: number`. The length of time since a debounced call (a call\n * that the debouncer prevented from being invoked) was made until it would be\n * invoked. Because the debouncer can be continually triggered and thus never\n * reach the end of the cool-down period, this allows the function to still\n * be invoked occasionally. IMPORTANT: This param is ignored when `timing` is\n * `'leading'`.\n * @returns A debouncer object. The main function is `call`. In addition to it\n * the debouncer comes with the following additional functions and properties:\n * - `cancel` method to cancel delayed `func` invocations\n * - `flush` method to end the cool-down period immediately.\n * - `cachedValue` the latest return value of an invocation (if one occurred).\n * - `isPending` flag to check if there is an inflight cool-down window.\n * @signature\n * debounce(func, options);\n * @example\n * const debouncer = debounce(identity(), { timing: 'trailing', waitMs: 1000 });\n * const result1 = debouncer.call(1); // => undefined\n * const result2 = debouncer.call(2); // => undefined\n * // after 1 second\n * const result3 = debouncer.call(3); // => 2\n * // after 1 second\n * debouncer.cachedValue; // => 3\n * @dataFirst\n * @category Function\n * @deprecated This implementation of debounce is known to have issues and might\n * not behave as expected. It should be replaced with the `funnel` utility\n * instead. The test file [funnel.remeda-debounce.test.ts](https://github.com/remeda/remeda/blob/main/packages/remeda/src/funnel.remeda-debounce.test.ts)\n * offers a drop-in replacement implemented via `funnel`: copy everything\n * between its REFERENCE START and REFERENCE END markers into your project.\n * @see https://css-tricks.com/debouncing-throttling-explained-examples/\n */\nexport function debounce<F extends StrictFunction>(\n func: F,\n options: DebounceOptions & { readonly timing?: \"trailing\" },\n): Debouncer<F>;\nexport function debounce<F extends StrictFunction>(\n func: F,\n options:\n | (DebounceOptions & { readonly timing: \"both\" })\n | (Omit<DebounceOptions, \"maxWaitMs\"> & { readonly timing: \"leading\" }),\n): Debouncer<F, false /* call CAN'T return null */>;\n\nexport function debounce<F extends StrictFunction>(\n func: F,\n {\n waitMs,\n timing = \"trailing\",\n maxWaitMs,\n }: DebounceOptions & {\n readonly timing?: \"both\" | \"leading\" | \"trailing\";\n },\n): Debouncer<F> {\n if (maxWaitMs !== undefined && waitMs !== undefined && maxWaitMs < waitMs) {\n throw new Error(\n `debounce: maxWaitMs (${maxWaitMs.toString()}) cannot be less than waitMs (${waitMs.toString()})`,\n );\n }\n\n // All these are part of the debouncer runtime state:\n\n // The timeout is the main object we use to tell if there's an active cool-\n // down period or not.\n let coolDownTimeoutId: ReturnType<typeof setTimeout> | undefined;\n\n // We use an additional timeout to track how long the last debounced call is\n // waiting.\n let maxWaitTimeoutId: ReturnType<typeof setTimeout> | undefined;\n\n // For 'trailing' invocations we need to keep the args around until we\n // actually invoke the function.\n let latestCallArgs: Parameters<F> | undefined;\n\n // To make any value of the debounced function we need to be able to return a\n // value. For any invocation except the first one when 'leading' is enabled we\n // will return this cached value.\n let result: ReturnType<F> | undefined;\n\n const handleInvoke = (): void => {\n if (maxWaitTimeoutId !== undefined) {\n // We are invoking the function so the wait is over...\n const timeoutId = maxWaitTimeoutId;\n maxWaitTimeoutId = undefined;\n clearTimeout(timeoutId);\n }\n\n /* v8 ignore if -- This protects us against changes to the logic, there is no known flow we can simulate to reach this condition. It can only happen if a previous timeout isn't cleared (or faces a race condition clearing). @preserve */\n if (latestCallArgs === undefined) {\n // If you see this error pop up when using this function please report\n // it on the Remeda github page!\n throw new Error(\n \"REMEDA[debounce]: latestCallArgs was unexpectedly undefined.\",\n );\n }\n\n const args = latestCallArgs;\n // Make sure the args aren't accidentally used again, this is mainly\n // relevant for the check above where we'll fail a subsequent call to\n // 'trailingEdge'.\n latestCallArgs = undefined;\n\n // Invoke the function and store the results locally.\n // @ts-expect-error [ts2345, ts2322] -- TypeScript infers the generic sub-\n // types too eagerly, making itself blind to the fact that the types match\n // here.\n result = func(...args);\n };\n\n const handleCoolDownEnd = (): void => {\n if (coolDownTimeoutId === undefined) {\n // It's rare to get here, it should only happen when `flush` is called\n // when the cool-down window isn't active.\n return;\n }\n\n // Make sure there are no more timers running.\n const timeoutId = coolDownTimeoutId;\n coolDownTimeoutId = undefined;\n clearTimeout(timeoutId);\n // Then reset state so a new cool-down window can begin on the next call.\n\n if (latestCallArgs !== undefined) {\n // If we have a debounced call waiting to be invoked at the end of the\n // cool-down period we need to invoke it now.\n handleInvoke();\n }\n };\n\n const handleDebouncedCall = (args: Parameters<F>): void => {\n // We save the latest call args so that (if and) when we invoke the function\n // in the future, we have args to invoke it with.\n latestCallArgs = args;\n\n if (maxWaitMs !== undefined && maxWaitTimeoutId === undefined) {\n // We only need to start the maxWait timeout once, on the first debounced\n // call that is now being delayed.\n maxWaitTimeoutId = setTimeout(handleInvoke, maxWaitMs);\n }\n };\n\n return {\n call: (...args) => {\n if (coolDownTimeoutId === undefined) {\n // This call is starting a new cool-down window!\n\n if (timing === \"trailing\") {\n // Only when the timing is \"trailing\" is the first call \"debounced\".\n handleDebouncedCall(args);\n } else {\n // Otherwise for \"leading\" and \"both\" the first call is actually\n // called directly and not via a timeout.\n // @ts-expect-error [ts2345, ts2322] -- TypeScript infers the generic\n // sub-types too eagerly, making itself blind to the fact that the\n // types match here.\n result = func(...args);\n }\n } else {\n // There's an inflight cool-down window.\n\n if (timing !== \"leading\") {\n // When the timing is 'leading' all following calls are just ignored\n // until the cool-down period ends. But for the other timings the call\n // is \"debounced\".\n handleDebouncedCall(args);\n }\n\n // The current timeout is no longer relevant because we need to wait the\n // full `waitMs` time from this call.\n const timeoutId = coolDownTimeoutId;\n coolDownTimeoutId = undefined;\n clearTimeout(timeoutId);\n }\n\n coolDownTimeoutId = setTimeout(\n handleCoolDownEnd,\n // If waitMs is not defined but maxWaitMs *is* it means the user is only\n // interested in the leaky-bucket nature of the debouncer which is\n // achieved by setting waitMs === maxWaitMs. If both are not defined we\n // default to 0 which would wait until the end of the execution frame.\n waitMs ?? maxWaitMs ?? 0,\n );\n\n // Return the last computed result while we \"debounce\" further calls.\n return result;\n },\n\n cancel: () => {\n // Reset all \"in-flight\" state of the debouncer. Notice that we keep the\n // cached value!\n\n if (coolDownTimeoutId !== undefined) {\n const timeoutId = coolDownTimeoutId;\n coolDownTimeoutId = undefined;\n clearTimeout(timeoutId);\n }\n\n if (maxWaitTimeoutId !== undefined) {\n const timeoutId = maxWaitTimeoutId;\n maxWaitTimeoutId = undefined;\n clearTimeout(timeoutId);\n }\n\n latestCallArgs = undefined;\n },\n\n flush: () => {\n // Flush is just a manual way to trigger the end of the cool-down window.\n handleCoolDownEnd();\n return result;\n },\n\n get isPending() {\n return coolDownTimeoutId !== undefined;\n },\n\n get cachedValue() {\n return result;\n },\n };\n}\n"],"mappings":"mEAgHA,SAAgB,EACd,EACA,CACE,SACA,SAAS,WACT,aAIY,CACd,GAAI,IAAc,IAAA,IAAa,IAAW,IAAA,IAAa,EAAY,EACjE,MAAU,MACR,wBAAwB,EAAU,SAAS,EAAE,gCAAgC,EAAO,SAAS,EAAE,EACjG,EAOF,IAAI,EAIA,EAIA,EAKA,EAEE,MAA2B,CAC/B,GAAI,IAAqB,IAAA,GAAW,CAElC,IAAM,EAAY,EAClB,EAAmB,IAAA,GACnB,aAAa,CAAS,CACxB,CAGA,GAAI,IAAmB,IAAA,GAGrB,MAAU,MACR,8DACF,EAGF,IAAM,EAAO,EAIb,EAAiB,IAAA,GAMjB,EAAS,EAAK,GAAG,CAAI,CACvB,EAEM,MAAgC,CACpC,GAAI,IAAsB,IAAA,GAGxB,OAIF,IAAM,EAAY,EAClB,EAAoB,IAAA,GACpB,aAAa,CAAS,EAGlB,IAAmB,IAAA,IAGrB,EAAa,CAEjB,EAEM,EAAuB,GAA8B,CAGzD,EAAiB,EAEb,IAAc,IAAA,IAAa,IAAqB,IAAA,KAGlD,EAAmB,WAAW,EAAc,CAAS,EAEzD,EAEA,MAAO,CACL,MAAO,GAAG,IAAS,CACjB,GAAI,IAAsB,IAAA,GAGpB,IAAW,WAEb,EAAoB,CAAI,EAOxB,EAAS,EAAK,GAAG,CAAI,MAElB,CAGD,IAAW,WAIb,EAAoB,CAAI,EAK1B,IAAM,EAAY,EAClB,EAAoB,IAAA,GACpB,aAAa,CAAS,CACxB,CAYA,MAVA,GAAoB,WAClB,EAKA,GAAU,GAAa,CACzB,EAGO,CACT,EAEA,WAAc,CAIZ,GAAI,IAAsB,IAAA,GAAW,CACnC,IAAM,EAAY,EAClB,EAAoB,IAAA,GACpB,aAAa,CAAS,CACxB,CAEA,GAAI,IAAqB,IAAA,GAAW,CAClC,IAAM,EAAY,EAClB,EAAmB,IAAA,GACnB,aAAa,CAAS,CACxB,CAEA,EAAiB,IAAA,EACnB,EAEA,WAEE,EAAkB,EACX,GAGT,IAAI,WAAY,CACd,OAAO,IAAsB,IAAA,EAC/B,EAEA,IAAI,aAAc,CAChB,OAAO,CACT,CACF,CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"debounce.js","names":[],"sources":["../src/debounce.ts"],"sourcesContent":["import type { StrictFunction } from \"./internal/types/StrictFunction\";\n\ntype Debouncer<F extends StrictFunction, IsNullable extends boolean = true> = {\n /**\n * Invoke the debounced function.\n *\n * @param args - Same as the args for the debounced function.\n * @returns The last computed value of the debounced function with the\n * latest args provided to it. If `timing` does not include `leading` then the\n * the function would return `undefined` until the first cool-down period is\n * over, otherwise the function would always return the return type of the\n * debounced function.\n */\n readonly call: (\n ...args: Parameters<F>\n ) => ReturnType<F> | (true extends IsNullable ? undefined : never);\n\n /**\n * Cancels any debounced functions without calling them, effectively resetting\n * the debouncer to the same state it is when initially created.\n */\n readonly cancel: () => void;\n\n /**\n * Similar to `cancel`, but would also trigger the `trailing` invocation if\n * the debouncer would run one at the end of the cool-down period.\n */\n readonly flush: () => ReturnType<F> | undefined;\n\n /**\n * Is `true` when there is an active cool-down period currently debouncing\n * invocations.\n */\n readonly isPending: boolean;\n\n /**\n * The last computed value of the debounced function.\n */\n readonly cachedValue: ReturnType<F> | undefined;\n};\n\ntype DebounceOptions = {\n readonly waitMs?: number;\n readonly maxWaitMs?: number;\n};\n\n/**\n * Wraps `func` with a debouncer object that \"debounces\" (delays) invocations of the function during a defined cool-down period (`waitMs`). It can be configured to invoke the function either at the start of the cool-down period, the end of it, or at both ends (`timing`).\n * It can also be configured to allow invocations during the cool-down period (`maxWaitMs`).\n * It stores the latest call's arguments so they could be used at the end of the cool-down period when invoking `func` (if configured to invoke the function at the end of the cool-down period).\n * It stores the value returned by `func` whenever its invoked. This value is returned on every call, and is accessible via the `cachedValue` property of the debouncer. Its important to note that the value might be different from the value that would be returned from running `func` with the current arguments as it is a cached value from a previous invocation.\n * **Important**: The cool-down period defines the minimum between two invocations, and not the maximum. The period will be **extended** each time a call is made until a full cool-down period has elapsed without any additional calls.\n *\n *! **DEPRECATED**: This implementation of debounce is known to have issues and might not behave as expected. It should be replaced with the `funnel` utility instead. The test file [funnel.remeda-debounce.test.ts](https://github.com/remeda/remeda/blob/main/packages/remeda/src/funnel.remeda-debounce.test.ts) offers a reference implementation that replicates `debounce` via `funnel`!\n *\n * @param func - The function to debounce, the returned `call` function will have\n * the exact same signature.\n * @param options - An object allowing further customization of the debouncer:\n * - `timing?: 'leading' | 'trailing' |'both'`. The default is `'trailing'`.\n * `leading` would result in the function being invoked at the start of the\n * cool-down period; `trailing` would result in the function being invoked at\n * the end of the cool-down period (using the args from the last call to the\n * debouncer). When `both` is selected the `trailing` invocation would only\n * take place if there were more than one call to the debouncer during the\n * cool-down period. **DEFAULT: 'trailing'**\n * - `waitMs?: number`. The length of the cool-down period in milliseconds. The\n * debouncer would wait until this amount of time has passed without **any**\n * additional calls to the debouncer before triggering the end-of-cool-down-\n * period event. When this happens, the function would be invoked (if `timing`\n * isn't `'leading'`) and the debouncer state would be reset. **DEFAULT: 0**\n * - `maxWaitMs?: number`. The length of time since a debounced call (a call\n * that the debouncer prevented from being invoked) was made until it would be\n * invoked. Because the debouncer can be continually triggered and thus never\n * reach the end of the cool-down period, this allows the function to still\n * be invoked occasionally. IMPORTANT: This param is ignored when `timing` is\n * `'leading'`.\n * @returns A debouncer object. The main function is `call`. In addition to it\n * the debouncer comes with the following additional functions and properties:\n * - `cancel` method to cancel delayed `func` invocations\n * - `flush` method to end the cool-down period immediately.\n * - `cachedValue` the latest return value of an invocation (if one occurred).\n * - `isPending` flag to check if there is an inflight cool-down window.\n * @signature\n * debounce(func, options);\n * @example\n * const debouncer = debounce(identity(), { timing: 'trailing', waitMs: 1000 });\n * const result1 = debouncer.call(1); // => undefined\n * const result2 = debouncer.call(2); // => undefined\n * // after 1 second\n * const result3 = debouncer.call(3); // => 2\n * // after 1 second\n * debouncer.cachedValue; // => 3\n * @dataFirst\n * @category Function\n * @deprecated This implementation of debounce is known to have issues and might\n * not behave as expected. It should be replaced with the `funnel` utility\n * instead. The test file `funnel.remeda-debounce.test.ts` offers a reference\n * implementation that replicates `debounce` via `funnel`.\n * @see https://css-tricks.com/debouncing-throttling-explained-examples/\n */\nexport function debounce<F extends StrictFunction>(\n func: F,\n options: DebounceOptions & { readonly timing?: \"trailing\" },\n): Debouncer<F>;\nexport function debounce<F extends StrictFunction>(\n func: F,\n options:\n | (DebounceOptions & { readonly timing: \"both\" })\n | (Omit<DebounceOptions, \"maxWaitMs\"> & { readonly timing: \"leading\" }),\n): Debouncer<F, false /* call CAN'T return null */>;\n\nexport function debounce<F extends StrictFunction>(\n func: F,\n {\n waitMs,\n timing = \"trailing\",\n maxWaitMs,\n }: DebounceOptions & {\n readonly timing?: \"both\" | \"leading\" | \"trailing\";\n },\n): Debouncer<F> {\n if (maxWaitMs !== undefined && waitMs !== undefined && maxWaitMs < waitMs) {\n throw new Error(\n `debounce: maxWaitMs (${maxWaitMs.toString()}) cannot be less than waitMs (${waitMs.toString()})`,\n );\n }\n\n // All these are part of the debouncer runtime state:\n\n // The timeout is the main object we use to tell if there's an active cool-\n // down period or not.\n let coolDownTimeoutId: ReturnType<typeof setTimeout> | undefined;\n\n // We use an additional timeout to track how long the last debounced call is\n // waiting.\n let maxWaitTimeoutId: ReturnType<typeof setTimeout> | undefined;\n\n // For 'trailing' invocations we need to keep the args around until we\n // actually invoke the function.\n let latestCallArgs: Parameters<F> | undefined;\n\n // To make any value of the debounced function we need to be able to return a\n // value. For any invocation except the first one when 'leading' is enabled we\n // will return this cached value.\n let result: ReturnType<F> | undefined;\n\n const handleInvoke = (): void => {\n if (maxWaitTimeoutId !== undefined) {\n // We are invoking the function so the wait is over...\n const timeoutId = maxWaitTimeoutId;\n maxWaitTimeoutId = undefined;\n clearTimeout(timeoutId);\n }\n\n /* v8 ignore if -- This protects us against changes to the logic, there is no known flow we can simulate to reach this condition. It can only happen if a previous timeout isn't cleared (or faces a race condition clearing). @preserve */\n if (latestCallArgs === undefined) {\n // If you see this error pop up when using this function please report\n // it on the Remeda github page!\n throw new Error(\n \"REMEDA[debounce]: latestCallArgs was unexpectedly undefined.\",\n );\n }\n\n const args = latestCallArgs;\n // Make sure the args aren't accidentally used again, this is mainly\n // relevant for the check above where we'll fail a subsequent call to\n // 'trailingEdge'.\n latestCallArgs = undefined;\n\n // Invoke the function and store the results locally.\n // @ts-expect-error [ts2345, ts2322] -- TypeScript infers the generic sub-\n // types too eagerly, making itself blind to the fact that the types match\n // here.\n result = func(...args);\n };\n\n const handleCoolDownEnd = (): void => {\n if (coolDownTimeoutId === undefined) {\n // It's rare to get here, it should only happen when `flush` is called\n // when the cool-down window isn't active.\n return;\n }\n\n // Make sure there are no more timers running.\n const timeoutId = coolDownTimeoutId;\n coolDownTimeoutId = undefined;\n clearTimeout(timeoutId);\n // Then reset state so a new cool-down window can begin on the next call.\n\n if (latestCallArgs !== undefined) {\n // If we have a debounced call waiting to be invoked at the end of the\n // cool-down period we need to invoke it now.\n handleInvoke();\n }\n };\n\n const handleDebouncedCall = (args: Parameters<F>): void => {\n // We save the latest call args so that (if and) when we invoke the function\n // in the future, we have args to invoke it with.\n latestCallArgs = args;\n\n if (maxWaitMs !== undefined && maxWaitTimeoutId === undefined) {\n // We only need to start the maxWait timeout once, on the first debounced\n // call that is now being delayed.\n maxWaitTimeoutId = setTimeout(handleInvoke, maxWaitMs);\n }\n };\n\n return {\n call: (...args) => {\n if (coolDownTimeoutId === undefined) {\n // This call is starting a new cool-down window!\n\n if (timing === \"trailing\") {\n // Only when the timing is \"trailing\" is the first call \"debounced\".\n handleDebouncedCall(args);\n } else {\n // Otherwise for \"leading\" and \"both\" the first call is actually\n // called directly and not via a timeout.\n // @ts-expect-error [ts2345, ts2322] -- TypeScript infers the generic\n // sub-types too eagerly, making itself blind to the fact that the\n // types match here.\n result = func(...args);\n }\n } else {\n // There's an inflight cool-down window.\n\n if (timing !== \"leading\") {\n // When the timing is 'leading' all following calls are just ignored\n // until the cool-down period ends. But for the other timings the call\n // is \"debounced\".\n handleDebouncedCall(args);\n }\n\n // The current timeout is no longer relevant because we need to wait the\n // full `waitMs` time from this call.\n const timeoutId = coolDownTimeoutId;\n coolDownTimeoutId = undefined;\n clearTimeout(timeoutId);\n }\n\n coolDownTimeoutId = setTimeout(\n handleCoolDownEnd,\n // If waitMs is not defined but maxWaitMs *is* it means the user is only\n // interested in the leaky-bucket nature of the debouncer which is\n // achieved by setting waitMs === maxWaitMs. If both are not defined we\n // default to 0 which would wait until the end of the execution frame.\n waitMs ?? maxWaitMs ?? 0,\n );\n\n // Return the last computed result while we \"debounce\" further calls.\n return result;\n },\n\n cancel: () => {\n // Reset all \"in-flight\" state of the debouncer. Notice that we keep the\n // cached value!\n\n if (coolDownTimeoutId !== undefined) {\n const timeoutId = coolDownTimeoutId;\n coolDownTimeoutId = undefined;\n clearTimeout(timeoutId);\n }\n\n if (maxWaitTimeoutId !== undefined) {\n const timeoutId = maxWaitTimeoutId;\n maxWaitTimeoutId = undefined;\n clearTimeout(timeoutId);\n }\n\n latestCallArgs = undefined;\n },\n\n flush: () => {\n // Flush is just a manual way to trigger the end of the cool-down window.\n handleCoolDownEnd();\n return result;\n },\n\n get isPending() {\n return coolDownTimeoutId !== undefined;\n },\n\n get cachedValue() {\n return result;\n },\n };\n}\n"],"mappings":"AA+GA,SAAgB,EACd,EACA,CACE,SACA,SAAS,WACT,aAIY,CACd,GAAI,IAAc,IAAA,IAAa,IAAW,IAAA,IAAa,EAAY,EACjE,MAAU,MACR,wBAAwB,EAAU,SAAS,EAAE,gCAAgC,EAAO,SAAS,EAAE,EACjG,EAOF,IAAI,EAIA,EAIA,EAKA,EAEE,MAA2B,CAC/B,GAAI,IAAqB,IAAA,GAAW,CAElC,IAAM,EAAY,EAClB,EAAmB,IAAA,GACnB,aAAa,CAAS,CACxB,CAGA,GAAI,IAAmB,IAAA,GAGrB,MAAU,MACR,8DACF,EAGF,IAAM,EAAO,EAIb,EAAiB,IAAA,GAMjB,EAAS,EAAK,GAAG,CAAI,CACvB,EAEM,MAAgC,CACpC,GAAI,IAAsB,IAAA,GAGxB,OAIF,IAAM,EAAY,EAClB,EAAoB,IAAA,GACpB,aAAa,CAAS,EAGlB,IAAmB,IAAA,IAGrB,EAAa,CAEjB,EAEM,EAAuB,GAA8B,CAGzD,EAAiB,EAEb,IAAc,IAAA,IAAa,IAAqB,IAAA,KAGlD,EAAmB,WAAW,EAAc,CAAS,EAEzD,EAEA,MAAO,CACL,MAAO,GAAG,IAAS,CACjB,GAAI,IAAsB,IAAA,GAGpB,IAAW,WAEb,EAAoB,CAAI,EAOxB,EAAS,EAAK,GAAG,CAAI,MAElB,CAGD,IAAW,WAIb,EAAoB,CAAI,EAK1B,IAAM,EAAY,EAClB,EAAoB,IAAA,GACpB,aAAa,CAAS,CACxB,CAYA,MAVA,GAAoB,WAClB,EAKA,GAAU,GAAa,CACzB,EAGO,CACT,EAEA,WAAc,CAIZ,GAAI,IAAsB,IAAA,GAAW,CACnC,IAAM,EAAY,EAClB,EAAoB,IAAA,GACpB,aAAa,CAAS,CACxB,CAEA,GAAI,IAAqB,IAAA,GAAW,CAClC,IAAM,EAAY,EAClB,EAAmB,IAAA,GACnB,aAAa,CAAS,CACxB,CAEA,EAAiB,IAAA,EACnB,EAEA,WAEE,EAAkB,EACX,GAGT,IAAI,WAAY,CACd,OAAO,IAAsB,IAAA,EAC/B,EAEA,IAAI,aAAc,CAChB,OAAO,CACT,CACF,CACF"}
1
+ {"version":3,"file":"debounce.js","names":[],"sources":["../src/debounce.ts"],"sourcesContent":["import type { StrictFunction } from \"./internal/types/StrictFunction\";\n\ntype Debouncer<F extends StrictFunction, IsNullable extends boolean = true> = {\n /**\n * Invoke the debounced function.\n *\n * @param args - Same as the args for the debounced function.\n * @returns The last computed value of the debounced function with the\n * latest args provided to it. If `timing` does not include `leading` then the\n * the function would return `undefined` until the first cool-down period is\n * over, otherwise the function would always return the return type of the\n * debounced function.\n */\n readonly call: (\n ...args: Parameters<F>\n ) => ReturnType<F> | (true extends IsNullable ? undefined : never);\n\n /**\n * Cancels any debounced functions without calling them, effectively resetting\n * the debouncer to the same state it is when initially created.\n */\n readonly cancel: () => void;\n\n /**\n * Similar to `cancel`, but would also trigger the `trailing` invocation if\n * the debouncer would run one at the end of the cool-down period.\n */\n readonly flush: () => ReturnType<F> | undefined;\n\n /**\n * Is `true` when there is an active cool-down period currently debouncing\n * invocations.\n */\n readonly isPending: boolean;\n\n /**\n * The last computed value of the debounced function.\n */\n readonly cachedValue: ReturnType<F> | undefined;\n};\n\ntype DebounceOptions = {\n readonly waitMs?: number;\n readonly maxWaitMs?: number;\n};\n\n/**\n * Wraps `func` with a debouncer object that \"debounces\" (delays) invocations of the function during a defined cool-down period (`waitMs`). It can be configured to invoke the function either at the start of the cool-down period, the end of it, or at both ends (`timing`).\n * It can also be configured to allow invocations during the cool-down period (`maxWaitMs`).\n * It stores the latest call's arguments so they could be used at the end of the cool-down period when invoking `func` (if configured to invoke the function at the end of the cool-down period).\n * It stores the value returned by `func` whenever its invoked. This value is returned on every call, and is accessible via the `cachedValue` property of the debouncer. Its important to note that the value might be different from the value that would be returned from running `func` with the current arguments as it is a cached value from a previous invocation.\n * **Important**: The cool-down period defines the minimum between two invocations, and not the maximum. The period will be **extended** each time a call is made until a full cool-down period has elapsed without any additional calls.\n *\n *! **DEPRECATED**: This implementation of debounce is known to have issues and might not behave as expected. It should be replaced with the `funnel` utility instead. The test file [funnel.remeda-debounce.test.ts](https://github.com/remeda/remeda/blob/main/packages/remeda/src/funnel.remeda-debounce.test.ts) offers a drop-in replacement implemented via `funnel`: copy everything between its REFERENCE START and REFERENCE END markers into your project!\n *\n * @param func - The function to debounce, the returned `call` function will have\n * the exact same signature.\n * @param options - An object allowing further customization of the debouncer:\n * - `timing?: 'leading' | 'trailing' |'both'`. The default is `'trailing'`.\n * `leading` would result in the function being invoked at the start of the\n * cool-down period; `trailing` would result in the function being invoked at\n * the end of the cool-down period (using the args from the last call to the\n * debouncer). When `both` is selected the `trailing` invocation would only\n * take place if there were more than one call to the debouncer during the\n * cool-down period. **DEFAULT: 'trailing'**\n * - `waitMs?: number`. The length of the cool-down period in milliseconds. The\n * debouncer would wait until this amount of time has passed without **any**\n * additional calls to the debouncer before triggering the end-of-cool-down-\n * period event. When this happens, the function would be invoked (if `timing`\n * isn't `'leading'`) and the debouncer state would be reset. **DEFAULT: 0**\n * - `maxWaitMs?: number`. The length of time since a debounced call (a call\n * that the debouncer prevented from being invoked) was made until it would be\n * invoked. Because the debouncer can be continually triggered and thus never\n * reach the end of the cool-down period, this allows the function to still\n * be invoked occasionally. IMPORTANT: This param is ignored when `timing` is\n * `'leading'`.\n * @returns A debouncer object. The main function is `call`. In addition to it\n * the debouncer comes with the following additional functions and properties:\n * - `cancel` method to cancel delayed `func` invocations\n * - `flush` method to end the cool-down period immediately.\n * - `cachedValue` the latest return value of an invocation (if one occurred).\n * - `isPending` flag to check if there is an inflight cool-down window.\n * @signature\n * debounce(func, options);\n * @example\n * const debouncer = debounce(identity(), { timing: 'trailing', waitMs: 1000 });\n * const result1 = debouncer.call(1); // => undefined\n * const result2 = debouncer.call(2); // => undefined\n * // after 1 second\n * const result3 = debouncer.call(3); // => 2\n * // after 1 second\n * debouncer.cachedValue; // => 3\n * @dataFirst\n * @category Function\n * @deprecated This implementation of debounce is known to have issues and might\n * not behave as expected. It should be replaced with the `funnel` utility\n * instead. The test file [funnel.remeda-debounce.test.ts](https://github.com/remeda/remeda/blob/main/packages/remeda/src/funnel.remeda-debounce.test.ts)\n * offers a drop-in replacement implemented via `funnel`: copy everything\n * between its REFERENCE START and REFERENCE END markers into your project.\n * @see https://css-tricks.com/debouncing-throttling-explained-examples/\n */\nexport function debounce<F extends StrictFunction>(\n func: F,\n options: DebounceOptions & { readonly timing?: \"trailing\" },\n): Debouncer<F>;\nexport function debounce<F extends StrictFunction>(\n func: F,\n options:\n | (DebounceOptions & { readonly timing: \"both\" })\n | (Omit<DebounceOptions, \"maxWaitMs\"> & { readonly timing: \"leading\" }),\n): Debouncer<F, false /* call CAN'T return null */>;\n\nexport function debounce<F extends StrictFunction>(\n func: F,\n {\n waitMs,\n timing = \"trailing\",\n maxWaitMs,\n }: DebounceOptions & {\n readonly timing?: \"both\" | \"leading\" | \"trailing\";\n },\n): Debouncer<F> {\n if (maxWaitMs !== undefined && waitMs !== undefined && maxWaitMs < waitMs) {\n throw new Error(\n `debounce: maxWaitMs (${maxWaitMs.toString()}) cannot be less than waitMs (${waitMs.toString()})`,\n );\n }\n\n // All these are part of the debouncer runtime state:\n\n // The timeout is the main object we use to tell if there's an active cool-\n // down period or not.\n let coolDownTimeoutId: ReturnType<typeof setTimeout> | undefined;\n\n // We use an additional timeout to track how long the last debounced call is\n // waiting.\n let maxWaitTimeoutId: ReturnType<typeof setTimeout> | undefined;\n\n // For 'trailing' invocations we need to keep the args around until we\n // actually invoke the function.\n let latestCallArgs: Parameters<F> | undefined;\n\n // To make any value of the debounced function we need to be able to return a\n // value. For any invocation except the first one when 'leading' is enabled we\n // will return this cached value.\n let result: ReturnType<F> | undefined;\n\n const handleInvoke = (): void => {\n if (maxWaitTimeoutId !== undefined) {\n // We are invoking the function so the wait is over...\n const timeoutId = maxWaitTimeoutId;\n maxWaitTimeoutId = undefined;\n clearTimeout(timeoutId);\n }\n\n /* v8 ignore if -- This protects us against changes to the logic, there is no known flow we can simulate to reach this condition. It can only happen if a previous timeout isn't cleared (or faces a race condition clearing). @preserve */\n if (latestCallArgs === undefined) {\n // If you see this error pop up when using this function please report\n // it on the Remeda github page!\n throw new Error(\n \"REMEDA[debounce]: latestCallArgs was unexpectedly undefined.\",\n );\n }\n\n const args = latestCallArgs;\n // Make sure the args aren't accidentally used again, this is mainly\n // relevant for the check above where we'll fail a subsequent call to\n // 'trailingEdge'.\n latestCallArgs = undefined;\n\n // Invoke the function and store the results locally.\n // @ts-expect-error [ts2345, ts2322] -- TypeScript infers the generic sub-\n // types too eagerly, making itself blind to the fact that the types match\n // here.\n result = func(...args);\n };\n\n const handleCoolDownEnd = (): void => {\n if (coolDownTimeoutId === undefined) {\n // It's rare to get here, it should only happen when `flush` is called\n // when the cool-down window isn't active.\n return;\n }\n\n // Make sure there are no more timers running.\n const timeoutId = coolDownTimeoutId;\n coolDownTimeoutId = undefined;\n clearTimeout(timeoutId);\n // Then reset state so a new cool-down window can begin on the next call.\n\n if (latestCallArgs !== undefined) {\n // If we have a debounced call waiting to be invoked at the end of the\n // cool-down period we need to invoke it now.\n handleInvoke();\n }\n };\n\n const handleDebouncedCall = (args: Parameters<F>): void => {\n // We save the latest call args so that (if and) when we invoke the function\n // in the future, we have args to invoke it with.\n latestCallArgs = args;\n\n if (maxWaitMs !== undefined && maxWaitTimeoutId === undefined) {\n // We only need to start the maxWait timeout once, on the first debounced\n // call that is now being delayed.\n maxWaitTimeoutId = setTimeout(handleInvoke, maxWaitMs);\n }\n };\n\n return {\n call: (...args) => {\n if (coolDownTimeoutId === undefined) {\n // This call is starting a new cool-down window!\n\n if (timing === \"trailing\") {\n // Only when the timing is \"trailing\" is the first call \"debounced\".\n handleDebouncedCall(args);\n } else {\n // Otherwise for \"leading\" and \"both\" the first call is actually\n // called directly and not via a timeout.\n // @ts-expect-error [ts2345, ts2322] -- TypeScript infers the generic\n // sub-types too eagerly, making itself blind to the fact that the\n // types match here.\n result = func(...args);\n }\n } else {\n // There's an inflight cool-down window.\n\n if (timing !== \"leading\") {\n // When the timing is 'leading' all following calls are just ignored\n // until the cool-down period ends. But for the other timings the call\n // is \"debounced\".\n handleDebouncedCall(args);\n }\n\n // The current timeout is no longer relevant because we need to wait the\n // full `waitMs` time from this call.\n const timeoutId = coolDownTimeoutId;\n coolDownTimeoutId = undefined;\n clearTimeout(timeoutId);\n }\n\n coolDownTimeoutId = setTimeout(\n handleCoolDownEnd,\n // If waitMs is not defined but maxWaitMs *is* it means the user is only\n // interested in the leaky-bucket nature of the debouncer which is\n // achieved by setting waitMs === maxWaitMs. If both are not defined we\n // default to 0 which would wait until the end of the execution frame.\n waitMs ?? maxWaitMs ?? 0,\n );\n\n // Return the last computed result while we \"debounce\" further calls.\n return result;\n },\n\n cancel: () => {\n // Reset all \"in-flight\" state of the debouncer. Notice that we keep the\n // cached value!\n\n if (coolDownTimeoutId !== undefined) {\n const timeoutId = coolDownTimeoutId;\n coolDownTimeoutId = undefined;\n clearTimeout(timeoutId);\n }\n\n if (maxWaitTimeoutId !== undefined) {\n const timeoutId = maxWaitTimeoutId;\n maxWaitTimeoutId = undefined;\n clearTimeout(timeoutId);\n }\n\n latestCallArgs = undefined;\n },\n\n flush: () => {\n // Flush is just a manual way to trigger the end of the cool-down window.\n handleCoolDownEnd();\n return result;\n },\n\n get isPending() {\n return coolDownTimeoutId !== undefined;\n },\n\n get cachedValue() {\n return result;\n },\n };\n}\n"],"mappings":"AAgHA,SAAgB,EACd,EACA,CACE,SACA,SAAS,WACT,aAIY,CACd,GAAI,IAAc,IAAA,IAAa,IAAW,IAAA,IAAa,EAAY,EACjE,MAAU,MACR,wBAAwB,EAAU,SAAS,EAAE,gCAAgC,EAAO,SAAS,EAAE,EACjG,EAOF,IAAI,EAIA,EAIA,EAKA,EAEE,MAA2B,CAC/B,GAAI,IAAqB,IAAA,GAAW,CAElC,IAAM,EAAY,EAClB,EAAmB,IAAA,GACnB,aAAa,CAAS,CACxB,CAGA,GAAI,IAAmB,IAAA,GAGrB,MAAU,MACR,8DACF,EAGF,IAAM,EAAO,EAIb,EAAiB,IAAA,GAMjB,EAAS,EAAK,GAAG,CAAI,CACvB,EAEM,MAAgC,CACpC,GAAI,IAAsB,IAAA,GAGxB,OAIF,IAAM,EAAY,EAClB,EAAoB,IAAA,GACpB,aAAa,CAAS,EAGlB,IAAmB,IAAA,IAGrB,EAAa,CAEjB,EAEM,EAAuB,GAA8B,CAGzD,EAAiB,EAEb,IAAc,IAAA,IAAa,IAAqB,IAAA,KAGlD,EAAmB,WAAW,EAAc,CAAS,EAEzD,EAEA,MAAO,CACL,MAAO,GAAG,IAAS,CACjB,GAAI,IAAsB,IAAA,GAGpB,IAAW,WAEb,EAAoB,CAAI,EAOxB,EAAS,EAAK,GAAG,CAAI,MAElB,CAGD,IAAW,WAIb,EAAoB,CAAI,EAK1B,IAAM,EAAY,EAClB,EAAoB,IAAA,GACpB,aAAa,CAAS,CACxB,CAYA,MAVA,GAAoB,WAClB,EAKA,GAAU,GAAa,CACzB,EAGO,CACT,EAEA,WAAc,CAIZ,GAAI,IAAsB,IAAA,GAAW,CACnC,IAAM,EAAY,EAClB,EAAoB,IAAA,GACpB,aAAa,CAAS,CACxB,CAEA,GAAI,IAAqB,IAAA,GAAW,CAClC,IAAM,EAAY,EAClB,EAAmB,IAAA,GACnB,aAAa,CAAS,CACxB,CAEA,EAAiB,IAAA,EACnB,EAEA,WAEE,EAAkB,EACX,GAGT,IAAI,WAAY,CACd,OAAO,IAAsB,IAAA,EAC/B,EAEA,IAAI,aAAc,CAChB,OAAO,CACT,CACF,CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"funnel.cjs","names":[],"sources":["../src/funnel.ts"],"sourcesContent":["import type { RequireAtLeastOne } from \"type-fest\";\n\n// We use the value provided by the reducer to also determine if a call\n// was done during a timeout period. This means that even when no reducer\n// is provided, we still need a dummy reducer that would return something\n// other than `undefined`. It is safe to cast this to R (which might be\n// anything) because the callback would never use it as it would be typed\n// as a zero-args function.\nconst VOID_REDUCER_SYMBOL = Symbol(\"funnel/voidReducer\");\n// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- Intentional, so that it could be used as a default value above.\nconst voidReducer = <R>(): R => VOID_REDUCER_SYMBOL as R;\n\ntype FunnelOptions<Args extends RestArguments, R> = {\n readonly reducer?: (accumulator: R | undefined, ...params: Args) => R;\n} & FunnelTimingOptions;\n\n// Not all combinations of timing options are valid, there are dependencies\n// between them to ensure users can't configure the funnel in a way which would\n// cause it to never trigger.\ntype FunnelTimingOptions =\n | ({ readonly triggerAt?: \"end\" } & (\n | ({ readonly minGapMs: number } & RequireAtLeastOne<{\n readonly minQuietPeriodMs: number;\n readonly maxBurstDurationMs: number;\n }>)\n | {\n readonly minQuietPeriodMs?: number;\n readonly maxBurstDurationMs?: number;\n readonly minGapMs?: never;\n }\n ))\n | {\n readonly triggerAt: \"start\" | \"both\";\n readonly minQuietPeriodMs?: number;\n readonly maxBurstDurationMs?: number;\n readonly minGapMs?: number;\n };\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TypeScript has some quirks with generic function types, and works best with `any` and not `unknown`. This follows the typing of built-in utilities like `ReturnType` and `Parameters`.\ntype RestArguments = any[];\n\ntype Funnel<Args extends RestArguments = []> = {\n /**\n * Call the function. This might result in the `execute` function being called\n * now or later, depending on it's configuration and it's current state.\n *\n * @param args - The args are defined by the `reducer` function.\n */\n\n readonly call: (...args: Args) => void;\n\n /**\n * Resets the funnel to it's initial state. Any calls made since the last\n * invocation will be discarded.\n */\n readonly cancel: () => void;\n\n /**\n * Triggers an invocation regardless of the current state of the funnel.\n * Like any other invocation, The funnel will also be reset to it's initial\n * state afterwards.\n */\n readonly flush: () => void;\n\n /**\n * The funnel is in it's initial state (there are no active timeouts).\n */\n readonly isIdle: boolean;\n};\n\n/**\n * Creates a funnel that controls the timing and execution of `callback`. Its\n * main purpose is to manage multiple consecutive (usually fast-paced) calls,\n * reshaping them according to a defined batching strategy and timing policy.\n * This is useful when handling uncontrolled call rates, such as DOM events or\n * network traffic. It can implement strategies like debouncing, throttling,\n * batching, and more.\n *\n * An optional `reducer` function can be provided to allow passing data to the\n * callback via calls to `call` (otherwise the signature of `call` takes no\n * arguments).\n *\n * Typing is inferred from `callback`s param, and from the rest params that\n * the optional `reducer` function accepts. Use **explicit** types for these\n * to ensure that everything _else_ is well-typed.\n *\n * Notice that this function constructs a funnel **object**, and does **not**\n * execute anything when called. The returned object should be used to execute\n * the funnel via the its `call` method.\n *\n * - Debouncing: use `minQuietPeriodMs` and any `triggerAt`.\n * - Throttling: use `minGapMs` and `triggerAt: \"start\"` or `\"both\"`.\n * - Batching: See the reference implementation in [`funnel.reference-batch.test.ts`](https://github.com/remeda/remeda/blob/main/packages/remeda/src/funnel.reference-batch.test.ts).\n *\n * @param callback - The main function that would be invoked periodically based\n * on `options`. The function would take the latest result of the `reducer`; if\n * no calls where made since the last time it was invoked it will not be\n * invoked. (If a return value is needed, it should be passed via a reference or\n * via closure to the outer scope of the funnel).\n * @param options - An object that defines when `execute` should be invoked,\n * relative to the calls of `call`. An empty/missing options object is\n * equivalent to setting `minQuietPeriodMs` to `0`.\n * @param options.reducer - Combines the arguments passed to `call` with the\n * value computed on the previous call (or `undefined` on the first time). The\n * goal of the function is to extract and summarize the data needed for\n * `callback`. It should be fast and simple as it is called often and should\n * defer heavy operations to the `execute` function. If the final value\n * is `undefined`, `callback` will not be called.\n * @param options.triggerAt - At what \"edges\" of the funnel's burst window\n * would `execute` invoke:\n * - `start` - the function will be invoked immediately (within the **same**\n * execution frame!), and any subsequent calls would be ignored until the funnel\n * is idle again. During this period `reducer` will also not be called.\n * - `end` - the function will **not** be invoked initially but the timer will\n * be started. Any calls during this time would be passed to the reducer, and\n * when the timers are done, the reduced result would trigger an invocation.\n * - `both` - the function will be invoked immediately, and then the funnel\n * would behave as if it was in the 'end' state. Default: 'end'.\n * @param options.minQuietPeriodMs - The burst timer prevents subsequent calls\n * in short succession to cause excessive invocations (aka \"debounce\"). This\n * duration represents the **minimum** amount of time that needs to pass\n * between calls (the \"quiet\" part) in order for the subsequent call to **not**\n * be considered part of the burst. In other words, as long as calls are faster\n * than this, they are considered part of the burst and the burst is extended.\n * @param options.maxBurstDurationMs - Bursts are extended every time a call is\n * made within the burst period. This means that the burst period could be\n * extended indefinitely. To prevent such cases, a maximum burst duration could\n * be defined. When `minQuietPeriodMs` is not defined and this option is, they\n * will both share the same value.\n * @param options.minGapMs - A minimum duration between calls of `execute`.\n * This is maintained regardless of the shape of the burst and is ensured even\n * if the `maxBurstDurationMs` is reached before it. (aka \"throttle\").\n * @returns A funnel with a `call` function that is used to trigger invocations.\n * In addition to it the funnel also comes with the following functions and\n * properties:\n * - `cancel` - Resets the funnel to it's initial state, discarding the current\n * `reducer` result without calling `execute` on it.\n * - `flush` - Triggers an invocation even if there are active timeouts, and\n * then resets the funnel to it's initial state.\n * - `isIdle` - Checks if there are any active timeouts.\n * @signature\n * funnel(callback, options);\n * @example\n * const debouncer = funnel(\n * () => {\n * console.log(\"Callback executed!\");\n * },\n * { minQuietPeriodMs: 100 },\n * );\n * debouncer.call();\n * debouncer.call();\n *\n * const throttle = funnel(\n * () => {\n * console.log(\"Callback executed!\");\n * },\n * { minGapMs: 100, triggerAt: \"start\" },\n * );\n * throttle.call();\n * throttle.call();\n * @category Function\n */\nexport function funnel<Args extends RestArguments = [], R = never>(\n callback: (data: R) => void,\n {\n triggerAt = \"end\",\n minQuietPeriodMs,\n maxBurstDurationMs,\n minGapMs,\n reducer = voidReducer,\n }: FunnelOptions<Args, R>,\n): Funnel<Args> {\n // We manage execution via 2 timeouts, one to track bursts of calls, and one\n // to track the interval between invocations. Together we refer to the period\n // where any of these are active as a \"cool-down period\".\n let burstTimeoutId: ReturnType<typeof setTimeout> | undefined;\n let intervalTimeoutId: ReturnType<typeof setTimeout> | undefined;\n\n // Until invoked, all calls are reduced into a single value that would be sent\n // to the executor on invocation.\n let preparedData: R | undefined;\n\n // In order to be able to limit the total size of the burst (when\n // `maxBurstDurationMs` is used) we need to track when the burst started.\n let burstStartTimestamp: number | undefined;\n\n const invoke = (): void => {\n const param = preparedData;\n if (param === undefined) {\n // There were no calls during both cool-down periods.\n return;\n }\n\n // Make sure the args aren't accidentally used again\n preparedData = undefined;\n\n if (param === VOID_REDUCER_SYMBOL) {\n // @ts-expect-error [ts2554] -- R is typed as `never` because we hide the\n // symbol that `voidReducer` returns; there's no way to make TypeScript\n // aware of this.\n callback();\n } else {\n callback(param);\n }\n\n if (minGapMs !== undefined) {\n intervalTimeoutId = setTimeout(handleIntervalEnd, minGapMs);\n }\n };\n\n const handleIntervalEnd = (): void => {\n // When called via a timeout the timeout is already cleared, but when called\n // via `flush` we need to manually clear it.\n clearTimeout(intervalTimeoutId);\n intervalTimeoutId = undefined;\n\n if (burstTimeoutId !== undefined) {\n // As long as one of the timeouts is active we don't invoke the function.\n // Each timeout's end event handler has a call to invoke, so we are\n // guaranteed to invoke the function eventually.\n return;\n }\n\n invoke();\n };\n\n const handleBurstEnd = (): void => {\n // When called via a timeout the timeout is already cleared, but when called\n // via `flush` we need to manually clear it.\n clearTimeout(burstTimeoutId);\n burstTimeoutId = undefined;\n burstStartTimestamp = undefined;\n\n if (intervalTimeoutId !== undefined) {\n // As long as one of the timeouts is active we don't invoke the function.\n // Each timeout's end event handler has a call to invoke, so we are\n // guaranteed to invoke the function eventually.\n return;\n }\n\n invoke();\n };\n\n return {\n call: (...args) => {\n // We act based on the initial state of the timeouts before the call is\n // handled and causes the timeouts to change.\n const wasIdle =\n burstTimeoutId === undefined && intervalTimeoutId === undefined;\n\n if (triggerAt !== \"start\" || wasIdle) {\n preparedData = reducer(preparedData, ...args);\n }\n\n if (burstTimeoutId === undefined && !wasIdle) {\n // We are not in an active burst period but in an interval period. We\n // don't start a new burst window until the next invoke.\n return;\n }\n\n if (\n minQuietPeriodMs !== undefined ||\n maxBurstDurationMs !== undefined ||\n minGapMs === undefined\n ) {\n // The timeout tracking the burst period needs to be reset every time\n // another call is made so that it waits the full cool-down duration\n // before it is released.\n clearTimeout(burstTimeoutId);\n\n const now = Date.now();\n\n burstStartTimestamp ??= now;\n\n const burstRemainingMs =\n maxBurstDurationMs === undefined\n ? (minQuietPeriodMs ?? 0)\n : Math.min(\n minQuietPeriodMs ?? maxBurstDurationMs,\n // We need to account for the time already spent so that we\n // don't wait longer than the maxDelay.\n Math.max(0, maxBurstDurationMs - (now - burstStartTimestamp)),\n );\n\n burstTimeoutId = setTimeout(handleBurstEnd, burstRemainingMs);\n }\n\n if (triggerAt !== \"end\" && wasIdle) {\n invoke();\n }\n },\n\n cancel: () => {\n clearTimeout(burstTimeoutId);\n burstTimeoutId = undefined;\n burstStartTimestamp = undefined;\n\n clearTimeout(intervalTimeoutId);\n intervalTimeoutId = undefined;\n\n preparedData = undefined;\n },\n\n flush: () => {\n handleBurstEnd();\n handleIntervalEnd();\n },\n\n get isIdle() {\n return burstTimeoutId === undefined && intervalTimeoutId === undefined;\n },\n };\n}\n"],"mappings":"mEAQA,MAAM,EAAsB,OAAO,oBAAoB,EAEjD,MAA0B,EAwJhC,SAAgB,EACd,EACA,CACE,YAAY,MACZ,mBACA,qBACA,WACA,UAAU,GAEE,CAId,IAAI,EACA,EAIA,EAIA,EAEE,MAAqB,CACzB,IAAM,EAAQ,EACV,IAAU,IAAA,KAMd,EAAe,IAAA,GAEX,IAAU,EAIZ,EAAS,EAET,EAAS,CAAK,EAGZ,IAAa,IAAA,KACf,EAAoB,WAAW,EAAmB,CAAQ,GAE9D,EAEM,MAAgC,CAGpC,aAAa,CAAiB,EAC9B,EAAoB,IAAA,GAEhB,IAAmB,IAAA,IAOvB,EAAO,CACT,EAEM,MAA6B,CAGjC,aAAa,CAAc,EAC3B,EAAiB,IAAA,GACjB,EAAsB,IAAA,GAElB,IAAsB,IAAA,IAO1B,EAAO,CACT,EAEA,MAAO,CACL,MAAO,GAAG,IAAS,CAGjB,IAAM,EACJ,IAAmB,IAAA,IAAa,IAAsB,IAAA,GAExD,IAAI,IAAc,SAAW,KAC3B,EAAe,EAAQ,EAAc,GAAG,CAAI,GAG1C,MAAmB,IAAA,IAAa,CAAC,GAMrC,IACE,IAAqB,IAAA,IACrB,IAAuB,IAAA,IACvB,IAAa,IAAA,GACb,CAIA,aAAa,CAAc,EAE3B,IAAM,EAAM,KAAK,IAAI,EAErB,IAAwB,EAExB,IAAM,EACJ,IAAuB,IAAA,GAClB,GAAoB,EACrB,KAAK,IACH,GAAoB,EAGpB,KAAK,IAAI,EAAG,GAAsB,EAAM,EAAoB,CAC9D,EAEN,EAAiB,WAAW,EAAgB,CAAgB,CAC9D,CAEI,IAAc,OAAS,GACzB,EAAO,CAHT,CAKF,EAEA,WAAc,CACZ,aAAa,CAAc,EAC3B,EAAiB,IAAA,GACjB,EAAsB,IAAA,GAEtB,aAAa,CAAiB,EAC9B,EAAoB,IAAA,GAEpB,EAAe,IAAA,EACjB,EAEA,UAAa,CACX,EAAe,EACf,EAAkB,CACpB,EAEA,IAAI,QAAS,CACX,OAAO,IAAmB,IAAA,IAAa,IAAsB,IAAA,EAC/D,CACF,CACF"}
1
+ {"version":3,"file":"funnel.cjs","names":[],"sources":["../src/funnel.ts"],"sourcesContent":["import type { RequireAtLeastOne } from \"type-fest\";\n\n// We use the value provided by the reducer to also determine if a call\n// was done during a timeout period. This means that even when no reducer\n// is provided, we still need a dummy reducer that would return something\n// other than `undefined`. It is safe to cast this to R (which might be\n// anything) because the callback would never use it as it would be typed\n// as a zero-args function.\nconst VOID_REDUCER_SYMBOL = Symbol(\"funnel/voidReducer\");\n// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- Intentional, so that it could be used as a default value above.\nconst voidReducer = <R>(): R => VOID_REDUCER_SYMBOL as R;\n\ntype FunnelOptions<Args extends RestArguments, R> = {\n readonly reducer?: (accumulator: R | undefined, ...params: Args) => R;\n} & FunnelTimingOptions;\n\n// Not all combinations of timing options are valid, there are dependencies\n// between them to ensure users can't configure the funnel in a way which would\n// cause it to never trigger.\ntype FunnelTimingOptions =\n | ({ readonly triggerAt?: \"end\" } & (\n | ({ readonly minGapMs: number } & RequireAtLeastOne<{\n readonly minQuietPeriodMs: number;\n readonly maxBurstDurationMs: number;\n }>)\n | {\n readonly minQuietPeriodMs?: number;\n readonly maxBurstDurationMs?: number;\n readonly minGapMs?: never;\n }\n ))\n | {\n readonly triggerAt: \"start\" | \"both\";\n readonly minQuietPeriodMs?: number;\n readonly maxBurstDurationMs?: number;\n readonly minGapMs?: number;\n };\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TypeScript has some quirks with generic function types, and works best with `any` and not `unknown`. This follows the typing of built-in utilities like `ReturnType` and `Parameters`.\ntype RestArguments = any[];\n\ntype Funnel<Args extends RestArguments = []> = {\n /**\n * Call the function. This might result in the `execute` function being called\n * now or later, depending on it's configuration and it's current state.\n *\n * @param args - The args are defined by the `reducer` function.\n */\n\n readonly call: (...args: Args) => void;\n\n /**\n * Resets the funnel to it's initial state. Any calls made since the last\n * invocation will be discarded.\n */\n readonly cancel: () => void;\n\n /**\n * Triggers an invocation regardless of the current state of the funnel.\n * Like any other invocation, The funnel will also be reset to it's initial\n * state afterwards.\n */\n readonly flush: () => void;\n\n /**\n * The funnel is in it's initial state (there are no active timeouts).\n */\n readonly isIdle: boolean;\n};\n\n/**\n * Creates a funnel that controls the timing and execution of `callback`. Its\n * main purpose is to manage multiple consecutive (usually fast-paced) calls,\n * reshaping them according to a defined batching strategy and timing policy.\n * This is useful when handling uncontrolled call rates, such as DOM events or\n * network traffic. It can implement strategies like debouncing, throttling,\n * batching, and more.\n *\n * An optional `reducer` function can be provided to allow passing data to the\n * callback via calls to `call` (otherwise the signature of `call` takes no\n * arguments).\n *\n * Typing is inferred from `callback`s param, and from the rest params that\n * the optional `reducer` function accepts. Use **explicit** types for these\n * to ensure that everything _else_ is well-typed.\n *\n * Notice that this function constructs a funnel **object**, and does **not**\n * execute anything when called. The returned object should be used to execute\n * the funnel via the its `call` method.\n *\n * The type of the funnel object is not exported; when you need to reference\n * it explicitly (e.g., a class property holding a funnel) use\n * `ReturnType<typeof funnel<[]>>`, replacing `[]` with the `reducer`'s rest\n * params when one is used (e.g., `ReturnType<typeof funnel<[string]>>`).\n *\n * - Debouncing: use `minQuietPeriodMs` and any `triggerAt`. Copy-paste\n * reference implementations are available for Remeda's deprecated `debounce`\n * function in [`funnel.remeda-debounce.test.ts`](https://github.com/remeda/remeda/blob/main/packages/remeda/src/funnel.remeda-debounce.test.ts),\n * and for the Lodash `debounce` function in the [Lodash migration docs](https://remedajs.com/migrate/lodash#debounce).\n * - Throttling: use `minGapMs` and `triggerAt: \"start\"` or `\"both\"`.\n * Copy-paste reference implementations for the Lodash `throttle` function\n * (which maps onto the burst options `minQuietPeriodMs` and\n * `maxBurstDurationMs` instead of `minGapMs`) are available in the\n * [Lodash migration docs](https://remedajs.com/migrate/lodash#throttle).\n * - Batching: a copy-paste reference implementation is available in [`funnel.reference-batch.test.ts`](https://github.com/remeda/remeda/blob/main/packages/remeda/src/funnel.reference-batch.test.ts).\n *\n * @param callback - The main function that would be invoked periodically based\n * on `options`. The function would take the latest result of the `reducer`; if\n * no calls where made since the last time it was invoked it will not be\n * invoked. (If a return value is needed, it should be passed via a reference or\n * via closure to the outer scope of the funnel).\n * @param options - An object that defines when `execute` should be invoked,\n * relative to the calls of `call`. An empty/missing options object is\n * equivalent to setting `minQuietPeriodMs` to `0`.\n * @param options.reducer - Combines the arguments passed to `call` with the\n * value computed on the previous call (or `undefined` on the first time). The\n * goal of the function is to extract and summarize the data needed for\n * `callback`. It should be fast and simple as it is called often and should\n * defer heavy operations to the `execute` function. If the final value\n * is `undefined`, `callback` will not be called.\n * @param options.triggerAt - At what \"edges\" of the funnel's burst window\n * would `execute` invoke:\n * - `start` - the function will be invoked immediately (within the **same**\n * execution frame!), and any subsequent calls would be ignored until the funnel\n * is idle again. During this period `reducer` will also not be called.\n * - `end` - the function will **not** be invoked initially but the timer will\n * be started. Any calls during this time would be passed to the reducer, and\n * when the timers are done, the reduced result would trigger an invocation.\n * - `both` - the function will be invoked immediately, and then the funnel\n * would behave as if it was in the 'end' state. Default: 'end'.\n * @param options.minQuietPeriodMs - The burst timer prevents subsequent calls\n * in short succession to cause excessive invocations (aka \"debounce\"). This\n * duration represents the **minimum** amount of time that needs to pass\n * between calls (the \"quiet\" part) in order for the subsequent call to **not**\n * be considered part of the burst. In other words, as long as calls are faster\n * than this, they are considered part of the burst and the burst is extended.\n * @param options.maxBurstDurationMs - Bursts are extended every time a call is\n * made within the burst period. This means that the burst period could be\n * extended indefinitely. To prevent such cases, a maximum burst duration could\n * be defined. When `minQuietPeriodMs` is not defined and this option is, they\n * will both share the same value.\n * @param options.minGapMs - A minimum duration between calls of `execute`.\n * This is maintained regardless of the shape of the burst and is ensured even\n * if the `maxBurstDurationMs` is reached before it. (aka \"throttle\").\n * @returns A funnel with a `call` function that is used to trigger invocations.\n * In addition to it the funnel also comes with the following functions and\n * properties:\n * - `cancel` - Resets the funnel to it's initial state, discarding the current\n * `reducer` result without calling `execute` on it.\n * - `flush` - Triggers an invocation even if there are active timeouts, and\n * then resets the funnel to it's initial state.\n * - `isIdle` - Checks if there are any active timeouts.\n * @signature\n * funnel(callback, options);\n * @example\n * const debouncer = funnel(\n * () => {\n * console.log(\"Callback executed!\");\n * },\n * { minQuietPeriodMs: 100 },\n * );\n * debouncer.call();\n * debouncer.call();\n *\n * const throttle = funnel(\n * () => {\n * console.log(\"Callback executed!\");\n * },\n * { minGapMs: 100, triggerAt: \"start\" },\n * );\n * throttle.call();\n * throttle.call();\n * @category Function\n */\nexport function funnel<Args extends RestArguments = [], R = never>(\n callback: (data: R) => void,\n {\n triggerAt = \"end\",\n minQuietPeriodMs,\n maxBurstDurationMs,\n minGapMs,\n reducer = voidReducer,\n }: FunnelOptions<Args, R>,\n): Funnel<Args> {\n // We manage execution via 2 timeouts, one to track bursts of calls, and one\n // to track the interval between invocations. Together we refer to the period\n // where any of these are active as a \"cool-down period\".\n let burstTimeoutId: ReturnType<typeof setTimeout> | undefined;\n let intervalTimeoutId: ReturnType<typeof setTimeout> | undefined;\n\n // Until invoked, all calls are reduced into a single value that would be sent\n // to the executor on invocation.\n let preparedData: R | undefined;\n\n // In order to be able to limit the total size of the burst (when\n // `maxBurstDurationMs` is used) we need to track when the burst started.\n let burstStartTimestamp: number | undefined;\n\n const invoke = (): void => {\n const param = preparedData;\n if (param === undefined) {\n // There were no calls during both cool-down periods.\n return;\n }\n\n // Make sure the args aren't accidentally used again\n preparedData = undefined;\n\n if (param === VOID_REDUCER_SYMBOL) {\n // @ts-expect-error [ts2554] -- R is typed as `never` because we hide the\n // symbol that `voidReducer` returns; there's no way to make TypeScript\n // aware of this.\n callback();\n } else {\n callback(param);\n }\n\n if (minGapMs !== undefined) {\n intervalTimeoutId = setTimeout(handleIntervalEnd, minGapMs);\n }\n };\n\n const handleIntervalEnd = (): void => {\n // When called via a timeout the timeout is already cleared, but when called\n // via `flush` we need to manually clear it.\n clearTimeout(intervalTimeoutId);\n intervalTimeoutId = undefined;\n\n if (burstTimeoutId !== undefined) {\n // As long as one of the timeouts is active we don't invoke the function.\n // Each timeout's end event handler has a call to invoke, so we are\n // guaranteed to invoke the function eventually.\n return;\n }\n\n invoke();\n };\n\n const handleBurstEnd = (): void => {\n // When called via a timeout the timeout is already cleared, but when called\n // via `flush` we need to manually clear it.\n clearTimeout(burstTimeoutId);\n burstTimeoutId = undefined;\n burstStartTimestamp = undefined;\n\n if (intervalTimeoutId !== undefined) {\n // As long as one of the timeouts is active we don't invoke the function.\n // Each timeout's end event handler has a call to invoke, so we are\n // guaranteed to invoke the function eventually.\n return;\n }\n\n invoke();\n };\n\n return {\n call: (...args) => {\n // We act based on the initial state of the timeouts before the call is\n // handled and causes the timeouts to change.\n const wasIdle =\n burstTimeoutId === undefined && intervalTimeoutId === undefined;\n\n if (triggerAt !== \"start\" || wasIdle) {\n preparedData = reducer(preparedData, ...args);\n }\n\n if (burstTimeoutId === undefined && !wasIdle) {\n // We are not in an active burst period but in an interval period. We\n // don't start a new burst window until the next invoke.\n return;\n }\n\n if (\n minQuietPeriodMs !== undefined ||\n maxBurstDurationMs !== undefined ||\n minGapMs === undefined\n ) {\n // The timeout tracking the burst period needs to be reset every time\n // another call is made so that it waits the full cool-down duration\n // before it is released.\n clearTimeout(burstTimeoutId);\n\n const now = Date.now();\n\n burstStartTimestamp ??= now;\n\n const burstRemainingMs =\n maxBurstDurationMs === undefined\n ? (minQuietPeriodMs ?? 0)\n : Math.min(\n minQuietPeriodMs ?? maxBurstDurationMs,\n // We need to account for the time already spent so that we\n // don't wait longer than the maxDelay.\n Math.max(0, maxBurstDurationMs - (now - burstStartTimestamp)),\n );\n\n burstTimeoutId = setTimeout(handleBurstEnd, burstRemainingMs);\n }\n\n if (triggerAt !== \"end\" && wasIdle) {\n invoke();\n }\n },\n\n cancel: () => {\n clearTimeout(burstTimeoutId);\n burstTimeoutId = undefined;\n burstStartTimestamp = undefined;\n\n clearTimeout(intervalTimeoutId);\n intervalTimeoutId = undefined;\n\n preparedData = undefined;\n },\n\n flush: () => {\n handleBurstEnd();\n handleIntervalEnd();\n },\n\n get isIdle() {\n return burstTimeoutId === undefined && intervalTimeoutId === undefined;\n },\n };\n}\n"],"mappings":"mEAQA,MAAM,EAAsB,OAAO,oBAAoB,EAEjD,MAA0B,EAoKhC,SAAgB,EACd,EACA,CACE,YAAY,MACZ,mBACA,qBACA,WACA,UAAU,GAEE,CAId,IAAI,EACA,EAIA,EAIA,EAEE,MAAqB,CACzB,IAAM,EAAQ,EACV,IAAU,IAAA,KAMd,EAAe,IAAA,GAEX,IAAU,EAIZ,EAAS,EAET,EAAS,CAAK,EAGZ,IAAa,IAAA,KACf,EAAoB,WAAW,EAAmB,CAAQ,GAE9D,EAEM,MAAgC,CAGpC,aAAa,CAAiB,EAC9B,EAAoB,IAAA,GAEhB,IAAmB,IAAA,IAOvB,EAAO,CACT,EAEM,MAA6B,CAGjC,aAAa,CAAc,EAC3B,EAAiB,IAAA,GACjB,EAAsB,IAAA,GAElB,IAAsB,IAAA,IAO1B,EAAO,CACT,EAEA,MAAO,CACL,MAAO,GAAG,IAAS,CAGjB,IAAM,EACJ,IAAmB,IAAA,IAAa,IAAsB,IAAA,GAExD,IAAI,IAAc,SAAW,KAC3B,EAAe,EAAQ,EAAc,GAAG,CAAI,GAG1C,MAAmB,IAAA,IAAa,CAAC,GAMrC,IACE,IAAqB,IAAA,IACrB,IAAuB,IAAA,IACvB,IAAa,IAAA,GACb,CAIA,aAAa,CAAc,EAE3B,IAAM,EAAM,KAAK,IAAI,EAErB,IAAwB,EAExB,IAAM,EACJ,IAAuB,IAAA,GAClB,GAAoB,EACrB,KAAK,IACH,GAAoB,EAGpB,KAAK,IAAI,EAAG,GAAsB,EAAM,EAAoB,CAC9D,EAEN,EAAiB,WAAW,EAAgB,CAAgB,CAC9D,CAEI,IAAc,OAAS,GACzB,EAAO,CAHT,CAKF,EAEA,WAAc,CACZ,aAAa,CAAc,EAC3B,EAAiB,IAAA,GACjB,EAAsB,IAAA,GAEtB,aAAa,CAAiB,EAC9B,EAAoB,IAAA,GAEpB,EAAe,IAAA,EACjB,EAEA,UAAa,CACX,EAAe,EACf,EAAkB,CACpB,EAEA,IAAI,QAAS,CACX,OAAO,IAAmB,IAAA,IAAa,IAAsB,IAAA,EAC/D,CACF,CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"funnel.js","names":[],"sources":["../src/funnel.ts"],"sourcesContent":["import type { RequireAtLeastOne } from \"type-fest\";\n\n// We use the value provided by the reducer to also determine if a call\n// was done during a timeout period. This means that even when no reducer\n// is provided, we still need a dummy reducer that would return something\n// other than `undefined`. It is safe to cast this to R (which might be\n// anything) because the callback would never use it as it would be typed\n// as a zero-args function.\nconst VOID_REDUCER_SYMBOL = Symbol(\"funnel/voidReducer\");\n// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- Intentional, so that it could be used as a default value above.\nconst voidReducer = <R>(): R => VOID_REDUCER_SYMBOL as R;\n\ntype FunnelOptions<Args extends RestArguments, R> = {\n readonly reducer?: (accumulator: R | undefined, ...params: Args) => R;\n} & FunnelTimingOptions;\n\n// Not all combinations of timing options are valid, there are dependencies\n// between them to ensure users can't configure the funnel in a way which would\n// cause it to never trigger.\ntype FunnelTimingOptions =\n | ({ readonly triggerAt?: \"end\" } & (\n | ({ readonly minGapMs: number } & RequireAtLeastOne<{\n readonly minQuietPeriodMs: number;\n readonly maxBurstDurationMs: number;\n }>)\n | {\n readonly minQuietPeriodMs?: number;\n readonly maxBurstDurationMs?: number;\n readonly minGapMs?: never;\n }\n ))\n | {\n readonly triggerAt: \"start\" | \"both\";\n readonly minQuietPeriodMs?: number;\n readonly maxBurstDurationMs?: number;\n readonly minGapMs?: number;\n };\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TypeScript has some quirks with generic function types, and works best with `any` and not `unknown`. This follows the typing of built-in utilities like `ReturnType` and `Parameters`.\ntype RestArguments = any[];\n\ntype Funnel<Args extends RestArguments = []> = {\n /**\n * Call the function. This might result in the `execute` function being called\n * now or later, depending on it's configuration and it's current state.\n *\n * @param args - The args are defined by the `reducer` function.\n */\n\n readonly call: (...args: Args) => void;\n\n /**\n * Resets the funnel to it's initial state. Any calls made since the last\n * invocation will be discarded.\n */\n readonly cancel: () => void;\n\n /**\n * Triggers an invocation regardless of the current state of the funnel.\n * Like any other invocation, The funnel will also be reset to it's initial\n * state afterwards.\n */\n readonly flush: () => void;\n\n /**\n * The funnel is in it's initial state (there are no active timeouts).\n */\n readonly isIdle: boolean;\n};\n\n/**\n * Creates a funnel that controls the timing and execution of `callback`. Its\n * main purpose is to manage multiple consecutive (usually fast-paced) calls,\n * reshaping them according to a defined batching strategy and timing policy.\n * This is useful when handling uncontrolled call rates, such as DOM events or\n * network traffic. It can implement strategies like debouncing, throttling,\n * batching, and more.\n *\n * An optional `reducer` function can be provided to allow passing data to the\n * callback via calls to `call` (otherwise the signature of `call` takes no\n * arguments).\n *\n * Typing is inferred from `callback`s param, and from the rest params that\n * the optional `reducer` function accepts. Use **explicit** types for these\n * to ensure that everything _else_ is well-typed.\n *\n * Notice that this function constructs a funnel **object**, and does **not**\n * execute anything when called. The returned object should be used to execute\n * the funnel via the its `call` method.\n *\n * - Debouncing: use `minQuietPeriodMs` and any `triggerAt`.\n * - Throttling: use `minGapMs` and `triggerAt: \"start\"` or `\"both\"`.\n * - Batching: See the reference implementation in [`funnel.reference-batch.test.ts`](https://github.com/remeda/remeda/blob/main/packages/remeda/src/funnel.reference-batch.test.ts).\n *\n * @param callback - The main function that would be invoked periodically based\n * on `options`. The function would take the latest result of the `reducer`; if\n * no calls where made since the last time it was invoked it will not be\n * invoked. (If a return value is needed, it should be passed via a reference or\n * via closure to the outer scope of the funnel).\n * @param options - An object that defines when `execute` should be invoked,\n * relative to the calls of `call`. An empty/missing options object is\n * equivalent to setting `minQuietPeriodMs` to `0`.\n * @param options.reducer - Combines the arguments passed to `call` with the\n * value computed on the previous call (or `undefined` on the first time). The\n * goal of the function is to extract and summarize the data needed for\n * `callback`. It should be fast and simple as it is called often and should\n * defer heavy operations to the `execute` function. If the final value\n * is `undefined`, `callback` will not be called.\n * @param options.triggerAt - At what \"edges\" of the funnel's burst window\n * would `execute` invoke:\n * - `start` - the function will be invoked immediately (within the **same**\n * execution frame!), and any subsequent calls would be ignored until the funnel\n * is idle again. During this period `reducer` will also not be called.\n * - `end` - the function will **not** be invoked initially but the timer will\n * be started. Any calls during this time would be passed to the reducer, and\n * when the timers are done, the reduced result would trigger an invocation.\n * - `both` - the function will be invoked immediately, and then the funnel\n * would behave as if it was in the 'end' state. Default: 'end'.\n * @param options.minQuietPeriodMs - The burst timer prevents subsequent calls\n * in short succession to cause excessive invocations (aka \"debounce\"). This\n * duration represents the **minimum** amount of time that needs to pass\n * between calls (the \"quiet\" part) in order for the subsequent call to **not**\n * be considered part of the burst. In other words, as long as calls are faster\n * than this, they are considered part of the burst and the burst is extended.\n * @param options.maxBurstDurationMs - Bursts are extended every time a call is\n * made within the burst period. This means that the burst period could be\n * extended indefinitely. To prevent such cases, a maximum burst duration could\n * be defined. When `minQuietPeriodMs` is not defined and this option is, they\n * will both share the same value.\n * @param options.minGapMs - A minimum duration between calls of `execute`.\n * This is maintained regardless of the shape of the burst and is ensured even\n * if the `maxBurstDurationMs` is reached before it. (aka \"throttle\").\n * @returns A funnel with a `call` function that is used to trigger invocations.\n * In addition to it the funnel also comes with the following functions and\n * properties:\n * - `cancel` - Resets the funnel to it's initial state, discarding the current\n * `reducer` result without calling `execute` on it.\n * - `flush` - Triggers an invocation even if there are active timeouts, and\n * then resets the funnel to it's initial state.\n * - `isIdle` - Checks if there are any active timeouts.\n * @signature\n * funnel(callback, options);\n * @example\n * const debouncer = funnel(\n * () => {\n * console.log(\"Callback executed!\");\n * },\n * { minQuietPeriodMs: 100 },\n * );\n * debouncer.call();\n * debouncer.call();\n *\n * const throttle = funnel(\n * () => {\n * console.log(\"Callback executed!\");\n * },\n * { minGapMs: 100, triggerAt: \"start\" },\n * );\n * throttle.call();\n * throttle.call();\n * @category Function\n */\nexport function funnel<Args extends RestArguments = [], R = never>(\n callback: (data: R) => void,\n {\n triggerAt = \"end\",\n minQuietPeriodMs,\n maxBurstDurationMs,\n minGapMs,\n reducer = voidReducer,\n }: FunnelOptions<Args, R>,\n): Funnel<Args> {\n // We manage execution via 2 timeouts, one to track bursts of calls, and one\n // to track the interval between invocations. Together we refer to the period\n // where any of these are active as a \"cool-down period\".\n let burstTimeoutId: ReturnType<typeof setTimeout> | undefined;\n let intervalTimeoutId: ReturnType<typeof setTimeout> | undefined;\n\n // Until invoked, all calls are reduced into a single value that would be sent\n // to the executor on invocation.\n let preparedData: R | undefined;\n\n // In order to be able to limit the total size of the burst (when\n // `maxBurstDurationMs` is used) we need to track when the burst started.\n let burstStartTimestamp: number | undefined;\n\n const invoke = (): void => {\n const param = preparedData;\n if (param === undefined) {\n // There were no calls during both cool-down periods.\n return;\n }\n\n // Make sure the args aren't accidentally used again\n preparedData = undefined;\n\n if (param === VOID_REDUCER_SYMBOL) {\n // @ts-expect-error [ts2554] -- R is typed as `never` because we hide the\n // symbol that `voidReducer` returns; there's no way to make TypeScript\n // aware of this.\n callback();\n } else {\n callback(param);\n }\n\n if (minGapMs !== undefined) {\n intervalTimeoutId = setTimeout(handleIntervalEnd, minGapMs);\n }\n };\n\n const handleIntervalEnd = (): void => {\n // When called via a timeout the timeout is already cleared, but when called\n // via `flush` we need to manually clear it.\n clearTimeout(intervalTimeoutId);\n intervalTimeoutId = undefined;\n\n if (burstTimeoutId !== undefined) {\n // As long as one of the timeouts is active we don't invoke the function.\n // Each timeout's end event handler has a call to invoke, so we are\n // guaranteed to invoke the function eventually.\n return;\n }\n\n invoke();\n };\n\n const handleBurstEnd = (): void => {\n // When called via a timeout the timeout is already cleared, but when called\n // via `flush` we need to manually clear it.\n clearTimeout(burstTimeoutId);\n burstTimeoutId = undefined;\n burstStartTimestamp = undefined;\n\n if (intervalTimeoutId !== undefined) {\n // As long as one of the timeouts is active we don't invoke the function.\n // Each timeout's end event handler has a call to invoke, so we are\n // guaranteed to invoke the function eventually.\n return;\n }\n\n invoke();\n };\n\n return {\n call: (...args) => {\n // We act based on the initial state of the timeouts before the call is\n // handled and causes the timeouts to change.\n const wasIdle =\n burstTimeoutId === undefined && intervalTimeoutId === undefined;\n\n if (triggerAt !== \"start\" || wasIdle) {\n preparedData = reducer(preparedData, ...args);\n }\n\n if (burstTimeoutId === undefined && !wasIdle) {\n // We are not in an active burst period but in an interval period. We\n // don't start a new burst window until the next invoke.\n return;\n }\n\n if (\n minQuietPeriodMs !== undefined ||\n maxBurstDurationMs !== undefined ||\n minGapMs === undefined\n ) {\n // The timeout tracking the burst period needs to be reset every time\n // another call is made so that it waits the full cool-down duration\n // before it is released.\n clearTimeout(burstTimeoutId);\n\n const now = Date.now();\n\n burstStartTimestamp ??= now;\n\n const burstRemainingMs =\n maxBurstDurationMs === undefined\n ? (minQuietPeriodMs ?? 0)\n : Math.min(\n minQuietPeriodMs ?? maxBurstDurationMs,\n // We need to account for the time already spent so that we\n // don't wait longer than the maxDelay.\n Math.max(0, maxBurstDurationMs - (now - burstStartTimestamp)),\n );\n\n burstTimeoutId = setTimeout(handleBurstEnd, burstRemainingMs);\n }\n\n if (triggerAt !== \"end\" && wasIdle) {\n invoke();\n }\n },\n\n cancel: () => {\n clearTimeout(burstTimeoutId);\n burstTimeoutId = undefined;\n burstStartTimestamp = undefined;\n\n clearTimeout(intervalTimeoutId);\n intervalTimeoutId = undefined;\n\n preparedData = undefined;\n },\n\n flush: () => {\n handleBurstEnd();\n handleIntervalEnd();\n },\n\n get isIdle() {\n return burstTimeoutId === undefined && intervalTimeoutId === undefined;\n },\n };\n}\n"],"mappings":"AAQA,MAAM,EAAsB,OAAO,oBAAoB,EAEjD,MAA0B,EAwJhC,SAAgB,EACd,EACA,CACE,YAAY,MACZ,mBACA,qBACA,WACA,UAAU,GAEE,CAId,IAAI,EACA,EAIA,EAIA,EAEE,MAAqB,CACzB,IAAM,EAAQ,EACV,IAAU,IAAA,KAMd,EAAe,IAAA,GAEX,IAAU,EAIZ,EAAS,EAET,EAAS,CAAK,EAGZ,IAAa,IAAA,KACf,EAAoB,WAAW,EAAmB,CAAQ,GAE9D,EAEM,MAAgC,CAGpC,aAAa,CAAiB,EAC9B,EAAoB,IAAA,GAEhB,IAAmB,IAAA,IAOvB,EAAO,CACT,EAEM,MAA6B,CAGjC,aAAa,CAAc,EAC3B,EAAiB,IAAA,GACjB,EAAsB,IAAA,GAElB,IAAsB,IAAA,IAO1B,EAAO,CACT,EAEA,MAAO,CACL,MAAO,GAAG,IAAS,CAGjB,IAAM,EACJ,IAAmB,IAAA,IAAa,IAAsB,IAAA,GAExD,IAAI,IAAc,SAAW,KAC3B,EAAe,EAAQ,EAAc,GAAG,CAAI,GAG1C,MAAmB,IAAA,IAAa,CAAC,GAMrC,IACE,IAAqB,IAAA,IACrB,IAAuB,IAAA,IACvB,IAAa,IAAA,GACb,CAIA,aAAa,CAAc,EAE3B,IAAM,EAAM,KAAK,IAAI,EAErB,IAAwB,EAExB,IAAM,EACJ,IAAuB,IAAA,GAClB,GAAoB,EACrB,KAAK,IACH,GAAoB,EAGpB,KAAK,IAAI,EAAG,GAAsB,EAAM,EAAoB,CAC9D,EAEN,EAAiB,WAAW,EAAgB,CAAgB,CAC9D,CAEI,IAAc,OAAS,GACzB,EAAO,CAHT,CAKF,EAEA,WAAc,CACZ,aAAa,CAAc,EAC3B,EAAiB,IAAA,GACjB,EAAsB,IAAA,GAEtB,aAAa,CAAiB,EAC9B,EAAoB,IAAA,GAEpB,EAAe,IAAA,EACjB,EAEA,UAAa,CACX,EAAe,EACf,EAAkB,CACpB,EAEA,IAAI,QAAS,CACX,OAAO,IAAmB,IAAA,IAAa,IAAsB,IAAA,EAC/D,CACF,CACF"}
1
+ {"version":3,"file":"funnel.js","names":[],"sources":["../src/funnel.ts"],"sourcesContent":["import type { RequireAtLeastOne } from \"type-fest\";\n\n// We use the value provided by the reducer to also determine if a call\n// was done during a timeout period. This means that even when no reducer\n// is provided, we still need a dummy reducer that would return something\n// other than `undefined`. It is safe to cast this to R (which might be\n// anything) because the callback would never use it as it would be typed\n// as a zero-args function.\nconst VOID_REDUCER_SYMBOL = Symbol(\"funnel/voidReducer\");\n// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- Intentional, so that it could be used as a default value above.\nconst voidReducer = <R>(): R => VOID_REDUCER_SYMBOL as R;\n\ntype FunnelOptions<Args extends RestArguments, R> = {\n readonly reducer?: (accumulator: R | undefined, ...params: Args) => R;\n} & FunnelTimingOptions;\n\n// Not all combinations of timing options are valid, there are dependencies\n// between them to ensure users can't configure the funnel in a way which would\n// cause it to never trigger.\ntype FunnelTimingOptions =\n | ({ readonly triggerAt?: \"end\" } & (\n | ({ readonly minGapMs: number } & RequireAtLeastOne<{\n readonly minQuietPeriodMs: number;\n readonly maxBurstDurationMs: number;\n }>)\n | {\n readonly minQuietPeriodMs?: number;\n readonly maxBurstDurationMs?: number;\n readonly minGapMs?: never;\n }\n ))\n | {\n readonly triggerAt: \"start\" | \"both\";\n readonly minQuietPeriodMs?: number;\n readonly maxBurstDurationMs?: number;\n readonly minGapMs?: number;\n };\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TypeScript has some quirks with generic function types, and works best with `any` and not `unknown`. This follows the typing of built-in utilities like `ReturnType` and `Parameters`.\ntype RestArguments = any[];\n\ntype Funnel<Args extends RestArguments = []> = {\n /**\n * Call the function. This might result in the `execute` function being called\n * now or later, depending on it's configuration and it's current state.\n *\n * @param args - The args are defined by the `reducer` function.\n */\n\n readonly call: (...args: Args) => void;\n\n /**\n * Resets the funnel to it's initial state. Any calls made since the last\n * invocation will be discarded.\n */\n readonly cancel: () => void;\n\n /**\n * Triggers an invocation regardless of the current state of the funnel.\n * Like any other invocation, The funnel will also be reset to it's initial\n * state afterwards.\n */\n readonly flush: () => void;\n\n /**\n * The funnel is in it's initial state (there are no active timeouts).\n */\n readonly isIdle: boolean;\n};\n\n/**\n * Creates a funnel that controls the timing and execution of `callback`. Its\n * main purpose is to manage multiple consecutive (usually fast-paced) calls,\n * reshaping them according to a defined batching strategy and timing policy.\n * This is useful when handling uncontrolled call rates, such as DOM events or\n * network traffic. It can implement strategies like debouncing, throttling,\n * batching, and more.\n *\n * An optional `reducer` function can be provided to allow passing data to the\n * callback via calls to `call` (otherwise the signature of `call` takes no\n * arguments).\n *\n * Typing is inferred from `callback`s param, and from the rest params that\n * the optional `reducer` function accepts. Use **explicit** types for these\n * to ensure that everything _else_ is well-typed.\n *\n * Notice that this function constructs a funnel **object**, and does **not**\n * execute anything when called. The returned object should be used to execute\n * the funnel via the its `call` method.\n *\n * The type of the funnel object is not exported; when you need to reference\n * it explicitly (e.g., a class property holding a funnel) use\n * `ReturnType<typeof funnel<[]>>`, replacing `[]` with the `reducer`'s rest\n * params when one is used (e.g., `ReturnType<typeof funnel<[string]>>`).\n *\n * - Debouncing: use `minQuietPeriodMs` and any `triggerAt`. Copy-paste\n * reference implementations are available for Remeda's deprecated `debounce`\n * function in [`funnel.remeda-debounce.test.ts`](https://github.com/remeda/remeda/blob/main/packages/remeda/src/funnel.remeda-debounce.test.ts),\n * and for the Lodash `debounce` function in the [Lodash migration docs](https://remedajs.com/migrate/lodash#debounce).\n * - Throttling: use `minGapMs` and `triggerAt: \"start\"` or `\"both\"`.\n * Copy-paste reference implementations for the Lodash `throttle` function\n * (which maps onto the burst options `minQuietPeriodMs` and\n * `maxBurstDurationMs` instead of `minGapMs`) are available in the\n * [Lodash migration docs](https://remedajs.com/migrate/lodash#throttle).\n * - Batching: a copy-paste reference implementation is available in [`funnel.reference-batch.test.ts`](https://github.com/remeda/remeda/blob/main/packages/remeda/src/funnel.reference-batch.test.ts).\n *\n * @param callback - The main function that would be invoked periodically based\n * on `options`. The function would take the latest result of the `reducer`; if\n * no calls where made since the last time it was invoked it will not be\n * invoked. (If a return value is needed, it should be passed via a reference or\n * via closure to the outer scope of the funnel).\n * @param options - An object that defines when `execute` should be invoked,\n * relative to the calls of `call`. An empty/missing options object is\n * equivalent to setting `minQuietPeriodMs` to `0`.\n * @param options.reducer - Combines the arguments passed to `call` with the\n * value computed on the previous call (or `undefined` on the first time). The\n * goal of the function is to extract and summarize the data needed for\n * `callback`. It should be fast and simple as it is called often and should\n * defer heavy operations to the `execute` function. If the final value\n * is `undefined`, `callback` will not be called.\n * @param options.triggerAt - At what \"edges\" of the funnel's burst window\n * would `execute` invoke:\n * - `start` - the function will be invoked immediately (within the **same**\n * execution frame!), and any subsequent calls would be ignored until the funnel\n * is idle again. During this period `reducer` will also not be called.\n * - `end` - the function will **not** be invoked initially but the timer will\n * be started. Any calls during this time would be passed to the reducer, and\n * when the timers are done, the reduced result would trigger an invocation.\n * - `both` - the function will be invoked immediately, and then the funnel\n * would behave as if it was in the 'end' state. Default: 'end'.\n * @param options.minQuietPeriodMs - The burst timer prevents subsequent calls\n * in short succession to cause excessive invocations (aka \"debounce\"). This\n * duration represents the **minimum** amount of time that needs to pass\n * between calls (the \"quiet\" part) in order for the subsequent call to **not**\n * be considered part of the burst. In other words, as long as calls are faster\n * than this, they are considered part of the burst and the burst is extended.\n * @param options.maxBurstDurationMs - Bursts are extended every time a call is\n * made within the burst period. This means that the burst period could be\n * extended indefinitely. To prevent such cases, a maximum burst duration could\n * be defined. When `minQuietPeriodMs` is not defined and this option is, they\n * will both share the same value.\n * @param options.minGapMs - A minimum duration between calls of `execute`.\n * This is maintained regardless of the shape of the burst and is ensured even\n * if the `maxBurstDurationMs` is reached before it. (aka \"throttle\").\n * @returns A funnel with a `call` function that is used to trigger invocations.\n * In addition to it the funnel also comes with the following functions and\n * properties:\n * - `cancel` - Resets the funnel to it's initial state, discarding the current\n * `reducer` result without calling `execute` on it.\n * - `flush` - Triggers an invocation even if there are active timeouts, and\n * then resets the funnel to it's initial state.\n * - `isIdle` - Checks if there are any active timeouts.\n * @signature\n * funnel(callback, options);\n * @example\n * const debouncer = funnel(\n * () => {\n * console.log(\"Callback executed!\");\n * },\n * { minQuietPeriodMs: 100 },\n * );\n * debouncer.call();\n * debouncer.call();\n *\n * const throttle = funnel(\n * () => {\n * console.log(\"Callback executed!\");\n * },\n * { minGapMs: 100, triggerAt: \"start\" },\n * );\n * throttle.call();\n * throttle.call();\n * @category Function\n */\nexport function funnel<Args extends RestArguments = [], R = never>(\n callback: (data: R) => void,\n {\n triggerAt = \"end\",\n minQuietPeriodMs,\n maxBurstDurationMs,\n minGapMs,\n reducer = voidReducer,\n }: FunnelOptions<Args, R>,\n): Funnel<Args> {\n // We manage execution via 2 timeouts, one to track bursts of calls, and one\n // to track the interval between invocations. Together we refer to the period\n // where any of these are active as a \"cool-down period\".\n let burstTimeoutId: ReturnType<typeof setTimeout> | undefined;\n let intervalTimeoutId: ReturnType<typeof setTimeout> | undefined;\n\n // Until invoked, all calls are reduced into a single value that would be sent\n // to the executor on invocation.\n let preparedData: R | undefined;\n\n // In order to be able to limit the total size of the burst (when\n // `maxBurstDurationMs` is used) we need to track when the burst started.\n let burstStartTimestamp: number | undefined;\n\n const invoke = (): void => {\n const param = preparedData;\n if (param === undefined) {\n // There were no calls during both cool-down periods.\n return;\n }\n\n // Make sure the args aren't accidentally used again\n preparedData = undefined;\n\n if (param === VOID_REDUCER_SYMBOL) {\n // @ts-expect-error [ts2554] -- R is typed as `never` because we hide the\n // symbol that `voidReducer` returns; there's no way to make TypeScript\n // aware of this.\n callback();\n } else {\n callback(param);\n }\n\n if (minGapMs !== undefined) {\n intervalTimeoutId = setTimeout(handleIntervalEnd, minGapMs);\n }\n };\n\n const handleIntervalEnd = (): void => {\n // When called via a timeout the timeout is already cleared, but when called\n // via `flush` we need to manually clear it.\n clearTimeout(intervalTimeoutId);\n intervalTimeoutId = undefined;\n\n if (burstTimeoutId !== undefined) {\n // As long as one of the timeouts is active we don't invoke the function.\n // Each timeout's end event handler has a call to invoke, so we are\n // guaranteed to invoke the function eventually.\n return;\n }\n\n invoke();\n };\n\n const handleBurstEnd = (): void => {\n // When called via a timeout the timeout is already cleared, but when called\n // via `flush` we need to manually clear it.\n clearTimeout(burstTimeoutId);\n burstTimeoutId = undefined;\n burstStartTimestamp = undefined;\n\n if (intervalTimeoutId !== undefined) {\n // As long as one of the timeouts is active we don't invoke the function.\n // Each timeout's end event handler has a call to invoke, so we are\n // guaranteed to invoke the function eventually.\n return;\n }\n\n invoke();\n };\n\n return {\n call: (...args) => {\n // We act based on the initial state of the timeouts before the call is\n // handled and causes the timeouts to change.\n const wasIdle =\n burstTimeoutId === undefined && intervalTimeoutId === undefined;\n\n if (triggerAt !== \"start\" || wasIdle) {\n preparedData = reducer(preparedData, ...args);\n }\n\n if (burstTimeoutId === undefined && !wasIdle) {\n // We are not in an active burst period but in an interval period. We\n // don't start a new burst window until the next invoke.\n return;\n }\n\n if (\n minQuietPeriodMs !== undefined ||\n maxBurstDurationMs !== undefined ||\n minGapMs === undefined\n ) {\n // The timeout tracking the burst period needs to be reset every time\n // another call is made so that it waits the full cool-down duration\n // before it is released.\n clearTimeout(burstTimeoutId);\n\n const now = Date.now();\n\n burstStartTimestamp ??= now;\n\n const burstRemainingMs =\n maxBurstDurationMs === undefined\n ? (minQuietPeriodMs ?? 0)\n : Math.min(\n minQuietPeriodMs ?? maxBurstDurationMs,\n // We need to account for the time already spent so that we\n // don't wait longer than the maxDelay.\n Math.max(0, maxBurstDurationMs - (now - burstStartTimestamp)),\n );\n\n burstTimeoutId = setTimeout(handleBurstEnd, burstRemainingMs);\n }\n\n if (triggerAt !== \"end\" && wasIdle) {\n invoke();\n }\n },\n\n cancel: () => {\n clearTimeout(burstTimeoutId);\n burstTimeoutId = undefined;\n burstStartTimestamp = undefined;\n\n clearTimeout(intervalTimeoutId);\n intervalTimeoutId = undefined;\n\n preparedData = undefined;\n },\n\n flush: () => {\n handleBurstEnd();\n handleIntervalEnd();\n },\n\n get isIdle() {\n return burstTimeoutId === undefined && intervalTimeoutId === undefined;\n },\n };\n}\n"],"mappings":"AAQA,MAAM,EAAsB,OAAO,oBAAoB,EAEjD,MAA0B,EAoKhC,SAAgB,EACd,EACA,CACE,YAAY,MACZ,mBACA,qBACA,WACA,UAAU,GAEE,CAId,IAAI,EACA,EAIA,EAIA,EAEE,MAAqB,CACzB,IAAM,EAAQ,EACV,IAAU,IAAA,KAMd,EAAe,IAAA,GAEX,IAAU,EAIZ,EAAS,EAET,EAAS,CAAK,EAGZ,IAAa,IAAA,KACf,EAAoB,WAAW,EAAmB,CAAQ,GAE9D,EAEM,MAAgC,CAGpC,aAAa,CAAiB,EAC9B,EAAoB,IAAA,GAEhB,IAAmB,IAAA,IAOvB,EAAO,CACT,EAEM,MAA6B,CAGjC,aAAa,CAAc,EAC3B,EAAiB,IAAA,GACjB,EAAsB,IAAA,GAElB,IAAsB,IAAA,IAO1B,EAAO,CACT,EAEA,MAAO,CACL,MAAO,GAAG,IAAS,CAGjB,IAAM,EACJ,IAAmB,IAAA,IAAa,IAAsB,IAAA,GAExD,IAAI,IAAc,SAAW,KAC3B,EAAe,EAAQ,EAAc,GAAG,CAAI,GAG1C,MAAmB,IAAA,IAAa,CAAC,GAMrC,IACE,IAAqB,IAAA,IACrB,IAAuB,IAAA,IACvB,IAAa,IAAA,GACb,CAIA,aAAa,CAAc,EAE3B,IAAM,EAAM,KAAK,IAAI,EAErB,IAAwB,EAExB,IAAM,EACJ,IAAuB,IAAA,GAClB,GAAoB,EACrB,KAAK,IACH,GAAoB,EAGpB,KAAK,IAAI,EAAG,GAAsB,EAAM,EAAoB,CAC9D,EAEN,EAAiB,WAAW,EAAgB,CAAgB,CAC9D,CAEI,IAAc,OAAS,GACzB,EAAO,CAHT,CAKF,EAEA,WAAc,CACZ,aAAa,CAAc,EAC3B,EAAiB,IAAA,GACjB,EAAsB,IAAA,GAEtB,aAAa,CAAiB,EAC9B,EAAoB,IAAA,GAEpB,EAAe,IAAA,EACjB,EAEA,UAAa,CACX,EAAe,EACf,EAAkB,CACpB,EAEA,IAAI,QAAS,CACX,OAAO,IAAmB,IAAA,IAAa,IAAsB,IAAA,EAC/D,CACF,CACF"}
package/dist/index.d.cts CHANGED
@@ -5979,7 +5979,7 @@ type DebounceOptions = {
5979
5979
  * It stores the value returned by `func` whenever its invoked. This value is returned on every call, and is accessible via the `cachedValue` property of the debouncer. Its important to note that the value might be different from the value that would be returned from running `func` with the current arguments as it is a cached value from a previous invocation.
5980
5980
  * **Important**: The cool-down period defines the minimum between two invocations, and not the maximum. The period will be **extended** each time a call is made until a full cool-down period has elapsed without any additional calls.
5981
5981
  *
5982
- *! **DEPRECATED**: This implementation of debounce is known to have issues and might not behave as expected. It should be replaced with the `funnel` utility instead. The test file [funnel.remeda-debounce.test.ts](https://github.com/remeda/remeda/blob/main/packages/remeda/src/funnel.remeda-debounce.test.ts) offers a reference implementation that replicates `debounce` via `funnel`!
5982
+ *! **DEPRECATED**: This implementation of debounce is known to have issues and might not behave as expected. It should be replaced with the `funnel` utility instead. The test file [funnel.remeda-debounce.test.ts](https://github.com/remeda/remeda/blob/main/packages/remeda/src/funnel.remeda-debounce.test.ts) offers a drop-in replacement implemented via `funnel`: copy everything between its REFERENCE START and REFERENCE END markers into your project!
5983
5983
  *
5984
5984
  * @param func - The function to debounce, the returned `call` function will have
5985
5985
  * the exact same signature.
@@ -6022,8 +6022,9 @@ type DebounceOptions = {
6022
6022
  * @category Function
6023
6023
  * @deprecated This implementation of debounce is known to have issues and might
6024
6024
  * not behave as expected. It should be replaced with the `funnel` utility
6025
- * instead. The test file `funnel.remeda-debounce.test.ts` offers a reference
6026
- * implementation that replicates `debounce` via `funnel`.
6025
+ * instead. The test file [funnel.remeda-debounce.test.ts](https://github.com/remeda/remeda/blob/main/packages/remeda/src/funnel.remeda-debounce.test.ts)
6026
+ * offers a drop-in replacement implemented via `funnel`: copy everything
6027
+ * between its REFERENCE START and REFERENCE END markers into your project.
6027
6028
  * @see https://css-tricks.com/debouncing-throttling-explained-examples/
6028
6029
  */
6029
6030
  declare function debounce<F extends StrictFunction>(func: F, options: DebounceOptions & {
@@ -7417,9 +7418,21 @@ type Funnel<Args extends RestArguments = []> = {
7417
7418
  * execute anything when called. The returned object should be used to execute
7418
7419
  * the funnel via the its `call` method.
7419
7420
  *
7420
- * - Debouncing: use `minQuietPeriodMs` and any `triggerAt`.
7421
+ * The type of the funnel object is not exported; when you need to reference
7422
+ * it explicitly (e.g., a class property holding a funnel) use
7423
+ * `ReturnType<typeof funnel<[]>>`, replacing `[]` with the `reducer`'s rest
7424
+ * params when one is used (e.g., `ReturnType<typeof funnel<[string]>>`).
7425
+ *
7426
+ * - Debouncing: use `minQuietPeriodMs` and any `triggerAt`. Copy-paste
7427
+ * reference implementations are available for Remeda's deprecated `debounce`
7428
+ * function in [`funnel.remeda-debounce.test.ts`](https://github.com/remeda/remeda/blob/main/packages/remeda/src/funnel.remeda-debounce.test.ts),
7429
+ * and for the Lodash `debounce` function in the [Lodash migration docs](https://remedajs.com/migrate/lodash#debounce).
7421
7430
  * - Throttling: use `minGapMs` and `triggerAt: "start"` or `"both"`.
7422
- * - Batching: See the reference implementation in [`funnel.reference-batch.test.ts`](https://github.com/remeda/remeda/blob/main/packages/remeda/src/funnel.reference-batch.test.ts).
7431
+ * Copy-paste reference implementations for the Lodash `throttle` function
7432
+ * (which maps onto the burst options `minQuietPeriodMs` and
7433
+ * `maxBurstDurationMs` instead of `minGapMs`) are available in the
7434
+ * [Lodash migration docs](https://remedajs.com/migrate/lodash#throttle).
7435
+ * - Batching: a copy-paste reference implementation is available in [`funnel.reference-batch.test.ts`](https://github.com/remeda/remeda/blob/main/packages/remeda/src/funnel.reference-batch.test.ts).
7423
7436
  *
7424
7437
  * @param callback - The main function that would be invoked periodically based
7425
7438
  * on `options`. The function would take the latest result of the `reducer`; if
@@ -10167,19 +10180,23 @@ type ArrayAt<T extends IterableContainer, I extends keyof T> = IsNumericLiteral<
10167
10180
  : T[number] | undefined;
10168
10181
  type HasIndex<T extends readonly unknown[], I> = I extends ArrayIndices<T> ? true : false;
10169
10182
  //#endregion
10183
+ //#region src/internal/types/Resolved.d.ts
10184
+ type Resolved<T> = T extends (infer U) ? U : never;
10185
+ //#endregion
10170
10186
  //#region src/prop.d.ts
10171
10187
  type KeysDeep<T, Path extends readonly unknown[]> = KeysOfUnion<PropDeep<T, Path>>;
10172
10188
  type PropDeep<T, Path extends readonly unknown[]> = Path extends readonly [infer Key, ...infer Rest] ? PropDeep<Prop<T, Key>, Rest> : T;
10173
- type Prop<T, Key> = T extends unknown ? Key extends keyof T ? T extends readonly unknown[] ? ArrayAt<T, Key> : T[Key] : undefined : never;
10189
+ type Prop<T, Key> = T extends unknown ? Key extends keyof T ? T extends readonly unknown[] ? ArrayAt<T, Key> : T[Key] | (Key extends keyof OmitIndexSignature<T> ? never : undefined) : undefined : never;
10174
10190
  type NonPropertyKey = object | null | undefined;
10175
10191
  /**
10176
10192
  * Gets the value of the given property from an object. Nested properties can
10177
10193
  * be accessed by providing a variadic array of keys that define the path from
10178
10194
  * the root to the desired property. Arrays can be accessed by using numeric
10179
- * keys. Unions and optional properties are handled gracefully by returning
10180
- * `undefined` early for any non-existing property on the path. Paths are
10181
- * validated against the object type to provide stronger type safety, better
10182
- * compile-time errors, and to enable autocompletion in IDEs.
10195
+ * keys. Unions, optional properties, and index signatures are handled
10196
+ * gracefully by returning `undefined` early for any non-existing property on
10197
+ * the path. Paths are validated against the object type to provide stronger
10198
+ * type safety, better compile-time errors, and to enable autocompletion in
10199
+ * IDEs.
10183
10200
  *
10184
10201
  * To check whether a key exists on the object, use `hasProp`.
10185
10202
  *
@@ -10194,24 +10211,25 @@ type NonPropertyKey = object | null | undefined;
10194
10211
  * @dataFirst
10195
10212
  * @category Object
10196
10213
  */
10197
- declare function prop<T extends NonPropertyKey, Key extends KeysDeep<T, []>>(data: T, key: Key): NoInfer<Prop<T, Key>>;
10198
- declare function prop<T extends NonPropertyKey, Key0 extends KeysDeep<T, []>, Key1 extends KeysDeep<T, [Key0]>>(data: T, key0: Key0, key1: Key1): NoInfer<PropDeep<T, [Key0, Key1]>>;
10199
- declare function prop<T extends NonPropertyKey, Key0 extends KeysDeep<T, []>, Key1 extends KeysDeep<T, [Key0]>, Key2 extends KeysDeep<T, [Key0, Key1]>>(data: T, key0: Key0, key1: Key1, key2: Key2): NoInfer<PropDeep<T, [Key0, Key1, Key2]>>;
10200
- declare function prop<T extends NonPropertyKey, Key0 extends KeysDeep<T, []>, Key1 extends KeysDeep<T, [Key0]>, Key2 extends KeysDeep<T, [Key0, Key1]>, Key3 extends KeysDeep<T, [Key0, Key1, Key2]>>(data: T, key0: Key0, key1: Key1, key2: Key2, key3: Key3): NoInfer<PropDeep<T, [Key0, Key1, Key2, Key3]>>;
10201
- declare function prop<T extends NonPropertyKey, Key0 extends KeysDeep<T, []>, Key1 extends KeysDeep<T, [Key0]>, Key2 extends KeysDeep<T, [Key0, Key1]>, Key3 extends KeysDeep<T, [Key0, Key1, Key2]>, Key4 extends KeysDeep<T, [Key0, Key1, Key2, Key3]>>(data: T, key0: Key0, key1: Key1, key2: Key2, key3: Key3, key4: Key4): NoInfer<PropDeep<T, [Key0, Key1, Key2, Key3, Key4]>>;
10202
- declare function prop<T extends NonPropertyKey, Key0 extends KeysDeep<T, []>, Key1 extends KeysDeep<T, [Key0]>, Key2 extends KeysDeep<T, [Key0, Key1]>, Key3 extends KeysDeep<T, [Key0, Key1, Key2]>, Key4 extends KeysDeep<T, [Key0, Key1, Key2, Key3]>, Key5 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4]>>(data: T, key0: Key0, key1: Key1, key2: Key2, key3: Key3, key4: Key4, key5: Key5): NoInfer<PropDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5]>>;
10203
- declare function prop<T extends NonPropertyKey, Key0 extends KeysDeep<T, []>, Key1 extends KeysDeep<T, [Key0]>, Key2 extends KeysDeep<T, [Key0, Key1]>, Key3 extends KeysDeep<T, [Key0, Key1, Key2]>, Key4 extends KeysDeep<T, [Key0, Key1, Key2, Key3]>, Key5 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4]>, Key6 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5]>>(data: T, key0: Key0, key1: Key1, key2: Key2, key3: Key3, key4: Key4, key5: Key5, key6: Key6): NoInfer<PropDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5, Key6]>>;
10204
- declare function prop<T extends NonPropertyKey, Key0 extends KeysDeep<T, []>, Key1 extends KeysDeep<T, [Key0]>, Key2 extends KeysDeep<T, [Key0, Key1]>, Key3 extends KeysDeep<T, [Key0, Key1, Key2]>, Key4 extends KeysDeep<T, [Key0, Key1, Key2, Key3]>, Key5 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4]>, Key6 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5]>, Key7 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5, Key6]>>(data: T, key0: Key0, key1: Key1, key2: Key2, key3: Key3, key4: Key4, key5: Key5, key6: Key6, key7: Key7): NoInfer<PropDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5, Key6, Key7]>>;
10205
- declare function prop<T extends NonPropertyKey, Key0 extends KeysDeep<T, []>, Key1 extends KeysDeep<T, [Key0]>, Key2 extends KeysDeep<T, [Key0, Key1]>, Key3 extends KeysDeep<T, [Key0, Key1, Key2]>, Key4 extends KeysDeep<T, [Key0, Key1, Key2, Key3]>, Key5 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4]>, Key6 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5]>, Key7 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5, Key6]>, Key8 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5, Key6, Key7]>>(data: T, key0: Key0, key1: Key1, key2: Key2, key3: Key3, key4: Key4, key5: Key5, key6: Key6, key7: Key7, key8: Key8): NoInfer<PropDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5, Key6, Key7, Key8]>>;
10206
- declare function prop<T extends NonPropertyKey, Key0 extends KeysDeep<T, []>, Key1 extends KeysDeep<T, [Key0]>, Key2 extends KeysDeep<T, [Key0, Key1]>, Key3 extends KeysDeep<T, [Key0, Key1, Key2]>, Key4 extends KeysDeep<T, [Key0, Key1, Key2, Key3]>, Key5 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4]>, Key6 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5]>, Key7 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5, Key6]>, Key8 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5, Key6, Key7]>, Key9 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5, Key6, Key7, Key8]>, AdditionalKeys extends readonly PropertyKey[] = []>(data: T, key0: Key0, key1: Key1, key2: Key2, key3: Key3, key4: Key4, key5: Key5, key6: Key6, key7: Key7, key8: Key8, key9: Key9, ...additionalKeys: AdditionalKeys): NoInfer<PropDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5, Key6, Key7, Key8, Key9, ...AdditionalKeys]>>;
10214
+ declare function prop<T extends NonPropertyKey, Key extends KeysDeep<T, []>>(data: T, key: Key): Resolved<Prop<T, Key>>;
10215
+ declare function prop<T extends NonPropertyKey, Key0 extends KeysDeep<T, []>, Key1 extends KeysDeep<T, [Key0]>>(data: T, key0: Key0, key1: Key1): Resolved<PropDeep<T, [Key0, Key1]>>;
10216
+ declare function prop<T extends NonPropertyKey, Key0 extends KeysDeep<T, []>, Key1 extends KeysDeep<T, [Key0]>, Key2 extends KeysDeep<T, [Key0, Key1]>>(data: T, key0: Key0, key1: Key1, key2: Key2): Resolved<PropDeep<T, [Key0, Key1, Key2]>>;
10217
+ declare function prop<T extends NonPropertyKey, Key0 extends KeysDeep<T, []>, Key1 extends KeysDeep<T, [Key0]>, Key2 extends KeysDeep<T, [Key0, Key1]>, Key3 extends KeysDeep<T, [Key0, Key1, Key2]>>(data: T, key0: Key0, key1: Key1, key2: Key2, key3: Key3): Resolved<PropDeep<T, [Key0, Key1, Key2, Key3]>>;
10218
+ declare function prop<T extends NonPropertyKey, Key0 extends KeysDeep<T, []>, Key1 extends KeysDeep<T, [Key0]>, Key2 extends KeysDeep<T, [Key0, Key1]>, Key3 extends KeysDeep<T, [Key0, Key1, Key2]>, Key4 extends KeysDeep<T, [Key0, Key1, Key2, Key3]>>(data: T, key0: Key0, key1: Key1, key2: Key2, key3: Key3, key4: Key4): Resolved<PropDeep<T, [Key0, Key1, Key2, Key3, Key4]>>;
10219
+ declare function prop<T extends NonPropertyKey, Key0 extends KeysDeep<T, []>, Key1 extends KeysDeep<T, [Key0]>, Key2 extends KeysDeep<T, [Key0, Key1]>, Key3 extends KeysDeep<T, [Key0, Key1, Key2]>, Key4 extends KeysDeep<T, [Key0, Key1, Key2, Key3]>, Key5 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4]>>(data: T, key0: Key0, key1: Key1, key2: Key2, key3: Key3, key4: Key4, key5: Key5): Resolved<PropDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5]>>;
10220
+ declare function prop<T extends NonPropertyKey, Key0 extends KeysDeep<T, []>, Key1 extends KeysDeep<T, [Key0]>, Key2 extends KeysDeep<T, [Key0, Key1]>, Key3 extends KeysDeep<T, [Key0, Key1, Key2]>, Key4 extends KeysDeep<T, [Key0, Key1, Key2, Key3]>, Key5 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4]>, Key6 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5]>>(data: T, key0: Key0, key1: Key1, key2: Key2, key3: Key3, key4: Key4, key5: Key5, key6: Key6): Resolved<PropDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5, Key6]>>;
10221
+ declare function prop<T extends NonPropertyKey, Key0 extends KeysDeep<T, []>, Key1 extends KeysDeep<T, [Key0]>, Key2 extends KeysDeep<T, [Key0, Key1]>, Key3 extends KeysDeep<T, [Key0, Key1, Key2]>, Key4 extends KeysDeep<T, [Key0, Key1, Key2, Key3]>, Key5 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4]>, Key6 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5]>, Key7 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5, Key6]>>(data: T, key0: Key0, key1: Key1, key2: Key2, key3: Key3, key4: Key4, key5: Key5, key6: Key6, key7: Key7): Resolved<PropDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5, Key6, Key7]>>;
10222
+ declare function prop<T extends NonPropertyKey, Key0 extends KeysDeep<T, []>, Key1 extends KeysDeep<T, [Key0]>, Key2 extends KeysDeep<T, [Key0, Key1]>, Key3 extends KeysDeep<T, [Key0, Key1, Key2]>, Key4 extends KeysDeep<T, [Key0, Key1, Key2, Key3]>, Key5 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4]>, Key6 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5]>, Key7 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5, Key6]>, Key8 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5, Key6, Key7]>>(data: T, key0: Key0, key1: Key1, key2: Key2, key3: Key3, key4: Key4, key5: Key5, key6: Key6, key7: Key7, key8: Key8): Resolved<PropDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5, Key6, Key7, Key8]>>;
10223
+ declare function prop<T extends NonPropertyKey, Key0 extends KeysDeep<T, []>, Key1 extends KeysDeep<T, [Key0]>, Key2 extends KeysDeep<T, [Key0, Key1]>, Key3 extends KeysDeep<T, [Key0, Key1, Key2]>, Key4 extends KeysDeep<T, [Key0, Key1, Key2, Key3]>, Key5 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4]>, Key6 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5]>, Key7 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5, Key6]>, Key8 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5, Key6, Key7]>, Key9 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5, Key6, Key7, Key8]>, AdditionalKeys extends readonly PropertyKey[] = []>(data: T, key0: Key0, key1: Key1, key2: Key2, key3: Key3, key4: Key4, key5: Key5, key6: Key6, key7: Key7, key8: Key8, key9: Key9, ...additionalKeys: AdditionalKeys): Resolved<PropDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5, Key6, Key7, Key8, Key9, ...AdditionalKeys]>>;
10207
10224
  /**
10208
10225
  * Gets the value of the given property from an object. Nested properties can
10209
10226
  * be accessed by providing a variadic array of keys that define the path from
10210
10227
  * the root to the desired property. Arrays can be accessed by using numeric
10211
- * keys. Unions and optional properties are handled gracefully by returning
10212
- * `undefined` early for any non-existing property on the path. Paths are
10213
- * validated against the object type to provide stronger type safety, better
10214
- * compile-time errors, and to enable autocompletion in IDEs.
10228
+ * keys. Unions, optional properties, and index signatures are handled
10229
+ * gracefully by returning `undefined` early for any non-existing property on
10230
+ * the path. Paths are validated against the object type to provide stronger
10231
+ * type safety, better compile-time errors, and to enable autocompletion in
10232
+ * IDEs.
10215
10233
  *
10216
10234
  * To check whether a key exists on the object, use `hasProp`.
10217
10235
  *
@@ -10225,16 +10243,16 @@ declare function prop<T extends NonPropertyKey, Key0 extends KeysDeep<T, []>, Ke
10225
10243
  * @dataLast
10226
10244
  * @category Object
10227
10245
  */
10228
- declare function prop<T extends NonPropertyKey, Key extends KeysOfUnion<T>>(key: Key): (data: T) => NoInfer<Prop<T, Key>>;
10229
- declare function prop<T extends NonPropertyKey, Key0 extends KeysDeep<T, []>, Key1 extends KeysDeep<T, [Key0]>>(key0: Key0, key1: Key1): (data: T) => NoInfer<PropDeep<T, [Key0, Key1]>>;
10230
- declare function prop<T extends NonPropertyKey, Key0 extends KeysDeep<T, []>, Key1 extends KeysDeep<T, [Key0]>, Key2 extends KeysDeep<T, [Key0, Key1]>>(key0: Key0, key1: Key1, key2: Key2): (data: T) => NoInfer<PropDeep<T, [Key0, Key1, Key2]>>;
10231
- declare function prop<T extends NonPropertyKey, Key0 extends KeysDeep<T, []>, Key1 extends KeysDeep<T, [Key0]>, Key2 extends KeysDeep<T, [Key0, Key1]>, Key3 extends KeysDeep<T, [Key0, Key1, Key2]>>(key0: Key0, key1: Key1, key2: Key2, key3: Key3): (data: T) => NoInfer<PropDeep<T, [Key0, Key1, Key2, Key3]>>;
10232
- declare function prop<T extends NonPropertyKey, Key0 extends KeysDeep<T, []>, Key1 extends KeysDeep<T, [Key0]>, Key2 extends KeysDeep<T, [Key0, Key1]>, Key3 extends KeysDeep<T, [Key0, Key1, Key2]>, Key4 extends KeysDeep<T, [Key0, Key1, Key2, Key3]>>(key0: Key0, key1: Key1, key2: Key2, key3: Key3, key4: Key4): (data: T) => NoInfer<PropDeep<T, [Key0, Key1, Key2, Key3, Key4]>>;
10233
- declare function prop<T extends NonPropertyKey, Key0 extends KeysDeep<T, []>, Key1 extends KeysDeep<T, [Key0]>, Key2 extends KeysDeep<T, [Key0, Key1]>, Key3 extends KeysDeep<T, [Key0, Key1, Key2]>, Key4 extends KeysDeep<T, [Key0, Key1, Key2, Key3]>, Key5 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4]>>(key0: Key0, key1: Key1, key2: Key2, key3: Key3, key4: Key4, key5: Key5): (data: T) => NoInfer<PropDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5]>>;
10234
- declare function prop<T extends NonPropertyKey, Key0 extends KeysDeep<T, []>, Key1 extends KeysDeep<T, [Key0]>, Key2 extends KeysDeep<T, [Key0, Key1]>, Key3 extends KeysDeep<T, [Key0, Key1, Key2]>, Key4 extends KeysDeep<T, [Key0, Key1, Key2, Key3]>, Key5 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4]>, Key6 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5]>>(key0: Key0, key1: Key1, key2: Key2, key3: Key3, key4: Key4, key5: Key5, key6: Key6): (data: T) => NoInfer<PropDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5, Key6]>>;
10235
- declare function prop<T extends NonPropertyKey, Key0 extends KeysDeep<T, []>, Key1 extends KeysDeep<T, [Key0]>, Key2 extends KeysDeep<T, [Key0, Key1]>, Key3 extends KeysDeep<T, [Key0, Key1, Key2]>, Key4 extends KeysDeep<T, [Key0, Key1, Key2, Key3]>, Key5 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4]>, Key6 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5]>, Key7 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5, Key6]>>(key0: Key0, key1: Key1, key2: Key2, key3: Key3, key4: Key4, key5: Key5, key6: Key6, key7: Key7): (data: T) => NoInfer<PropDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5, Key6, Key7]>>;
10236
- declare function prop<T extends NonPropertyKey, Key0 extends KeysDeep<T, []>, Key1 extends KeysDeep<T, [Key0]>, Key2 extends KeysDeep<T, [Key0, Key1]>, Key3 extends KeysDeep<T, [Key0, Key1, Key2]>, Key4 extends KeysDeep<T, [Key0, Key1, Key2, Key3]>, Key5 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4]>, Key6 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5]>, Key7 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5, Key6]>, Key8 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5, Key6, Key7]>>(key0: Key0, key1: Key1, key2: Key2, key3: Key3, key4: Key4, key5: Key5, key6: Key6, key7: Key7, key8: Key8): (data: T) => NoInfer<PropDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5, Key6, Key7, Key8]>>;
10237
- declare function prop<T extends NonPropertyKey, Key0 extends KeysDeep<T, []>, Key1 extends KeysDeep<T, [Key0]>, Key2 extends KeysDeep<T, [Key0, Key1]>, Key3 extends KeysDeep<T, [Key0, Key1, Key2]>, Key4 extends KeysDeep<T, [Key0, Key1, Key2, Key3]>, Key5 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4]>, Key6 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5]>, Key7 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5, Key6]>, Key8 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5, Key6, Key7]>, Key9 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5, Key6, Key7, Key8]>, AdditionalKeys extends readonly PropertyKey[] = []>(key0: Key0, key1: Key1, key2: Key2, key3: Key3, key4: Key4, key5: Key5, key6: Key6, key7: Key7, key8: Key8, key9: Key9, ...additionalKeys: AdditionalKeys): (data: T) => NoInfer<PropDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5, Key6, Key7, Key8, Key9, ...AdditionalKeys]>>;
10246
+ declare function prop<T extends NonPropertyKey, Key extends KeysOfUnion<T>>(key: Key): (data: T) => Resolved<Prop<T, Key>>;
10247
+ declare function prop<T extends NonPropertyKey, Key0 extends KeysDeep<T, []>, Key1 extends KeysDeep<T, [Key0]>>(key0: Key0, key1: Key1): (data: T) => Resolved<PropDeep<T, [Key0, Key1]>>;
10248
+ declare function prop<T extends NonPropertyKey, Key0 extends KeysDeep<T, []>, Key1 extends KeysDeep<T, [Key0]>, Key2 extends KeysDeep<T, [Key0, Key1]>>(key0: Key0, key1: Key1, key2: Key2): (data: T) => Resolved<PropDeep<T, [Key0, Key1, Key2]>>;
10249
+ declare function prop<T extends NonPropertyKey, Key0 extends KeysDeep<T, []>, Key1 extends KeysDeep<T, [Key0]>, Key2 extends KeysDeep<T, [Key0, Key1]>, Key3 extends KeysDeep<T, [Key0, Key1, Key2]>>(key0: Key0, key1: Key1, key2: Key2, key3: Key3): (data: T) => Resolved<PropDeep<T, [Key0, Key1, Key2, Key3]>>;
10250
+ declare function prop<T extends NonPropertyKey, Key0 extends KeysDeep<T, []>, Key1 extends KeysDeep<T, [Key0]>, Key2 extends KeysDeep<T, [Key0, Key1]>, Key3 extends KeysDeep<T, [Key0, Key1, Key2]>, Key4 extends KeysDeep<T, [Key0, Key1, Key2, Key3]>>(key0: Key0, key1: Key1, key2: Key2, key3: Key3, key4: Key4): (data: T) => Resolved<PropDeep<T, [Key0, Key1, Key2, Key3, Key4]>>;
10251
+ declare function prop<T extends NonPropertyKey, Key0 extends KeysDeep<T, []>, Key1 extends KeysDeep<T, [Key0]>, Key2 extends KeysDeep<T, [Key0, Key1]>, Key3 extends KeysDeep<T, [Key0, Key1, Key2]>, Key4 extends KeysDeep<T, [Key0, Key1, Key2, Key3]>, Key5 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4]>>(key0: Key0, key1: Key1, key2: Key2, key3: Key3, key4: Key4, key5: Key5): (data: T) => Resolved<PropDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5]>>;
10252
+ declare function prop<T extends NonPropertyKey, Key0 extends KeysDeep<T, []>, Key1 extends KeysDeep<T, [Key0]>, Key2 extends KeysDeep<T, [Key0, Key1]>, Key3 extends KeysDeep<T, [Key0, Key1, Key2]>, Key4 extends KeysDeep<T, [Key0, Key1, Key2, Key3]>, Key5 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4]>, Key6 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5]>>(key0: Key0, key1: Key1, key2: Key2, key3: Key3, key4: Key4, key5: Key5, key6: Key6): (data: T) => Resolved<PropDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5, Key6]>>;
10253
+ declare function prop<T extends NonPropertyKey, Key0 extends KeysDeep<T, []>, Key1 extends KeysDeep<T, [Key0]>, Key2 extends KeysDeep<T, [Key0, Key1]>, Key3 extends KeysDeep<T, [Key0, Key1, Key2]>, Key4 extends KeysDeep<T, [Key0, Key1, Key2, Key3]>, Key5 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4]>, Key6 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5]>, Key7 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5, Key6]>>(key0: Key0, key1: Key1, key2: Key2, key3: Key3, key4: Key4, key5: Key5, key6: Key6, key7: Key7): (data: T) => Resolved<PropDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5, Key6, Key7]>>;
10254
+ declare function prop<T extends NonPropertyKey, Key0 extends KeysDeep<T, []>, Key1 extends KeysDeep<T, [Key0]>, Key2 extends KeysDeep<T, [Key0, Key1]>, Key3 extends KeysDeep<T, [Key0, Key1, Key2]>, Key4 extends KeysDeep<T, [Key0, Key1, Key2, Key3]>, Key5 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4]>, Key6 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5]>, Key7 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5, Key6]>, Key8 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5, Key6, Key7]>>(key0: Key0, key1: Key1, key2: Key2, key3: Key3, key4: Key4, key5: Key5, key6: Key6, key7: Key7, key8: Key8): (data: T) => Resolved<PropDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5, Key6, Key7, Key8]>>;
10255
+ declare function prop<T extends NonPropertyKey, Key0 extends KeysDeep<T, []>, Key1 extends KeysDeep<T, [Key0]>, Key2 extends KeysDeep<T, [Key0, Key1]>, Key3 extends KeysDeep<T, [Key0, Key1, Key2]>, Key4 extends KeysDeep<T, [Key0, Key1, Key2, Key3]>, Key5 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4]>, Key6 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5]>, Key7 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5, Key6]>, Key8 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5, Key6, Key7]>, Key9 extends KeysDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5, Key6, Key7, Key8]>, AdditionalKeys extends readonly PropertyKey[] = []>(key0: Key0, key1: Key1, key2: Key2, key3: Key3, key4: Key4, key5: Key5, key6: Key6, key7: Key7, key8: Key8, key9: Key9, ...additionalKeys: AdditionalKeys): (data: T) => Resolved<PropDeep<T, [Key0, Key1, Key2, Key3, Key4, Key5, Key6, Key7, Key8, Key9, ...AdditionalKeys]>>;
10238
10256
  declare function prop<K extends PropertyKey>(key: K): <T extends Partial<Record<K, unknown>>>(data: T) => T[K];
10239
10257
  //#endregion
10240
10258
  //#region src/pullObject.d.ts