2024-05-21 16:04:56 +08:00
|
|
|
const factorySymbol = Symbol("lazyFactory");
|
|
|
|
|
const cacheSymbol = Symbol("lazyCache");
|
2024-05-20 20:57:48 +08:00
|
|
|
|
|
|
|
|
const unconfigurable = ["arguments", "caller", "prototype"];
|
|
|
|
|
const isUnconfigurable = (key: PropertyKey) => typeof key === "string" && unconfigurable.includes(key);
|
|
|
|
|
|
|
|
|
|
const lazyHandler: ProxyHandler<any> = {
|
|
|
|
|
ownKeys: target => {
|
2024-05-21 16:04:56 +08:00
|
|
|
const cacheKeys = Reflect.ownKeys(target[factorySymbol]());
|
2024-05-20 20:57:48 +08:00
|
|
|
unconfigurable.forEach(key => isUnconfigurable(key) && cacheKeys.push(key));
|
|
|
|
|
return cacheKeys;
|
|
|
|
|
},
|
|
|
|
|
getOwnPropertyDescriptor: (target, p) => {
|
|
|
|
|
if (isUnconfigurable(p)) return Reflect.getOwnPropertyDescriptor(target, p);
|
|
|
|
|
|
2024-05-21 16:04:56 +08:00
|
|
|
const descriptor = Reflect.getOwnPropertyDescriptor(target[factorySymbol](), p);
|
2024-05-20 20:57:48 +08:00
|
|
|
if (descriptor) Object.defineProperty(target, p, descriptor);
|
|
|
|
|
return descriptor;
|
2024-05-21 16:04:56 +08:00
|
|
|
}
|
2024-05-20 20:57:48 +08:00
|
|
|
};
|
|
|
|
|
|
2024-05-21 16:04:56 +08:00
|
|
|
// Mirror all Reflect methods
|
|
|
|
|
Object.getOwnPropertyNames(Reflect).forEach(fnName => {
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
lazyHandler[fnName] ??= (target: any, ...args: any[]) => {
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
return Reflect[fnName](target[factorySymbol](), ...args);
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
2024-05-20 20:57:48 +08:00
|
|
|
/**
|
|
|
|
|
* Lazy proxy that will only call the factory function when needed (when a property is accessed)
|
|
|
|
|
* @param factory Factory function to create the object
|
2024-05-21 16:04:56 +08:00
|
|
|
* @param fallback A fallback value to return if the factory returns undefined
|
2024-05-20 20:57:48 +08:00
|
|
|
* @returns A proxy that will call the factory function only when needed
|
2024-05-21 16:04:56 +08:00
|
|
|
* @example const ChannelStore = proxyLazy(() => findByProps("getChannelId"));
|
2024-05-20 20:57:48 +08:00
|
|
|
*/
|
2024-05-22 03:02:13 +08:00
|
|
|
export function proxyLazy<T>(factory: () => T, asFunction = true): T {
|
|
|
|
|
const dummy = asFunction ? function () { } as any : {};
|
2024-05-21 16:04:56 +08:00
|
|
|
dummy[factorySymbol] = () => {
|
2024-05-22 03:02:13 +08:00
|
|
|
return dummy[cacheSymbol] ??= factory();
|
2024-05-21 16:04:56 +08:00
|
|
|
};
|
2024-05-20 20:57:48 +08:00
|
|
|
|
|
|
|
|
return new Proxy(dummy, lazyHandler) as any;
|
|
|
|
|
}
|
2024-05-22 19:41:00 +08:00
|
|
|
|
|
|
|
|
export function lazyDestructure<T extends Record<PropertyKey, unknown>>(factory: () => T, asFunction = false): T {
|
|
|
|
|
const proxiedObject = proxyLazy(factory, asFunction);
|
|
|
|
|
|
|
|
|
|
return new Proxy({}, {
|
|
|
|
|
get(_, property) {
|
|
|
|
|
if (property === Symbol.iterator) {
|
|
|
|
|
return function* () {
|
|
|
|
|
yield proxiedObject;
|
|
|
|
|
yield new Proxy({}, {
|
|
|
|
|
get: (_, p) => proxyLazy(() => proxiedObject[p])
|
|
|
|
|
});
|
|
|
|
|
throw new Error("This is not a real iterator, this is likely used incorrectly");
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
return proxyLazy(() => proxiedObject[property]);
|
|
|
|
|
}
|
|
|
|
|
}) as T;
|
|
|
|
|
}
|