@loopback/context 4.0.0-alpha.6 → 4.0.0

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.
Files changed (137) hide show
  1. package/LICENSE +25 -0
  2. package/README.md +116 -0
  3. package/dist/binding-config.d.ts +40 -0
  4. package/dist/binding-config.js +33 -0
  5. package/dist/binding-config.js.map +1 -0
  6. package/dist/binding-decorator.d.ts +45 -0
  7. package/dist/binding-decorator.js +118 -0
  8. package/dist/binding-decorator.js.map +1 -0
  9. package/dist/binding-filter.d.ts +108 -0
  10. package/dist/binding-filter.js +162 -0
  11. package/dist/binding-filter.js.map +1 -0
  12. package/dist/binding-inspector.d.ts +150 -0
  13. package/dist/binding-inspector.js +249 -0
  14. package/dist/binding-inspector.js.map +1 -0
  15. package/dist/binding-key.d.ts +66 -0
  16. package/dist/binding-key.js +121 -0
  17. package/dist/binding-key.js.map +1 -0
  18. package/dist/binding-sorter.d.ts +71 -0
  19. package/dist/binding-sorter.js +89 -0
  20. package/dist/binding-sorter.js.map +1 -0
  21. package/dist/binding.d.ts +577 -0
  22. package/dist/binding.js +788 -0
  23. package/dist/binding.js.map +1 -0
  24. package/dist/context-event.d.ts +23 -0
  25. package/dist/context-event.js +7 -0
  26. package/dist/context-event.js.map +1 -0
  27. package/dist/context-observer.d.ts +36 -0
  28. package/dist/context-observer.js +7 -0
  29. package/dist/context-observer.js.map +1 -0
  30. package/dist/context-subscription.d.ts +147 -0
  31. package/dist/context-subscription.js +317 -0
  32. package/dist/context-subscription.js.map +1 -0
  33. package/dist/context-tag-indexer.d.ts +42 -0
  34. package/dist/context-tag-indexer.js +135 -0
  35. package/dist/context-tag-indexer.js.map +1 -0
  36. package/dist/context-view.d.ts +209 -0
  37. package/dist/context-view.js +240 -0
  38. package/dist/context-view.js.map +1 -0
  39. package/dist/context.d.ts +513 -0
  40. package/dist/context.js +717 -0
  41. package/dist/context.js.map +1 -0
  42. package/dist/index.d.ts +52 -0
  43. package/dist/index.js +60 -0
  44. package/dist/index.js.map +1 -0
  45. package/dist/inject-config.d.ts +67 -0
  46. package/dist/inject-config.js +181 -0
  47. package/dist/inject-config.js.map +1 -0
  48. package/dist/inject.d.ts +250 -0
  49. package/dist/inject.js +535 -0
  50. package/dist/inject.js.map +1 -0
  51. package/dist/interception-proxy.d.ts +76 -0
  52. package/dist/interception-proxy.js +67 -0
  53. package/dist/interception-proxy.js.map +1 -0
  54. package/dist/interceptor-chain.d.ts +121 -0
  55. package/dist/interceptor-chain.js +148 -0
  56. package/dist/interceptor-chain.js.map +1 -0
  57. package/dist/interceptor.d.ts +138 -0
  58. package/dist/interceptor.js +299 -0
  59. package/dist/interceptor.js.map +1 -0
  60. package/dist/invocation.d.ts +101 -0
  61. package/dist/invocation.js +163 -0
  62. package/dist/invocation.js.map +1 -0
  63. package/dist/json-types.d.ts +28 -0
  64. package/dist/json-types.js +7 -0
  65. package/dist/json-types.js.map +1 -0
  66. package/dist/keys.d.ts +65 -0
  67. package/dist/keys.js +74 -0
  68. package/dist/keys.js.map +1 -0
  69. package/dist/provider.d.ts +31 -0
  70. package/dist/provider.js +7 -0
  71. package/dist/provider.js.map +1 -0
  72. package/dist/resolution-session.d.ts +180 -0
  73. package/dist/resolution-session.js +274 -0
  74. package/dist/resolution-session.js.map +1 -0
  75. package/dist/resolver.d.ts +46 -0
  76. package/dist/resolver.js +203 -0
  77. package/dist/resolver.js.map +1 -0
  78. package/dist/unique-id.d.ts +14 -0
  79. package/dist/unique-id.js +26 -0
  80. package/dist/unique-id.js.map +1 -0
  81. package/dist/value-promise.d.ts +134 -0
  82. package/dist/value-promise.js +277 -0
  83. package/dist/value-promise.js.map +1 -0
  84. package/package.json +49 -36
  85. package/src/binding-config.ts +73 -0
  86. package/src/binding-decorator.ts +136 -0
  87. package/src/binding-filter.ts +250 -0
  88. package/src/binding-inspector.ts +371 -0
  89. package/src/binding-key.ts +136 -0
  90. package/src/binding-sorter.ts +124 -0
  91. package/src/binding.ts +1107 -0
  92. package/src/context-event.ts +30 -0
  93. package/src/context-observer.ts +50 -0
  94. package/src/context-subscription.ts +402 -0
  95. package/src/context-tag-indexer.ts +147 -0
  96. package/src/context-view.ts +440 -0
  97. package/src/context.ts +1079 -0
  98. package/src/index.ts +58 -0
  99. package/src/inject-config.ts +239 -0
  100. package/src/inject.ts +796 -0
  101. package/src/interception-proxy.ts +127 -0
  102. package/src/interceptor-chain.ts +268 -0
  103. package/src/interceptor.ts +430 -0
  104. package/src/invocation.ts +269 -0
  105. package/src/json-types.ts +35 -0
  106. package/src/keys.ts +85 -0
  107. package/src/provider.ts +37 -0
  108. package/src/resolution-session.ts +414 -0
  109. package/src/resolver.ts +282 -0
  110. package/src/unique-id.ts +24 -0
  111. package/src/value-promise.ts +318 -0
  112. package/index.d.ts +0 -6
  113. package/index.js +0 -9
  114. package/lib/binding.d.ts +0 -75
  115. package/lib/binding.js +0 -102
  116. package/lib/context.d.ts +0 -14
  117. package/lib/context.js +0 -96
  118. package/lib/index.d.ts +0 -5
  119. package/lib/index.js +0 -13
  120. package/lib/inject.d.ts +0 -24
  121. package/lib/inject.js +0 -43
  122. package/lib/isPromise.d.ts +0 -1
  123. package/lib/isPromise.js +0 -14
  124. package/lib/resolver.d.ts +0 -26
  125. package/lib/resolver.js +0 -72
  126. package/lib6/binding.d.ts +0 -75
  127. package/lib6/binding.js +0 -102
  128. package/lib6/context.d.ts +0 -14
  129. package/lib6/context.js +0 -96
  130. package/lib6/index.d.ts +0 -5
  131. package/lib6/index.js +0 -13
  132. package/lib6/inject.d.ts +0 -24
  133. package/lib6/inject.js +0 -43
  134. package/lib6/isPromise.d.ts +0 -1
  135. package/lib6/isPromise.js +0 -14
  136. package/lib6/resolver.d.ts +0 -26
  137. package/lib6/resolver.js +0 -72
@@ -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
+ }