Skip to content

Instantly share code, notes, and snippets.

@temoncher
Created April 16, 2024 17:51
Show Gist options
  • Save temoncher/21bd6497a00085068f74c7c4dc84317f to your computer and use it in GitHub Desktop.
Save temoncher/21bd6497a00085068f74c7c4dc84317f to your computer and use it in GitHub Desktop.
Simple di container with object property access to services via proxies
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { type O } from 'ts-toolbelt';
type MapLayer<TServices> =
TServices extends Record<string, (di: any) => any>
? {
[K in keyof TServices]: ReturnType<TServices[K]>;
}
: never;
export class Container<TContainerServices extends object> {
constructor(
private readonly unsafeMap = new Map<
keyof TContainerServices,
(di: any) => any
>(),
private readonly instances = new Map<keyof TContainerServices, any>()
) {}
unbind<Keys extends keyof TContainerServices>(
...keys: Keys[]
): Container<Omit<TContainerServices, Keys>> {
const newMap = new Map(this.unsafeMap);
keys.forEach((key) => newMap.delete(key));
return new Container(newMap, this.instances) as any;
}
bind<TServices extends Record<string, (di: TContainerServices) => any>>(
services: TServices
): Container<O.Merge<TContainerServices, MapLayer<TServices>>> {
const newMap = new Map(this.unsafeMap);
Object.entries(services).forEach(([key, factory]) => {
newMap.set(key as keyof TContainerServices, factory);
});
return new Container(newMap, this.instances) as any;
}
get<K extends keyof TContainerServices>(
key: K
): TContainerServices[K] | undefined {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const instance = this.instances.get(key);
if (instance) return instance;
const factory = this.unsafeMap.get(key);
if (!factory) return undefined;
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const newInstance = factory(this.build());
this.instances.set(key, newInstance);
return newInstance;
}
clone(): Container<TContainerServices> {
return new Container(new Map(this.unsafeMap));
}
build(): TContainerServices {
return new Proxy(
{},
{
get: (target, prop) => this.get(prop as any),
}
) as any;
}
}
// eslint-disable-next-line @typescript-eslint/no-namespace
export declare namespace Container {
export type Services<TContainer> =
TContainer extends Container<infer S> ? S : never;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment