Skip to content

Commit

Permalink
feat: 토큰 기반 주입 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
Coalery committed Oct 3, 2024
1 parent 79deda2 commit f4eeffb
Show file tree
Hide file tree
Showing 12 changed files with 126 additions and 103 deletions.
8 changes: 8 additions & 0 deletions src/common/Provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Token } from './Token';

export type Provider<T = any> = ValueProvider<T>;

export type ValueProvider<T = any> = {
provide: Token;
useValue: T;
};
3 changes: 3 additions & 0 deletions src/common/Token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { Type } from './Type';

export type Token<T = any> = string | symbol | Type<T>;
17 changes: 17 additions & 0 deletions src/core/container/InstanceContainer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Token } from '@lery/common/Token';

export class InstanceContainer {
private readonly instanceMap: Map<Token, any> = new Map();

register<T>(token: Token<T>, instance: T): void {
this.instanceMap.set(token, instance);
}

get<T>(token: Token<T>): T {
if (!this.instanceMap.has(token)) {
throw new Error(`No instance found for token ${token.toString()}`);
}

return this.instanceMap.get(token);
}
}
6 changes: 4 additions & 2 deletions src/core/metadata/InjectMetadata.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { Token } from '@lery/common/Token';

export const PARAMETER_INJECT_METADATA = 'PARAMETER_INJECT_METADATA' as const;
export const PROPERTY_INJECT_METADATA = 'PROPERTY_INJECT_METADATA' as const;

export type ParameterInjectMetadata = {
token: string;
token: Token;
index: number;
};

export type PropertyInjectMetadata = {
token: string;
token: Token;
propertyKey: string | symbol;
};
53 changes: 53 additions & 0 deletions src/core/resolver/Resolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Provider } from '@lery/common/Provider';
import { Token } from '@lery/common/Token';
import { Type } from '@lery/common/Type';
import {
PARAMETER_INJECT_METADATA,
PROPERTY_INJECT_METADATA,
ParameterInjectMetadata,
PropertyInjectMetadata,
} from '@lery/core/metadata/InjectMetadata';
import { InstanceContainer } from '../container/InstanceContainer';

export class Resolver {
private readonly instanceContainer: InstanceContainer;

constructor() {
this.instanceContainer = new InstanceContainer();
}

registerProviders(providers: Provider[]): void {
providers.forEach((provider) => {
this.instanceContainer.register(provider.provide, provider.useValue);
});
}

resolve<T>(ctor: Type<T>): T {
const parameters = this.resolveConstructorParameters(ctor);
const instance = new ctor(...parameters);
this.resolveProperty(ctor, instance);

return instance;
}

resolveConstructorParameters<T>(ctor: Type<T>): any[] {
const metadata: ParameterInjectMetadata[] =
Reflect.getMetadata(PARAMETER_INJECT_METADATA, ctor) ?? [];

const result: any[] = [];
metadata.map((m) => {
result[m.index] = this.instanceContainer.get(m.token);
});

return result;
}

resolveProperty<T>(ctor: Type<T>, instance: T): void {
const metadata: PropertyInjectMetadata[] =
Reflect.getMetadata(PROPERTY_INJECT_METADATA, ctor) ?? [];

metadata.forEach((m) => {
(instance as any)[m.propertyKey] = this.instanceContainer.get(m.token);
});
}
}
15 changes: 2 additions & 13 deletions src/decorators/Inject.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Token } from '@lery/common/Token';
import {
PARAMETER_INJECT_METADATA,
PROPERTY_INJECT_METADATA,
Expand All @@ -6,7 +7,7 @@ import {
} from '@lery/core/metadata/InjectMetadata';

export const Inject =
(token: string) =>
(token: Token) =>
(
target: object,
propertyKey: string | symbol | undefined,
Expand All @@ -27,15 +28,3 @@ export const Inject =
appendMetadata(PROPERTY_INJECT_METADATA, metadata, target.constructor);
}
};

export const CalculatorToken = 'Calculator';
export const InjectCalculator = () => Inject(CalculatorToken);

