3
3
// This file is licensed under the MIT License.
4
4
// License text available at https://opensource.org/licenses/MIT
5
5
6
- import { Binding , BoundValue } from './binding' ;
6
+ import { Binding , BoundValue , ValueOrPromise } from './binding' ;
7
7
import { inject } from './inject' ;
8
8
import { isPromise } from './is-promise' ;
9
9
@@ -16,7 +16,6 @@ export class Context {
16
16
17
17
bind ( key : string ) : Binding {
18
18
Binding . validateKey ( key ) ;
19
- key = Binding . normalizeKey ( key ) ;
20
19
const keyExists = this . registry . has ( key ) ;
21
20
if ( keyExists ) {
22
21
const existingBinding = this . registry . get ( key ) ;
@@ -31,15 +30,15 @@ export class Context {
31
30
}
32
31
33
32
contains ( key : string ) : boolean {
34
- key = Binding . normalizeKey ( key ) ;
33
+ Binding . validateKey ( key ) ;
35
34
return this . registry . has ( key ) ;
36
35
}
37
36
38
37
find ( pattern ?: string ) : Binding [ ] {
39
38
let bindings : Binding [ ] = [ ] ;
40
39
if ( pattern ) {
41
40
// TODO(@superkhau): swap with production grade glob to regex lib
42
- pattern = Binding . normalizeKey ( pattern ) ;
41
+ Binding . validateKey ( pattern ) ;
43
42
const glob = new RegExp ( '^' + pattern . split ( '*' ) . join ( '.*' ) + '$' ) ;
44
43
this . registry . forEach ( binding => {
45
44
const isMatch = glob . test ( binding . key ) ;
@@ -76,32 +75,71 @@ export class Context {
76
75
return childList . concat ( additions ) ;
77
76
}
78
77
78
+ /**
79
+ * Get the value bound to the given key, optionally return a (deep) property
80
+ * of the bound value.
81
+ *
82
+ * @example
83
+ *
84
+ * ```ts
85
+ * // get the value bound to "application.instance"
86
+ * const app = await ctx.get('application.instance');
87
+ *
88
+ * // get "rest" property from the value bound to "config"
89
+ * const config = await ctx.getValueOrPromise('config#rest');
90
+ *
91
+ * // get "a" property of "numbers" property from the value bound to "data"
92
+ * ctx.bind('data').to({numbers: {a: 1, b: 2}, port: 3000 });
93
+ * const a = await ctx.get('data#numbers.a');
94
+ * ```
95
+ *
96
+ * @param keyWithPath The binding key, optionally suffixed with a path to the
97
+ * (deeply) nested property to retrieve.
98
+ * @returns A promise of the bound value.
99
+ */
79
100
get ( key : string ) : Promise < BoundValue > {
80
101
try {
81
- const path = Binding . getKeyPath ( key ) ;
82
- const binding = this . getBinding ( key ) ;
83
- return Promise . resolve ( binding . getValue ( this ) ) . then (
84
- val => getValue ( val , path ) ) ;
102
+ return Promise . resolve ( this . getValueOrPromise ( key ) ) ;
85
103
} catch ( err ) {
86
104
return Promise . reject ( err ) ;
87
105
}
88
106
}
89
107
108
+ /**
109
+ * Get the synchronous value bound to the given key, optionally
110
+ * return a (deep) property of the bound value.
111
+ *
112
+ * This method throws an error if the bound value requires async computation
113
+ * (returns a promise). You should never rely on sync bindings in production
114
+ * code.
115
+ *
116
+ * @example
117
+ *
118
+ * ```ts
119
+ * // get the value bound to "application.instance"
120
+ * const app = ctx.get('application.instance');
121
+ *
122
+ * // get "rest" property from the value bound to "config"
123
+ * const config = ctx.getValueOrPromise('config#rest');
124
+ * ```
125
+ *
126
+ * @param keyWithPath The binding key, optionally suffixed with a path to the
127
+ * (deeply) nested property to retrieve.
128
+ * @returns A promise of the bound value.
129
+ */
90
130
getSync ( key : string ) : BoundValue {
91
- const path = Binding . getKeyPath ( key ) ;
92
- const binding = this . getBinding ( key ) ;
93
- const valueOrPromise = binding . getValue ( this ) ;
131
+ const valueOrPromise = this . getValueOrPromise ( key ) ;
94
132
95
133
if ( isPromise ( valueOrPromise ) ) {
96
134
throw new Error (
97
135
`Cannot get ${ key } synchronously: the value is a promise` ) ;
98
136
}
99
137
100
- return getValue ( valueOrPromise , path ) ;
138
+ return valueOrPromise ;
101
139
}
102
140
103
141
getBinding ( key : string ) : Binding {
104
- key = Binding . normalizeKey ( key ) ;
142
+ Binding . validateKey ( key ) ;
105
143
const binding = this . registry . get ( key ) ;
106
144
if ( binding ) {
107
145
return binding ;
@@ -113,22 +151,55 @@ export class Context {
113
151
114
152
throw new Error ( `The key ${ key } was not bound to any value.` ) ;
115
153
}
154
+
155
+ /**
156
+ * Get the value bound to the given key.
157
+ *
158
+ * This is an internal version that preserves the dual sync/async result
159
+ * of `Binding#getValue()`. Users should use `get()` or `getSync()` instead.
160
+ *
161
+ * @example
162
+ *
163
+ * ```ts
164
+ * // get the value bound to "application.instance"
165
+ * ctx.getValueOrPromise('application.instance');
166
+ *
167
+ * // get "rest" property from the value bound to "config"
168
+ * ctx.getValueOrPromise('config#rest');
169
+ *
170
+ * // get "a" property of "numbers" property from the value bound to "data"
171
+ * ctx.bind('data').to({numbers: {a: 1, b: 2}, port: 3000 });
172
+ * ctx.getValueOrPromise('data#numbers.a');
173
+ * ```
174
+ *
175
+ * @param keyWithPath The binding key, optionally suffixed with a path to the
176
+ * (deeply) nested property to retrieve.
177
+ * @returns The bound value or a promise of the bound value, depending
178
+ * on how the binding was configured.
179
+ * @internal
180
+ */
181
+ getValueOrPromise ( keyWithPath : string ) : ValueOrPromise < BoundValue > {
182
+ const { key, path} = Binding . parseKeyWithPath ( keyWithPath ) ;
183
+ const boundValue = this . getBinding ( key ) . getValue ( this ) ;
184
+ if ( path === undefined || path === '' ) {
185
+ return boundValue ;
186
+ }
187
+
188
+ if ( isPromise ( boundValue ) ) {
189
+ return boundValue . then ( v => getDeepProperty ( v , path ) ) ;
190
+ }
191
+
192
+ return getDeepProperty ( boundValue , path ) ;
193
+ }
116
194
}
117
195
118
- /**
119
- * Get the value by `.` notation
120
- * @param obj The source value
121
- * @param path A path to the nested property, such as `x`, `x.y`, `x.length`,
122
- * or `x.0`
123
- */
124
- function getValue ( obj : BoundValue , path ?: string ) : BoundValue {
125
- if ( ! path ) return obj ;
196
+ function getDeepProperty ( value : BoundValue , path : string ) {
126
197
const props = path . split ( '.' ) ;
127
- let val = undefined ;
128
198
for ( const p of props ) {
129
- val = obj [ p ] ;
130
- if ( val == null ) return val ;
131
- obj = val ;
199
+ value = value [ p ] ;
200
+ if ( value === undefined || value === null ) {
201
+ return value ;
202
+ }
132
203
}
133
- return val ;
204
+ return value ;
134
205
}
0 commit comments