Skip to content

Instantly share code, notes, and snippets.

@lellimecnar
Last active April 25, 2023 14:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lellimecnar/7f7764e371532054062c771fb358d2e1 to your computer and use it in GitHub Desktop.
Save lellimecnar/7f7764e371532054062c771fb358d2e1 to your computer and use it in GitHub Desktop.
Utils
const MEMOIZE_RESULT = Symbol("MEMOIZE_RESULT");
export interface MemoizeResolver<P extends any[], R extends any> {
(arg: P[number]): R;
}
export interface MemoizeCache<P extends any[], R extends any> {
get(
arg: P[0]
): P extends [any, ...infer Rest] ? MemoizeCache<Rest, R> | undefined : never;
get(arg: typeof MEMOIZE_RESULT): R | undefined;
set(
arg: P[0],
cache: P extends [any, ...infer Rest] ? MemoizeCache<Rest, R> : never
): void;
set(arg: typeof MEMOIZE_RESULT, value: R): void;
has(arg: P[0] | typeof MEMOIZE_RESULT): boolean;
clear(): void;
}
export interface Memoize {
<P extends any[], R extends any, K extends any>(
fn: (...params: P) => R,
resolver?: MemoizeResolver<P, K>
): typeof fn;
getCache<P extends any[], R extends any>(
fn: (...params: P) => R
): MemoizeCache<P, R>;
}
const memoizeCacheMap = new WeakMap<Function, MemoizeCache<any[], any>>();
export const memoize = Object.defineProperties(
<P extends any[], R extends any, K extends any>(
fn: (...params: P) => R,
resolver?: MemoizeResolver<P, K>,
rootCache: MemoizeCache<P, R> = new Map()
) => {
const Cache = Object.getPrototypeOf(rootCache).constructor;
const memoized: typeof fn = (...params: P): R => {
const cache = params.reduce((thisCache, arg) => {
const key: K = typeof resolver === "function" ? resolver(arg) : arg;
if (!thisCache.has(key)) {
thisCache.set(key, new Cache());
}
return thisCache.get(key);
}, rootCache);
if (!cache.has(MEMOIZE_RESULT)) {
cache.set(MEMOIZE_RESULT, fn(...params));
}
return cache.get(MEMOIZE_RESULT);
};
memoizeCacheMap.set(memoized, rootCache);
return memoized;
},
{
getCache: {
configurable: false,
writable: false,
enumerable: false,
value: (memoizedFn: Function) => memoizeCacheMap.get(memoizedFn),
},
}
) as Memoize;
type PropertyPathKey<
P extends string | number,
K extends number | string
> = `${number}` extends `${K}`
? `${P}[${number}]` | `${P}.${number}`
: "" extends P
? K
: `${P}.${K}`;
type PropertyPathStr<
T extends Record<any, any>,
P extends string = ""
> = T extends ArrayLike<any>
? PropertyPathStr<Record<Extract<keyof T, number>, T[number]>, P>
: Exclude<
| P
| {
[K in Extract<keyof T, string | number>]: T[K] extends Record<
any,
any
>
? PropertyPathStr<T[K], PropertyPathKey<P, `${K}`>>
: PropertyPathKey<P, `${K}`>;
}[Extract<keyof T, string | number>],
""
>;
type PropertyPathArray<T extends Record<any, any>> =
PropertyPathStr<T> extends `${infer P}` ? ExplodePath<P> : never;
type PropertyPath<T extends Record<string | number, any>> =
| PropertyPathArray<T>
| PropertyPathStr<T>;
type ExplodePath<P extends string> = "" extends P
? []
: P extends `${infer K}.${infer Rest}`
? [...ExplodePath<K>, ...ExplodePath<Rest>]
: P extends `${infer K}[${infer Index}]${infer Rest}`
? [...ExplodePath<K>, number | Index, ...ExplodePath<Rest>]
: [P extends `${number}` ? number | P : P];
type PropertyPathValue<
T extends Record<any, any>,
P extends PropertyPath<T>
> = P extends PropertyPathStr<T> & `${infer S}`
? PropertyPathValue<T, Extract<ExplodePath<S>, PropertyPathArray<T>>>
: [] extends P
? T
: P extends PropertyPathArray<T> & [infer K, ...infer Rest]
? K extends keyof T
? [] extends Rest
? T[K]
: Rest extends PropertyPathArray<T[K]>
? PropertyPathValue<T[K], Rest>
: T[K]
: never
: never;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment