@lifi/sdk 4.9.0 → 4.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/cjs/core/tasks/helpers/checkBalance.js +6 -3
  3. package/dist/cjs/core/tasks/helpers/checkBalance.js.map +1 -1
  4. package/dist/cjs/index.d.ts +2 -1
  5. package/dist/cjs/index.js +2 -1
  6. package/dist/cjs/utils/abort.d.ts +6 -0
  7. package/dist/cjs/utils/abort.js +10 -0
  8. package/dist/cjs/utils/abort.js.map +1 -0
  9. package/dist/cjs/utils/checkPackageUpdates.js +1 -1
  10. package/dist/cjs/utils/formatUnits.js +1 -0
  11. package/dist/cjs/utils/formatUnits.js.map +1 -1
  12. package/dist/cjs/utils/lru.d.ts +14 -0
  13. package/dist/cjs/utils/lru.js +35 -0
  14. package/dist/cjs/utils/lru.js.map +1 -0
  15. package/dist/cjs/utils/parseUnits.js +30 -16
  16. package/dist/cjs/utils/parseUnits.js.map +1 -1
  17. package/dist/cjs/utils/sleep.d.ts +7 -1
  18. package/dist/cjs/utils/sleep.js +22 -3
  19. package/dist/cjs/utils/sleep.js.map +1 -1
  20. package/dist/cjs/utils/waitForResult.d.ts +4 -4
  21. package/dist/cjs/utils/waitForResult.js +4 -4
  22. package/dist/cjs/utils/waitForResult.js.map +1 -1
  23. package/dist/cjs/utils/withDedupe.d.ts +0 -10
  24. package/dist/cjs/utils/withDedupe.js +4 -23
  25. package/dist/cjs/utils/withDedupe.js.map +1 -1
  26. package/dist/cjs/utils/withTimeout.js +6 -2
  27. package/dist/cjs/utils/withTimeout.js.map +1 -1
  28. package/dist/cjs/version.d.ts +1 -1
  29. package/dist/cjs/version.js +1 -1
  30. package/dist/cjs/version.js.map +1 -1
  31. package/dist/esm/core/tasks/helpers/checkBalance.js +6 -3
  32. package/dist/esm/core/tasks/helpers/checkBalance.js.map +1 -1
  33. package/dist/esm/index.d.ts +2 -1
  34. package/dist/esm/index.js +2 -1
  35. package/dist/esm/utils/abort.d.ts +6 -0
  36. package/dist/esm/utils/abort.d.ts.map +1 -0
  37. package/dist/esm/utils/abort.js +8 -0
  38. package/dist/esm/utils/abort.js.map +1 -0
  39. package/dist/esm/utils/checkPackageUpdates.js +1 -1
  40. package/dist/esm/utils/formatUnits.js +1 -0
  41. package/dist/esm/utils/formatUnits.js.map +1 -1
  42. package/dist/esm/utils/lru.d.ts +14 -0
  43. package/dist/esm/utils/lru.d.ts.map +1 -0
  44. package/dist/esm/utils/lru.js +34 -0
  45. package/dist/esm/utils/lru.js.map +1 -0
  46. package/dist/esm/utils/parseUnits.js +30 -16
  47. package/dist/esm/utils/parseUnits.js.map +1 -1
  48. package/dist/esm/utils/sleep.d.ts +7 -1
  49. package/dist/esm/utils/sleep.d.ts.map +1 -1
  50. package/dist/esm/utils/sleep.js +22 -3
  51. package/dist/esm/utils/sleep.js.map +1 -1
  52. package/dist/esm/utils/waitForResult.d.ts +4 -4
  53. package/dist/esm/utils/waitForResult.js +4 -4
  54. package/dist/esm/utils/waitForResult.js.map +1 -1
  55. package/dist/esm/utils/withDedupe.d.ts +0 -10
  56. package/dist/esm/utils/withDedupe.d.ts.map +1 -1
  57. package/dist/esm/utils/withDedupe.js +5 -22
  58. package/dist/esm/utils/withDedupe.js.map +1 -1
  59. package/dist/esm/utils/withTimeout.d.ts.map +1 -1
  60. package/dist/esm/utils/withTimeout.js +6 -2
  61. package/dist/esm/utils/withTimeout.js.map +1 -1
  62. package/dist/esm/version.d.ts +1 -1
  63. package/dist/esm/version.js +1 -1
  64. package/dist/esm/version.js.map +1 -1
  65. package/package.json +2 -2
  66. package/src/core/tasks/helpers/checkBalance.ts +11 -3
  67. package/src/index.ts +2 -1
  68. package/src/utils/abort.ts +10 -0
  69. package/src/utils/formatUnits.ts +6 -0
  70. package/src/utils/lru.ts +38 -0
  71. package/src/utils/parseUnits.ts +53 -20
  72. package/src/utils/sleep.ts +27 -3
  73. package/src/utils/waitForResult.ts +4 -4
  74. package/src/utils/withDedupe.ts +4 -28
  75. package/src/utils/withTimeout.ts +7 -2
  76. package/src/version.ts +1 -1
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  //#region src/version.ts
3
3
  const name = "@lifi/sdk";
4
- const version = "4.9.0";
4
+ const version = "4.10.0";
5
5
  //#endregion
6
6
  exports.name = name;
7
7
  exports.version = version;
@@ -1 +1 @@
1
- {"version":3,"file":"version.js","names":[],"sources":["../../src/version.ts"],"sourcesContent":["export const name = '@lifi/sdk'\nexport const version = '4.9.0'\n"],"mappings":";;AAAA,MAAa,OAAO;AACpB,MAAa,UAAU"}
1
+ {"version":3,"file":"version.js","names":[],"sources":["../../src/version.ts"],"sourcesContent":["export const name = '@lifi/sdk'\nexport const version = '4.10.0'\n"],"mappings":";;AAAA,MAAa,OAAO;AACpB,MAAa,UAAU"}
@@ -51,6 +51,8 @@ const checkBalance = async (client, walletAddress, step, options = {}) => {
51
51
  const tokens = reqs.map((r) => r.token);
52
52
  const slippage = step.action.slippage ?? 0;
53
53
  const slippageScaled = BigInt(Math.floor((1 - slippage) * Number(SLIPPAGE_PRECISION)));
54
+ const controller = new AbortController();
55
+ const { signal } = controller;
54
56
  await withTimeout(async () => {
55
57
  for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
56
58
  const isFinal = attempt === 5;
@@ -59,9 +61,10 @@ const checkBalance = async (client, walletAddress, step, options = {}) => {
59
61
  balances = await provider.getBalance(client, walletAddress, tokens);
60
62
  } catch (error) {
61
63
  if (isFinal) throw new BalanceError("Could not read wallet balance.", error);
62
- await sleep(BACKOFF_BASE_MS * 2 ** attempt);
64
+ await sleep(BACKOFF_BASE_MS * 2 ** attempt, { signal });
63
65
  continue;
64
66
  }
67
+ if (signal.aborted) return;
65
68
  const balanceByAddress = new Map(balances.map((b) => [b.address.toLowerCase(), b.amount]));
66
69
  const unknown = [];
67
70
  const insufficient = [];
@@ -94,12 +97,12 @@ const checkBalance = async (client, walletAddress, step, options = {}) => {
94
97
  });
95
98
  throw new BalanceError("The balance is too low.", /* @__PURE__ */ new Error(`${lines.join(" ")} No funds have been sent.`));
96
99
  }
97
- await sleep(BACKOFF_BASE_MS * 2 ** attempt);
100
+ await sleep(BACKOFF_BASE_MS * 2 ** attempt, { signal });
98
101
  }
99
102
  }, {
100
103
  timeout: OVERALL_TIMEOUT_MS,
101
104
  errorInstance: new BalanceError("Could not read wallet balance.")
102
- });
105
+ }).finally(() => controller.abort());
103
106
  };
104
107
  //#endregion
105
108
  export { checkBalance };
