UNPKG

4.44 kBPlain TextView Raw
1// Copyright IBM Corp. and LoopBack contributors 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
6import {generateUniqueId} from './unique-id';
7
8export type BindingAddress<T = unknown> = string | BindingKey<T>;
9
10// eslint-disable-next-line @typescript-eslint/no-unused-vars
11export class BindingKey<ValueType> {
12 static readonly PROPERTY_SEPARATOR = '#';
13
14 /**
15 * Create a new key for a binding bound to a value of type `ValueType`.
16 *
17 * @example
18 *
19 * ```ts
20 * BindingKey.create<string>('application.name');
21 * BindingKey.create<number>('config', 'rest.port);
22 * BindingKey.create<number>('config#rest.port');
23 * ```
24 *
25 * @param key - The binding key. When propertyPath is not provided, the key
26 * is allowed to contain propertyPath as encoded via `BindingKey#toString()`
27 * @param propertyPath - Optional path to a deep property of the bound value.
28 */
29 public static create<V>(key: string, propertyPath?: string): BindingKey<V> {
30 // TODO(bajtos) allow chaining of propertyPaths, e.g.
31 // BindingKey.create('config#rest', 'port')
32 // should create {key: 'config', path: 'rest.port'}
33 if (propertyPath) {
34 BindingKey.validate(key);
35 return new BindingKey<V>(key, propertyPath);
36 }
37
38 return BindingKey.parseKeyWithPath(key);
39 }
40
41 private constructor(
42 public readonly key: string,
43 public readonly propertyPath?: string,
44 ) {}
45
46 toString() {
47 return this.propertyPath
48 ? `${this.key}${BindingKey.PROPERTY_SEPARATOR}${this.propertyPath}`
49 : this.key;
50 }
51
52 /**
53 * Get a binding address for retrieving a deep property of the object
54 * bound to the current binding key.
55 *
56 * @param propertyPath - A dot-separated path to a (deep) property, e.g. "server.port".
57 */
58 deepProperty<PropertyValueType>(propertyPath: string) {
59 // TODO(bajtos) allow chaining of propertyPaths, e.g.
60 // BindingKey.create('config', 'rest').deepProperty('port')
61 // should create {key: 'config', path: 'rest.port'}
62 return BindingKey.create<PropertyValueType>(this.key, propertyPath);
63 }
64
65 /**
66 * Validate the binding key format. Please note that `#` is reserved.
67 * Returns a string representation of the binding key.
68 *
69 * @param key - Binding key, such as `a`, `a.b`, `a:b`, or `a/b`
70 */
71 static validate<T>(key: BindingAddress<T>): string {
72 if (!key) throw new Error('Binding key must be provided.');
73 key = key.toString();
74 if (key.includes(BindingKey.PROPERTY_SEPARATOR)) {
75 throw new Error(
76 `Binding key ${key} cannot contain` +
77 ` '${BindingKey.PROPERTY_SEPARATOR}'.`,
78 );
79 }
80 return key;
81 }
82
83 /**
84 * Parse a string containing both the binding key and the path to the deeply
85 * nested property to retrieve.
86 *
87 * @param keyWithPath - The key with an optional path,
88 * e.g. "application.instance" or "config#rest.port".
89 */
90 static parseKeyWithPath<T>(keyWithPath: BindingAddress<T>): BindingKey<T> {
91 if (typeof keyWithPath !== 'string') {
92 return BindingKey.create<T>(keyWithPath.key, keyWithPath.propertyPath);
93 }
94
95 const index = keyWithPath.indexOf(BindingKey.PROPERTY_SEPARATOR);
96 if (index === -1) {
97 return new BindingKey<T>(keyWithPath);
98 }
99
100 return BindingKey.create<T>(
101 keyWithPath.slice(0, index).trim(),
102 keyWithPath.slice(index + 1),
103 );
104 }
105
106 /**
107 * Name space for configuration binding keys
108 */
109 static CONFIG_NAMESPACE = '$config';
110
111 /**
112 * Build a binding key for the configuration of the given binding.
113 * The format is `<key>:$config`
114 *
115 * @param key - Key of the target binding to be configured
116 */
117 static buildKeyForConfig<T>(key: BindingAddress = ''): BindingAddress<T> {
118 const suffix = BindingKey.CONFIG_NAMESPACE;
119 const bindingKey = key ? `${key}:${suffix}` : suffix;
120 return bindingKey;
121 }
122
123 /**
124 * Generate a universally unique binding key.
125 *
126 * Please note the format of they generated key is not specified, you must
127 * not rely on any specific formatting (e.g. UUID style).
128 *
129 * @param namespace - Namespace for the binding
130 */
131 static generate<T>(namespace = ''): BindingKey<T> {
132 const prefix = namespace ? `${namespace}.` : '';
133 const name = generateUniqueId();
134 return BindingKey.create(`${prefix}${name}`);
135 }
136}