export const LoggerToken = 'Logger';
export const InjectLogger = () => Inject(LoggerToken);

export const HelloWorldToken = 'HelloWorld';
export const InjectHelloWorld = () => Inject(HelloWorldToken);

export const FileManagerToken = 'FileManager';
export const InjectFileManager = () => Inject(FileManagerToken);
2 changes: 2 additions & 0 deletions src/instances/calculator/ICalculator.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export const CalculatorToken = Symbol('Calculator');

export interface ICalculator {
add: (a: number, b: number) => number;
}
2 changes: 2 additions & 0 deletions src/instances/fileManager/IFileManager.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export const FileManagerToken = Symbol('FileManager');

export interface IFileManager {
read: (path: string) => string;
write: (path: string, content: string) => void;
Expand Down
2 changes: 2 additions & 0 deletions src/instances/helloWorld/IHelloWorld.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export const HelloWorldToken = Symbol('HelloWorld');

export interface IHelloWorld {
sayHello: () => void;
}
2 changes: 2 additions & 0 deletions src/instances/logger/ILogger.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export const LoggerToken = Symbol('Logger');

export interface ILogger {
log: (message: string) => void;
error: (message: string) => void;
Expand Down
52 changes: 31 additions & 21 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,55 @@
import 'reflect-metadata';
import { Resolver } from '@lery/resolver/Resolver';

import { Resolver } from '@lery/core/resolver/Resolver';
import { Calculator } from '@lery/instances/calculator/Calculator';
import { Logger } from '@lery/instances/logger/Logger';
import { HelloWorld } from '@lery/instances/helloWorld/HelloWorld';
import { FileManager } from '@lery/instances/fileManager/FileManager';
import { Inject } from '@lery/decorators/Inject';
import {
CalculatorToken,
ICalculator,
} from '@lery/instances/calculator/ICalculator';
import { ILogger, LoggerToken } from '@lery/instances/logger/ILogger';
import {
InjectCalculator,
InjectFileManager,
InjectHelloWorld,
InjectLogger,
} from './decorators/Inject';
import { ICalculator } from './instances/calculator/ICalculator';
import { ILogger } from './instances/logger/ILogger';
import { IHelloWorld } from './instances/helloWorld/IHelloWorld';
import { IFileManager } from './instances/fileManager/IFileManager';
HelloWorldToken,
IHelloWorld,
} from '@lery/instances/helloWorld/IHelloWorld';
import {
FileManagerToken,
IFileManager,
} from '@lery/instances/fileManager/IFileManager';

class Hello {
@InjectCalculator()
@Inject(CalculatorToken)
calculator!: ICalculator;

@InjectLogger()
@Inject(LoggerToken)
logger!: ILogger;

constructor(
@InjectHelloWorld()
@Inject(HelloWorldToken)
readonly helloWorld: IHelloWorld,
@InjectFileManager()
@Inject(FileManagerToken)
readonly fileManager: IFileManager
) {}
}

function main() {
const resolver = new Resolver({
calculator: new Calculator(),
logger: new Logger(),
helloWorld: new HelloWorld(),
fileManager: new FileManager(),
});
const resolver = new Resolver();
resolver.registerProviders([
{ provide: CalculatorToken, useValue: new Calculator() },
{ provide: LoggerToken, useValue: new Logger() },
{ provide: HelloWorldToken, useValue: new HelloWorld() },
{ provide: FileManagerToken, useValue: new FileManager() },
]);

const hello = resolver.resolve(Hello);
console.log(hello);

console.log(hello.calculator.add(1, 2)); // 3
hello.logger.log('Hello from logger'); // Hello from logger
hello.helloWorld.sayHello(); // Hello, World!
hello.fileManager.write('some.txt', 'Hello!'); // Writing file to some.txt with content: Hello!
}

main();
67 changes: 0 additions & 67 deletions src/resolver/Resolver.ts

This file was deleted.

0 comments on commit f4eeffb

Please sign in to comment.