@shrub/core 0.5.26
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 +21 -0
- package/README.md +151 -0
- package/dist/esm/index.js +3 -0
- package/dist/esm/module.js +247 -0
- package/dist/esm/service-collection.js +382 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +15 -0
- package/dist/module.d.ts +110 -0
- package/dist/module.js +253 -0
- package/dist/service-collection.d.ts +146 -0
- package/dist/service-collection.js +395 -0
- package/package.json +26 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2019 jjvainav
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# Description
|
|
2
|
+
|
|
3
|
+
Shrub is a simple framework for building modular server-side applications and front-end components. The Core package provides a basic API for defining and loading modules.
|
|
4
|
+
|
|
5
|
+
## Creating a Module
|
|
6
|
+
|
|
7
|
+
A module can be defined as a class that implements the IModule interface or as a JSON object that satisfies the IModule interface.
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
export class FooModule implements IModule {
|
|
11
|
+
readonly name = "foo";
|
|
12
|
+
}
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
const fooModule: IModule = {
|
|
17
|
+
name: "foo"
|
|
18
|
+
};
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
There are a few methods a module can define that allow it to configure functionality provided by the module and each method is invoked in the following order.
|
|
22
|
+
|
|
23
|
+
### 1. `initialize(init: IModuleInitializer): void`
|
|
24
|
+
|
|
25
|
+
Allows a module the ability to perform pre-initialization that needs to happen before any modules or services are configured. Such tasks include binding settings or registering a configuration interface that is exposed to other modules.
|
|
26
|
+
|
|
27
|
+
### 2. `configureServices(registration: IServiceRegistration): void`
|
|
28
|
+
|
|
29
|
+
Register services with a service collection.
|
|
30
|
+
|
|
31
|
+
### 3. `configure(configurator: IModuleConfigurator): void | Promise<void>`
|
|
32
|
+
|
|
33
|
+
Allows a module to perform additional configuration, such as configure a dependency module. This method supports async operations which can be useful if data needs to be fetched from a remote service during configuration.
|
|
34
|
+
|
|
35
|
+
## Dependencies
|
|
36
|
+
|
|
37
|
+
Module depenedencies are specified by defining a `dependencies` property on a module and lifecycle methods get invoked on the dependency before they are invoked on the dependent.
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
export class FooModule implements IModule {
|
|
41
|
+
readonly name = "foo";
|
|
42
|
+
readonly dependencies = [BarModule];
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Configuration
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
export const IBarModuleConfiguration = createConfig<IBarModuleConfiguration>();
|
|
50
|
+
export interface IBarModuleConfiguration {
|
|
51
|
+
registerWidget(widget: IWidget): void;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export class BarModule implements IModule {
|
|
55
|
+
readonly name = "bar";
|
|
56
|
+
|
|
57
|
+
initialize({ config }: IModuleInitializer): void {
|
|
58
|
+
config(IBarModuleConfiguration).register(() => ({
|
|
59
|
+
registerWidget: widget => {}
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export class FooModule implements IModule {
|
|
65
|
+
readonly name = "foo";
|
|
66
|
+
readonly dependencies = [BarModule];
|
|
67
|
+
|
|
68
|
+
configure({ config }: IModuleConfigurator): void {
|
|
69
|
+
config.get(IBarModuleConfiguration).registerWidget({});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Settings and Options
|
|
75
|
+
|
|
76
|
+
While Dependency Configuration is a way for one module to configure another at load time. Settings provide a way to provide settings/configuration externally, such as from a config file.
|
|
77
|
+
|
|
78
|
+
Module Settings are provided as a simple object keyed by a module's name; for example, the below is an example settings object that defines settings for the 'foo' module:
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
const settings = {
|
|
82
|
+
foo: {
|
|
83
|
+
key: value
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
A module can access these settings directly from the `configure` method:
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
export class FooModule implements IModule {
|
|
92
|
+
readonly name = "foo";
|
|
93
|
+
|
|
94
|
+
configure({ settings }: IModuleConfigurator): void {
|
|
95
|
+
const keyValue = settings.key;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Sometimes it's useful to pass settings to service instances and this can be done via Service Options.
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
export const IFooOptions = createOptions<IFooOptions>("foo-options");
|
|
104
|
+
export interface IFooOptions {
|
|
105
|
+
readonly value: string;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export class FooModule implements IModule {
|
|
109
|
+
readonly name = "foo";
|
|
110
|
+
|
|
111
|
+
initialize({ settings }: IModuleInitializer): void {
|
|
112
|
+
settings.bindToOptions(IFooOptions);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
configureServices(registration: IServiceRegistration): void {
|
|
116
|
+
registration.registerTransient(IFooService, FooService);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export class FooService implements IFooService {
|
|
121
|
+
constructor(@IFooOptions private readonly options: IFooOptions) {
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Loading Modules
|
|
127
|
+
|
|
128
|
+
Modules are loaded using the `ModuleLoader` class by simply invoking `ModuleLoader.load`.
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
await ModuleLoader.load([
|
|
132
|
+
FooModule,
|
|
133
|
+
BarModule
|
|
134
|
+
]);
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Note: If a module has any dependencies not specified when calling `ModuleLoader.load` those dependencies will automatically get loaded.
|
|
138
|
+
|
|
139
|
+
If you want a little more control the module loader provides additional methods for configuring the service collection or settings.
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
await ModuleLoader()
|
|
143
|
+
.useModules([
|
|
144
|
+
FooModule,
|
|
145
|
+
BarModule
|
|
146
|
+
])
|
|
147
|
+
.useSettings({
|
|
148
|
+
foo: { value: "Hello!" }
|
|
149
|
+
})
|
|
150
|
+
.load();
|
|
151
|
+
```
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export * from "./module";
|
|
2
|
+
export * from "./service-collection";
|
|
3
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsY0FBYyxVQUFVLENBQUM7QUFDekIsY0FBYyxzQkFBc0IsQ0FBQyJ9
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { ServiceMap } from "./service-collection";
|
|
2
|
+
/** Creates a module configuration used to configure a module. */
|
|
3
|
+
export function createConfig() {
|
|
4
|
+
return { key: Symbol() };
|
|
5
|
+
}
|
|
6
|
+
export class ModuleLoadError extends Error {
|
|
7
|
+
constructor(message) {
|
|
8
|
+
super(message);
|
|
9
|
+
Object.setPrototypeOf(this, ModuleLoadError.prototype);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/** Handles initializing and loading modules. */
|
|
13
|
+
export class ModuleLoader {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.services = new ServiceMap();
|
|
16
|
+
this.modules = [];
|
|
17
|
+
this.settings = {};
|
|
18
|
+
}
|
|
19
|
+
static load(modulesOrOptions) {
|
|
20
|
+
const options = Array.isArray(modulesOrOptions) ? { modules: modulesOrOptions } : modulesOrOptions;
|
|
21
|
+
const loader = new ModuleLoader();
|
|
22
|
+
if (options.settings) {
|
|
23
|
+
loader.useSettings(options.settings);
|
|
24
|
+
}
|
|
25
|
+
return loader.useModules(options.modules).load();
|
|
26
|
+
}
|
|
27
|
+
static configureServices(callback) {
|
|
28
|
+
return new ModuleLoader().configureServices(callback);
|
|
29
|
+
}
|
|
30
|
+
static useModules(modules) {
|
|
31
|
+
return new ModuleLoader().useModules(modules);
|
|
32
|
+
}
|
|
33
|
+
static useSettings(settings) {
|
|
34
|
+
return new ModuleLoader().useSettings(settings);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Loads the specified core modules into the manager. The load operation will discover
|
|
38
|
+
* the core module dependencies and then initialize/configure each module.
|
|
39
|
+
*
|
|
40
|
+
* This will first discover all dependent modules, create module instances, and sort the
|
|
41
|
+
* list of discovered modules by dependency (note: there is no guarantee of the order when creating
|
|
42
|
+
* module instances but each step against the discovered modules will execute against module dependents first).
|
|
43
|
+
*
|
|
44
|
+
* Module Steps:
|
|
45
|
+
*
|
|
46
|
+
* 1) Initialize
|
|
47
|
+
* 2) Configure Services
|
|
48
|
+
* 3) Configure
|
|
49
|
+
*/
|
|
50
|
+
async load() {
|
|
51
|
+
this.ensureNotLoaded();
|
|
52
|
+
this.isLoaded = true;
|
|
53
|
+
// get all modules and sort by dependencies
|
|
54
|
+
const loadedModules = await this.expandAndSortModules();
|
|
55
|
+
// initialize discovered modules
|
|
56
|
+
const loader = this;
|
|
57
|
+
const configs = new Map();
|
|
58
|
+
loadedModules.forEach(module => {
|
|
59
|
+
if (module.initialize) {
|
|
60
|
+
module.initialize({
|
|
61
|
+
config: type => ({
|
|
62
|
+
register: callback => configs.set(type.key, { callback, module })
|
|
63
|
+
}),
|
|
64
|
+
get settings() {
|
|
65
|
+
return {
|
|
66
|
+
bindToOptions: (options, sectionName) => {
|
|
67
|
+
loader.services.addOptionsProvider({
|
|
68
|
+
tryGet: (opt) => {
|
|
69
|
+
if (options === opt) {
|
|
70
|
+
const settings = loader.getSettingsForModule(module);
|
|
71
|
+
return sectionName ? settings[sectionName] : settings;
|
|
72
|
+
}
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
// configure the module services
|
|
83
|
+
for (const module of loadedModules) {
|
|
84
|
+
if (module.configureServices) {
|
|
85
|
+
module.configureServices(this.services);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
this.services.freeze();
|
|
89
|
+
// lastly, configure the modules
|
|
90
|
+
const iterator = loadedModules[Symbol.iterator]();
|
|
91
|
+
const next = async () => {
|
|
92
|
+
for (const module of iterator) {
|
|
93
|
+
await configure(module);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
const self = this;
|
|
97
|
+
const configure = (module) => module.configure && module.configure({
|
|
98
|
+
services: this.services,
|
|
99
|
+
settings: this.getSettingsForModule(module),
|
|
100
|
+
get options() {
|
|
101
|
+
return {
|
|
102
|
+
get: (options) => self.services.getOptions(options)
|
|
103
|
+
};
|
|
104
|
+
},
|
|
105
|
+
get config() {
|
|
106
|
+
return {
|
|
107
|
+
get: (type) => {
|
|
108
|
+
const item = configs.get(type.key);
|
|
109
|
+
if (!item) {
|
|
110
|
+
throw new Error(`Config ${type.key.toString()} not found`);
|
|
111
|
+
}
|
|
112
|
+
return item.callback({
|
|
113
|
+
...this,
|
|
114
|
+
// need to grab the module's settings that is being configured
|
|
115
|
+
settings: loader.getSettingsForModule(item.module)
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
},
|
|
120
|
+
next
|
|
121
|
+
});
|
|
122
|
+
// invoke the iterator to start configuring the modules
|
|
123
|
+
return next().then(() => ({
|
|
124
|
+
services: this.services,
|
|
125
|
+
getInstance: (ctor) => {
|
|
126
|
+
const result = loadedModules.find(module => module instanceof ctor);
|
|
127
|
+
if (!result) {
|
|
128
|
+
throw new Error("Module instance not found");
|
|
129
|
+
}
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
132
|
+
}));
|
|
133
|
+
}
|
|
134
|
+
configureServices(callback) {
|
|
135
|
+
this.ensureNotLoaded();
|
|
136
|
+
callback(this.services);
|
|
137
|
+
return this;
|
|
138
|
+
}
|
|
139
|
+
useModules(modules) {
|
|
140
|
+
this.ensureNotLoaded();
|
|
141
|
+
this.modules.push(...modules);
|
|
142
|
+
return this;
|
|
143
|
+
}
|
|
144
|
+
useSettings(settings) {
|
|
145
|
+
this.ensureNotLoaded();
|
|
146
|
+
this.settings = this.merge(settings, this.settings);
|
|
147
|
+
return this;
|
|
148
|
+
}
|
|
149
|
+
ensureNotLoaded() {
|
|
150
|
+
if (this.isLoaded) {
|
|
151
|
+
throw new Error("Modules have already been loaded.");
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
getSettingsForModule(module) {
|
|
155
|
+
return this.settings[module.name] || {};
|
|
156
|
+
}
|
|
157
|
+
async expandAndSortModules() {
|
|
158
|
+
const instances = new Map();
|
|
159
|
+
const sorted = [];
|
|
160
|
+
const visited = {};
|
|
161
|
+
const getInstance = async (dependency) => {
|
|
162
|
+
if (typeof dependency === "function") {
|
|
163
|
+
let instance = instances.get(dependency);
|
|
164
|
+
if (!instance) {
|
|
165
|
+
try {
|
|
166
|
+
instance = new dependency();
|
|
167
|
+
if (instance.constructor === dependency) {
|
|
168
|
+
// save the instance if the dependency is a constructor
|
|
169
|
+
instances.set(dependency, instance);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
// the dependency is a non-constructble function so invoke and assume the return will be a promise and use Promise.resolve to resolve it
|
|
174
|
+
const instanceOrCtor = await Promise.resolve(dependency());
|
|
175
|
+
instance = await getInstance(instanceOrCtor);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// resolve incase the dependency is a function that returned a promise
|
|
179
|
+
return instance;
|
|
180
|
+
}
|
|
181
|
+
if (typeof dependency === "object") {
|
|
182
|
+
if (!this.isModule(dependency)) {
|
|
183
|
+
throw new ModuleLoadError(`Object (${dependency.constructor.name}) is not a module.`);
|
|
184
|
+
}
|
|
185
|
+
const ctor = dependency.constructor;
|
|
186
|
+
if (ctor !== Object.prototype.constructor) {
|
|
187
|
+
if (instances.has(ctor)) {
|
|
188
|
+
throw new ModuleLoadError(`Duplicate module instances ${dependency.name}.`);
|
|
189
|
+
}
|
|
190
|
+
instances.set(ctor, dependency);
|
|
191
|
+
}
|
|
192
|
+
return dependency;
|
|
193
|
+
}
|
|
194
|
+
throw new ModuleLoadError(`Invalid dependency type (${typeof dependency}).`);
|
|
195
|
+
};
|
|
196
|
+
const visit = async (dependency, ancestors) => {
|
|
197
|
+
const module = await getInstance(dependency);
|
|
198
|
+
if (visited[module.name]) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (!ancestors) {
|
|
202
|
+
ancestors = {};
|
|
203
|
+
}
|
|
204
|
+
if (ancestors[module.name]) {
|
|
205
|
+
throw new ModuleLoadError(`Circular dependency has been detected with module ${module.name}`);
|
|
206
|
+
}
|
|
207
|
+
ancestors[module.name] = true;
|
|
208
|
+
if (module.dependencies) {
|
|
209
|
+
for (const dependency of module.dependencies) {
|
|
210
|
+
await visit(dependency, ancestors);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
visited[module.name] = true;
|
|
214
|
+
// this performs a depth-first topological sort but instead of inserting
|
|
215
|
+
// to the front, push the module to the back of the list so that dependencies
|
|
216
|
+
// come first
|
|
217
|
+
sorted.push(module);
|
|
218
|
+
};
|
|
219
|
+
for (const module of this.modules) {
|
|
220
|
+
await visit(module);
|
|
221
|
+
}
|
|
222
|
+
return sorted;
|
|
223
|
+
}
|
|
224
|
+
merge(source, target) {
|
|
225
|
+
if (this.isObject(source) && this.isObject(target)) {
|
|
226
|
+
for (const key in source) {
|
|
227
|
+
if (source[key] === undefined) {
|
|
228
|
+
// do not merge if the source value is undefined
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
target = {
|
|
232
|
+
...target,
|
|
233
|
+
[key]: this.isObject(source[key]) ? this.merge(source[key], target[key] || {}) : source[key]
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return target;
|
|
238
|
+
}
|
|
239
|
+
isObject(obj) {
|
|
240
|
+
return typeof obj === "object" && !Array.isArray(obj);
|
|
241
|
+
}
|
|
242
|
+
isModule(obj) {
|
|
243
|
+
// a module only requires a name so that's all we can really check for
|
|
244
|
+
return typeof obj === "object" && obj.name !== undefined;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibW9kdWxlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL21vZHVsZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQXNELFVBQVUsRUFBRSxNQUFNLHNCQUFzQixDQUFDO0FBcUZ0RyxpRUFBaUU7QUFDakUsTUFBTSxVQUFVLFlBQVk7SUFDeEIsT0FBTyxFQUFFLEdBQUcsRUFBRSxNQUFNLEVBQUUsRUFBRSxDQUFDO0FBQzdCLENBQUM7QUFFRCxNQUFNLE9BQU8sZUFBZ0IsU0FBUSxLQUFLO0lBQ3RDLFlBQVksT0FBZTtRQUN2QixLQUFLLENBQUMsT0FBTyxDQUFDLENBQUM7UUFDZixNQUFNLENBQUMsY0FBYyxDQUFDLElBQUksRUFBRSxlQUFlLENBQUMsU0FBUyxDQUFDLENBQUM7SUFDM0QsQ0FBQztDQUNKO0FBRUQsZ0RBQWdEO0FBQ2hELE1BQU0sT0FBTyxZQUFZO0lBQXpCO1FBQ3FCLGFBQVEsR0FBRyxJQUFJLFVBQVUsRUFBRSxDQUFDO1FBQzVCLFlBQU8sR0FBa0MsRUFBRSxDQUFDO1FBQ3JELGFBQVEsR0FBOEIsRUFBRSxDQUFDO0lBb1JyRCxDQUFDO0lBL1FHLE1BQU0sQ0FBQyxJQUFJLENBQUMsZ0JBQThEO1FBQ3RFLE1BQU0sT0FBTyxHQUFHLEtBQUssQ0FBQyxPQUFPLENBQUMsZ0JBQWdCLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsQ0FBQyxDQUFDLENBQUMsZ0JBQWdCLENBQUM7UUFDbkcsTUFBTSxNQUFNLEdBQUcsSUFBSSxZQUFZLEVBQUUsQ0FBQztRQUVsQyxJQUFJLE9BQU8sQ0FBQyxRQUFRLEVBQUU7WUFDbEIsTUFBTSxDQUFDLFdBQVcsQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDLENBQUM7U0FDeEM7UUFFRCxPQUFPLE1BQU0sQ0FBQyxVQUFVLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxDQUFDLElBQUksRUFBRSxDQUFDO0lBQ3JELENBQUM7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
|