Skip to content

Instantly share code, notes, and snippets.

@lzehrung
Last active January 28, 2026 01:45
Show Gist options
  • Select an option

  • Save lzehrung/e6f3d69cf02b153ce8a0de540259e926 to your computer and use it in GitHub Desktop.

Select an option

Save lzehrung/e6f3d69cf02b153ce8a0de540259e926 to your computer and use it in GitHub Desktop.
TS class and function name helper
// see https://www.typescriptlang.org/play/?#code/C4TwDgpgBAggdiAYnKBeKAKAdDghgJwHMBnALilwQG0BdASjQD4KEBuAKHYHoupEBXOAGNgASwD2KANYQQxKOIBmFKKEjs10ZAGlZxADwAVZugDe7KFCraoo6bKVRDNALQB+coes0oEAB7AEHAAJvLwSChuUDbkcBAAbhD47AC+VDIgjs5QAGRQxMD4doQc3LwAon5CADb8wdAA8gBGAFYQIlBg+OLAPeDQBfyKygDUUEKSBfj8IuL4mD0AFkkA7qLE0CvQhBDAUABEvQDKhcX7ADS+wEJ0Gv1QAMLVEJQ6ekYmUJU1dRD6b3IPpcMo5mm0OgAfA4TOBTGa9fD7RilTRQBpgAAK3T8IH0RyS8VEQggADlcABbaD+QIheRTYqXXRyXwBIKhfKnOCET7mSzWWwoJnEGjkAAGABJTPj8ITiWTKSksJLtClRRwUqVFIIRBIUOTcDJ0VjxDi8QSiaSKVTWbSOUUuYy9Cyaez6VzGBgLPlzXKreRpbLLZT2HRyEbsbiAxb5RBHXJmLzxpM9kJcEJlmgoHEVlAALK4MD6N2ES7Fj10UqWfC7fj4FDZqDGnEYMHtYBYITV3CBDBwfjVap0S6Jyw7YAYAD6wAIY8uXXEYAYI8stmUGE0jnnYCgAEJUOh9sX9gxq8BayhBPVFHYIMFKyvLDCClBFqI9uhU+mIFgxxgtxWvQfUQ11fYATxrOsXzfe8HyfPZ4lwWpoHQCVTA2GVoytRVJS3VUOAfR802WLANnHLdLgQpCAIIqBT3PKBKP4CB8JXFJzi9FIGFweRKBAdVOC4AAqQSLEEtE4igLVhDESRyGIfgwDAOZgHkJolg5bsiSgMY7AKShiSgSlgEWcRQiwUTRKgFwoFFBcMFzEAnm44g6BI8RKROTShFzXYTOCUVLOs2ywHsxzqmc1ziHciAAElYWnYQIB84zTICqARPSpwjigQh+AIShAggeRjOgSlySaJIsytBRlAgN9lnmPSxCELhdISgz1hUKSdUkcz0q4dh-CU-A9m6mSUAXIwHgRZ02R4popjTPYG2wPAiDIFgQFoBhUGYXiPS9WZ8E8aa5lyKBTCqyk5M5QgoBSEMw0xCMi1uy4nheOAAQMQxTvwZgoQ+144CFfQ4r0xLDH6KaEUYOGLq9OjIP1Q1npNXFi3e55gdB37YagQHsa+kH3nB9qIChyAYbmOGPSOrA4CtACHrKKyXGsyoKTAZ55HZlxOBqZyoBJHpgKJTTJijAzEyarT52JYhiAAIW7dMMCXB7LFloRfDgABHJimPVi6Wa1tkGjiY3TE1l8QGCfBuwgK3TagHgs0kFwxt1STRAgap2RWSQAHI9gLSACC9bWGKSYhvfQABGFimv4eQD3EKR9n49g7JFsRr1TcbiCliBXPlorldVxZWFd3h9lzsWC91IufW-MvFZV65Fn2bOQvr-OJdhYvIvNuJq4It269F-vC+LkiR4gbvWcAGXJ3dDyTtXGwyIHKpIe4wPvxZnlvXMSfBY8kUo3ZXtRxD3g-G8l4+sDbiv0w4IA
type AnyFn = (...args: any[]) => any;
// Function keys of a type
type FnKeys<T> = {
[K in keyof T]-?: T[K] extends AnyFn ? K : never
}[keyof T] & string;
// Exclude Object prototype stuff + constructor (otherwise we get "toString", etc)
type CleanFnKeys<T> = Exclude<FnKeys<T>, keyof Object | "constructor">;
type OpProxy<ServiceName extends string, Keys extends string> = {
[K in Keys]: `${ServiceName}.${K}`;
};
function makeOpProxy<ServiceName extends string, Keys extends string>(
serviceName: ServiceName
): OpProxy<ServiceName, Keys> {
const cache = new Map<string, string>();
return new Proxy(Object.create(null), {
get(_target, prop) {
if (typeof prop !== "string") return undefined;
const hit = cache.get(prop);
if (hit) return hit;
const value = `${serviceName}.${prop}`;
cache.set(prop, value);
return value;
},
}) as any;
}
/**
* One function: supports both static + instance methods.
*
* - `op(MyClass).someStaticMethod`
* - `op(MyClass).someInstanceMethod`
*
* TS guarantees the member name of either static/instance is a function.
*/
export function op<TCtor extends abstract new (...args: any[]) => any>(
ctor: TCtor & { name: string }
): OpProxy<string, CleanFnKeys<TCtor> | CleanFnKeys<InstanceType<TCtor>>> {
return makeOpProxy<string, CleanFnKeys<TCtor> | CleanFnKeys<InstanceType<TCtor>>>(ctor.name);
}
// --- Examples ---
class NotificationsService {
static processBatch() {}
static enqueue() {}
sendOne() {}
hydrate() {}
// non-function fields won't appear
static version = 1;
status = "ok";
}
op(NotificationsService).processBatch; // "NotificationsService.processBatch"
op(NotificationsService).sendOne; // "NotificationsService.sendOne"
// ❌ not a function member
op(NotificationsService).version;
// ❌ typo
op(NotificationsService).processBach;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment