@loopback/context 4.0.0-alpha.7 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +25 -0
- package/README.md +116 -0
- package/dist/binding-config.d.ts +40 -0
- package/dist/binding-config.js +33 -0
- package/dist/binding-config.js.map +1 -0
- package/dist/binding-decorator.d.ts +45 -0
- package/dist/binding-decorator.js +118 -0
- package/dist/binding-decorator.js.map +1 -0
- package/dist/binding-filter.d.ts +108 -0
- package/dist/binding-filter.js +162 -0
- package/dist/binding-filter.js.map +1 -0
- package/dist/binding-inspector.d.ts +150 -0
- package/dist/binding-inspector.js +249 -0
- package/dist/binding-inspector.js.map +1 -0
- package/dist/binding-key.d.ts +66 -0
- package/dist/binding-key.js +121 -0
- package/dist/binding-key.js.map +1 -0
- package/dist/binding-sorter.d.ts +71 -0
- package/dist/binding-sorter.js +89 -0
- package/dist/binding-sorter.js.map +1 -0
- package/dist/binding.d.ts +577 -0
- package/dist/binding.js +788 -0
- package/dist/binding.js.map +1 -0
- package/dist/context-event.d.ts +23 -0
- package/dist/context-event.js +7 -0
- package/dist/context-event.js.map +1 -0
- package/dist/context-observer.d.ts +36 -0
- package/dist/context-observer.js +7 -0
- package/dist/context-observer.js.map +1 -0
- package/dist/context-subscription.d.ts +147 -0
- package/dist/context-subscription.js +317 -0
- package/dist/context-subscription.js.map +1 -0
- package/dist/context-tag-indexer.d.ts +42 -0
- package/dist/context-tag-indexer.js +135 -0
- package/dist/context-tag-indexer.js.map +1 -0
- package/dist/context-view.d.ts +209 -0
- package/dist/context-view.js +240 -0
- package/dist/context-view.js.map +1 -0
- package/dist/context.d.ts +513 -0
- package/dist/context.js +717 -0
- package/dist/context.js.map +1 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.js +60 -0
- package/dist/index.js.map +1 -0
- package/dist/inject-config.d.ts +67 -0
- package/dist/inject-config.js +181 -0
- package/dist/inject-config.js.map +1 -0
- package/dist/inject.d.ts +250 -0
- package/dist/inject.js +535 -0
- package/dist/inject.js.map +1 -0
- package/dist/interception-proxy.d.ts +76 -0
- package/dist/interception-proxy.js +67 -0
- package/dist/interception-proxy.js.map +1 -0
- package/dist/interceptor-chain.d.ts +121 -0
- package/dist/interceptor-chain.js +148 -0
- package/dist/interceptor-chain.js.map +1 -0
- package/dist/interceptor.d.ts +138 -0
- package/dist/interceptor.js +299 -0
- package/dist/interceptor.js.map +1 -0
- package/dist/invocation.d.ts +101 -0
- package/dist/invocation.js +163 -0
- package/dist/invocation.js.map +1 -0
- package/dist/json-types.d.ts +28 -0
- package/dist/json-types.js +7 -0
- package/dist/json-types.js.map +1 -0
- package/dist/keys.d.ts +65 -0
- package/dist/keys.js +74 -0
- package/dist/keys.js.map +1 -0
- package/dist/provider.d.ts +31 -0
- package/dist/provider.js +7 -0
- package/dist/provider.js.map +1 -0
- package/dist/resolution-session.d.ts +180 -0
- package/dist/resolution-session.js +274 -0
- package/dist/resolution-session.js.map +1 -0
- package/dist/resolver.d.ts +46 -0
- package/dist/resolver.js +203 -0
- package/dist/resolver.js.map +1 -0
- package/dist/unique-id.d.ts +14 -0
- package/dist/unique-id.js +26 -0
- package/dist/unique-id.js.map +1 -0
- package/dist/value-promise.d.ts +134 -0
- package/dist/value-promise.js +277 -0
- package/dist/value-promise.js.map +1 -0
- package/package.json +49 -35
- package/src/binding-config.ts +73 -0
- package/src/binding-decorator.ts +136 -0
- package/src/binding-filter.ts +250 -0
- package/src/binding-inspector.ts +371 -0
- package/src/binding-key.ts +136 -0
- package/src/binding-sorter.ts +124 -0
- package/src/binding.ts +1107 -0
- package/src/context-event.ts +30 -0
- package/src/context-observer.ts +50 -0
- package/src/context-subscription.ts +402 -0
- package/src/context-tag-indexer.ts +147 -0
- package/src/context-view.ts +440 -0
- package/src/context.ts +1079 -0
- package/src/index.ts +58 -0
- package/src/inject-config.ts +239 -0
- package/src/inject.ts +796 -0
- package/src/interception-proxy.ts +127 -0
- package/src/interceptor-chain.ts +268 -0
- package/src/interceptor.ts +430 -0
- package/src/invocation.ts +269 -0
- package/src/json-types.ts +35 -0
- package/src/keys.ts +85 -0
- package/src/provider.ts +37 -0
- package/src/resolution-session.ts +414 -0
- package/src/resolver.ts +282 -0
- package/src/unique-id.ts +24 -0
- package/src/value-promise.ts +318 -0
- package/index.d.ts +0 -6
- package/index.js +0 -9
- package/lib/binding.d.ts +0 -75
- package/lib/binding.js +0 -102
- package/lib/context.d.ts +0 -14
- package/lib/context.js +0 -96
- package/lib/index.d.ts +0 -5
- package/lib/index.js +0 -13
- package/lib/inject.d.ts +0 -47
- package/lib/inject.js +0 -73
- package/lib/isPromise.d.ts +0 -1
- package/lib/isPromise.js +0 -14
- package/lib/resolver.d.ts +0 -30
- package/lib/resolver.js +0 -128
- package/lib6/binding.d.ts +0 -75
- package/lib6/binding.js +0 -102
- package/lib6/context.d.ts +0 -14
- package/lib6/context.js +0 -96
- package/lib6/index.d.ts +0 -5
- package/lib6/index.js +0 -13
- package/lib6/inject.d.ts +0 -47
- package/lib6/inject.js +0 -73
- package/lib6/isPromise.d.ts +0 -1
- package/lib6/isPromise.js +0 -14
- package/lib6/resolver.d.ts +0 -30
- package/lib6/resolver.js +0 -128
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
// Copyright IBM Corp. 2019,2020. All Rights Reserved.
|
|
2
|
+
// Node module: @loopback/context
|
|
3
|
+
// This file is licensed under the MIT License.
|
|
4
|
+
// License text available at https://opensource.org/licenses/MIT
|
|
5
|
+
|
|
6
|
+
import {Binding, BindingTag} from './binding';
|
|
7
|
+
import {BindingAddress} from './binding-key';
|
|
8
|
+
import {MapObject} from './value-promise';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A function that filters bindings. It returns `true` to select a given
|
|
12
|
+
* binding.
|
|
13
|
+
*
|
|
14
|
+
* @remarks
|
|
15
|
+
* Originally, we allowed filters to be tied with a single value type.
|
|
16
|
+
* This actually does not make much sense - the filter function is typically
|
|
17
|
+
* invoked on all bindings to find those ones matching the given criteria.
|
|
18
|
+
* Filters must be prepared to handle bindings of any value type. We learned
|
|
19
|
+
* about this problem after enabling TypeScript's `strictFunctionTypes` check.
|
|
20
|
+
* This aspect is resolved by typing the input argument as `Binding<unknown>`.
|
|
21
|
+
*
|
|
22
|
+
* Ideally, `BindingFilter` should be declared as a type guard as follows:
|
|
23
|
+
* ```ts
|
|
24
|
+
* export type BindingFilterGuard<ValueType = unknown> = (
|
|
25
|
+
* binding: Readonly<Binding<unknown>>,
|
|
26
|
+
* ) => binding is Readonly<Binding<ValueType>>;
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* But TypeScript treats the following types as incompatible and does not accept
|
|
30
|
+
* type 1 for type 2.
|
|
31
|
+
*
|
|
32
|
+
* 1. `(binding: Readonly<Binding<unknown>>) => boolean`
|
|
33
|
+
* 2. `(binding: Readonly<Binding<unknown>>) => binding is Readonly<Binding<ValueType>>`
|
|
34
|
+
*
|
|
35
|
+
* If we described BindingFilter as a type-guard, then all filter implementations
|
|
36
|
+
* would have to be explicitly typed as type-guards too, which would make it
|
|
37
|
+
* tedious to write quick filter functions like `b => b.key.startsWith('services')`.
|
|
38
|
+
*
|
|
39
|
+
* To keep things simple and easy to use, we use `boolean` as the return type
|
|
40
|
+
* of a binding filter function.
|
|
41
|
+
*/
|
|
42
|
+
export interface BindingFilter {
|
|
43
|
+
(binding: Readonly<Binding<unknown>>): boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Select binding(s) by key or a filter function
|
|
48
|
+
*/
|
|
49
|
+
export type BindingSelector<ValueType = unknown> =
|
|
50
|
+
| BindingAddress<ValueType>
|
|
51
|
+
| BindingFilter;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Check if an object is a `BindingKey` by duck typing
|
|
55
|
+
* @param selector Binding selector
|
|
56
|
+
*/
|
|
57
|
+
function isBindingKey(selector: BindingSelector) {
|
|
58
|
+
if (selector == null || typeof selector !== 'object') return false;
|
|
59
|
+
return (
|
|
60
|
+
typeof selector.key === 'string' &&
|
|
61
|
+
typeof selector.deepProperty === 'function'
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Type guard for binding address
|
|
67
|
+
* @param bindingSelector - Binding key or filter function
|
|
68
|
+
*/
|
|
69
|
+
export function isBindingAddress(
|
|
70
|
+
bindingSelector: BindingSelector,
|
|
71
|
+
): bindingSelector is BindingAddress {
|
|
72
|
+
return (
|
|
73
|
+
typeof bindingSelector !== 'function' &&
|
|
74
|
+
(typeof bindingSelector === 'string' ||
|
|
75
|
+
// See https://github.com/loopbackio/loopback-next/issues/4570
|
|
76
|
+
// `bindingSelector instanceof BindingKey` is not always reliable as the
|
|
77
|
+
// `@loopback/context` module might be loaded from multiple locations if
|
|
78
|
+
// `npm install` does not dedupe or there are mixed versions in the tree
|
|
79
|
+
isBindingKey(bindingSelector))
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Binding filter function that holds a binding tag pattern. `Context.find()`
|
|
85
|
+
* uses the `bindingTagPattern` to optimize the matching of bindings by tag to
|
|
86
|
+
* avoid expensive check for all bindings.
|
|
87
|
+
*/
|
|
88
|
+
export interface BindingTagFilter extends BindingFilter {
|
|
89
|
+
/**
|
|
90
|
+
* A special property on the filter function to provide access to the binding
|
|
91
|
+
* tag pattern which can be utilized to optimize the matching of bindings by
|
|
92
|
+
* tag in a context.
|
|
93
|
+
*/
|
|
94
|
+
bindingTagPattern: BindingTag | RegExp;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Type guard for BindingTagFilter
|
|
99
|
+
* @param filter - A BindingFilter function
|
|
100
|
+
*/
|
|
101
|
+
export function isBindingTagFilter(
|
|
102
|
+
filter?: BindingFilter,
|
|
103
|
+
): filter is BindingTagFilter {
|
|
104
|
+
if (filter == null || !('bindingTagPattern' in filter)) return false;
|
|
105
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
106
|
+
const tagPattern = (filter as any).bindingTagPattern;
|
|
107
|
+
return (
|
|
108
|
+
tagPattern instanceof RegExp ||
|
|
109
|
+
typeof tagPattern === 'string' ||
|
|
110
|
+
typeof tagPattern === 'object'
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* A function to check if a given tag value is matched for `filterByTag`
|
|
116
|
+
*/
|
|
117
|
+
export interface TagValueMatcher {
|
|
118
|
+
/**
|
|
119
|
+
* Check if the given tag value matches the search criteria
|
|
120
|
+
* @param tagValue - Tag value from the binding
|
|
121
|
+
* @param tagName - Tag name
|
|
122
|
+
* @param tagMap - Tag map from the binding
|
|
123
|
+
*/
|
|
124
|
+
(tagValue: unknown, tagName: string, tagMap: MapObject<unknown>): boolean;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* A symbol that can be used to match binding tags by name regardless of the
|
|
129
|
+
* value.
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
*
|
|
133
|
+
* The following code matches bindings with tag `{controller: 'A'}` or
|
|
134
|
+
* `{controller: 'controller'}`. But if the tag name 'controller' does not
|
|
135
|
+
* exist for a binding, the binding will NOT be included.
|
|
136
|
+
*
|
|
137
|
+
* ```ts
|
|
138
|
+
* ctx.findByTag({controller: ANY_TAG_VALUE})
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
export const ANY_TAG_VALUE: TagValueMatcher = (tagValue, tagName, tagMap) =>
|
|
142
|
+
tagName in tagMap;
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Create a tag value matcher function that returns `true` if the target tag
|
|
146
|
+
* value equals to the item value or is an array that includes the item value.
|
|
147
|
+
* @param itemValues - A list of tag item value
|
|
148
|
+
*/
|
|
149
|
+
export function includesTagValue(...itemValues: unknown[]): TagValueMatcher {
|
|
150
|
+
return tagValue => {
|
|
151
|
+
return itemValues.some(
|
|
152
|
+
itemValue =>
|
|
153
|
+
// The tag value equals the item value
|
|
154
|
+
tagValue === itemValue ||
|
|
155
|
+
// The tag value contains the item value
|
|
156
|
+
(Array.isArray(tagValue) && tagValue.includes(itemValue)),
|
|
157
|
+
);
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Create a binding filter for the tag pattern
|
|
163
|
+
* @param tagPattern - Binding tag name, regexp, or object
|
|
164
|
+
*/
|
|
165
|
+
export function filterByTag(tagPattern: BindingTag | RegExp): BindingTagFilter {
|
|
166
|
+
let filter: BindingFilter;
|
|
167
|
+
let regex: RegExp | undefined = undefined;
|
|
168
|
+
if (tagPattern instanceof RegExp) {
|
|
169
|
+
// RegExp for tag names
|
|
170
|
+
regex = tagPattern;
|
|
171
|
+
}
|
|
172
|
+
if (
|
|
173
|
+
typeof tagPattern === 'string' &&
|
|
174
|
+
(tagPattern.includes('*') || tagPattern.includes('?'))
|
|
175
|
+
) {
|
|
176
|
+
// Wildcard tag name
|
|
177
|
+
regex = wildcardToRegExp(tagPattern);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (regex != null) {
|
|
181
|
+
// RegExp or wildcard match
|
|
182
|
+
filter = b => b.tagNames.some(t => regex!.test(t));
|
|
183
|
+
} else if (typeof tagPattern === 'string') {
|
|
184
|
+
// Plain tag string match
|
|
185
|
+
filter = b => b.tagNames.includes(tagPattern);
|
|
186
|
+
} else {
|
|
187
|
+
// Match tag name/value pairs
|
|
188
|
+
const tagMap = tagPattern as MapObject<unknown>;
|
|
189
|
+
filter = b => {
|
|
190
|
+
for (const t in tagMap) {
|
|
191
|
+
if (!matchTagValue(tagMap[t], t, b.tagMap)) return false;
|
|
192
|
+
}
|
|
193
|
+
// All tag name/value pairs match
|
|
194
|
+
return true;
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
// Set up binding tag for the filter
|
|
198
|
+
const tagFilter = filter as BindingTagFilter;
|
|
199
|
+
tagFilter.bindingTagPattern = regex ?? tagPattern;
|
|
200
|
+
return tagFilter;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function matchTagValue(
|
|
204
|
+
tagValueOrMatcher: unknown,
|
|
205
|
+
tagName: string,
|
|
206
|
+
tagMap: MapObject<unknown>,
|
|
207
|
+
) {
|
|
208
|
+
const tagValue = tagMap[tagName];
|
|
209
|
+
if (tagValue === tagValueOrMatcher) return true;
|
|
210
|
+
|
|
211
|
+
if (typeof tagValueOrMatcher === 'function') {
|
|
212
|
+
return (tagValueOrMatcher as TagValueMatcher)(tagValue, tagName, tagMap);
|
|
213
|
+
}
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Create a binding filter from key pattern
|
|
219
|
+
* @param keyPattern - Binding key/wildcard, regexp, or a filter function
|
|
220
|
+
*/
|
|
221
|
+
export function filterByKey(
|
|
222
|
+
keyPattern?: string | RegExp | BindingFilter,
|
|
223
|
+
): BindingFilter {
|
|
224
|
+
if (typeof keyPattern === 'string') {
|
|
225
|
+
const regex = wildcardToRegExp(keyPattern);
|
|
226
|
+
return binding => regex.test(binding.key);
|
|
227
|
+
} else if (keyPattern instanceof RegExp) {
|
|
228
|
+
return binding => keyPattern.test(binding.key);
|
|
229
|
+
} else if (typeof keyPattern === 'function') {
|
|
230
|
+
return keyPattern;
|
|
231
|
+
}
|
|
232
|
+
return () => true;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Convert a wildcard pattern to RegExp
|
|
237
|
+
* @param pattern - A wildcard string with `*` and `?` as special characters.
|
|
238
|
+
* - `*` matches zero or more characters except `.` and `:`
|
|
239
|
+
* - `?` matches exactly one character except `.` and `:`
|
|
240
|
+
*/
|
|
241
|
+
function wildcardToRegExp(pattern: string): RegExp {
|
|
242
|
+
// Escape reserved chars for RegExp:
|
|
243
|
+
// `- \ ^ $ + . ( ) | { } [ ] :`
|
|
244
|
+
let regexp = pattern.replace(/[\-\[\]\/\{\}\(\)\+\.\\\^\$\|\:]/g, '\\$&');
|
|
245
|
+
// Replace wildcard chars `*` and `?`
|
|
246
|
+
// `*` matches zero or more characters except `.` and `:`
|
|
247
|
+
// `?` matches one character except `.` and `:`
|
|
248
|
+
regexp = regexp.replace(/\*/g, '[^.:]*').replace(/\?/g, '[^.:]');
|
|
249
|
+
return new RegExp(`^${regexp}$`);
|
|
250
|
+
}
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
// Copyright IBM Corp. 2018,2020. All Rights Reserved.
|
|
2
|
+
// Node module: @loopback/context
|
|
3
|
+
// This file is licensed under the MIT License.
|
|
4
|
+
// License text available at https://opensource.org/licenses/MIT
|
|
5
|
+
|
|
6
|
+
import {MetadataAccessor, MetadataInspector} from '@loopback/metadata';
|
|
7
|
+
import debugFactory from 'debug';
|
|
8
|
+
import {
|
|
9
|
+
Binding,
|
|
10
|
+
BindingScope,
|
|
11
|
+
BindingTag,
|
|
12
|
+
BindingTemplate,
|
|
13
|
+
DynamicValueProviderClass,
|
|
14
|
+
isDynamicValueProviderClass,
|
|
15
|
+
} from './binding';
|
|
16
|
+
import {BindingAddress} from './binding-key';
|
|
17
|
+
import {ContextTags} from './keys';
|
|
18
|
+
import {Provider} from './provider';
|
|
19
|
+
import {Constructor} from './value-promise';
|
|
20
|
+
|
|
21
|
+
const debug = debugFactory('loopback:context:binding-inspector');
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Binding metadata from `@injectable`
|
|
25
|
+
*
|
|
26
|
+
* @typeParam T - Value type
|
|
27
|
+
*/
|
|
28
|
+
export type BindingMetadata<T = unknown> = {
|
|
29
|
+
/**
|
|
30
|
+
* An array of template functions to configure a binding
|
|
31
|
+
*/
|
|
32
|
+
templates: BindingTemplate<T>[];
|
|
33
|
+
/**
|
|
34
|
+
* The target class where binding metadata is decorated
|
|
35
|
+
*/
|
|
36
|
+
target: Constructor<T>;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Metadata key for binding metadata
|
|
41
|
+
*/
|
|
42
|
+
export const BINDING_METADATA_KEY = MetadataAccessor.create<
|
|
43
|
+
BindingMetadata,
|
|
44
|
+
ClassDecorator
|
|
45
|
+
>('binding.metadata');
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* An object to configure binding scope and tags
|
|
49
|
+
*/
|
|
50
|
+
export type BindingScopeAndTags = {
|
|
51
|
+
scope?: BindingScope;
|
|
52
|
+
tags?: BindingTag | BindingTag[];
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Specification of parameters for `@injectable()`
|
|
57
|
+
*/
|
|
58
|
+
export type BindingSpec<T = unknown> = BindingTemplate<T> | BindingScopeAndTags;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Check if a class implements `Provider` interface
|
|
62
|
+
* @param cls - A class
|
|
63
|
+
*
|
|
64
|
+
* @typeParam T - Value type
|
|
65
|
+
*/
|
|
66
|
+
export function isProviderClass<T>(
|
|
67
|
+
cls: unknown,
|
|
68
|
+
): cls is Constructor<Provider<T>> {
|
|
69
|
+
return (
|
|
70
|
+
typeof cls === 'function' && typeof cls.prototype?.value === 'function'
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* A factory function to create a template function to bind the target class
|
|
76
|
+
* as a `Provider`.
|
|
77
|
+
* @param target - Target provider class
|
|
78
|
+
*
|
|
79
|
+
* @typeParam T - Value type
|
|
80
|
+
*/
|
|
81
|
+
export function asProvider<T>(
|
|
82
|
+
target: Constructor<Provider<T>>,
|
|
83
|
+
): BindingTemplate<T> {
|
|
84
|
+
return function bindAsProvider(binding) {
|
|
85
|
+
binding.toProvider(target).tag(ContextTags.PROVIDER, {
|
|
86
|
+
[ContextTags.TYPE]: ContextTags.PROVIDER,
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* A factory function to create a template function to bind the target class
|
|
93
|
+
* as a class or `Provider`.
|
|
94
|
+
* @param target - Target class, which can be an implementation of `Provider`
|
|
95
|
+
* or `DynamicValueProviderClass`
|
|
96
|
+
*
|
|
97
|
+
* @typeParam T - Value type
|
|
98
|
+
*/
|
|
99
|
+
export function asClassOrProvider<T>(
|
|
100
|
+
target: Constructor<T | Provider<T>> | DynamicValueProviderClass<T>,
|
|
101
|
+
): BindingTemplate<T> {
|
|
102
|
+
// Add a template to bind to a class or provider
|
|
103
|
+
return function bindAsClassOrProvider(binding) {
|
|
104
|
+
if (isProviderClass(target)) {
|
|
105
|
+
asProvider(target)(binding);
|
|
106
|
+
} else if (isDynamicValueProviderClass<T>(target)) {
|
|
107
|
+
binding.toDynamicValue(target).tag(ContextTags.DYNAMIC_VALUE_PROVIDER, {
|
|
108
|
+
[ContextTags.TYPE]: ContextTags.DYNAMIC_VALUE_PROVIDER,
|
|
109
|
+
});
|
|
110
|
+
} else {
|
|
111
|
+
binding.toClass(target as Constructor<T>);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Convert binding scope and tags as a template function
|
|
118
|
+
* @param scopeAndTags - Binding scope and tags
|
|
119
|
+
*
|
|
120
|
+
* @typeParam T - Value type
|
|
121
|
+
*/
|
|
122
|
+
export function asBindingTemplate<T = unknown>(
|
|
123
|
+
scopeAndTags: BindingScopeAndTags,
|
|
124
|
+
): BindingTemplate<T> {
|
|
125
|
+
return function applyBindingScopeAndTag(binding) {
|
|
126
|
+
if (scopeAndTags.scope) {
|
|
127
|
+
binding.inScope(scopeAndTags.scope);
|
|
128
|
+
}
|
|
129
|
+
if (scopeAndTags.tags) {
|
|
130
|
+
if (Array.isArray(scopeAndTags.tags)) {
|
|
131
|
+
binding.tag(...scopeAndTags.tags);
|
|
132
|
+
} else {
|
|
133
|
+
binding.tag(scopeAndTags.tags);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Get binding metadata for a class
|
|
141
|
+
* @param target - The target class
|
|
142
|
+
*
|
|
143
|
+
* @typeParam T - Value type
|
|
144
|
+
*/
|
|
145
|
+
export function getBindingMetadata<T = unknown>(
|
|
146
|
+
target: Function,
|
|
147
|
+
): BindingMetadata<T> | undefined {
|
|
148
|
+
return MetadataInspector.getClassMetadata<BindingMetadata<T>>(
|
|
149
|
+
BINDING_METADATA_KEY,
|
|
150
|
+
target,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* A binding template function to delete `name` and `key` tags
|
|
156
|
+
*/
|
|
157
|
+
export function removeNameAndKeyTags(binding: Binding<unknown>) {
|
|
158
|
+
if (binding.tagMap) {
|
|
159
|
+
delete binding.tagMap.name;
|
|
160
|
+
delete binding.tagMap.key;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Get the binding template for a class with binding metadata
|
|
166
|
+
*
|
|
167
|
+
* @param cls - A class with optional `@injectable`
|
|
168
|
+
*
|
|
169
|
+
* @typeParam T - Value type
|
|
170
|
+
*/
|
|
171
|
+
export function bindingTemplateFor<T>(
|
|
172
|
+
cls: Constructor<T | Provider<T>> | DynamicValueProviderClass<T>,
|
|
173
|
+
options?: BindingFromClassOptions,
|
|
174
|
+
): BindingTemplate<T> {
|
|
175
|
+
const spec = getBindingMetadata(cls);
|
|
176
|
+
debug('class %s has binding metadata', cls.name, spec);
|
|
177
|
+
const templateFunctions = spec?.templates ?? [];
|
|
178
|
+
if (spec?.target !== cls) {
|
|
179
|
+
// Make sure the subclass is used as the binding source
|
|
180
|
+
templateFunctions.push(asClassOrProvider(cls) as BindingTemplate<unknown>);
|
|
181
|
+
}
|
|
182
|
+
return function applyBindingTemplatesFromMetadata(binding) {
|
|
183
|
+
for (const t of templateFunctions) {
|
|
184
|
+
binding.apply(t);
|
|
185
|
+
}
|
|
186
|
+
if (spec?.target !== cls) {
|
|
187
|
+
// Remove name/key tags inherited from base classes
|
|
188
|
+
binding.apply(removeNameAndKeyTags);
|
|
189
|
+
}
|
|
190
|
+
if (options != null) {
|
|
191
|
+
applyClassBindingOptions(binding, options);
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Mapping artifact types to binding key namespaces (prefixes).
|
|
198
|
+
*
|
|
199
|
+
* @example
|
|
200
|
+
* ```ts
|
|
201
|
+
* {
|
|
202
|
+
* repository: 'repositories'
|
|
203
|
+
* }
|
|
204
|
+
* ```
|
|
205
|
+
*/
|
|
206
|
+
export type TypeNamespaceMapping = {[name: string]: string};
|
|
207
|
+
|
|
208
|
+
export const DEFAULT_TYPE_NAMESPACES: TypeNamespaceMapping = {
|
|
209
|
+
class: 'classes',
|
|
210
|
+
provider: 'providers',
|
|
211
|
+
dynamicValueProvider: 'dynamicValueProviders',
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Options to customize the binding created from a class
|
|
216
|
+
*/
|
|
217
|
+
export type BindingFromClassOptions = {
|
|
218
|
+
/**
|
|
219
|
+
* Binding key
|
|
220
|
+
*/
|
|
221
|
+
key?: BindingAddress;
|
|
222
|
+
/**
|
|
223
|
+
* Artifact type, such as `server`, `controller`, `repository` or `service`
|
|
224
|
+
*/
|
|
225
|
+
type?: string;
|
|
226
|
+
/**
|
|
227
|
+
* Artifact name, such as `my-rest-server` and `my-controller`. It
|
|
228
|
+
* overrides the name tag
|
|
229
|
+
*/
|
|
230
|
+
name?: string;
|
|
231
|
+
/**
|
|
232
|
+
* Namespace for the binding key, such as `servers` and `controllers`. It
|
|
233
|
+
* overrides the default namespace or namespace tag
|
|
234
|
+
*/
|
|
235
|
+
namespace?: string;
|
|
236
|
+
/**
|
|
237
|
+
* Mapping artifact type to binding key namespaces
|
|
238
|
+
*/
|
|
239
|
+
typeNamespaceMapping?: TypeNamespaceMapping;
|
|
240
|
+
/**
|
|
241
|
+
* Default namespace if the binding does not have an explicit namespace
|
|
242
|
+
*/
|
|
243
|
+
defaultNamespace?: string;
|
|
244
|
+
/**
|
|
245
|
+
* Default scope if the binding does not have an explicit scope
|
|
246
|
+
*/
|
|
247
|
+
defaultScope?: BindingScope;
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Create a binding from a class with decorated metadata. The class is attached
|
|
252
|
+
* to the binding as follows:
|
|
253
|
+
* - `binding.toClass(cls)`: if `cls` is a plain class such as `MyController`
|
|
254
|
+
* - `binding.toProvider(cls)`: if `cls` is a value provider class with a
|
|
255
|
+
* prototype method `value()`
|
|
256
|
+
* - `binding.toDynamicValue(cls)`: if `cls` is a dynamic value provider class
|
|
257
|
+
* with a static method `value()`
|
|
258
|
+
*
|
|
259
|
+
* @param cls - A class. It can be either a plain class, a value provider class,
|
|
260
|
+
* or a dynamic value provider class
|
|
261
|
+
* @param options - Options to customize the binding key
|
|
262
|
+
*
|
|
263
|
+
* @typeParam T - Value type
|
|
264
|
+
*/
|
|
265
|
+
export function createBindingFromClass<T>(
|
|
266
|
+
cls: Constructor<T | Provider<T>> | DynamicValueProviderClass<T>,
|
|
267
|
+
options: BindingFromClassOptions = {},
|
|
268
|
+
): Binding<T> {
|
|
269
|
+
debug('create binding from class %s with options', cls.name, options);
|
|
270
|
+
try {
|
|
271
|
+
const templateFn = bindingTemplateFor(cls, options);
|
|
272
|
+
const key = buildBindingKey(cls, options);
|
|
273
|
+
const binding = Binding.bind<T>(key).apply(templateFn);
|
|
274
|
+
return binding;
|
|
275
|
+
} catch (err) {
|
|
276
|
+
err.message += ` (while building binding for class ${cls.name})`;
|
|
277
|
+
throw err;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function applyClassBindingOptions<T>(
|
|
282
|
+
binding: Binding<T>,
|
|
283
|
+
options: BindingFromClassOptions,
|
|
284
|
+
) {
|
|
285
|
+
if (options.name) {
|
|
286
|
+
binding.tag({name: options.name});
|
|
287
|
+
}
|
|
288
|
+
if (options.type) {
|
|
289
|
+
binding.tag({type: options.type}, options.type);
|
|
290
|
+
}
|
|
291
|
+
if (options.defaultScope) {
|
|
292
|
+
binding.applyDefaultScope(options.defaultScope);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Find/infer binding key namespace for a type
|
|
298
|
+
* @param type - Artifact type, such as `controller`, `datasource`, or `server`
|
|
299
|
+
* @param typeNamespaces - An object mapping type names to namespaces
|
|
300
|
+
*/
|
|
301
|
+
function getNamespace(type: string, typeNamespaces = DEFAULT_TYPE_NAMESPACES) {
|
|
302
|
+
if (type in typeNamespaces) {
|
|
303
|
+
return typeNamespaces[type];
|
|
304
|
+
} else {
|
|
305
|
+
// Return the plural form
|
|
306
|
+
return `${type}s`;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Build the binding key for a class with optional binding metadata.
|
|
312
|
+
* The binding key is resolved in the following steps:
|
|
313
|
+
*
|
|
314
|
+
* 1. Check `options.key`, if it exists, return it
|
|
315
|
+
* 2. Check if the binding metadata has `key` tag, if yes, return its tag value
|
|
316
|
+
* 3. Identify `namespace` and `name` to form the binding key as
|
|
317
|
+
* `<namespace>.<name>`.
|
|
318
|
+
* - namespace
|
|
319
|
+
* - `options.namespace`
|
|
320
|
+
* - `namespace` tag value
|
|
321
|
+
* - Map `options.type` or `type` tag value to a namespace, for example,
|
|
322
|
+
* 'controller` to 'controller'.
|
|
323
|
+
* - name
|
|
324
|
+
* - `options.name`
|
|
325
|
+
* - `name` tag value
|
|
326
|
+
* - the class name
|
|
327
|
+
*
|
|
328
|
+
* @param cls - A class to be bound
|
|
329
|
+
* @param options - Options to customize how to build the key
|
|
330
|
+
*
|
|
331
|
+
* @typeParam T - Value type
|
|
332
|
+
*/
|
|
333
|
+
function buildBindingKey<T>(
|
|
334
|
+
cls: Constructor<T | Provider<T>>,
|
|
335
|
+
options: BindingFromClassOptions = {},
|
|
336
|
+
) {
|
|
337
|
+
if (options.key) return options.key;
|
|
338
|
+
|
|
339
|
+
const templateFn = bindingTemplateFor(cls);
|
|
340
|
+
// Create a temporary binding
|
|
341
|
+
const bindingTemplate = new Binding('template').apply(templateFn);
|
|
342
|
+
// Is there a `key` tag?
|
|
343
|
+
let key: string = bindingTemplate.tagMap[ContextTags.KEY];
|
|
344
|
+
if (key) return key;
|
|
345
|
+
|
|
346
|
+
let namespace =
|
|
347
|
+
options.namespace ??
|
|
348
|
+
bindingTemplate.tagMap[ContextTags.NAMESPACE] ??
|
|
349
|
+
options.defaultNamespace;
|
|
350
|
+
if (!namespace) {
|
|
351
|
+
const namespaces = Object.assign(
|
|
352
|
+
{},
|
|
353
|
+
DEFAULT_TYPE_NAMESPACES,
|
|
354
|
+
options.typeNamespaceMapping,
|
|
355
|
+
);
|
|
356
|
+
// Derive the key from type + name
|
|
357
|
+
let type = options.type ?? bindingTemplate.tagMap[ContextTags.TYPE];
|
|
358
|
+
if (!type) {
|
|
359
|
+
type =
|
|
360
|
+
bindingTemplate.tagNames.find(t => namespaces[t] != null) ??
|
|
361
|
+
ContextTags.CLASS;
|
|
362
|
+
}
|
|
363
|
+
namespace = getNamespace(type, namespaces);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const name =
|
|
367
|
+
options.name ?? (bindingTemplate.tagMap[ContextTags.NAME] || cls.name);
|
|
368
|
+
key = `${namespace}.${name}`;
|
|
369
|
+
|
|
370
|
+
return key;
|
|
371
|
+
}
|