@metamask-previews/assets-controller 9.0.2-preview-2a256aff2 → 9.0.2-preview-8633f7f2a

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dedupingBatchFetcher.mjs","sourceRoot":"","sources":["../../src/utils/dedupingBatchFetcher.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Executes the underlying batched fetch for a set of keys. Only keys that have\n * a value need to be present in the returned record; keys the source had no\n * data for are simply omitted (they are still marked fresh — see\n * {@link DedupingBatchFetcher.fetch}).\n *\n * @param keys - The keys to fetch (already filtered to stale, not-inflight keys).\n * @returns The fetched values keyed by key.\n */\nexport type BatchFetchFn<Key extends string, Value> = (\n keys: Key[],\n) => Promise<Record<Key, Value>>;\n\nexport type DedupingBatchFetcherOptions<Key extends string, Value> = {\n /** Performs the actual batched fetch for stale, not-inflight keys. */\n fetchBatch: BatchFetchFn<Key, Value>;\n /**\n * Minimum age (ms) before a key is considered stale and re-fetched. Keys\n * fetched more recently than this are skipped entirely.\n */\n freshnessTtlMs: number;\n};\n\n/**\n * Deduplicates batched fetches by key across two dimensions:\n *\n * 1. **Freshness TTL** — keys fetched within `freshnessTtlMs` are skipped\n * entirely. Note the freshness window only starts once a fetch *completes*,\n * so it does not cover requests that are still in flight.\n * 2. **Inflight coalescing** — if a fetch is already in progress for a key,\n * concurrent callers join the existing promise instead of issuing a new\n * request. This covers the request-in-flight window the freshness TTL\n * structurally cannot.\n *\n * Both layers are per-key, so a call for a partially-overlapping set of keys\n * reuses the fresh/inflight keys and only fetches the genuinely-missing ones.\n *\n * The fetch is batched: all stale, not-inflight keys from a single `fetch()`\n * call are passed to `fetchBatch` together, then split into per-key results so\n * each key can be joined independently.\n */\nexport class DedupingBatchFetcher<Key extends string, Value> {\n readonly #fetchBatch: BatchFetchFn<Key, Value>;\n\n #freshnessTtlMs: number;\n\n /** Tracks the last successful fetch time per key (freshness gating). */\n readonly #fetchedAt = new Map<Key, number>();\n\n /**\n * Per-key inflight fetch promises. Each resolves to the value, or `undefined`\n * if the batch failed or returned no data for that key.\n */\n readonly #inflight = new Map<Key, Promise<Value | undefined>>();\n\n constructor(options: DedupingBatchFetcherOptions<Key, Value>) {\n this.#fetchBatch = options.fetchBatch;\n this.#freshnessTtlMs = options.freshnessTtlMs;\n }\n\n /**\n * Minimum age (ms) before a key is re-fetched.\n *\n * @returns The current freshness TTL in milliseconds.\n */\n get freshnessTtlMs(): number {\n return this.#freshnessTtlMs;\n }\n\n set freshnessTtlMs(ms: number) {\n this.#freshnessTtlMs = ms;\n }\n\n /**\n * Fetch values for the given keys, deduplicating against fresh and inflight\n * fetches.\n *\n * @param keys - The keys to fetch.\n * @returns Values keyed by key. Only contains entries for keys that were\n * actually fetched (or joined from inflight) and had a value.\n */\n async fetch(keys: Key[]): Promise<Record<Key, Value>> {\n const { staleKeys, inflightKeys } = this.#partition(keys);\n\n if (staleKeys.length === 0 && inflightKeys.length === 0) {\n return {} as Record<Key, Value>;\n }\n\n // Start a fetch for stale keys and join any fetches already in progress.\n const batchPromise =\n staleKeys.length > 0 ? this.#startBatchFetch(staleKeys) : undefined;\n const values = await this.#joinInflight(inflightKeys);\n\n if (batchPromise) {\n Object.assign(values, await batchPromise);\n }\n\n return values;\n }\n\n /**\n * Clear the freshness cache, forcing the next fetch to re-request every key\n * regardless of TTL. Does not affect inflight fetches.\n */\n invalidate(): void {\n this.#fetchedAt.clear();\n }\n\n /** Clear all freshness and inflight state. */\n destroy(): void {\n this.#fetchedAt.clear();\n this.#inflight.clear();\n }\n\n /**\n * Split keys into those that need a fresh fetch and those already being\n * fetched by another caller. Keys still within the freshness TTL are dropped.\n *\n * @param keys - The keys to classify.\n * @returns `staleKeys` (need fetching) and `inflightKeys` (join existing fetch).\n */\n #partition(keys: Key[]): { staleKeys: Key[]; inflightKeys: Key[] } {\n const now = Date.now();\n const staleKeys: Key[] = [];\n const inflightKeys: Key[] = [];\n\n for (const key of keys) {\n if (this.#isFresh(key, now)) {\n continue;\n }\n if (this.#inflight.has(key)) {\n inflightKeys.push(key);\n } else {\n staleKeys.push(key);\n }\n }\n\n return { staleKeys, inflightKeys };\n }\n\n /**\n * Returns true if the key's last fetch is still within the freshness TTL.\n *\n * @param key - The key to check.\n * @param now - Current timestamp (avoids repeated clock reads).\n * @returns True if the key was fetched within the freshness TTL.\n */\n #isFresh(key: Key, now: number): boolean {\n const fetchedAt = this.#fetchedAt.get(key);\n return fetchedAt !== undefined && now - fetchedAt < this.#freshnessTtlMs;\n }\n\n /**\n * Launch a batch fetch and register a per-key inflight promise for each key\n * so concurrent callers can join. On success, all requested keys are marked\n * fresh — including keys the source returned no value for, since the absence\n * of a value is itself a valid answer that should not be re-asked until the\n * TTL expires. A failed batch leaves keys stale so they are retried.\n *\n * @param staleKeys - Keys to fetch (none of which are already inflight).\n * @returns The batch fetch promise.\n */\n #startBatchFetch(staleKeys: Key[]): Promise<Record<Key, Value>> {\n const batchPromise = this.#fetchBatch(staleKeys).then((values) => {\n const fetchedAt = Date.now();\n for (const key of staleKeys) {\n this.#fetchedAt.set(key, fetchedAt);\n }\n return values;\n });\n\n for (const key of staleKeys) {\n const perKey = batchPromise.then(\n (values) => values[key],\n () => undefined,\n );\n this.#inflight.set(key, perKey);\n }\n\n // Clean up inflight entries once the batch settles (success or failure).\n // Rejection is already surfaced to the caller via the returned batchPromise.\n batchPromise\n .finally(() => {\n for (const key of staleKeys) {\n this.#inflight.delete(key);\n }\n })\n .catch(() => undefined);\n\n return batchPromise;\n }\n\n /**\n * Join the inflight fetches for the given keys and collect their values. Keys\n * whose inflight fetch produced no value are omitted.\n *\n * @param inflightKeys - Keys whose fetches are already in progress.\n * @returns Values keyed by key.\n */\n async #joinInflight(inflightKeys: Key[]): Promise<Record<Key, Value>> {\n const values = {} as Record<Key, Value>;\n\n const results = await Promise.all(\n inflightKeys.map(async (key) => {\n const value = await this.#inflight.get(key);\n return [key, value] as const;\n }),\n );\n\n for (const [key, value] of results) {\n if (value !== undefined) {\n values[key] = value;\n }\n }\n\n return values;\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metamask-previews/assets-controller",
3
- "version": "9.0.2-preview-2a256aff2",
3
+ "version": "9.0.2-preview-8633f7f2a",
4
4
  "description": "Tracks assets balances/prices and handles token detection across all digital assets",
5
5
  "keywords": [
6
6
  "Ethereum",