UNPKG

@saberhq/sail

Version:

Account caching and batched loading for React-based Solana applications.

194 lines 7.82 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.useAccountsInternal = exports.fetchKeysMaybe = exports.getCacheKeyOfPublicKey = void 0; const tslib_1 = require("tslib"); const solana_contrib_1 = require("@saberhq/solana-contrib"); const use_solana_1 = require("@saberhq/use-solana"); const web3_js_1 = require("@solana/web3.js"); const dataloader_1 = tslib_1.__importDefault(require("dataloader")); const react_1 = require("react"); const __1 = require(".."); const batchProvider_1 = require("./batchProvider"); const emitter_1 = require("./emitter"); const fetchers_1 = require("./fetchers"); const fetchKeysUsingLoader_1 = require("./fetchKeysUsingLoader"); /** * Gets the cache key associated with the given pubkey. * @param pubkey * @returns */ const getCacheKeyOfPublicKey = (pubkey) => pubkey.toString(); exports.getCacheKeyOfPublicKey = getCacheKeyOfPublicKey; const newState = () => ({ accountsCache: new Map(), emitter: new emitter_1.AccountsEmitter(), subscribedAccounts: new Map(), }); /** * Fetches keys, passing through null/undefined values. * @param fetchKeys * @param keys * @returns */ const fetchKeysMaybe = async (fetchKeys, keys) => { const keysWithIndex = keys.map((k, i) => [k, i]); const nonEmptyKeysWithIndex = keysWithIndex.filter((key) => (0, solana_contrib_1.exists)(key[0])); const nonEmptyKeys = nonEmptyKeysWithIndex.map((n) => n[0]); const accountsData = await fetchKeys(nonEmptyKeys); const result = keys.slice(); nonEmptyKeysWithIndex.forEach(([_, originalIndex], i) => { result[originalIndex] = accountsData[i]; }); return result; }; exports.fetchKeysMaybe = fetchKeysMaybe; const useAccountsInternal = (args) => { const { batchDurationMs = 500, refreshIntervalMs = 60000, onError, useWebsocketAccountUpdates = false, disableAutoRefresh: disableRefresh = false, } = args; const { network, connection, providerMut } = (0, use_solana_1.useSolana)(); // Cache of accounts const [{ accountsCache, emitter, subscribedAccounts }, setState] = (0, react_1.useState)(newState()); (0, react_1.useEffect)(() => { setState((prevState) => { // clear accounts cache and subscriptions whenever the network changes prevState.accountsCache.clear(); prevState.subscribedAccounts.clear(); prevState.emitter.raiseCacheCleared(); return newState(); }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [network]); const accountLoader = (0, react_1.useMemo)(() => new dataloader_1.default(async (keys) => { const result = await (0, fetchers_1.getMultipleAccounts)(connection, keys, onError, "confirmed"); const batch = new Set(); result.array.forEach((info, i) => { const addr = keys[i]; if (addr && !(info instanceof Error)) { const cacheKey = (0, exports.getCacheKeyOfPublicKey)(addr); accountsCache.set(cacheKey, info); batch.add(cacheKey); } }); emitter.raiseBatchCacheUpdated(batch); return result.array; }, { // aggregate all requests over 500ms batchScheduleFn: (callback) => setTimeout(callback, batchDurationMs), cacheKeyFn: exports.getCacheKeyOfPublicKey, }), [accountsCache, batchDurationMs, connection, emitter, onError]); const { batchFetcher, batchProviderMut } = (0, react_1.useMemo)(() => { return { batchFetcher: new batchProvider_1.SailBatchFetcher(accountLoader), batchProviderMut: providerMut ? new batchProvider_1.SailBatchProvider(providerMut, accountLoader) : null, }; }, [providerMut, accountLoader]); const fetchKeys = (0, react_1.useCallback)(async (keys) => { return await (0, fetchKeysUsingLoader_1.fetchKeysUsingLoader)(accountLoader, keys); }, [accountLoader]); const onBatchCache = emitter.onBatchCache; const refetch = (0, react_1.useCallback)(async (key) => { const result = await accountLoader.clear(key).load(key); return result; }, [accountLoader]); const refetchMany = (0, react_1.useCallback)(async (keys) => { keys.forEach((key) => { accountLoader.clear(key); }); return await accountLoader.loadMany(keys); }, [accountLoader]); const getCached = (0, react_1.useCallback)((key) => { // null: account not found on blockchain // undefined: cache miss (not yet fetched) return accountsCache.get((0, exports.getCacheKeyOfPublicKey)(key)); }, [accountsCache]); const subscribe = (0, react_1.useCallback)((key) => { const keyStr = (0, exports.getCacheKeyOfPublicKey)(key); const amount = subscribedAccounts.get(keyStr); if (amount === undefined || amount === 0) { subscribedAccounts.set(keyStr, 1); } else { subscribedAccounts.set(keyStr, amount + 1); } let listener = null; if (useWebsocketAccountUpdates) { listener = connection.onAccountChange(key, (data) => { const cacheKey = (0, exports.getCacheKeyOfPublicKey)(key); accountsCache.set(cacheKey, data); accountLoader.clear(key).prime(key, data); emitter.raiseBatchCacheUpdated(new Set([cacheKey])); }); } return async () => { const currentAmount = subscribedAccounts.get(keyStr); if ((currentAmount !== null && currentAmount !== void 0 ? currentAmount : 0) > 1) { subscribedAccounts.set(keyStr, (currentAmount !== null && currentAmount !== void 0 ? currentAmount : 0) - 1); } else { subscribedAccounts.delete(keyStr); } if (listener) { await connection.removeAccountChangeListener(listener); } }; }, [ accountLoader, accountsCache, connection, emitter, subscribedAccounts, useWebsocketAccountUpdates, ]); const refetchAllSubscriptions = (0, react_1.useCallback)(async () => { const keysToFetch = [...subscribedAccounts.keys()].map((keyStr) => { return new web3_js_1.PublicKey(keyStr); }); await refetchMany(keysToFetch); }, [refetchMany, subscribedAccounts]); (0, react_1.useEffect)(() => { // don't auto refetch if we're disabling the refresh if (disableRefresh) { return; } const interval = setInterval(() => { void refetchAllSubscriptions().catch((e) => { onError(new __1.SailRefetchSubscriptionsError(e)); }); }, refreshIntervalMs); return () => clearInterval(interval); }, [disableRefresh, onError, refetchAllSubscriptions, refreshIntervalMs]); const getDatum = (0, react_1.useCallback)((k) => { if (!k) { return k; } const accountInfo = getCached(k); if (accountInfo) { return { accountId: k, accountInfo, }; } return accountInfo; }, [getCached]); return { loader: accountLoader, getCached, getDatum, refetch, refetchMany, refetchAllSubscriptions, onBatchCache, fetchKeys, subscribe, batchDurationMs, refreshIntervalMs, useWebsocketAccountUpdates, disableAutoRefresh: disableRefresh, onError, batchFetcher, batchProviderMut, }; }; exports.useAccountsInternal = useAccountsInternal; //# sourceMappingURL=useAccountsInternal.js.map