Skip to content

Commit c52a680

Browse files
authored
feat: support factory (#35)
1 parent 732062a commit c52a680

File tree

6 files changed

+210
-86
lines changed

6 files changed

+210
-86
lines changed

src/container.ts

Lines changed: 83 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,26 @@ import {
2222
getMetadata,
2323
getParamMetadata,
2424
isClass,
25+
isFunction,
2526
isPrimitiveFunction,
2627
isUndefined,
2728
recursiveGetMetadata,
2829
} from './util';
29-
import { NotFoundError, NoTypeError, NoHandlerError } from './error';
30+
31+
import {
32+
NotFoundError,
33+
NoTypeError,
34+
NoHandlerError,
35+
NoIdentifierError,
36+
InjectionError,
37+
} from './error';
3038

3139
export default class Container implements ContainerType {
3240
private registry: Map<Identifier, InjectableMetadata>;
3341
private tags: Map<string, Set<any>>;
3442
// @ts-ignore
3543
protected name: string;
36-
protected handlerMap: Map<string, HandlerFunction>;
44+
protected handlerMap: Map<string | symbol, HandlerFunction>;
3745

3846
constructor(name: string) {
3947
this.name = name;
@@ -72,40 +80,42 @@ export default class Container implements ContainerType {
7280
}
7381

7482
const { type, id, scope } = this.getDefinedMetaData(options);
75-
const args = getMetadata(CLASS_CONSTRUCTOR_ARGS, type) as ReflectMetadataType[];
76-
const props = recursiveGetMetadata(CLASS_PROPERTY, type) as ReflectMetadataType[];
77-
const initMethodMd = getMetadata(CLASS_ASYNC_INIT_METHOD, type) as ReflectMetadataType;
78-
const handlerArgs = getMetadata(INJECT_HANDLER_ARGS, type) as ReflectMetadataType[];
79-
const handlerProps = recursiveGetMetadata(
80-
INJECT_HANDLER_PROPS,
81-
type
82-
) as ReflectMetadataType[];
83-
8483
const md: InjectableMetadata = {
8584
...options,
8685
id,
8786
type,
8887
scope,
89-
constructorArgs: (args ?? []).concat(handlerArgs ?? []),
90-
properties: (props ?? []).concat(handlerProps ?? []),
91-
initMethod: initMethodMd?.propertyName ?? 'init',
9288
};
89+
if (type) {
90+
const args = getMetadata(CLASS_CONSTRUCTOR_ARGS, type) as ReflectMetadataType[];
91+
const props = recursiveGetMetadata(CLASS_PROPERTY, type) as ReflectMetadataType[];
92+
const initMethodMd = getMetadata(CLASS_ASYNC_INIT_METHOD, type) as ReflectMetadataType;
93+
const handlerArgs = getMetadata(INJECT_HANDLER_ARGS, type) as ReflectMetadataType[];
94+
const handlerProps = recursiveGetMetadata(
95+
INJECT_HANDLER_PROPS,
96+
type
97+
) as ReflectMetadataType[];
98+
99+
md.constructorArgs = (args ?? []).concat(handlerArgs ?? []);
100+
md.properties = (props ?? []).concat(handlerProps ?? []);
101+
md.initMethod = initMethodMd?.propertyName ?? 'init';
102+
/**
103+
* compatible with inject type identifier when identifier is string
104+
*/
105+
if (md.id !== type) {
106+
md[MAP_TYPE] = type;
107+
this.registry.set(type, md);
108+
}
93109

94-
/**
95-
* compatible with inject type identifier when identifier is string
96-
*/
97-
if (md.id !== type) {
98-
md[MAP_TYPE] = type;
99-
this.registry.set(type, md);
110+
this.handleTag(type);
100111
}
101-
this.registry.set(md.id, md);
102112

113+
this.registry.set(md.id, md);
103114
if (md.eager && md.scope !== ScopeEnum.TRANSIENT) {
115+
// TODO: handle async
104116
this.get(md.id);
105117
}
106118

107-
this.handleTag(type);
108-
109119
return this;
110120
}
111121

@@ -128,11 +138,11 @@ export default class Container implements ContainerType {
128138
return Promise.all(clazzes.map(clazz => this.getAsync(clazz)));
129139
}
130140

131-
public registerHandler(name: string, handler: HandlerFunction) {
141+
public registerHandler(name: string | symbol, handler: HandlerFunction) {
132142
this.handlerMap.set(name, handler);
133143
}
134144

135-
public getHandler(name: string) {
145+
public getHandler(name: string | symbol) {
136146
return this.handlerMap.get(name);
137147
}
138148

@@ -146,10 +156,18 @@ export default class Container implements ContainerType {
146156
if (!isUndefined(md.value)) {
147157
return md.value;
148158
}
149-
const clazz = md.type!;
150-
const params = this.resolveParams(clazz, md.constructorArgs);
151-
const value = new clazz(...params);
152-
this.handleProps(value, md.properties ?? []);
159+
let value;
160+
if (md.factory) {
161+
value = md.factory(md.id, this);
162+
}
163+
164+
if (!value && md.type) {
165+
const clazz = md.type!;
166+
const params = this.resolveParams(clazz, md.constructorArgs);
167+
value = new clazz(...params);
168+
this.handleProps(value, md.properties ?? []);
169+
}
170+
153171
if (md.scope === ScopeEnum.SINGLETON) {
154172
md.value = value;
155173
}
@@ -160,10 +178,18 @@ export default class Container implements ContainerType {
160178
if (!isUndefined(md.value)) {
161179
return md.value;
162180
}
163-
const clazz = md.type!;
164-
const params = await this.resolveParamsAsync(clazz, md.constructorArgs);
165-
const value = new clazz(...params);
166-
await this.handlePropsAsync(value, md.properties ?? []);
181+
let value;
182+
if (md.factory) {
183+
value = await md.factory(md.id, this);
184+
}
185+
186+
if (!value && md.type) {
187+
const clazz = md.type!;
188+
const params = await this.resolveParamsAsync(clazz, md.constructorArgs);
189+
value = new clazz(...params);
190+
await this.handlePropsAsync(value, md.properties ?? []);
191+
}
192+
167193
if (md.scope === ScopeEnum.SINGLETON) {
168194
md.value = value;
169195
}
@@ -179,26 +205,36 @@ export default class Container implements ContainerType {
179205
}
180206

181207
private getDefinedMetaData(options: Partial<InjectableDefinition>): {
182-
type: Constructable;
183208
id: Identifier;
184209
scope: ScopeEnum;
210+
type?: Constructable | null;
185211
} {
186-
let type = options.type;
212+
let { type, id, scope = ScopeEnum.SINGLETON, factory } = options;
187213
if (!type) {
188-
if (options.id && isClass(options.id)) {
189-
type = options.id as Constructable;
214+
if (id && isClass(id)) {
215+
type = id as Constructable;
190216
}
191217
}
192218

193-
if (!type) {
194-
throw new NoTypeError('type is required');
219+
if (!type && !factory) {
220+
throw new NoTypeError(`injectable ${id?.toString()}`);
195221
}
196222

197-
const targetMd = (getMetadata(CLASS_CONSTRUCTOR, type) as ReflectMetadataType) || {};
198-
const id = targetMd.id ?? options.id ?? type;
199-
const scope = targetMd.scope ?? options.scope ?? ScopeEnum.SINGLETON;
223+
if (factory && !isFunction(factory)) {
224+
throw new InjectionError('factory option must be function');
225+
}
200226

201-
return { type, id, scope };
227+
if (type) {
228+
const targetMd = (getMetadata(CLASS_CONSTRUCTOR, type) as ReflectMetadataType) || {};
229+
id = targetMd.id ?? id ?? type;
230+
scope = targetMd.scope ?? scope;
231+
}
232+
233+
if (!id && factory) {
234+
throw new NoIdentifierError(`injectable with factory option`);
235+
}
236+
237+
return { type, id: id!, scope };
202238
}
203239

204240
private resolveParams(clazz: any, args?: ReflectMetadataType[]): any[] {
@@ -280,12 +316,13 @@ export default class Container implements ContainerType {
280316
});
281317
}
282318

283-
private resolveHandler(handlerName: string, id?: Identifier): any {
319+
private resolveHandler(handlerName: string | symbol, id?: Identifier): any {
284320
const handler = this.getHandler(handlerName);
285321

286322
if (!handler) {
287-
throw new NoHandlerError(handlerName);
323+
throw new NoHandlerError(handlerName.toString());
288324
}
289-
return handler(id, this);
325+
326+
return id ? handler(id, this) : handler(this);
290327
}
291328
}

src/decorator/handler.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,22 @@ import { INJECT_HANDLER_ARGS, INJECT_HANDLER_PROPS } from '../constant';
22
import { ReflectMetadataType } from '../types';
33
import { isUndefined, isObject, setMetadata, getMetadata } from '../util';
44

5-
export function InjectHandler(handlerName: string, id) {
5+
export function InjectHandler(handlerName: string | symbol, id) {
66
return function (target: any, key: string, index?: number) {
77
if (isObject(target)) {
88
target = target.constructor;
99
}
1010

1111
if (!isUndefined(index)) {
12-
const metadatas = (getMetadata(INJECT_HANDLER_ARGS, target) || []) as ReflectMetadataType[];
12+
const metadatas = (getMetadata(INJECT_HANDLER_ARGS, target) ||
13+
[]) as ReflectMetadataType[];
1314
metadatas.push({ handler: handlerName, id, index });
1415
setMetadata(INJECT_HANDLER_ARGS, metadatas, target);
1516
return;
1617
}
17-
const metadatas = (getMetadata(INJECT_HANDLER_PROPS, target) || []) as ReflectMetadataType[];
18+
const metadatas = (getMetadata(INJECT_HANDLER_PROPS, target) ||
19+
[]) as ReflectMetadataType[];
1820
metadatas.push({ handler: handlerName, id, propertyName: key });
1921
setMetadata(INJECT_HANDLER_PROPS, metadatas, target);
20-
}
21-
}
22+
};
23+
}

src/error.ts

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
import { Constructable, Identifier } from './types';
22
import { createErrorClass } from './base_error';
33

4-
export class CannotInjectValueError
5-
extends createErrorClass('CannotInjectValueError') {
6-
constructor(
7-
target: Constructable<unknown>,
8-
propertyName: string | symbol,
9-
) {
10-
super(() => (
11-
`[@artus/injection] Cannot inject value into "` +
12-
`${target.name}.${String(propertyName)}". `));
4+
export class CannotInjectValueError extends createErrorClass('CannotInjectValueError') {
5+
constructor(target: Constructable<unknown>, propertyName: string | symbol) {
6+
super(
7+
() =>
8+
`[@artus/injection] Cannot inject value into "` +
9+
`${target.name}.${String(propertyName)}". `
10+
);
1311
}
1412
}
1513

@@ -21,22 +19,35 @@ export class NoTypeError extends createErrorClass('NoTypeError') {
2119

2220
export class NotFoundError extends createErrorClass('NotFoundError') {
2321
constructor(identifier: Identifier) {
24-
const normalizedIdentifier = typeof identifier === 'string' ?
25-
identifier :
26-
(identifier?.name ?? 'Unknown');
22+
const normalizedIdentifier =
23+
typeof identifier === 'function'
24+
? identifier.name
25+
: (identifier ?? 'Unknown').toString();
2726
super(() => {
2827
return (
2928
`[@artus/injection] with "${normalizedIdentifier}" ` +
30-
`identifier was not found in the container. `);
29+
`identifier was not found in the container. `
30+
);
3131
});
3232
}
3333
}
3434

3535
export class NoHandlerError extends createErrorClass('NoHandlerError') {
3636
constructor(handler: string) {
3737
super(() => {
38-
return (
39-
`[@artus/injection] "${handler}" handler was not found in the container.`);
38+
return `[@artus/injection] "${handler}" handler was not found in the container.`;
4039
});
4140
}
4241
}
42+
43+
export class NoIdentifierError extends createErrorClass('NoIdentifierError') {
44+
constructor(message: string) {
45+
super(`[@artus/injection] id is required: ${message}`);
46+
}
47+
}
48+
49+
export class InjectionError extends createErrorClass('InjectionError') {
50+
constructor(message: string) {
51+
super(`[@artus/injection] ${message}`);
52+
}
53+
}

src/types.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
export type Constructable<T = unknown> = new (...args: any[]) => T;
22
export type AbstractConstructable<T> = NewableFunction & { prototype: T };
3-
export type Identifier<T = unknown> = AbstractConstructable<T> | Constructable<T> | string;
3+
export type Identifier<T = unknown> = AbstractConstructable<T> | Constructable<T> | string | symbol;
44

55
export enum ScopeEnum {
66
SINGLETON = 'singleton',
@@ -24,6 +24,7 @@ export interface InjectableDefinition<T = unknown> {
2424
* By default the registered classes are only instantiated when they are requested from the container.
2525
*/
2626
eager?: boolean;
27+
factory?: CallableFunction;
2728
}
2829

2930
export interface InjectableMetadata<T = any> extends InjectableDefinition<T> {
@@ -37,7 +38,7 @@ export interface ReflectMetadataType {
3738
scope?: ScopeEnum;
3839
index?: number;
3940
propertyName?: string | symbol;
40-
handler?: string;
41+
handler?: string | symbol;
4142
}
4243

4344
export interface ContainerType {
@@ -51,4 +52,8 @@ export interface ContainerType {
5152
getHandler(name: string): HandlerFunction | undefined;
5253
}
5354

54-
export type HandlerFunction = (handlerKey: any, instance?: any) => any;
55+
/**
56+
* A function that is used to handle a property
57+
* last parameter is the instance of the Container
58+
*/
59+
export type HandlerFunction = CallableFunction;

0 commit comments

Comments
 (0)