Skip to content

Instantly share code, notes, and snippets.

@temoncher
Created April 12, 2024 21:34
Show Gist options
  • Save temoncher/09455c7568dd3d9444e39e63ae2c91f9 to your computer and use it in GitHub Desktop.
Save temoncher/09455c7568dd3d9444e39e63ae2c91f9 to your computer and use it in GitHub Desktop.
IoC containers with almost zero features
class Token<T, Id extends string> {
/** @typeonly */
private readonly __type: T = null!;
constructor(readonly id: Id) {}
}
declare namespace Token {
export type Type<T> = T extends Token<infer TokenType, string> ? TokenType : never;
export type Id<T> = T extends Token<unknown, infer TokenId> ? TokenId : never;
}
interface TinyjectRequired {
}
interface TinyjectOptional {
}
interface TinyjectRequired {
// [TGetPosts.id]: typeof TGetPosts;
// [THttpClient.id]: typeof THttpClient;
// [TPostsApi.id]: typeof TPostsApi;
}
interface TinyjectOptional {
}
type AnyFunction = (...args: any[]) => any;
type AnyClass = new (...args: any[]) => any;
type ValueOf<T> = T[keyof T];
const defaultMap = new Map();
let currentMap = defaultMap;
type TokenIfDeclared = {} extends TinyjectRequired ? Token<any, any> : ValueOf<TinyjectRequired>;
function inject<T extends TokenIfDeclared>(token: T): Token.Type<T> {
console.log(`Injecting: ${token.id}`);
const factory = currentMap.get(token);
if (!factory) console.error(`Instance of ${token.id} not found`);
return factory();
}
function provideAll(providers: { [K in TokenIfDeclared['id']]: [token: TokenIfDeclared & { id: K }, instance: () => Token.Type<TokenIfDeclared & { id: K }> ] }): void {
for (const [token, instance] of Object.values(providers)) {
provide(token, instance);
}
}
function provide<T>(token: Token<T, string>, factory: () => T): void {
console.log(`Providing: ${token.id}`);
currentMap.set(token, factory);
}
function unprovide(token: Token<unknown, string>): void {
currentMap.delete(token);
}
function unprovideAll(): void {
currentMap.clear();
}
function runInContext(map: Map<any, any>, fn: () => void) {
const prevMap = currentMap;
currentMap = map;
try {
fn();
} finally {
currentMap = prevMap;
}
}
interface IHttpClient {
get(url: string): Promise<unknown>;
}
const THttpClient = new Token<IHttpClient, "THttpClient">("THttpClient");
class HttpClient implements IHttpClient {
get(url: string): Promise<unknown> {
console.log('httpClient.get')
return Promise.resolve();
// return fetch(url).then(r => r.json());
}
}
interface IPostsApi {
getPosts(): Promise<unknown>;
}
const TPostsApi = new Token<IPostsApi, "TPostsApi">("TPostsApi");
class PostsApi implements Token.Type<typeof TPostsApi> {
readonly httpClient = inject(THttpClient)
getPosts() {
console.log('PostsApi.getPosts');
return this.httpClient.get('some-url');
}
}
const TGetPosts = new Token<() => Promise<unknown>, "TGetPosts">("TGetPosts");
function getPosts() {
return inject(THttpClient).get('some-url');
}
function singleton<T>(factory: () => T, mode: 'lazy' | 'eager' = 'lazy') {
let instance: T = mode === 'eager' ? factory() : null!;
return () => instance ??= factory();
}
async function main() {
const client = new HttpClient();
provideAll({
[TGetPosts.id]: [TGetPosts, () => getPosts],
[THttpClient.id]: [THttpClient, () => client],
[TPostsApi.id]: [TPostsApi, singleton(() => new PostsApi())]
})
// provide(THttpClient, () => client);
// provide(TPostsApi, () => new PostsApi());
// provide(TPostsApi, () => ({ getPosts: () => Promise.resolve("SOMETHING") }));
// provide(TGetPosts, () => () => Promise.resolve("SOMETHING"));
// const res = await inject(TGetPosts)();
// console.log(res);
const res = await inject(TPostsApi).getPosts();
console.log(res);
}
main();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment