Created
April 12, 2024 21:34
-
-
Save temoncher/09455c7568dd3d9444e39e63ae2c91f9 to your computer and use it in GitHub Desktop.
IoC containers with almost zero features
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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