-
Notifications
You must be signed in to change notification settings - Fork 5
/
inject.ts
159 lines (131 loc) · 5.36 KB
/
inject.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
import { stashEager, stashInject } from '../common/decorators.js';
import { validateDecoratorOptions } from '../common/validators.js';
import { logger } from '../singletons/logger.js';
import type { ClassFieldDecorator, ClassFieldDecoratorTarget } from '../types.js';
/**
* Customizes the injection.
*/
interface InjectOptions {
/**
* Defines the inject as eager.
* If {@link true}, the class field will be populated during instantiation.
* If {@link false}, the class field will be populated when it's first accessed.
*
* @default false
*/
eager?: boolean;
/**
* The name of the injectable to inject.
*/
name?: string;
/**
* Defines the inject as optional.
* If {@link true}, it will inject undefined when not being provided by a container.
* If {@link false}, it will throw an {@link Error} when not being provided by a container.
*
* @default false
*/
optional?: boolean;
}
const knownInjectOptions: (keyof InjectOptions)[] = [
'eager',
'name',
'optional',
];
/**
* Decorates and registers a class field to be injected with an injectable.
* The class field will be populated with the injectable instance/value provided by the container.
*
* @param nameOrOptions the name of the injectable to inject or options to customize the injection.
*
* @throws Error if decorating something other than a class field.
* @throws Error if called with too few or too many arguments.
*/
function Inject<This>(nameOrOptions: string | InjectOptions): ClassFieldDecorator<This>;
/**
* Decorates and registers a class field to be injected with an injectable.
* The class field will be populated with the injectable instance/value provided by the container.
*
* @throws Error if decorating something other than a class field.
* @throws Error if called with too few or too many arguments.
*/
function Inject<This>(...parameters: Parameters<ClassFieldDecorator<This>>): ReturnType<ClassFieldDecorator<This>>;
function Inject<This, Target extends ClassFieldDecoratorTarget>(target: string | InjectOptions | Target, context?: ClassFieldDecoratorContext<This>): ClassFieldDecorator<This> | ReturnType<ClassFieldDecorator<This>> {
logger.decorators.debug('Inject:', { target, context });
if (arguments.length <= 0) {
throw new Error(logger.createMessage(`The @Inject decorator cannot be called without any arguments. Add an argument or remove the ().`));
}
let options: InjectOptions | undefined;
if (target === undefined && context) {
logger.notifyDeprecation({
name: '@Inject decorator without any arguments',
nameExample: '@Inject',
since: 'v3.0.0',
use: '@Inject decorator with options',
useExample: '@Inject({ ...options })',
whatWillHappen: 'It will be removed',
});
return InjectDecorator(target, context);
}
if (arguments.length > 1) {
throw new Error(logger.createMessage(`The @Inject decorator cannot be called with more than one argument.`));
}
if (typeof target === 'string') {
logger.notifyDeprecation({
name: '@Inject decorator with a string',
nameExample: `@Inject('${target}')`,
since: 'v3.0.0',
use: '@Inject decorator with options',
useExample: `@Inject({ name: '${target}' })`,
whatWillHappen: 'It will be removed',
});
options = options ?? {};
options.name = target;
}
if (typeof target === 'object') {
if (Array.isArray(target)) {
throw new Error(logger.createMessage(`The @Inject decorator cannot be called with an array as argument.`));
}
if (Object.getOwnPropertyNames(target).length <= 0) {
throw new Error(logger.createMessage(`The @Inject decorator cannot be called with an empty object as argument.`));
}
options = { ...target } as InjectOptions;
}
return InjectDecorator;
function InjectDecorator<This, Target extends ClassFieldDecoratorTarget>(target: Target, context: ClassFieldDecoratorContext<This>): Target | undefined {
logger.decorators.debug('InjectDecorator:', { target, context, options });
if (options && typeof options.name !== 'string') {
logger.notifyDeprecation({
name: '@Inject decorator options without a name',
nameExample: '@Inject({})',
since: 'v3.0.0',
use: '@Inject decorator options with a name',
useExample: `@Inject({ name: '${String(context.name)}' })`,
whatWillHappen: 'The name option will be required',
});
}
options = options ?? {};
if (context.kind !== 'field') {
throw new Error(logger.createMessage(`The @Inject decorator can only decorate a class field. Check the ${String(context.kind)} named '${String(context.name)}'.`));
}
validateDecoratorOptions(options, {
allowUnknown: true,
known: knownInjectOptions,
});
// @ts-expect-error: TS2322, if this initializer is added to the decorator signature, it allows for calling it, and we'd like to avoid it
return function InjectDecoratorInitializer<This>(this: This, initialValue: unknown): unknown {
logger.decorators.debug('InjectDecoratorInitializer:', { target, context, initialValue, options });
options = options as InjectOptions;
if (options.eager) {
stashEager(this as any, String(context.name));
}
stashInject(this as any, String(context.name), String(options.name ?? context.name), options);
};
}
}
export {
Inject,
};
export type {
InjectOptions,
};