-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #6 from bloq/simplify-strategy-creation
Refactor to simplify strategy creation
- Loading branch information
Showing
9 changed files
with
283 additions
and
116 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
.eslintcache | ||
.nyc_output | ||
.vscode | ||
coverage | ||
dist | ||
lerna-debug.log | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,59 +1,3 @@ | ||
import debugConstructor from 'debug' | ||
|
||
import { errors } from './error' | ||
import { perBlockStrategy } from './strategies/per-block' | ||
import { permanentStrategy } from './strategies/permanent' | ||
import { type JsonRpcCallFn, type Strategy } from './types' | ||
import { clone } from './utils/clone' | ||
|
||
const debug = debugConstructor('eth-rpc-cache') | ||
|
||
type Options = { | ||
allowOthers?: boolean | ||
cache?: Map<string, unknown> | ||
strategies?: Strategy[] | ||
} | ||
|
||
export const createEthRpcCache = function ( | ||
rpc: JsonRpcCallFn, | ||
options: Options = {} | ||
): JsonRpcCallFn { | ||
debug('Creating EVM RPC cache') | ||
|
||
const { | ||
allowOthers = true, | ||
cache = new Map(), | ||
strategies = [perBlockStrategy, permanentStrategy] | ||
} = options | ||
|
||
const cachedRpcByMethod: Record<string, ReturnType<Strategy['getRpc']>> = {} | ||
strategies.forEach(function ({ getRpc, methods, name }) { | ||
debug('Using strategy "%s"', name) | ||
methods.forEach(function (method) { | ||
// @ts-expect-error allow for options that can be dynamically forwarded to the strategy | ||
cachedRpcByMethod[method] = getRpc(rpc, cache, options[name]) | ||
}) | ||
}) | ||
|
||
// Return the cached `rpc` function. | ||
// | ||
// If an strategy defined an RPC function for the incoming method, use that. | ||
// Otherwise call the method directly if allowed or return proper errors. | ||
// | ||
// To prevent user code to mutate the cached results, the cached RPC functions | ||
// will always return a clone of the result and not the result object itself. | ||
return function (method, params) { | ||
const cachedRpc = cachedRpcByMethod[method] | ||
|
||
try { | ||
return cachedRpc | ||
? cachedRpc(method, params).then(clone) | ||
: allowOthers | ||
? rpc(method, params) | ||
: Promise.reject(errors.methodNotFound()) | ||
} catch (err) { | ||
// @ts-expect-error error is typed as unknown by default | ||
return Promise.reject(errors.internalServerError(err)) | ||
} | ||
} | ||
} | ||
export { createEthRpcCache } from './rpc' | ||
export { perBlockStrategy } from './strategies/per-block' | ||
export { permanentStrategy } from './strategies/permanent' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import debugConstructor from 'debug' | ||
import pMemoize from 'promise-mem' | ||
|
||
import { errors } from './error' | ||
import { perBlockStrategy } from './strategies/per-block' | ||
import { permanentStrategy } from './strategies/permanent' | ||
import { type JsonRpcCallFn, type Strategy } from './types' | ||
import { getKey } from './utils/cache-key' | ||
import { clone } from './utils/clone' | ||
|
||
const debug = debugConstructor('eth-rpc-cache') | ||
|
||
type Options = { | ||
allowOthers?: boolean | ||
cache?: Map<string, unknown> | ||
strategies?: Strategy[] | ||
} | ||
|
||
export const createEthRpcCache = function ( | ||
rpc: JsonRpcCallFn, | ||
options: Options = {} | ||
): JsonRpcCallFn { | ||
debug('Creating EVM RPC cache') | ||
|
||
const { | ||
allowOthers = true, | ||
cache = new Map(), | ||
strategies = [perBlockStrategy, permanentStrategy] | ||
} = options | ||
|
||
// Each strategy resolves to a cache if it has a maxAge defined. | ||
// Index all caches into the object by strategy name | ||
const cachesByStrategy = strategies | ||
.filter(({ maxAge }) => maxAge !== undefined) | ||
.map(({ name, maxAge }) => ({ | ||
[name]: pMemoize(rpc, { | ||
cache, | ||
maxAge, | ||
resolver: getKey, | ||
...options | ||
}) | ||
})) | ||
.reduce((acc, curr) => ({ ...acc, ...curr }), {}) | ||
|
||
// This object indexed by method holds a function that returns which strategy (and cache) | ||
// should be used. By default, each strategy resolves to use its own cache, but some strategies | ||
// may resolve to other strategies' caches, depending on the method | ||
const strategyResolver = strategies | ||
.flatMap(({ methods, name, resolver = () => name }) => | ||
methods.map(method => ({ | ||
[method]: resolver | ||
})) | ||
) | ||
.reduce((acc, curr) => ({ ...acc, ...curr }), {}) | ||
|
||
// Return the cached `rpc` function. | ||
// | ||
// If an strategy defined an RPC function for the incoming method, use that. | ||
// Otherwise call the method directly if allowed or return proper errors. | ||
// | ||
// To prevent user code to mutate the cached results, the cached RPC functions | ||
// will always return a clone of the result and not the result object itself. | ||
return function (method, params) { | ||
try { | ||
const strategyName = strategyResolver[method]?.(method, params) | ||
if (strategyName) { | ||
return cachesByStrategy[strategyName](method, params).then(clone) | ||
} | ||
if (allowOthers) { | ||
// not configured to be cached, call the method directly | ||
return rpc(method, params) | ||
} | ||
return Promise.reject(errors.methodNotFound()) | ||
} catch (err) { | ||
// @ts-expect-error error is typed as unknown by default | ||
return Promise.reject(errors.internalServerError(err)) | ||
} | ||
} | ||
} |
Oops, something went wrong.