-
Notifications
You must be signed in to change notification settings - Fork 0
/
TypeKey.ts
346 lines (318 loc) · 10.5 KB
/
TypeKey.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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
import { Inject } from './Inject'
import { ComputedKey } from './ComputedKey'
import { AbstractKey } from './AbstractKey'
import { Scope, ScopeList } from './Scope'
import { DependencyKey, Target } from './DependencyKey'
import { AbstractClass, Class, asMixin, isObject } from './_internal'
import { ClassWithoutDefault, ClassWithDefault } from './InjectableClass'
import TypeKeyClass = TypeKey.TypeKeyClass
/** @ignore */
export interface HasTypeKeySymbol<out T> {
/** @ignore */
readonly [_typeKeySymbol]: readonly [T] | null
}
type ClassLike<T> = Class<T> | ((...args: any[]) => T)
// Use this to prevent library consumers from generating types equivalent to `TypeKey`.
const _typeKeySymbol: unique symbol = Symbol()
export interface BaseTypeKey<out T = any, Def extends ComputedKey<T> = any> extends HasTypeKeySymbol<T> {
/** @ignore */
readonly keyTag?: symbol
/** The {@link Scope:type | Scope} or {@link ScopeList} to which this `TypeKey` should be bound. */
readonly scope?: ScopeList | (() => ScopeList)
/** The name that will be displayed in exception messages. */
readonly fullName: string
/** A class or function returning the target value of this `TypeKey`. */
readonly of?: ClassLike<T>
/** @ignore prevent a TypeKey from being an InjectableClass */
readonly inject: null
/** @ignore */
readonly defaultInit?: Def | (() => Def)
}
/**
* A key for a custom dependency not bound to a specific class.
*
* @see The {@link TypeKey | TypeKey()} function for implementing TypeKey.
* A TypeKey implementation is a class object that extends {@link TypeKey}().
*
* @example
*
* ```ts
* // These TypeKeys both resolve to `string` but are separate from each other:
* class NameKey extends TypeKey<string>() { private _: any }
* class IdKey extends TypeKey<string>() { private _: any }
*
* class User { constructor(name: string, id: string) {} }
*
* const ct = Container.create()
* .provideInstance(NameKey, 'Alice')
* .provideInstance(IdKey, '123')
* .provide(User, {
* name: NameKey,
* id: IdKey,
* }, ({ name, id }) => new User(name, id))
*
* const user = ct.request(User)
* ```
*
* @group Dependencies
* @category TypeKey
*/
export interface TypeKey<out T = any, Def extends ComputedKey<T> = any> extends BaseTypeKey<T, Def>, AbstractKey {
/** @ignore */
readonly keyTag?: symbol
}
/** @ignore */
export interface BaseTypeKeyWithoutDefault extends BaseTypeKey<any, never> { }
/** @ignore */
export interface BaseTypeKeyWithDefault<
out T,
D,
Sync,
> extends BaseTypeKey<T, ComputedKey<T, any, D, Sync>> { }
/** @ignore */
export type KeyWithoutDefault = BaseTypeKeyWithoutDefault | ClassWithoutDefault
/** @ignore */
export type KeyWithDefault<T, D, Sync> =
| BaseTypeKeyWithDefault<T, D, Sync>
| ClassWithDefault<T, D, Sync>
/**
* Generates a base class for a class object that extends {@link TypeKey:type}\<T>.
* Classes that extend the returned base class should have a
* `private _: any` property (or any other private member) to ensure the key has its own unique type.
*
* @returns A base class that can be extended to produce a `TypeKey<T>` class object.
*
* @example
*
* ```ts
* class NameKey extends TypeKey<string>() { private _: any }
* ```
*
* @example with {@link Scope:type}:
*
* ```ts
* class NameKey extends TypeKey<string>() {
* private _: any
* static scope = Singleton
* }
* ```
*
* @example with default instance:
*
* ```ts
* class NameKey extends TypeKey({ default: Inject.value('Alice') }) {
* private _: any
* }
* ```
*
* @example with custom default:
*
* ```ts
* class NameKey extends TypeKey({
* default: Inject.map(DataSource, ds => ds.getName()),
* }) {
* private _: any
* }
* ```
*
* @example using the `TypeKey`:
*
* ### Provide
* ```ts
* const ct = Container.create().provide(NameKey, () => 'Alice')
* ```
* ### Request
* ```ts
* const name: string = ct.request(NameKey)
* ```
*
* ### Use as dependency
*
* ```ts
* const UserModule = Module(ct => ct
* .provide(User, { name: NameKey }, ({ name }) => new User(name))
* )
* ```
*
* ### Use key operators
*
* ```ts
* const UserModule = Module(ct => ct
* .provide(User, { name: NameKey.Lazy() }, ({ name }) => new User(name()))
* )
* ```
*
* @group Dependencies
* @category TypeKey
*/
export function TypeKey<T>(): TypeKeyClass<T, never>
export function TypeKey<T>(options: TypeKey.Options<T, never>): TypeKeyClass<T, never>
export function TypeKey<
Def extends ComputedKey<T>,
T = Def extends ComputedKey<infer _T> ? _T : never,
>(options: TypeKey.Options<T, Def>): TypeKeyClass<T, Def>
export function TypeKey<
Def extends ComputedKey<T>,
T,
>({ default: defaultInit, of, name = of?.name }: TypeKey.Options<T, Def> = {} as any): TypeKeyClass<T, Def> {
return asMixin(class _TypeKey {
static readonly [_typeKeySymbol]: TypeKeyClass<T, Def>[typeof _typeKeySymbol] = null
/** @ignore */
static readonly keyTag?: symbol
static readonly of = of
static get fullName() { return this.name + (name ? `(${name})` : '') }
static readonly defaultInit = defaultInit
static readonly inject = null
static toString() { return this.fullName }
}, AbstractKey)
}
/**
* @group Dependencies
* @category TypeKey
*/
export namespace TypeKey {
export interface Options<T, Def extends ComputedKey<T>> {
/**
* A class or function. The name will be used to name this `TypeKey`
* and the return type can be used to infer the target type.
*/
of?: ClassLike<T>
/** A name for this TypeKey, largely for use in error messages. */
name?: string
/** A {@link ComputedKey} providing a default value for this TypeKey if none is provided to the {@link Container}. */
default?: Def | (() => Def)
}
/** @ignore */
export function isTypeKey(target: any): target is BaseTypeKey<any> {
if (!isObject(target)) return false
return _typeKeySymbol in target
}
/** Class returned by {@link TypeKey}. Extend this to implement {@link TypeKey:type}. */
export interface TypeKeyClass<out T, Def extends ComputedKey<T>> extends
AbstractKey,
AbstractClass<any, never>,
BaseTypeKey<T, Def> { }
}
/**
* Convenience for a {@link TypeKey:type | TypeKey} that resolves a function of the form
* `(...args: Args) => T`.
*
* @group Dependencies
* @category TypeKey
*/
export interface FactoryKey<Args extends any[], T> extends TypeKey<(...args: Args) => T> { }
/**
* A specialized form of {@link TypeKey} that resolves a function of the form
* `(...args: Args) => T`.
*
* @group Dependencies
* @category TypeKey
*/
export function FactoryKey<T, Args extends any[] = []>(): TypeKeyClass<(...args: Args) => T, never>
/**
* @param fac - A default value for the factory function
*/
export function FactoryKey<T, Args extends any[]>(
fac: (...args: Args) => T,
): TypeKeyClass<(...args: Args) => T, ComputedKey.WithDepsOf<(...args: Args) => T, void>>
/**
* @param deps - A {@link DependencyKey} specifying dependencies of the factory function.
* Since the factory returns synchronously, `deps` must resolve synchronously as well.
* If `deps` cannot be resolved synchronously, consider {@link AsyncFactoryKey}.
* @param fac - A function that accepts the specified dependency followed by {@link Args}
*/
export function FactoryKey<
T,
Args extends any[],
K extends DependencyKey,
>(deps: K, fac: (deps: Target<K>, ...args: Args) => T): TypeKeyClass<
(...args: Args) => T,
ComputedKey.WithDepsOf<(...args: Args) => T, Inject.GetProvider<K>>
>
export function FactoryKey<
T,
Args extends any[],
K extends DependencyKey,
>(
...args:
| []
| [fac: (...args: Args) => T]
| [deps: K, fac: (deps: Target<K>, ...args: Args) => T]
): TypeKeyClass<
(...args: Args) => T,
ComputedKey.WithDepsOf<(...args: Args) => T, Inject.GetProvider<K>>
> {
if (args.length == 2) {
const [deps, fac] = args
return class extends TypeKey({
default: Inject.map(Inject.provider(deps), d => (...args: Args) => fac(d(), ...args))
}) {
static readonly scope?: ScopeList | (() => ScopeList) = Scope.Local
}
}
if (args.length == 1) {
return TypeKey({ default: Inject.value(args[0]) })
}
return TypeKey()
}
/**
* Convenience for a {@link TypeKey:type | TypeKey} that resolves an async function of the form
* `(...args: Args) => Promise<T>`.
*
* @group Dependencies
* @category TypeKey
*/
export type AsyncFactoryKey<Args extends any[], T> = FactoryKey<Args, Promise<T>>
/**
* A specialized form of {@link TypeKey} that resolves an async function of the form
* `(...args: Args) => Promise<T>`.
*
* @group Dependencies
* @category TypeKey
*/
export function AsyncFactoryKey<T, Args extends any[] = []>(): TypeKeyClass<(...args: Args) => Promise<T>, never>
/**
* @param fac - A default value for the async factory function
*/
export function AsyncFactoryKey<T, Args extends any[]>(
fac: (...args: Args) => T | Promise<T>,
): TypeKeyClass<(...args: Args) => Promise<T>, ComputedKey.WithDepsOf<(...args: Args) => Promise<T>, void>>
/**
* @param deps - A {@link DependencyKey} specifying dependencies of the async factory function
* @param fac - An async function that accepts the specified dependency followed by {@link Args}
*/
export function AsyncFactoryKey<
T,
Args extends any[],
K extends DependencyKey,
>(deps: K, fac: (deps: Target<K>, ...args: Args) => T | Promise<T>): TypeKeyClass<
(...args: Args) => Promise<T>,
ComputedKey.WithDepsOf<(...args: Args) => Promise<T>, Inject.Async<K>>
>
export function AsyncFactoryKey<
T,
Args extends any[],
K extends DependencyKey,
>(
...args:
| []
| [fac: (...args: Args) => T | Promise<T>]
| [deps: K, fac: (deps: Target<K>, ...args: Args) => T | Promise<T>]
): TypeKeyClass<
(...args: Args) => Promise<T>,
ComputedKey.WithDepsOf<(...args: Args) => Promise<T>, Inject.Async<K>>
> {
if (args.length == 2) {
const [deps, fac] = args
return class extends TypeKey({
default: Inject.map(Inject.async(deps).Provider(), d => (...args: Args) => d().then(d => fac(d, ...args)))
}) {
static readonly scope?: ScopeList | (() => ScopeList) = Scope.Local
}
}
if (args.length == 1) {
const fac = args[0]
return TypeKey({ default: Inject.value((...args: Args) => Promise.resolve(fac(...args))) })
}
return TypeKey()
}