@@ -1 +1 @@
1
- {"version":3,"file":"checkBalance.js","names":[],"sources":["../../../../../src/core/tasks/helpers/checkBalance.ts"],"sourcesContent":["import type { LiFiStep, Token, TokenAmount } from '@lifi/types'\nimport { BalanceError } from '../../../errors/errors.js'\nimport type { SDKClient } from '../../../types/core.js'\nimport { formatUnits } from '../../../utils/formatUnits.js'\nimport { sleep } from '../../../utils/sleep.js'\nimport { withTimeout } from '../../../utils/withTimeout.js'\n\nconst MAX_ATTEMPTS = 6\n// Exponential backoff: 150, 300, 600, 1200, 2400 → ≈4.65s of sleep total.\nconst BACKOFF_BASE_MS = 150\nconst OVERALL_TIMEOUT_MS = 10_000\nconst SLIPPAGE_PRECISION = 1_000_000_000n\n\ntype Bucket = 'source' | 'gas' | 'fee'\n\ntype Requirement = {\n token: Token\n sourcePart: bigint // step.action.fromAmount\n gasPart: bigint // step.estimate.gasCosts in this token\n feePart: bigint // non-included step.estimate.feeCosts in this token\n}\n\nexport type CheckBalanceOptions = {\n /**\n * Set to `false` when outer-tx gas is paid by something other than\n * `walletAddress` (SCA executor, 4337 bundler / paymaster, relayer):\n * `gasPart` is excluded from the sufficiency check and slippage rescue.\n * Source amount and non-included fees (e.g. LZ `msg.value`) are still\n * verified. Defaults to `true` (strict, today's behavior).\n */\n walletPaysGas?: boolean\n}\n\n/**\n * Verifies that the wallet holds enough of every token required to execute\n * the step on its source chain — the source-token amount, any gas costs, and\n * any non-included fee costs. Reads all balances in one batched provider\n * call, retries within a bounded budget to absorb transient RPC failures and\n * post-confirmation propagation lag, and applies slippage to the source-token\n * portion only as a last resort (overhead is never trimmed).\n *\n * Throws BalanceError(\"The balance is too low.\") on a genuine shortfall, or\n * BalanceError(\"Could not read wallet balance.\") if the balance can't be read\n * after retries.\n */\nexport const checkBalance = async (\n client: SDKClient,\n walletAddress: string,\n step: LiFiStep,\n options: CheckBalanceOptions = {}\n): Promise<void> => {\n const fromChainId = step.action.fromChainId\n // Chains with non-standard native decimals (e.g. Tempo, Stable) report\n // gas costs in units that don't align with wallet balances — drop the\n // gas portion of the check on those chains.\n const fromChain = await client.getChainById(fromChainId)\n const walletPaysGas =\n (options.walletPaysGas ?? true) && !fromChain.nonStandardNativeDecimals\n const requirements = new Map<string, Requirement>()\n const add = (token: Token, amount: bigint, bucket: Bucket): void => {\n if (token.chainId !== fromChainId || amount === 0n) {\n return\n }\n const key = token.address.toLowerCase()\n const req = requirements.get(key) ?? {\n token,\n sourcePart: 0n,\n gasPart: 0n,\n feePart: 0n,\n }\n if (bucket === 'source') {\n req.sourcePart += amount\n } else if (bucket === 'gas') {\n req.gasPart += amount\n } else {\n req.feePart += amount\n }\n requirements.set(key, req)\n }\n add(step.action.fromToken, BigInt(step.action.fromAmount), 'source')\n for (const gas of step.estimate?.gasCosts ?? []) {\n add(gas.token, BigInt(gas.amount), 'gas')\n }\n for (const fee of step.estimate?.feeCosts ?? []) {\n // Included fees are already part of fromAmount — don't count twice.\n if (!fee.included) {\n add(fee.token, BigInt(fee.amount), 'fee')\n }\n }\n\n const reservedOverhead = (r: Requirement): bigint =>\n r.feePart + (walletPaysGas ? r.gasPart : 0n)\n const need = (r: Requirement): bigint => r.sourcePart + reservedOverhead(r)\n\n // Drop pure-gas entries when `walletPaysGas` is false (e.g. native ETH\n // on Safe Apps with only `gasPart`) — saves one balance read each.\n for (const [key, req] of requirements) {\n if (need(req) === 0n) {\n requirements.delete(key)\n }\n }\n\n if (requirements.size === 0) {\n return\n }\n\n // Provider is dispatched by wallet address; all requirements share the\n // source chain, which matches this provider by virtue of the address.\n const provider = client.providers.find((p) => p.isAddress(walletAddress))\n if (!provider) {\n throw new Error(`SDK Token Provider for ${walletAddress} is not found.`)\n }\n\n const reqs = Array.from(requirements.values())\n const tokens = reqs.map((r) => r.token)\n const slippage = step.action.slippage ?? 0\n const slippageScaled = BigInt(\n Math.floor((1 - slippage) * Number(SLIPPAGE_PRECISION))\n )\n\n await withTimeout(\n async () => {\n for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {\n const isFinal = attempt === MAX_ATTEMPTS - 1\n\n let balances: TokenAmount[]\n try {\n balances = await provider.getBalance(client, walletAddress, tokens)\n } catch (error) {\n if (isFinal) {\n throw new BalanceError(\n 'Could not read wallet balance.',\n error as Error\n )\n }\n await sleep(BACKOFF_BASE_MS * 2 ** attempt)\n continue\n }\n\n const balanceByAddress = new Map(\n balances.map((b) => [b.address.toLowerCase(), b.amount] as const)\n )\n\n const unknown: Token[] = []\n const insufficient: { req: Requirement; have: bigint }[] = []\n for (const req of reqs) {\n const have = balanceByAddress.get(req.token.address.toLowerCase())\n if (have === undefined) {\n unknown.push(req.token)\n } else if (have < need(req)) {\n insufficient.push({ req, have })\n }\n }\n\n if (unknown.length === 0 && insufficient.length === 0) {\n return\n }\n\n // Final-attempt slippage rescue: only when the sole shortfall is\n // the source-token portion. Trim source to (balance − reserved\n // overhead) so the overhead reserve is preserved.\n if (\n isFinal &&\n unknown.length === 0 &&\n insufficient.length === 1 &&\n insufficient[0].req.sourcePart > 0n\n ) {\n const { req, have } = insufficient[0]\n const reserved = reservedOverhead(req)\n const minAcceptable =\n (req.sourcePart * slippageScaled) / SLIPPAGE_PRECISION + reserved\n if (have >= minAcceptable) {\n const newFromAmount = (have - reserved).toString()\n step.action.fromAmount = newFromAmount\n if (step.includedSteps?.length) {\n step.includedSteps[0].action.fromAmount = newFromAmount\n }\n return\n }\n }\n\n if (isFinal) {\n if (unknown.length > 0) {\n throw new BalanceError(\n 'Could not read wallet balance.',\n new Error(\n `Could not read balance for: ${unknown\n .map((t) => t.symbol || t.address)\n .join(', ')}.`\n )\n )\n }\n const lines = insufficient.map(({ req, have }) => {\n const needed = formatUnits(need(req), req.token.decimals)\n const current = formatUnits(have, req.token.decimals)\n const symbol = req.token.symbol\n // The \"fees\" branch covers pure-overhead tokens; with\n // walletPaysGas=false, gas is excluded from `need(req)` so it\n // only fires for genuine fee shortfalls.\n return req.sourcePart > 0n\n ? `Your ${symbol} balance is too low, you try to transfer ${needed} ${symbol}, but your wallet only holds ${current} ${symbol}.`\n : `Insufficient ${symbol} for fees: need ${needed} ${symbol}, have ${current} ${symbol}.`\n })\n throw new BalanceError(\n 'The balance is too low.',\n new Error(`${lines.join(' ')} No funds have been sent.`)\n )\n }\n\n await sleep(BACKOFF_BASE_MS * 2 ** attempt)\n }\n },\n {\n timeout: OVERALL_TIMEOUT_MS,\n errorInstance: new BalanceError('Could not read wallet balance.'),\n }\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"checkBalance.js","names":[],"sources":["../../../../../src/core/tasks/helpers/checkBalance.ts"],"sourcesContent":["import type { LiFiStep, Token, TokenAmount } from '@lifi/types'\nimport { BalanceError } from '../../../errors/errors.js'\nimport type { SDKClient } from '../../../types/core.js'\nimport { formatUnits } from '../../../utils/formatUnits.js'\nimport { sleep } from '../../../utils/sleep.js'\nimport { withTimeout } from '../../../utils/withTimeout.js'\n\nconst MAX_ATTEMPTS = 6\n// Exponential backoff: 150, 300, 600, 1200, 2400 → ≈4.65s of sleep total.\nconst BACKOFF_BASE_MS = 150\nconst OVERALL_TIMEOUT_MS = 10_000\nconst SLIPPAGE_PRECISION = 1_000_000_000n\n\ntype Bucket = 'source' | 'gas' | 'fee'\n\ntype Requirement = {\n token: Token\n sourcePart: bigint // step.action.fromAmount\n gasPart: bigint // step.estimate.gasCosts in this token\n feePart: bigint // non-included step.estimate.feeCosts in this token\n}\n\nexport type CheckBalanceOptions = {\n /**\n * Set to `false` when outer-tx gas is paid by something other than\n * `walletAddress` (SCA executor, 4337 bundler / paymaster, relayer):\n * `gasPart` is excluded from the sufficiency check and slippage rescue.\n * Source amount and non-included fees (e.g. LZ `msg.value`) are still\n * verified. Defaults to `true` (strict, today's behavior).\n */\n walletPaysGas?: boolean\n}\n\n/**\n * Verifies that the wallet holds enough of every token required to execute\n * the step on its source chain — the source-token amount, any gas costs, and\n * any non-included fee costs. Reads all balances in one batched provider\n * call, retries within a bounded budget to absorb transient RPC failures and\n * post-confirmation propagation lag, and applies slippage to the source-token\n * portion only as a last resort (overhead is never trimmed).\n *\n * Throws BalanceError(\"The balance is too low.\") on a genuine shortfall, or\n * BalanceError(\"Could not read wallet balance.\") if the balance can't be read\n * after retries.\n */\nexport const checkBalance = async (\n client: SDKClient,\n walletAddress: string,\n step: LiFiStep,\n options: CheckBalanceOptions = {}\n): Promise<void> => {\n const fromChainId = step.action.fromChainId\n // Chains with non-standard native decimals (e.g. Tempo, Stable) report\n // gas costs in units that don't align with wallet balances — drop the\n // gas portion of the check on those chains.\n const fromChain = await client.getChainById(fromChainId)\n const walletPaysGas =\n (options.walletPaysGas ?? true) && !fromChain.nonStandardNativeDecimals\n const requirements = new Map<string, Requirement>()\n const add = (token: Token, amount: bigint, bucket: Bucket): void => {\n if (token.chainId !== fromChainId || amount === 0n) {\n return\n }\n const key = token.address.toLowerCase()\n const req = requirements.get(key) ?? {\n token,\n sourcePart: 0n,\n gasPart: 0n,\n feePart: 0n,\n }\n if (bucket === 'source') {\n req.sourcePart += amount\n } else if (bucket === 'gas') {\n req.gasPart += amount\n } else {\n req.feePart += amount\n }\n requirements.set(key, req)\n }\n add(step.action.fromToken, BigInt(step.action.fromAmount), 'source')\n for (const gas of step.estimate?.gasCosts ?? []) {\n add(gas.token, BigInt(gas.amount), 'gas')\n }\n for (const fee of step.estimate?.feeCosts ?? []) {\n // Included fees are already part of fromAmount — don't count twice.\n if (!fee.included) {\n add(fee.token, BigInt(fee.amount), 'fee')\n }\n }\n\n const reservedOverhead = (r: Requirement): bigint =>\n r.feePart + (walletPaysGas ? r.gasPart : 0n)\n const need = (r: Requirement): bigint => r.sourcePart + reservedOverhead(r)\n\n // Drop pure-gas entries when `walletPaysGas` is false (e.g. native ETH\n // on Safe Apps with only `gasPart`) — saves one balance read each.\n for (const [key, req] of requirements) {\n if (need(req) === 0n) {\n requirements.delete(key)\n }\n }\n\n if (requirements.size === 0) {\n return\n }\n\n // Provider is dispatched by wallet address; all requirements share the\n // source chain, which matches this provider by virtue of the address.\n const provider = client.providers.find((p) => p.isAddress(walletAddress))\n if (!provider) {\n throw new Error(`SDK Token Provider for ${walletAddress} is not found.`)\n }\n\n const reqs = Array.from(requirements.values())\n const tokens = reqs.map((r) => r.token)\n const slippage = step.action.slippage ?? 0\n const slippageScaled = BigInt(\n Math.floor((1 - slippage) * Number(SLIPPAGE_PRECISION))\n )\n\n // Aborted once `withTimeout` settles. A timeout rejects at once, but the\n // loop keeps running; the signal stops it before it can change `step`.\n const controller = new AbortController()\n const { signal } = controller\n\n await withTimeout(\n async () => {\n for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {\n const isFinal = attempt === MAX_ATTEMPTS - 1\n\n let balances: TokenAmount[]\n try {\n balances = await provider.getBalance(client, walletAddress, tokens)\n } catch (error) {\n if (isFinal) {\n throw new BalanceError(\n 'Could not read wallet balance.',\n error as Error\n )\n }\n await sleep(BACKOFF_BASE_MS * 2 ** attempt, { signal })\n continue\n }\n if (signal.aborted) {\n return\n }\n\n const balanceByAddress = new Map(\n balances.map((b) => [b.address.toLowerCase(), b.amount] as const)\n )\n\n const unknown: Token[] = []\n const insufficient: { req: Requirement; have: bigint }[] = []\n for (const req of reqs) {\n const have = balanceByAddress.get(req.token.address.toLowerCase())\n if (have === undefined) {\n unknown.push(req.token)\n } else if (have < need(req)) {\n insufficient.push({ req, have })\n }\n }\n\n if (unknown.length === 0 && insufficient.length === 0) {\n return\n }\n\n // Final-attempt slippage rescue: only when the sole shortfall is\n // the source-token portion. Trim source to (balance − reserved\n // overhead) so the overhead reserve is preserved.\n if (\n isFinal &&\n unknown.length === 0 &&\n insufficient.length === 1 &&\n insufficient[0].req.sourcePart > 0n\n ) {\n const { req, have } = insufficient[0]\n const reserved = reservedOverhead(req)\n const minAcceptable =\n (req.sourcePart * slippageScaled) / SLIPPAGE_PRECISION + reserved\n if (have >= minAcceptable) {\n const newFromAmount = (have - reserved).toString()\n step.action.fromAmount = newFromAmount\n if (step.includedSteps?.length) {\n step.includedSteps[0].action.fromAmount = newFromAmount\n }\n return\n }\n }\n\n if (isFinal) {\n if (unknown.length > 0) {\n throw new BalanceError(\n 'Could not read wallet balance.',\n new Error(\n `Could not read balance for: ${unknown\n .map((t) => t.symbol || t.address)\n .join(', ')}.`\n )\n )\n }\n const lines = insufficient.map(({ req, have }) => {\n const needed = formatUnits(need(req), req.token.decimals)\n const current = formatUnits(have, req.token.decimals)\n const symbol = req.token.symbol\n // The \"fees\" branch covers pure-overhead tokens; with\n // walletPaysGas=false, gas is excluded from `need(req)` so it\n // only fires for genuine fee shortfalls.\n return req.sourcePart > 0n\n ? `Your ${symbol} balance is too low, you try to transfer ${needed} ${symbol}, but your wallet only holds ${current} ${symbol}.`\n : `Insufficient ${symbol} for fees: need ${needed} ${symbol}, have ${current} ${symbol}.`\n })\n throw new BalanceError(\n 'The balance is too low.',\n new Error(`${lines.join(' ')} No funds have been sent.`)\n )\n }\n\n await sleep(BACKOFF_BASE_MS * 2 ** attempt, { signal })\n }\n },\n {\n timeout: OVERALL_TIMEOUT_MS,\n errorInstance: new BalanceError('Could not read wallet balance.'),\n }\n ).finally(() => controller.abort())\n}\n"],"mappings":"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"}
@@ -46,10 +46,11 @@ import { convertQuoteToRoute } from "./utils/convertQuoteToRoute.js";
46
46
  import { fetchTxErrorDetails } from "./utils/fetchTxErrorDetails.js";
47
47
  import { formatUnits } from "./utils/formatUnits.js";
48
48
  import { isHex } from "./utils/isHex.js";
49
+ import { LruMap } from "./utils/lru.js";
49
50
  import { parseUnits } from "./utils/parseUnits.js";
50
51
  import { sleep } from "./utils/sleep.js";
51
52
  import { waitForResult } from "./utils/waitForResult.js";
52
- import { LruMap, withDedupe } from "./utils/withDedupe.js";
53
+ import { withDedupe } from "./utils/withDedupe.js";
53
54
  import { withTimeout } from "./utils/withTimeout.js";
54
55
  import { name, version } from "./version.js";
55
56
  export * from "@lifi/types";
package/dist/esm/index.js CHANGED
@@ -5,7 +5,8 @@ import { ErrorMessage, ErrorName, LiFiErrorCode } from "./errors/constants.js";
5
5
  import { BalanceError, ExecuteStepRetryError, ProviderError, RPCError, ServerError, TransactionError, UnknownError, ValidationError } from "./errors/errors.js";
6
6
  import { HTTPError } from "./errors/httpError.js";
7
7
  import { sleep } from "./utils/sleep.js";
8
- import { LruMap, withDedupe } from "./utils/withDedupe.js";
8
+ import { LruMap } from "./utils/lru.js";
9
+ import { withDedupe } from "./utils/withDedupe.js";
9
10
  import { getChains } from "./actions/getChains.js";
10
11
  import { getConnections } from "./actions/getConnections.js";
11
12
  import { getContractCallsQuote } from "./actions/getContractCallsQuote.js";
@@ -0,0 +1,6 @@
1
+ //#region src/utils/abort.d.ts
2
+ /** Older runtimes and polyfills can abort a signal without a `reason`. */
3
+ export declare const getAbortError: (signal: AbortSignal) => unknown;
4
+ export declare const isAbortError: (error: unknown) => boolean;
5
+ //#endregion
6
+ //# sourceMappingURL=abort.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"abort.d.ts","names":[],"sources":["../../../src/utils/abort.ts"],"mappings":";;qBACa,gBAAiB,QAAQ;qBAIzB,eAAgB"}
@@ -0,0 +1,8 @@
1
+ //#region src/utils/abort.ts
2
+ /** Older runtimes and polyfills can abort a signal without a `reason`. */
3
+ const getAbortError = (signal) => signal.reason ?? Object.assign(/* @__PURE__ */ new Error("This operation was aborted"), { name: "AbortError" });
4
+ const isAbortError = (error) => typeof error === "object" && error !== null && "name" in error && error.name === "AbortError";
5
+ //#endregion
6
+ export { getAbortError, isAbortError };
7
+
8
+ //# sourceMappingURL=abort.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"abort.js","names":[],"sources":["../../../src/utils/abort.ts"],"sourcesContent":["/** Older runtimes and polyfills can abort a signal without a `reason`. */\nexport const getAbortError = (signal: AbortSignal): unknown =>\n signal.reason ??\n Object.assign(new Error('This operation was aborted'), { name: 'AbortError' })\n\nexport const isAbortError = (error: unknown): boolean =>\n typeof error === 'object' &&\n error !== null &&\n 'name' in error &&\n error.name === 'AbortError'\n"],"mappings":";;AACA,MAAa,iBAAiB,WAC5B,OAAO,UACP,OAAO,uBAAO,IAAI,MAAM,4BAA4B,GAAG,EAAE,MAAM,aAAa,CAAC;AAE/E,MAAa,gBAAgB,UAC3B,OAAO,UAAU,YACjB,UAAU,QACV,UAAU,SACV,MAAM,SAAS"}
@@ -4,7 +4,7 @@ const checkPackageUpdates = async (packageName, packageVersion) => {
4
4
  try {
5
5
  const pkgName = packageName ?? "@lifi/sdk";
6
6
  const latestVersion = (await (await fetch(`https://registry.npmjs.org/${pkgName}/latest`)).json()).version;
7
- const currentVersion = packageVersion ?? "4.9.0";
7
+ const currentVersion = packageVersion ?? "4.10.0";
8
8
  if (latestVersion > currentVersion) console.warn(`${pkgName}: new package version is available. Please update as soon as possible to enjoy the newest features. Current version: ${currentVersion}. Latest version: ${latestVersion}.`);
9
9
  } catch (_error) {}
10
10
  };
@@ -3,6 +3,7 @@
3
3
  * Divides a number by a given exponent of base 10 (10exponent), and formats it into a string representation of the number.
4
4
  */
5
5
  function formatUnits(value, decimals) {
6
+ if (!Number.isInteger(decimals) || decimals < 0) throw new Error(`\`decimals\` must be a non-negative integer. Got \`${decimals}\`.`);
6
7
  let display = value.toString();
7
8
  const negative = display.startsWith("-");
8
9
  if (negative) display = display.slice(1);
@@ -1 +1 @@
1
- {"version":3,"file":"formatUnits.js","names":[],"sources":["../../../src/utils/formatUnits.ts"],"sourcesContent":["/**\n * Divides a number by a given exponent of base 10 (10exponent), and formats it into a string representation of the number.\n */\nexport function formatUnits(value: bigint, decimals: number): string {\n let display = value.toString()\n\n const negative = display.startsWith('-')\n if (negative) {\n display = display.slice(1)\n }\n\n display = display.padStart(decimals, '0')\n\n let [integer, fraction] = [\n display.slice(0, display.length - decimals),\n display.slice(display.length - decimals),\n ]\n fraction = fraction.replace(/(0+)$/, '')\n return `${negative ? '-' : ''}${integer || '0'}${\n fraction ? `.${fraction}` : ''\n }`\n}\n"],"mappings":";;;;AAGA,SAAgB,YAAY,OAAe,UAA0B;CACnE,IAAI,UAAU,MAAM,SAAS;CAE7B,MAAM,WAAW,QAAQ,WAAW,GAAG;CACvC,IAAI,UACF,UAAU,QAAQ,MAAM,CAAC;CAG3B,UAAU,QAAQ,SAAS,UAAU,GAAG;CAExC,IAAI,CAAC,SAAS,YAAY,CACxB,QAAQ,MAAM,GAAG,QAAQ,SAAS,QAAQ,GAC1C,QAAQ,MAAM,QAAQ,SAAS,QAAQ,CACzC;CACA,WAAW,SAAS,QAAQ,SAAS,EAAE;CACvC,OAAO,GAAG,WAAW,MAAM,KAAK,WAAW,MACzC,WAAW,IAAI,aAAa;AAEhC"}
1
+ {"version":3,"file":"formatUnits.js","names":[],"sources":["../../../src/utils/formatUnits.ts"],"sourcesContent":["/**\n * Divides a number by a given exponent of base 10 (10exponent), and formats it into a string representation of the number.\n */\nexport function formatUnits(value: bigint, decimals: number): string {\n if (!Number.isInteger(decimals) || decimals < 0) {\n throw new Error(\n `\\`decimals\\` must be a non-negative integer. Got \\`${decimals}\\`.`\n )\n }\n\n let display = value.toString()\n\n const negative = display.startsWith('-')\n if (negative) {\n display = display.slice(1)\n }\n\n display = display.padStart(decimals, '0')\n\n let [integer, fraction] = [\n display.slice(0, display.length - decimals),\n display.slice(display.length - decimals),\n ]\n fraction = fraction.replace(/(0+)$/, '')\n return `${negative ? '-' : ''}${integer || '0'}${\n fraction ? `.${fraction}` : ''\n }`\n}\n"],"mappings":";;;;AAGA,SAAgB,YAAY,OAAe,UAA0B;CACnE,IAAI,CAAC,OAAO,UAAU,QAAQ,KAAK,WAAW,GAC5C,MAAM,IAAI,MACR,sDAAsD,SAAS,IACjE;CAGF,IAAI,UAAU,MAAM,SAAS;CAE7B,MAAM,WAAW,QAAQ,WAAW,GAAG;CACvC,IAAI,UACF,UAAU,QAAQ,MAAM,CAAC;CAG3B,UAAU,QAAQ,SAAS,UAAU,GAAG;CAExC,IAAI,CAAC,SAAS,YAAY,CACxB,QAAQ,MAAM,GAAG,QAAQ,SAAS,QAAQ,GAC1C,QAAQ,MAAM,QAAQ,SAAS,QAAQ,CACzC;CACA,WAAW,SAAS,QAAQ,SAAS,EAAE;CACvC,OAAO,GAAG,WAAW,MAAM,KAAK,WAAW,MACzC,WAAW,IAAI,aAAa;AAEhC"}
@@ -0,0 +1,14 @@
1
+ //#region src/utils/lru.d.ts
2
+ /**
3
+ * Map with a LRU (Least recently used) policy.
4
+ *
5
+ * https://en.wikipedia.org/wiki/Cache_replacement_policies#LRU
6
+ */
7
+ export declare class LruMap<value = unknown> extends Map<string, value> {
8
+ maxSize: number;
9
+ constructor(size: number);
10
+ override get(key: string): value | undefined;
11
+ override set(key: string, value: value): this;
12
+ }
13
+ //#endregion
14
+ //# sourceMappingURL=lru.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lru.d.ts","names":[],"sources":["../../../src/utils/lru.ts"],"mappings":";;;;;;qBAKa,OAAO,yBAAyB,YAAY;EACvD;EAEA,YAAY;WAKH,IAAI,cAAc;WASlB,IAAI,aAAa,OAAO"}
@@ -0,0 +1,34 @@
1
+ //#region src/utils/lru.ts
2
+ /**
3
+ * Map with a LRU (Least recently used) policy.
4
+ *
5
+ * https://en.wikipedia.org/wiki/Cache_replacement_policies#LRU
6
+ */
7
+ var LruMap = class extends Map {
8
+ maxSize;
9
+ constructor(size) {
10
+ super();
11
+ this.maxSize = size;
12
+ }
13
+ get(key) {
14
+ const value = super.get(key);
15
+ if (super.has(key)) {
16
+ super.delete(key);
17
+ super.set(key, value);
18
+ }
19
+ return value;
20
+ }
21
+ set(key, value) {
22
+ if (super.has(key)) super.delete(key);
23
+ super.set(key, value);
24
+ if (this.maxSize && this.size > this.maxSize) {
25
+ const firstKey = super.keys().next().value;
26
+ if (firstKey !== void 0) super.delete(firstKey);
27
+ }
28
+ return this;
29
+ }
30
+ };
31
+ //#endregion
32
+ export { LruMap };
33
+
34
+ //# sourceMappingURL=lru.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lru.js","names":[],"sources":["../../../src/utils/lru.ts"],"sourcesContent":["/**\n * Map with a LRU (Least recently used) policy.\n *\n * https://en.wikipedia.org/wiki/Cache_replacement_policies#LRU\n */\nexport class LruMap<value = unknown> extends Map<string, value> {\n maxSize: number\n\n constructor(size: number) {\n super()\n this.maxSize = size\n }\n\n override get(key: string): value | undefined {\n const value = super.get(key)\n if (super.has(key)) {\n super.delete(key)\n super.set(key, value as value)\n }\n return value\n }\n\n override set(key: string, value: value): this {\n if (super.has(key)) {\n super.delete(key)\n }\n super.set(key, value)\n if (this.maxSize && this.size > this.maxSize) {\n // `super.keys()`, not `this.keys()`: on iOS 18 the subclass iterator\n // can yield `undefined`, and the map then grows without limit.\n const firstKey = super.keys().next().value\n if (firstKey !== undefined) {\n super.delete(firstKey)\n }\n }\n return this\n }\n}\n"],"mappings":";;;;;;AAKA,IAAa,SAAb,cAA6C,IAAmB;CAC9D;CAEA,YAAY,MAAc;EACxB,MAAM;EACN,KAAK,UAAU;CACjB;CAEA,IAAa,KAAgC;EAC3C,MAAM,QAAQ,MAAM,IAAI,GAAG;EAC3B,IAAI,MAAM,IAAI,GAAG,GAAG;GAClB,MAAM,OAAO,GAAG;GAChB,MAAM,IAAI,KAAK,KAAc;EAC/B;EACA,OAAO;CACT;CAEA,IAAa,KAAa,OAAoB;EAC5C,IAAI,MAAM,IAAI,GAAG,GACf,MAAM,OAAO,GAAG;EAElB,MAAM,IAAI,KAAK,KAAK;EACpB,IAAI,KAAK,WAAW,KAAK,OAAO,KAAK,SAAS;GAG5C,MAAM,WAAW,MAAM,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC;GACrC,IAAI,aAAa,KAAA,GACf,MAAM,OAAO,QAAQ;EAEzB;EACA,OAAO;CACT;AACF"}
@@ -3,31 +3,45 @@
3
3
  * Multiplies a string representation of a number by a given exponent of base 10 (10exponent).
4
4
  */
5
5
  function parseUnits(value, decimals) {
6
- if (!/^(-?)([0-9]*)\.?([0-9]*)$/.test(value)) throw new Error(`Number \`${value}\` is not a valid decimal number.`);
7
- let [integer, fraction = "0"] = value.split(".");
6
+ if (!Number.isInteger(decimals) || decimals < 0) throw new Error(`\`decimals\` must be a non-negative integer. Got \`${decimals}\`.`);
7
+ if (/^-?\.?$/.test(value)) return 0n;
8
+ if (!/^-?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)$/.test(value)) throw new Error(`Number \`${value}\` is not a valid decimal number.`);
9
+ let [integer = "", fraction = "0"] = value.split(".");
8
10
  const negative = integer.startsWith("-");
9
11
  if (negative) integer = integer.slice(1);
12
+ if (integer === "") integer = "0";
10
13
  fraction = fraction.replace(/(0+)$/, "");
11
14
  if (decimals === 0) {
12
- if (Math.round(Number(`.${fraction}`)) === 1) integer = `${BigInt(integer) + 1n}`;
15
+ if (fraction.length > 0 && Number.parseInt(fraction[0], 10) >= 5) integer = `${BigInt(integer) + 1n}`;
13
16
  fraction = "";
14
17
  } else if (fraction.length > decimals) {
15
- const [left, unit, right] = [
16
- fraction.slice(0, decimals - 1),
17
- fraction.slice(decimals - 1, decimals),
18
- fraction.slice(decimals)
19
- ];
20
- const rounded = Math.round(Number(`${unit}.${right}`));
21
- if (rounded > 9) fraction = `${BigInt(left) + BigInt(1)}0`.padStart(left.length + 1, "0");
22
- else fraction = `${left}${rounded}`;
23
- if (fraction.length > decimals) {
24
- fraction = fraction.slice(1);
25
- integer = `${BigInt(integer) + 1n}`;
26
- }
27
- fraction = fraction.slice(0, decimals);
18
+ const left = fraction.slice(0, decimals);
19
+ if (Number.parseInt(fraction.slice(decimals, decimals + 1), 10) >= 5) {
20
+ const carried = carry(left);
21
+ if (carried.length > decimals) {
22
+ fraction = carried.slice(1);
23
+ integer = `${BigInt(integer) + 1n}`;
24
+ } else fraction = carried;
25
+ } else fraction = left;
28
26
  } else fraction = fraction.padEnd(decimals, "0");
29
27
  return BigInt(`${negative ? "-" : ""}${integer}${fraction}`);
30
28
  }
29
+ /**
30
+ * Adds 1 to a digit string. The result has the same length, unless the carry
31
+ * passes the most significant digit: then it is one digit longer.
32
+ */
33
+ function carry(digits) {
34
+ const out = digits.split("");
35
+ for (let i = out.length - 1; i >= 0; i--) {
36
+ const digit = Number.parseInt(out[i], 10) + 1;
37
+ if (digit < 10) {
38
+ out[i] = String(digit);
39
+ return out.join("");
40
+ }
41
+ out[i] = "0";
42
+ }
43
+ return `1${out.join("")}`;
44
+ }
31
45
  //#endregion
32
46
  export { parseUnits };
33
47
 
@@ -1 +1 @@
1
- {"version":3,"file":"parseUnits.js","names":[],"sources":["../../../src/utils/parseUnits.ts"],"sourcesContent":["/**\n * Multiplies a string representation of a number by a given exponent of base 10 (10exponent).\n */\nexport function parseUnits(value: string, decimals: number): bigint {\n if (!/^(-?)([0-9]*)\\.?([0-9]*)$/.test(value)) {\n throw new Error(`Number \\`${value}\\` is not a valid decimal number.`)\n }\n\n let [integer, fraction = '0'] = value.split('.')\n\n const negative = integer.startsWith('-')\n if (negative) {\n integer = integer.slice(1)\n }\n\n // trim trailing zeros.\n fraction = fraction.replace(/(0+)$/, '')\n\n // round off if the fraction is larger than the number of decimals.\n if (decimals === 0) {\n if (Math.round(Number(`.${fraction}`)) === 1) {\n integer = `${BigInt(integer) + 1n}`\n }\n fraction = ''\n } else if (fraction.length > decimals) {\n const [left, unit, right] = [\n fraction.slice(0, decimals - 1),\n fraction.slice(decimals - 1, decimals),\n fraction.slice(decimals),\n ]\n\n const rounded = Math.round(Number(`${unit}.${right}`))\n if (rounded > 9) {\n fraction = `${BigInt(left) + BigInt(1)}0`.padStart(left.length + 1, '0')\n } else {\n fraction = `${left}${rounded}`\n }\n\n if (fraction.length > decimals) {\n fraction = fraction.slice(1)\n integer = `${BigInt(integer) + 1n}`\n }\n\n fraction = fraction.slice(0, decimals)\n } else {\n fraction = fraction.padEnd(decimals, '0')\n }\n\n return BigInt(`${negative ? '-' : ''}${integer}${fraction}`)\n}\n"],"mappings":";;;;AAGA,SAAgB,WAAW,OAAe,UAA0B;CAClE,IAAI,CAAC,4BAA4B,KAAK,KAAK,GACzC,MAAM,IAAI,MAAM,YAAY,MAAM,kCAAkC;CAGtE,IAAI,CAAC,SAAS,WAAW,OAAO,MAAM,MAAM,GAAG;CAE/C,MAAM,WAAW,QAAQ,WAAW,GAAG;CACvC,IAAI,UACF,UAAU,QAAQ,MAAM,CAAC;CAI3B,WAAW,SAAS,QAAQ,SAAS,EAAE;CAGvC,IAAI,aAAa,GAAG;EAClB,IAAI,KAAK,MAAM,OAAO,IAAI,UAAU,CAAC,MAAM,GACzC,UAAU,GAAG,OAAO,OAAO,IAAI;EAEjC,WAAW;CACb,OAAO,IAAI,SAAS,SAAS,UAAU;EACrC,MAAM,CAAC,MAAM,MAAM,SAAS;GAC1B,SAAS,MAAM,GAAG,WAAW,CAAC;GAC9B,SAAS,MAAM,WAAW,GAAG,QAAQ;GACrC,SAAS,MAAM,QAAQ;EACzB;EAEA,MAAM,UAAU,KAAK,MAAM,OAAO,GAAG,KAAK,GAAG,OAAO,CAAC;EACrD,IAAI,UAAU,GACZ,WAAW,GAAG,OAAO,IAAI,IAAI,OAAO,CAAC,EAAE,GAAG,SAAS,KAAK,SAAS,GAAG,GAAG;OAEvE,WAAW,GAAG,OAAO;EAGvB,IAAI,SAAS,SAAS,UAAU;GAC9B,WAAW,SAAS,MAAM,CAAC;GAC3B,UAAU,GAAG,OAAO,OAAO,IAAI;EACjC;EAEA,WAAW,SAAS,MAAM,GAAG,QAAQ;CACvC,OACE,WAAW,SAAS,OAAO,UAAU,GAAG;CAG1C,OAAO,OAAO,GAAG,WAAW,MAAM,KAAK,UAAU,UAAU;AAC7D"}
1
+ {"version":3,"file":"parseUnits.js","names":[],"sources":["../../../src/utils/parseUnits.ts"],"sourcesContent":["/**\n * Multiplies a string representation of a number by a given exponent of base 10 (10exponent).\n */\nexport function parseUnits(value: string, decimals: number): bigint {\n if (!Number.isInteger(decimals) || decimals < 0) {\n throw new Error(\n `\\`decimals\\` must be a non-negative integer. Got \\`${decimals}\\`.`\n )\n }\n\n // An input with no digit is 0n: the widget parses an empty amount field.\n if (/^-?\\.?$/.test(value)) {\n return 0n\n }\n\n if (!/^-?(?:[0-9]+(?:\\.[0-9]*)?|\\.[0-9]+)$/.test(value)) {\n throw new Error(`Number \\`${value}\\` is not a valid decimal number.`)\n }\n\n let [integer = '', fraction = '0'] = value.split('.')\n\n const negative = integer.startsWith('-')\n if (negative) {\n integer = integer.slice(1)\n }\n if (integer === '') {\n integer = '0'\n }\n\n // trim trailing zeros.\n fraction = fraction.replace(/(0+)$/, '')\n\n // round off if the fraction is larger than the number of decimals.\n if (decimals === 0) {\n // Round half away from zero by the first fractional digit.\n if (fraction.length > 0 && Number.parseInt(fraction[0], 10) >= 5) {\n integer = `${BigInt(integer) + 1n}`\n }\n fraction = ''\n } else if (fraction.length > decimals) {\n const left = fraction.slice(0, decimals)\n const roundDigit = Number.parseInt(\n fraction.slice(decimals, decimals + 1),\n 10\n )\n\n if (roundDigit >= 5) {\n // Carry in decimal space, not through a JS number: a float loses\n // precision on long fractions and can round the wrong way.\n const carried = carry(left)\n if (carried.length > decimals) {\n // The carry overflowed into the integer part.\n fraction = carried.slice(1)\n integer = `${BigInt(integer) + 1n}`\n } else {\n fraction = carried\n }\n } else {\n fraction = left\n }\n } else {\n fraction = fraction.padEnd(decimals, '0')\n }\n\n return BigInt(`${negative ? '-' : ''}${integer}${fraction}`)\n}\n\n/**\n * Adds 1 to a digit string. The result has the same length, unless the carry\n * passes the most significant digit: then it is one digit longer.\n */\nfunction carry(digits: string): string {\n const out = digits.split('')\n for (let i = out.length - 1; i >= 0; i--) {\n const digit = Number.parseInt(out[i], 10) + 1\n if (digit < 10) {\n out[i] = String(digit)\n return out.join('')\n }\n out[i] = '0'\n }\n return `1${out.join('')}`\n}\n"],"mappings":";;;;AAGA,SAAgB,WAAW,OAAe,UAA0B;CAClE,IAAI,CAAC,OAAO,UAAU,QAAQ,KAAK,WAAW,GAC5C,MAAM,IAAI,MACR,sDAAsD,SAAS,IACjE;CAIF,IAAI,UAAU,KAAK,KAAK,GACtB,OAAO;CAGT,IAAI,CAAC,uCAAuC,KAAK,KAAK,GACpD,MAAM,IAAI,MAAM,YAAY,MAAM,kCAAkC;CAGtE,IAAI,CAAC,UAAU,IAAI,WAAW,OAAO,MAAM,MAAM,GAAG;CAEpD,MAAM,WAAW,QAAQ,WAAW,GAAG;CACvC,IAAI,UACF,UAAU,QAAQ,MAAM,CAAC;CAE3B,IAAI,YAAY,IACd,UAAU;CAIZ,WAAW,SAAS,QAAQ,SAAS,EAAE;CAGvC,IAAI,aAAa,GAAG;EAElB,IAAI,SAAS,SAAS,KAAK,OAAO,SAAS,SAAS,IAAI,EAAE,KAAK,GAC7D,UAAU,GAAG,OAAO,OAAO,IAAI;EAEjC,WAAW;CACb,OAAO,IAAI,SAAS,SAAS,UAAU;EACrC,MAAM,OAAO,SAAS,MAAM,GAAG,QAAQ;EAMvC,IALmB,OAAO,SACxB,SAAS,MAAM,UAAU,WAAW,CAAC,GACrC,EAGW,KAAK,GAAG;GAGnB,MAAM,UAAU,MAAM,IAAI;GAC1B,IAAI,QAAQ,SAAS,UAAU;IAE7B,WAAW,QAAQ,MAAM,CAAC;IAC1B,UAAU,GAAG,OAAO,OAAO,IAAI;GACjC,OACE,WAAW;EAEf,OACE,WAAW;CAEf,OACE,WAAW,SAAS,OAAO,UAAU,GAAG;CAG1C,OAAO,OAAO,GAAG,WAAW,MAAM,KAAK,UAAU,UAAU;AAC7D;;;;;AAMA,SAAS,MAAM,QAAwB;CACrC,MAAM,MAAM,OAAO,MAAM,EAAE;CAC3B,KAAK,IAAI,IAAI,IAAI,SAAS,GAAG,KAAK,GAAG,KAAK;EACxC,MAAM,QAAQ,OAAO,SAAS,IAAI,IAAI,EAAE,IAAI;EAC5C,IAAI,QAAQ,IAAI;GACd,IAAI,KAAK,OAAO,KAAK;GACrB,OAAO,IAAI,KAAK,EAAE;EACpB;EACA,IAAI,KAAK;CACX;CACA,OAAO,IAAI,IAAI,KAAK,EAAE;AACxB"}
@@ -1,4 +1,10 @@
1
1
  //#region src/utils/sleep.d.ts
2
- export declare function sleep(ms: number): Promise<null>;
2
+ /**
3
+ * Resolves with `null` after `ms`. With a `signal`, it rejects with the abort
4
+ * reason as soon as the signal aborts, and clears its timer.
5
+ */
6
+ export declare function sleep(ms: number, { signal }?: {
7
+ signal?: AbortSignal | undefined;
8
+ }): Promise<null>;
3
9
  //#endregion
4
10
  //# sourceMappingURL=sleep.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sleep.d.ts","names":[],"sources":["../../../src/utils/sleep.ts"],"mappings":";wBAAgB,MAAM,aAAa"}
1
+ {"version":3,"file":"sleep.d.ts","names":[],"sources":["../../../src/utils/sleep.ts"],"mappings":";;;;;wBAMgB,MACd,cACE;EAAY,SAAS;IACtB"}
@@ -1,7 +1,26 @@
1
+ import { getAbortError } from "./abort.js";
1
2
  //#region src/utils/sleep.ts
2
- function sleep(ms) {
3
- return new Promise((resolve) => {
4
- setTimeout(() => resolve(null), ms);
3
+ /**
4
+ * Resolves with `null` after `ms`. With a `signal`, it rejects with the abort
5
+ * reason as soon as the signal aborts, and clears its timer.
6
+ */
7
+ function sleep(ms, { signal } = {}) {
8
+ return new Promise((resolve, reject) => {
9
+ if (signal?.aborted) {
10
+ reject(getAbortError(signal));
11
+ return;
12
+ }
13
+ const cleanup = () => signal?.removeEventListener("abort", onAbort);
14
+ const timer = setTimeout(() => {
15
+ cleanup();
16
+ resolve(null);
17
+ }, ms);
18
+ const onAbort = () => {
19
+ clearTimeout(timer);
20
+ cleanup();
21
+ reject(getAbortError(signal));
22
+ };
23
+ signal?.addEventListener("abort", onAbort, { once: true });
5
24
  });
6
25
  }
7
26
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"sleep.js","names":[],"sources":["../../../src/utils/sleep.ts"],"sourcesContent":["export function sleep(ms: number): Promise<null> {\n return new Promise((resolve) => {\n setTimeout(() => resolve(null), ms)\n })\n}\n"],"mappings":";AAAA,SAAgB,MAAM,IAA2B;CAC/C,OAAO,IAAI,SAAS,YAAY;EAC9B,iBAAiB,QAAQ,IAAI,GAAG,EAAE;CACpC,CAAC;AACH"}
1
+ {"version":3,"file":"sleep.js","names":[],"sources":["../../../src/utils/sleep.ts"],"sourcesContent":["import { getAbortError } from './abort.js'\n\n/**\n * Resolves with `null` after `ms`. With a `signal`, it rejects with the abort\n * reason as soon as the signal aborts, and clears its timer.\n */\nexport function sleep(\n ms: number,\n { signal }: { signal?: AbortSignal | undefined } = {}\n): Promise<null> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(getAbortError(signal))\n return\n }\n\n const cleanup = (): void => signal?.removeEventListener('abort', onAbort)\n const timer = setTimeout(() => {\n cleanup()\n resolve(null)\n }, ms)\n const onAbort = (): void => {\n clearTimeout(timer)\n cleanup()\n reject(getAbortError(signal!))\n }\n signal?.addEventListener('abort', onAbort, { once: true })\n })\n}\n"],"mappings":";;;;;;AAMA,SAAgB,MACd,IACA,EAAE,WAAiD,CAAC,GACrC;CACf,OAAO,IAAI,SAAS,SAAS,WAAW;EACtC,IAAI,QAAQ,SAAS;GACnB,OAAO,cAAc,MAAM,CAAC;GAC5B;EACF;EAEA,MAAM,gBAAsB,QAAQ,oBAAoB,SAAS,OAAO;EACxE,MAAM,QAAQ,iBAAiB;GAC7B,QAAQ;GACR,QAAQ,IAAI;EACd,GAAG,EAAE;EACL,MAAM,gBAAsB;GAC1B,aAAa,KAAK;GAClB,QAAQ;GACR,OAAO,cAAc,MAAO,CAAC;EAC/B;EACA,QAAQ,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;CAC3D,CAAC;AACH"}
@@ -1,12 +1,12 @@
1
1
  //#region src/utils/waitForResult.d.ts
2
2
  /**
3
- * Repeatedly calls a given asynchronous function until it resolves with a value
4
- * @param fn The function that should be repeated
3
+ * Repeatedly calls a given asynchronous function until it resolves with a truthy value
4
+ * @param fn The function that should be repeated. A falsy result means "no result yet" and polls again, with no limit on the number of polls
5
5
  * @param interval The timeout in milliseconds between retries, or a function that receives the current poll count and returns the interval. Defaults to 5000
6
- * @param maxRetries Maximum number of retries before throwing an error, defaults to 3
6
+ * @param maxRetries Maximum number of calls that throw before the error is rethrown, defaults to 3
7
7
  * @param shouldRetry Optional predicate to determine if an error should trigger a retry
8
8
  * @returns The result of the fn function
9
- * @throws Error if maximum retries is reached, if function keeps returning undefined, or if shouldRetry returns false
9
+ * @throws The error of fn if maximum retries is reached, or if shouldRetry returns false
10
10
  */
11
11
  export declare const waitForResult: <T>(fn: () => Promise<T | undefined>, interval?: number | ((poll: number) => number), maxRetries?: number, shouldRetry?: (count: number, error: unknown) => boolean) => Promise<T>;
12
12
  //#endregion
@@ -1,13 +1,13 @@
1
1
  import { sleep } from "./sleep.js";
2
2
  //#region src/utils/waitForResult.ts
3
3
  /**
4
- * Repeatedly calls a given asynchronous function until it resolves with a value
5
- * @param fn The function that should be repeated
4
+ * Repeatedly calls a given asynchronous function until it resolves with a truthy value
5
+ * @param fn The function that should be repeated. A falsy result means "no result yet" and polls again, with no limit on the number of polls
6
6
  * @param interval The timeout in milliseconds between retries, or a function that receives the current poll count and returns the interval. Defaults to 5000
7
- * @param maxRetries Maximum number of retries before throwing an error, defaults to 3
7
+ * @param maxRetries Maximum number of calls that throw before the error is rethrown, defaults to 3
8
8
  * @param shouldRetry Optional predicate to determine if an error should trigger a retry
9
9
  * @returns The result of the fn function
10
- * @throws Error if maximum retries is reached, if function keeps returning undefined, or if shouldRetry returns false
10
+ * @throws The error of fn if maximum retries is reached, or if shouldRetry returns false
11
11
  */
12
12
  const waitForResult = async (fn, interval = 5e3, maxRetries = 3, shouldRetry = () => true) => {
13
13
  let result;
@@ -1 +1 @@
1
- {"version":3,"file":"waitForResult.js","names":[],"sources":["../../../src/utils/waitForResult.ts"],"sourcesContent":["import { sleep } from './sleep.js'\n\n/**\n * Repeatedly calls a given asynchronous function until it resolves with a value\n * @param fn The function that should be repeated\n * @param interval The timeout in milliseconds between retries, or a function that receives the current poll count and returns the interval. Defaults to 5000\n * @param maxRetries Maximum number of retries before throwing an error, defaults to 3\n * @param shouldRetry Optional predicate to determine if an error should trigger a retry\n * @returns The result of the fn function\n * @throws Error if maximum retries is reached, if function keeps returning undefined, or if shouldRetry returns false\n */\nexport const waitForResult = async <T>(\n fn: () => Promise<T | undefined>,\n interval: number | ((poll: number) => number) = 5000,\n maxRetries = 3,\n shouldRetry: (count: number, error: unknown) => boolean = () => true\n): Promise<T> => {\n let result: T | undefined\n let attempts = 0\n let polls = 0\n\n const getInterval = typeof interval === 'function' ? interval : () => interval\n\n while (!result) {\n try {\n result = await fn()\n if (!result) {\n await sleep(getInterval(polls))\n polls++\n }\n } catch (error) {\n if (!shouldRetry(attempts, error)) {\n throw error\n }\n attempts++\n if (attempts === maxRetries) {\n throw error\n }\n await sleep(getInterval(polls))\n polls++\n }\n }\n\n return result\n}\n"],"mappings":";;;;;;;;;;;AAWA,MAAa,gBAAgB,OAC3B,IACA,WAAgD,KAChD,aAAa,GACb,oBAAgE,SACjD;CACf,IAAI;CACJ,IAAI,WAAW;CACf,IAAI,QAAQ;CAEZ,MAAM,cAAc,OAAO,aAAa,aAAa,iBAAiB;CAEtE,OAAO,CAAC,QACN,IAAI;EACF,SAAS,MAAM,GAAG;EAClB,IAAI,CAAC,QAAQ;GACX,MAAM,MAAM,YAAY,KAAK,CAAC;GAC9B;EACF;CACF,SAAS,OAAO;EACd,IAAI,CAAC,YAAY,UAAU,KAAK,GAC9B,MAAM;EAER;EACA,IAAI,aAAa,YACf,MAAM;EAER,MAAM,MAAM,YAAY,KAAK,CAAC;EAC9B;CACF;CAGF,OAAO;AACT"}
1
+ {"version":3,"file":"waitForResult.js","names":[],"sources":["../../../src/utils/waitForResult.ts"],"sourcesContent":["import { sleep } from './sleep.js'\n\n/**\n * Repeatedly calls a given asynchronous function until it resolves with a truthy value\n * @param fn The function that should be repeated. A falsy result means \"no result yet\" and polls again, with no limit on the number of polls\n * @param interval The timeout in milliseconds between retries, or a function that receives the current poll count and returns the interval. Defaults to 5000\n * @param maxRetries Maximum number of calls that throw before the error is rethrown, defaults to 3\n * @param shouldRetry Optional predicate to determine if an error should trigger a retry\n * @returns The result of the fn function\n * @throws The error of fn if maximum retries is reached, or if shouldRetry returns false\n */\nexport const waitForResult = async <T>(\n fn: () => Promise<T | undefined>,\n interval: number | ((poll: number) => number) = 5000,\n maxRetries = 3,\n shouldRetry: (count: number, error: unknown) => boolean = () => true\n): Promise<T> => {\n let result: T | undefined\n let attempts = 0\n let polls = 0\n\n const getInterval = typeof interval === 'function' ? interval : () => interval\n\n while (!result) {\n try {\n result = await fn()\n if (!result) {\n await sleep(getInterval(polls))\n polls++\n }\n } catch (error) {\n if (!shouldRetry(attempts, error)) {\n throw error\n }\n attempts++\n if (attempts === maxRetries) {\n throw error\n }\n await sleep(getInterval(polls))\n polls++\n }\n }\n\n return result\n}\n"],"mappings":";;;;;;;;;;;AAWA,MAAa,gBAAgB,OAC3B,IACA,WAAgD,KAChD,aAAa,GACb,oBAAgE,SACjD;CACf,IAAI;CACJ,IAAI,WAAW;CACf,IAAI,QAAQ;CAEZ,MAAM,cAAc,OAAO,aAAa,aAAa,iBAAiB;CAEtE,OAAO,CAAC,QACN,IAAI;EACF,SAAS,MAAM,GAAG;EAClB,IAAI,CAAC,QAAQ;GACX,MAAM,MAAM,YAAY,KAAK,CAAC;GAC9B;EACF;CACF,SAAS,OAAO;EACd,IAAI,CAAC,YAAY,UAAU,KAAK,GAC9B,MAAM;EAER;EACA,IAAI,aAAa,YACf,MAAM;EAER,MAAM,MAAM,YAAY,KAAK,CAAC;EAC9B;CACF;CAGF,OAAO;AACT"}
@@ -1,14 +1,4 @@
1
1
  //#region src/utils/withDedupe.d.ts
2
- /**
3
- * Map with a LRU (Least recently used) policy.
4
- *
5
- * https://en.wikipedia.org/wiki/Cache_replacement_policies#LRU
6
- */
7
- export declare class LruMap<value = unknown> extends Map<string, value> {
8
- maxSize: number;
9
- constructor(size: number);
10
- override set(key: string, value: value): this;
11
- }
12
2
  type WithDedupeOptions = {
13
3
  enabled?: boolean | undefined;
14
4
  id?: string | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"withDedupe.d.ts","names":[],"sources":["../../../src/utils/withDedupe.ts"],"mappings":";;;;;;qBAKa,OAAO,yBAAyB,YAAY;EACvD;EAEA,YAAY;WAKH,IAAI,aAAa,OAAO;;KA2B9B;EACH;EACA;;EAEA,SAAS;;;;;;;;;;;wBAYK,WAAW,GACzB,KAAK,SAAS,gBAAgB,QAAQ,IACtC,SAAS,oBACR,QAAQ;wBACK,WAAW,GACzB,UAAU,QAAQ,IAClB,SAAS;EAAsB;IAC9B,QAAQ"}
1
+ {"version":3,"file":"withDedupe.d.ts","names":[],"sources":["../../../src/utils/withDedupe.ts"],"mappings":";KAqBK;EACH;EACA;;EAEA,SAAS;;;;;;;;;;;wBAYK,WAAW,GACzB,KAAK,SAAS,gBAAgB,QAAQ,IACtC,SAAS,oBACR,QAAQ;wBACK,WAAW,GACzB,UAAU,QAAQ,IAClB,SAAS;EAAsB;IAC9B,QAAQ"}
@@ -1,26 +1,11 @@
1
+ import { getAbortError } from "./abort.js";
2
+ import { LruMap } from "./lru.js";
1
3
  //#region src/utils/withDedupe.ts
2
- /**
3
- * Map with a LRU (Least recently used) policy.
4
- *
5
- * https://en.wikipedia.org/wiki/Cache_replacement_policies#LRU
6
- */
7
- var LruMap = class extends Map {
8
- maxSize;
9
- constructor(size) {
10
- super();
11
- this.maxSize = size;
12
- }
13
- set(key, value) {
14
- super.set(key, value);
15
- if (this.maxSize && this.size > this.maxSize) this.delete(this.keys().next().value);
16
- return this;
17
- }
18
- };
19
4
  /** @internal */
20
5
  const promiseCache = /*#__PURE__*/ new LruMap(8192);
21
6
  function withDedupe(fn, { enabled = true, id, signal }) {
22
7
  if (!enabled || !id) return fn(signal);
23
- if (signal?.aborted) return Promise.reject(abortReason(signal));
8
+ if (signal?.aborted) return Promise.reject(getAbortError(signal));
24
9
  let inFlight = promiseCache.get(id);
25
10
  if (!inFlight) {
26
11
  const controller = signal ? new AbortController() : void 0;
@@ -54,7 +39,7 @@ function join(id, inFlight, signal) {
54
39
  const leave = () => {
55
40
  signal.removeEventListener("abort", leave);
56
41
  callers.delete(caller);
57
- const reason = abortReason(signal);
42
+ const reason = getAbortError(signal);
58
43
  reject(reason);
59
44
  if (!inFlight.pinned && callers.size === 0) {
60
45
  evict(id, inFlight.promise);
@@ -87,9 +72,7 @@ function subscribe(inFlight) {
87
72
  function evict(id, promise) {
88
73
  if (promiseCache.get(id)?.promise === promise) promiseCache.delete(id);
89
74
  }
90
- /** Older runtimes and polyfills can abort a signal without a `reason`. */
91
- const abortReason = (signal) => signal.reason ?? Object.assign(/* @__PURE__ */ new Error("This operation was aborted"), { name: "AbortError" });
92
75
  //#endregion
93
- export { LruMap, withDedupe };
76
+ export { withDedupe };
94
77
 
95
78
  //# sourceMappingURL=withDedupe.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"withDedupe.js","names":[],"sources":["../../../src/utils/withDedupe.ts"],"sourcesContent":["/**\n * Map with a LRU (Least recently used) policy.\n *\n * https://en.wikipedia.org/wiki/Cache_replacement_policies#LRU\n */\nexport class LruMap<value = unknown> extends Map<string, value> {\n maxSize: number\n\n constructor(size: number) {\n super()\n this.maxSize = size\n }\n\n override set(key: string, value: value): this {\n super.set(key, value)\n if (this.maxSize && this.size > this.maxSize) {\n this.delete(this.keys().next().value!)\n }\n return this\n }\n}\n\ntype Caller = {\n resolve: (value: any) => void\n reject: (error: unknown) => void\n}\n\ntype InFlight = {\n promise: Promise<any>\n /** Absent when the first caller had no signal: then nothing can abort it. */\n controller?: AbortController | undefined\n /** Callers with a signal that are still waiting, once the first one joins. */\n callers?: Set<Caller> | undefined\n /** A caller without a signal joined, so the request must run to the end. */\n pinned: boolean\n}\n\n/** @internal */\nconst promiseCache = /*#__PURE__*/ new LruMap<InFlight>(8192)\n\ntype WithDedupeOptions = {\n enabled?: boolean | undefined\n id?: string | undefined\n /** Lets this caller leave a shared request without aborting it for others. */\n signal?: AbortSignal | undefined\n}\n\n/**\n * Deduplicates in-flight promises.\n *\n * When the first caller passes a signal, `fn` receives the signal to hand on\n * to its request. A caller that aborts leaves the shared request at once; the\n * request itself is aborted only when every caller has aborted. The next\n * caller then starts a new request, so `fn` should pass the signal on: a run\n * that ignores it keeps going alongside the new one.\n */\nexport function withDedupe<T>(\n fn: (signal?: AbortSignal) => Promise<T>,\n options: WithDedupeOptions\n): Promise<T>\nexport function withDedupe<T>(\n fn: () => Promise<T>,\n options: WithDedupeOptions & { signal?: undefined }\n): Promise<T>\nexport function withDedupe<T>(\n fn: (signal?: AbortSignal) => Promise<T>,\n { enabled = true, id, signal }: WithDedupeOptions\n): Promise<T> {\n if (!enabled || !id) {\n return fn(signal)\n }\n if (signal?.aborted) {\n return Promise.reject(abortReason(signal))\n }\n let inFlight = promiseCache.get(id)\n if (!inFlight) {\n const controller = signal ? new AbortController() : undefined\n const promise = fn(controller?.signal).finally(() => evict(id, promise))\n inFlight = { promise, controller, pinned: false }\n promiseCache.set(id, inFlight)\n }\n return join<T>(id, inFlight, signal)\n}\n\nfunction join<T>(\n id: string,\n inFlight: InFlight,\n signal: AbortSignal | undefined\n): Promise<T> {\n if (!signal) {\n inFlight.pinned = true\n return inFlight.promise\n }\n const callers = inFlight.callers ?? subscribe(inFlight)\n return new Promise<T>((resolve, reject) => {\n // Stop listening before settling, so a later abort cannot reach the request.\n const caller: Caller = {\n resolve: (value) => {\n signal.removeEventListener('abort', leave)\n resolve(value)\n },\n reject: (error) => {\n signal.removeEventListener('abort', leave)\n reject(error)\n },\n }\n const leave = () => {\n // `leave` can run without the event, so the listener is not always gone.\n signal.removeEventListener('abort', leave)\n callers.delete(caller)\n const reason = abortReason(signal)\n reject(reason)\n if (!inFlight.pinned && callers.size === 0) {\n // A caller arriving now must start a new request, not join this one.\n evict(id, inFlight.promise)\n inFlight.controller?.abort(reason)\n }\n }\n callers.add(caller)\n signal.addEventListener('abort', leave, { once: true })\n // `fn` runs before this listener exists and may have aborted the signal.\n if (signal.aborted) {\n leave()\n }\n })\n}\n\n/**\n * Hands the result to the callers with a signal, through one reaction per\n * request, so a caller that leaves holds nothing here. It is added only when\n * the first such caller joins: a request shared only by callers without a\n * signal gets no handler of ours, and a failure they all ignore is still\n * reported as an unhandled rejection.\n */\nfunction subscribe(inFlight: InFlight): Set<Caller> {\n const callers = new Set<Caller>()\n inFlight.callers = callers\n inFlight.promise.then(\n (value) => {\n for (const caller of callers) {\n caller.resolve(value)\n }\n },\n (error) => {\n for (const caller of callers) {\n caller.reject(error)\n }\n }\n )\n return callers\n}\n\n/** Drops the entry for `id` only while it still holds this request. */\nfunction evict(id: string, promise: Promise<unknown>): void {\n if (promiseCache.get(id)?.promise === promise) {\n promiseCache.delete(id)\n }\n}\n\n/** Older runtimes and polyfills can abort a signal without a `reason`. */\nconst abortReason = (signal: AbortSignal): unknown =>\n signal.reason ??\n Object.assign(new Error('This operation was aborted'), { name: 'AbortError' })\n"],"mappings":";;;;;;AAKA,IAAa,SAAb,cAA6C,IAAmB;CAC9D;CAEA,YAAY,MAAc;EACxB,MAAM;EACN,KAAK,UAAU;CACjB;CAEA,IAAa,KAAa,OAAoB;EAC5C,MAAM,IAAI,KAAK,KAAK;EACpB,IAAI,KAAK,WAAW,KAAK,OAAO,KAAK,SACnC,KAAK,OAAO,KAAK,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,KAAM;EAEvC,OAAO;CACT;AACF;;AAkBA,MAAM,6BAA6B,IAAI,OAAiB,IAAI;AA0B5D,SAAgB,WACd,IACA,EAAE,UAAU,MAAM,IAAI,UACV;CACZ,IAAI,CAAC,WAAW,CAAC,IACf,OAAO,GAAG,MAAM;CAElB,IAAI,QAAQ,SACV,OAAO,QAAQ,OAAO,YAAY,MAAM,CAAC;CAE3C,IAAI,WAAW,aAAa,IAAI,EAAE;CAClC,IAAI,CAAC,UAAU;EACb,MAAM,aAAa,SAAS,IAAI,gBAAgB,IAAI,KAAA;EACpD,MAAM,UAAU,GAAG,YAAY,MAAM,CAAC,CAAC,cAAc,MAAM,IAAI,OAAO,CAAC;EACvE,WAAW;GAAE;GAAS;GAAY,QAAQ;EAAM;EAChD,aAAa,IAAI,IAAI,QAAQ;CAC/B;CACA,OAAO,KAAQ,IAAI,UAAU,MAAM;AACrC;AAEA,SAAS,KACP,IACA,UACA,QACY;CACZ,IAAI,CAAC,QAAQ;EACX,SAAS,SAAS;EAClB,OAAO,SAAS;CAClB;CACA,MAAM,UAAU,SAAS,WAAW,UAAU,QAAQ;CACtD,OAAO,IAAI,SAAY,SAAS,WAAW;EAEzC,MAAM,SAAiB;GACrB,UAAU,UAAU;IAClB,OAAO,oBAAoB,SAAS,KAAK;IACzC,QAAQ,KAAK;GACf;GACA,SAAS,UAAU;IACjB,OAAO,oBAAoB,SAAS,KAAK;IACzC,OAAO,KAAK;GACd;EACF;EACA,MAAM,cAAc;GAElB,OAAO,oBAAoB,SAAS,KAAK;GACzC,QAAQ,OAAO,MAAM;GACrB,MAAM,SAAS,YAAY,MAAM;GACjC,OAAO,MAAM;GACb,IAAI,CAAC,SAAS,UAAU,QAAQ,SAAS,GAAG;IAE1C,MAAM,IAAI,SAAS,OAAO;IAC1B,SAAS,YAAY,MAAM,MAAM;GACnC;EACF;EACA,QAAQ,IAAI,MAAM;EAClB,OAAO,iBAAiB,SAAS,OAAO,EAAE,MAAM,KAAK,CAAC;EAEtD,IAAI,OAAO,SACT,MAAM;CAEV,CAAC;AACH;;;;;;;;AASA,SAAS,UAAU,UAAiC;CAClD,MAAM,0BAAU,IAAI,IAAY;CAChC,SAAS,UAAU;CACnB,SAAS,QAAQ,MACd,UAAU;EACT,KAAK,MAAM,UAAU,SACnB,OAAO,QAAQ,KAAK;CAExB,IACC,UAAU;EACT,KAAK,MAAM,UAAU,SACnB,OAAO,OAAO,KAAK;CAEvB,CACF;CACA,OAAO;AACT;;AAGA,SAAS,MAAM,IAAY,SAAiC;CAC1D,IAAI,aAAa,IAAI,EAAE,CAAC,EAAE,YAAY,SACpC,aAAa,OAAO,EAAE;AAE1B;;AAGA,MAAM,eAAe,WACnB,OAAO,UACP,OAAO,uBAAO,IAAI,MAAM,4BAA4B,GAAG,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"withDedupe.js","names":[],"sources":["../../../src/utils/withDedupe.ts"],"sourcesContent":["import { getAbortError } from './abort.js'\nimport { LruMap } from './lru.js'\n\ntype Caller = {\n resolve: (value: any) => void\n reject: (error: unknown) => void\n}\n\ntype InFlight = {\n promise: Promise<any>\n /** Absent when the first caller had no signal: then nothing can abort it. */\n controller?: AbortController | undefined\n /** Callers with a signal that are still waiting, once the first one joins. */\n callers?: Set<Caller> | undefined\n /** A caller without a signal joined, so the request must run to the end. */\n pinned: boolean\n}\n\n/** @internal */\nconst promiseCache = /*#__PURE__*/ new LruMap<InFlight>(8192)\n\ntype WithDedupeOptions = {\n enabled?: boolean | undefined\n id?: string | undefined\n /** Lets this caller leave a shared request without aborting it for others. */\n signal?: AbortSignal | undefined\n}\n\n/**\n * Deduplicates in-flight promises.\n *\n * When the first caller passes a signal, `fn` receives the signal to hand on\n * to its request. A caller that aborts leaves the shared request at once; the\n * request itself is aborted only when every caller has aborted. The next\n * caller then starts a new request, so `fn` should pass the signal on: a run\n * that ignores it keeps going alongside the new one.\n */\nexport function withDedupe<T>(\n fn: (signal?: AbortSignal) => Promise<T>,\n options: WithDedupeOptions\n): Promise<T>\nexport function withDedupe<T>(\n fn: () => Promise<T>,\n options: WithDedupeOptions & { signal?: undefined }\n): Promise<T>\nexport function withDedupe<T>(\n fn: (signal?: AbortSignal) => Promise<T>,\n { enabled = true, id, signal }: WithDedupeOptions\n): Promise<T> {\n if (!enabled || !id) {\n return fn(signal)\n }\n if (signal?.aborted) {\n return Promise.reject(getAbortError(signal))\n }\n let inFlight = promiseCache.get(id)\n if (!inFlight) {\n const controller = signal ? new AbortController() : undefined\n const promise = fn(controller?.signal).finally(() => evict(id, promise))\n inFlight = { promise, controller, pinned: false }\n promiseCache.set(id, inFlight)\n }\n return join<T>(id, inFlight, signal)\n}\n\nfunction join<T>(\n id: string,\n inFlight: InFlight,\n signal: AbortSignal | undefined\n): Promise<T> {\n if (!signal) {\n inFlight.pinned = true\n return inFlight.promise\n }\n const callers = inFlight.callers ?? subscribe(inFlight)\n return new Promise<T>((resolve, reject) => {\n // Stop listening before settling, so a later abort cannot reach the request.\n const caller: Caller = {\n resolve: (value) => {\n signal.removeEventListener('abort', leave)\n resolve(value)\n },\n reject: (error) => {\n signal.removeEventListener('abort', leave)\n reject(error)\n },\n }\n const leave = () => {\n // `leave` can run without the event, so the listener is not always gone.\n signal.removeEventListener('abort', leave)\n callers.delete(caller)\n const reason = getAbortError(signal)\n reject(reason)\n if (!inFlight.pinned && callers.size === 0) {\n // A caller arriving now must start a new request, not join this one.\n evict(id, inFlight.promise)\n inFlight.controller?.abort(reason)\n }\n }\n callers.add(caller)\n signal.addEventListener('abort', leave, { once: true })\n // `fn` runs before this listener exists and may have aborted the signal.\n if (signal.aborted) {\n leave()\n }\n })\n}\n\n/**\n * Hands the result to the callers with a signal, through one reaction per\n * request, so a caller that leaves holds nothing here. It is added only when\n * the first such caller joins: a request shared only by callers without a\n * signal gets no handler of ours, and a failure they all ignore is still\n * reported as an unhandled rejection.\n */\nfunction subscribe(inFlight: InFlight): Set<Caller> {\n const callers = new Set<Caller>()\n inFlight.callers = callers\n inFlight.promise.then(\n (value) => {\n for (const caller of callers) {\n caller.resolve(value)\n }\n },\n (error) => {\n for (const caller of callers) {\n caller.reject(error)\n }\n }\n )\n return callers\n}\n\n/** Drops the entry for `id` only while it still holds this request. */\nfunction evict(id: string, promise: Promise<unknown>): void {\n if (promiseCache.get(id)?.promise === promise) {\n promiseCache.delete(id)\n }\n}\n"],"mappings":";;;;AAmBA,MAAM,6BAA6B,IAAI,OAAiB,IAAI;AA0B5D,SAAgB,WACd,IACA,EAAE,UAAU,MAAM,IAAI,UACV;CACZ,IAAI,CAAC,WAAW,CAAC,IACf,OAAO,GAAG,MAAM;CAElB,IAAI,QAAQ,SACV,OAAO,QAAQ,OAAO,cAAc,MAAM,CAAC;CAE7C,IAAI,WAAW,aAAa,IAAI,EAAE;CAClC,IAAI,CAAC,UAAU;EACb,MAAM,aAAa,SAAS,IAAI,gBAAgB,IAAI,KAAA;EACpD,MAAM,UAAU,GAAG,YAAY,MAAM,CAAC,CAAC,cAAc,MAAM,IAAI,OAAO,CAAC;EACvE,WAAW;GAAE;GAAS;GAAY,QAAQ;EAAM;EAChD,aAAa,IAAI,IAAI,QAAQ;CAC/B;CACA,OAAO,KAAQ,IAAI,UAAU,MAAM;AACrC;AAEA,SAAS,KACP,IACA,UACA,QACY;CACZ,IAAI,CAAC,QAAQ;EACX,SAAS,SAAS;EAClB,OAAO,SAAS;CAClB;CACA,MAAM,UAAU,SAAS,WAAW,UAAU,QAAQ;CACtD,OAAO,IAAI,SAAY,SAAS,WAAW;EAEzC,MAAM,SAAiB;GACrB,UAAU,UAAU;IAClB,OAAO,oBAAoB,SAAS,KAAK;IACzC,QAAQ,KAAK;GACf;GACA,SAAS,UAAU;IACjB,OAAO,oBAAoB,SAAS,KAAK;IACzC,OAAO,KAAK;GACd;EACF;EACA,MAAM,cAAc;GAElB,OAAO,oBAAoB,SAAS,KAAK;GACzC,QAAQ,OAAO,MAAM;GACrB,MAAM,SAAS,cAAc,MAAM;GACnC,OAAO,MAAM;GACb,IAAI,CAAC,SAAS,UAAU,QAAQ,SAAS,GAAG;IAE1C,MAAM,IAAI,SAAS,OAAO;IAC1B,SAAS,YAAY,MAAM,MAAM;GACnC;EACF;EACA,QAAQ,IAAI,MAAM;EAClB,OAAO,iBAAiB,SAAS,OAAO,EAAE,MAAM,KAAK,CAAC;EAEtD,IAAI,OAAO,SACT,MAAM;CAEV,CAAC;AACH;;;;;;;;AASA,SAAS,UAAU,UAAiC;CAClD,MAAM,0BAAU,IAAI,IAAY;CAChC,SAAS,UAAU;CACnB,SAAS,QAAQ,MACd,UAAU;EACT,KAAK,MAAM,UAAU,SACnB,OAAO,QAAQ,KAAK;CAExB,IACC,UAAU;EACT,KAAK,MAAM,UAAU,SACnB,OAAO,OAAO,KAAK;CAEvB,CACF;CACA,OAAO;AACT;;AAGA,SAAS,MAAM,IAAY,SAAiC;CAC1D,IAAI,aAAa,IAAI,EAAE,CAAC,EAAE,YAAY,SACpC,aAAa,OAAO,EAAE;AAE1B"}
@@ -1 +1 @@
1
- {"version":3,"file":"withTimeout.d.ts","names":[],"sources":["../../../src/utils/withTimeout.ts"],"mappings":";;;;;;;;;;wBASgB,YAAY,GAC1B,OAAO;EAAY,QAAQ;MAAuC,QAAQ,MAExE,eACA,SACA;EAGA,gBAAgB;EAEhB;EAEA;IAED,QAAQ"}
1
+ {"version":3,"file":"withTimeout.d.ts","names":[],"sources":["../../../src/utils/withTimeout.ts"],"mappings":";;;;;;;;;;wBAWgB,YAAY,GAC1B,OAAO;EAAY,QAAQ;MAAuC,QAAQ,MAExE,eACA,SACA;EAGA,gBAAgB;EAEhB;EAEA;IAED,QAAQ"}
@@ -1,3 +1,4 @@
1
+ import { isAbortError } from "./abort.js";
1
2
  //#region src/utils/withTimeout.ts
2
3
  /**
3
4
  * Wraps a function in a timeout.
@@ -12,15 +13,18 @@ function withTimeout(fn, { errorInstance = /* @__PURE__ */ new Error("Timed out
12
13
  return new Promise((resolve, reject) => {
13
14
  (async () => {
14
15
  let timeoutId;
16
+ const controller = new AbortController();
15
17
  try {
16
- const controller = new AbortController();
17
18
  if (timeout > 0) timeoutId = setTimeout(() => {
18
19
  if (signal) controller.abort();
19
20
  else reject(errorInstance);
20
21
  }, timeout);
21
22
  resolve(await fn({ signal: controller?.signal || null }));
22
23
  } catch (err) {
23
- if (err?.name === "AbortError") reject(errorInstance);
24
+ if (controller.signal.aborted && isAbortError(err)) {
25
+ reject(errorInstance);
26
+ return;
27
+ }
24
28
  reject(err);
25
29
  } finally {
26
30
  clearTimeout(timeoutId);
@@ -1 +1 @@
1
- {"version":3,"file":"withTimeout.js","names":[],"sources":["../../../src/utils/withTimeout.ts"],"sourcesContent":["/**\n * Wraps a function in a timeout.\n * Based on viem's withTimeout implementation.\n * @param fn - The function to wrap.\n * @param timeout - The timeout in milliseconds.\n * @param errorInstance - The error instance to throw when the timeout is reached.\n * @param signal - Whether or not the timeout should use an abort signal.\n * @returns The result of the function.\n */\nexport function withTimeout<T>(\n fn: ({ signal }: { signal: AbortController['signal'] | null }) => Promise<T>,\n {\n errorInstance = new Error('Timed out after waiting for too long.'),\n timeout,\n signal,\n }: {\n // The error instance to throw when the timeout is reached.\n errorInstance?: Error | undefined\n // The timeout (in ms).\n timeout: number\n // Whether or not the timeout should use an abort signal.\n signal?: boolean | undefined\n }\n): Promise<T> {\n return new Promise((resolve, reject) => {\n ;(async () => {\n let timeoutId!: NodeJS.Timeout\n try {\n const controller = new AbortController()\n if (timeout > 0) {\n timeoutId = setTimeout(() => {\n if (signal) {\n controller.abort()\n } else {\n reject(errorInstance)\n }\n }, timeout) as NodeJS.Timeout // need to cast because bun globals.d.ts overrides @types/node\n }\n resolve(await fn({ signal: controller?.signal || null }))\n } catch (err) {\n if ((err as Error)?.name === 'AbortError') {\n reject(errorInstance)\n }\n reject(err)\n } finally {\n clearTimeout(timeoutId)\n }\n })()\n })\n}\n"],"mappings":";;;;;;;;;;AASA,SAAgB,YACd,IACA,EACE,gCAAgB,IAAI,MAAM,uCAAuC,GACjE,SACA,UASU;CACZ,OAAO,IAAI,SAAS,SAAS,WAAW;EACrC,CAAC,YAAY;GACZ,IAAI;GACJ,IAAI;IACF,MAAM,aAAa,IAAI,gBAAgB;IACvC,IAAI,UAAU,GACZ,YAAY,iBAAiB;KAC3B,IAAI,QACF,WAAW,MAAM;UAEjB,OAAO,aAAa;IAExB,GAAG,OAAO;IAEZ,QAAQ,MAAM,GAAG,EAAE,QAAQ,YAAY,UAAU,KAAK,CAAC,CAAC;GAC1D,SAAS,KAAK;IACZ,IAAK,KAAe,SAAS,cAC3B,OAAO,aAAa;IAEtB,OAAO,GAAG;GACZ,UAAU;IACR,aAAa,SAAS;GACxB;EACF,EAAA,CAAG;CACL,CAAC;AACH"}
1
+ {"version":3,"file":"withTimeout.js","names":[],"sources":["../../../src/utils/withTimeout.ts"],"sourcesContent":["import { isAbortError } from './abort.js'\n\n/**\n * Wraps a function in a timeout.\n * Based on viem's withTimeout implementation.\n * @param fn - The function to wrap.\n * @param timeout - The timeout in milliseconds.\n * @param errorInstance - The error instance to throw when the timeout is reached.\n * @param signal - Whether or not the timeout should use an abort signal.\n * @returns The result of the function.\n */\nexport function withTimeout<T>(\n fn: ({ signal }: { signal: AbortController['signal'] | null }) => Promise<T>,\n {\n errorInstance = new Error('Timed out after waiting for too long.'),\n timeout,\n signal,\n }: {\n // The error instance to throw when the timeout is reached.\n errorInstance?: Error | undefined\n // The timeout (in ms).\n timeout: number\n // Whether or not the timeout should use an abort signal.\n signal?: boolean | undefined\n }\n): Promise<T> {\n return new Promise((resolve, reject) => {\n ;(async () => {\n let timeoutId!: NodeJS.Timeout\n const controller = new AbortController()\n try {\n if (timeout > 0) {\n timeoutId = setTimeout(() => {\n if (signal) {\n controller.abort()\n } else {\n reject(errorInstance)\n }\n }, timeout) as NodeJS.Timeout // need to cast because bun globals.d.ts overrides @types/node\n }\n resolve(await fn({ signal: controller?.signal || null }))\n } catch (err) {\n // Only an abort from our own timeout is a timeout. Any other\n // AbortError belongs to the caller and passes through.\n if (controller.signal.aborted && isAbortError(err)) {\n reject(errorInstance)\n return\n }\n reject(err)\n } finally {\n clearTimeout(timeoutId)\n }\n })()\n })\n}\n"],"mappings":";;;;;;;;;;;AAWA,SAAgB,YACd,IACA,EACE,gCAAgB,IAAI,MAAM,uCAAuC,GACjE,SACA,UASU;CACZ,OAAO,IAAI,SAAS,SAAS,WAAW;EACrC,CAAC,YAAY;GACZ,IAAI;GACJ,MAAM,aAAa,IAAI,gBAAgB;GACvC,IAAI;IACF,IAAI,UAAU,GACZ,YAAY,iBAAiB;KAC3B,IAAI,QACF,WAAW,MAAM;UAEjB,OAAO,aAAa;IAExB,GAAG,OAAO;IAEZ,QAAQ,MAAM,GAAG,EAAE,QAAQ,YAAY,UAAU,KAAK,CAAC,CAAC;GAC1D,SAAS,KAAK;IAGZ,IAAI,WAAW,OAAO,WAAW,aAAa,GAAG,GAAG;KAClD,OAAO,aAAa;KACpB;IACF;IACA,OAAO,GAAG;GACZ,UAAU;IACR,aAAa,SAAS;GACxB;EACF,EAAA,CAAG;CACL,CAAC;AACH"}
@@ -1,5 +1,5 @@
1
1
  //#region src/version.d.ts
2
2
  export declare const name = "@lifi/sdk";
3
- export declare const version = "4.9.0";
3
+ export declare const version = "4.10.0";
4
4
  //#endregion
5
5
  //# sourceMappingURL=version.d.ts.map