qtools-library-dot-d 1.0.9 → 1.0.11
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/README.md +2 -0
- package/README2.md +418 -0
- package/package.json +1 -1
- package/qtools-library-dot-d.js +5 -12
package/README.md
CHANGED
|
@@ -229,6 +229,8 @@ This pattern is used extensively in the qtools ecosystem for building scalable,
|
|
|
229
229
|
|
|
230
230
|
## HISTORY
|
|
231
231
|
|
|
232
|
+
v1.11 - clarified error message "Ignoring invalid file".
|
|
233
|
+
v1.10 - there was a reason I did not return the exception list. Undone.
|
|
232
234
|
v1.09 - fixed broken error handling when library file path is bad; added demonstration application
|
|
233
235
|
v1.08 - updated README (several times)
|
|
234
236
|
v1.05 - cleaned up toString() report, returns exception list (or err if callback) if needed, added qtools-functional-library
|
package/README2.md
ADDED
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
## NAME
|
|
2
|
+
|
|
3
|
+
**qtools-library-dot-d** is system for creating dynamic, modular libraries in Node.js. The dotD (dot-D) pattern provides a powerful way to build extensible applications through automatic module discovery and loading.
|
|
4
|
+
|
|
5
|
+
## What is qtools-library-dot-d?
|
|
6
|
+
|
|
7
|
+
**qtools-library-dot-d** is a Node.js library that enables dynamic module loading from directories. It automatically discovers JavaScript files in specified directories and makes them available as methods on a library object, creating a plugin-like architecture without the complexity of traditional plugin systems.
|
|
8
|
+
|
|
9
|
+
## The dotD Pattern
|
|
10
|
+
|
|
11
|
+
The "dotD" naming convention (short for "dot-D") refers to directories that contain dynamically loadable modules:
|
|
12
|
+
|
|
13
|
+
- `modules.d/` - Directory containing module files
|
|
14
|
+
- `endpoints.d/` - Directory containing API endpoint modules
|
|
15
|
+
- `accessPoints.d/` - Directory containing data access modules
|
|
16
|
+
- `mappers.d/` - Directory containing data mapping modules
|
|
17
|
+
- `processors.d/` - Directory containing processing modules
|
|
18
|
+
|
|
19
|
+
Each module, `.js` file or directory with package.json, in a dotD directory becomes available through the library object, with the filename determining the module identifier.
|
|
20
|
+
|
|
21
|
+
## Two Primary Usage Patterns
|
|
22
|
+
|
|
23
|
+
### Pattern 1: Function Registration (Classic dotD)
|
|
24
|
+
|
|
25
|
+
Modules register functions that become methods on the library object:
|
|
26
|
+
|
|
27
|
+
```javascript
|
|
28
|
+
// In dotD module: myFunction.js
|
|
29
|
+
const moduleFunction = ({ moduleName } = {}) => ({ dotD, passThroughParameters } = {}) => {
|
|
30
|
+
const workingFunction = (params) => {
|
|
31
|
+
// Function logic here
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Register the function
|
|
36
|
+
dotD.library.add(moduleName, workingFunction);
|
|
37
|
+
};
|
|
38
|
+
module.exports = moduleFunction({ moduleName });
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
```javascript
|
|
42
|
+
// Usage after loading:
|
|
43
|
+
myLibrary.myFunction(params); // Calls the registered function
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Pattern 2: Initialization Scripts (Modern dotD)
|
|
47
|
+
|
|
48
|
+
Modules are initialization scripts that run once during `loadModules()`, performing setup work and optionally returning results:
|
|
49
|
+
|
|
50
|
+
```javascript
|
|
51
|
+
// In dotD module: setupWatcher.js
|
|
52
|
+
const moduleFunction = ({ dotD: myLibraryDotD, passThroughParameters }) => {
|
|
53
|
+
const { xLog, config, userVisibleProcessLogger } = passThroughParameters;
|
|
54
|
+
|
|
55
|
+
// Do initialization work
|
|
56
|
+
const result = initializeWatcher(config.watchPath);
|
|
57
|
+
|
|
58
|
+
// Track what was done
|
|
59
|
+
myLibraryDotD.logList.push(`${moduleName}: ${result.status}`);
|
|
60
|
+
|
|
61
|
+
// Return result (optional)
|
|
62
|
+
return result;
|
|
63
|
+
};
|
|
64
|
+
module.exports = moduleFunction;
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
This pattern is particularly useful for:
|
|
68
|
+
|
|
69
|
+
- Setting up file watchers, database connections, or other persistent resources
|
|
70
|
+
- Configuring Express routes or middleware
|
|
71
|
+
- Initializing services that run in the background
|
|
72
|
+
- One-time setup tasks that don't need to be called repeatedly
|
|
73
|
+
|
|
74
|
+
## Why Use Dynamic dotD Libraries?
|
|
75
|
+
|
|
76
|
+
### 1. **Automatic Module Discovery**
|
|
77
|
+
|
|
78
|
+
No need to manually import or register modules. Simply add a `.js` file to the dotD directory and it's automatically available.
|
|
79
|
+
|
|
80
|
+
```javascript
|
|
81
|
+
// Traditional approach - manual imports
|
|
82
|
+
const module1 = require('./modules/module1');
|
|
83
|
+
const module2 = require('./modules/module2');
|
|
84
|
+
const module3 = require('./modules/module3');
|
|
85
|
+
|
|
86
|
+
// dotD approach - automatic discovery
|
|
87
|
+
const myLibrary = qtLibDotD({ libraryName: 'myLibrary' }); //libraryName is used as a label in reports
|
|
88
|
+
myLibrary.setLibraryPath('./modules.d');
|
|
89
|
+
myLibrary.loadModules();
|
|
90
|
+
// All modules in modules.d/ are now processed/available
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 2. **Plugin Architecture**
|
|
94
|
+
|
|
95
|
+
Create extensible applications where new functionality can be added by simply dropping files into directories.
|
|
96
|
+
|
|
97
|
+
```javascript
|
|
98
|
+
// Add new functionality by creating a new file
|
|
99
|
+
// modules.d/newFeature.js -> automatically processed during loadModules()
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 3. **Shared Resources**
|
|
103
|
+
|
|
104
|
+
Pass common resources (configuration, database connections, utilities) to all modules through `passThroughParameters`. These are given, by that name, to the module when it is instantiated during the loadModules() process.
|
|
105
|
+
|
|
106
|
+
```javascript
|
|
107
|
+
const passThroughParameters = {
|
|
108
|
+
config: getConfig(),
|
|
109
|
+
database: dbConnection,
|
|
110
|
+
logger: logger,
|
|
111
|
+
utilities: commonUtils,
|
|
112
|
+
queue: jobQueue,
|
|
113
|
+
eventHandler: eventHandler
|
|
114
|
+
};
|
|
115
|
+
myLibrary.loadModules({ passThroughParameters });
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### 4. **Clean Separation of Concerns**
|
|
119
|
+
|
|
120
|
+
Each module is self-contained and focused on a single responsibility, making code easier to maintain and test.
|
|
121
|
+
|
|
122
|
+
### 5. **Runtime Flexibility**
|
|
123
|
+
|
|
124
|
+
Modules can be loaded synchronously or asynchronously, and the library can be sealed to prevent modifications after loading.
|
|
125
|
+
|
|
126
|
+
## Real-World Use Cases
|
|
127
|
+
|
|
128
|
+
### API Endpoint Management (Initialization Pattern)
|
|
129
|
+
|
|
130
|
+
```javascript
|
|
131
|
+
// endpoints.d/userEndpoints.js - Sets up Express routes
|
|
132
|
+
const moduleFunction = ({ dotD: endpointsDotD, passThroughParameters }) => {
|
|
133
|
+
const { expressApp, routingPrefix } = passThroughParameters;
|
|
134
|
+
|
|
135
|
+
// Set up routes
|
|
136
|
+
expressApp.get(`${routingPrefix}users`, getUserHandler);
|
|
137
|
+
expressApp.post(`${routingPrefix}users`, createUserHandler);
|
|
138
|
+
|
|
139
|
+
endpointsDotD.logList.push('User endpoints configured');
|
|
140
|
+
return { routesAdded: 2 };
|
|
141
|
+
};
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Service Initialization (Initialization Pattern)
|
|
145
|
+
|
|
146
|
+
```javascript
|
|
147
|
+
// processors.d/fileWatcher.js - Sets up file system monitoring
|
|
148
|
+
const moduleFunction = ({ dotD: processorsDotD, passThroughParameters }) => {
|
|
149
|
+
const { config, eventHandler, logger } = passThroughParameters;
|
|
150
|
+
|
|
151
|
+
// Initialize file watcher
|
|
152
|
+
const watcher = setupFileWatcher({
|
|
153
|
+
path: config.watchedPath,
|
|
154
|
+
onFileDetected: (file) => eventHandler({ filePath: file })
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
logger(`File watcher initialized: ${config.watchedPath}`);
|
|
158
|
+
processorsDotD.logList.push(`fileWatcher: active`);
|
|
159
|
+
|
|
160
|
+
return { watcher, status: 'active' };
|
|
161
|
+
};
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Data Access Layer (Function Registration Pattern)
|
|
165
|
+
|
|
166
|
+
```javascript
|
|
167
|
+
// accessPoints.d/getUserData.js
|
|
168
|
+
const moduleFunction = ({ moduleName } = {}) => ({ dotD, passThroughParameters } = {}) => {
|
|
169
|
+
const { database } = passThroughParameters;
|
|
170
|
+
|
|
171
|
+
const getUserData = (userId, callback) => {
|
|
172
|
+
// Database access logic
|
|
173
|
+
database.query('SELECT * FROM users WHERE id = ?', [userId], callback);
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
dotD.library.add(moduleName, getUserData);
|
|
177
|
+
// Later called as dotD.getUserData(userId, callback)
|
|
178
|
+
};
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Business Logic Modules (Function Registration with Init Pattern)
|
|
182
|
+
|
|
183
|
+
```javascript
|
|
184
|
+
// processors.d/validateOrder.js
|
|
185
|
+
const moduleFunction = ({ dotD, passThroughParameters }) => {
|
|
186
|
+
const { xLog, getConfig } = process.global;
|
|
187
|
+
const localConfig = getConfig(moduleName);
|
|
188
|
+
|
|
189
|
+
let newInstanceData;
|
|
190
|
+
|
|
191
|
+
const init = () => {
|
|
192
|
+
// Initialization logic called after dotD loading completes
|
|
193
|
+
xLog.status(`${moduleName} validator initialized`);
|
|
194
|
+
// This is where you'd set up the module's initial state
|
|
195
|
+
return {
|
|
196
|
+
toolName: moduleName,
|
|
197
|
+
status: 'ready',
|
|
198
|
+
type: 'validator'
|
|
199
|
+
};
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const validateOrder = (orderData) => {
|
|
203
|
+
// Validation logic using newInstanceData and orderData
|
|
204
|
+
return { valid: true, errors: [] };
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
// Register both the main function and the init function
|
|
208
|
+
dotD.library.add(moduleName, { validateOrder, init });
|
|
209
|
+
// Later called as dotD.validateOrder.validateOrder(orderData)
|
|
210
|
+
// Init called as dotD.validateOrder.init()
|
|
211
|
+
|
|
212
|
+
dotD.logList.push(`${moduleName}: loaded`);
|
|
213
|
+
};
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## How It Works
|
|
217
|
+
|
|
218
|
+
### Basic Setup (Both Patterns)
|
|
219
|
+
|
|
220
|
+
```javascript
|
|
221
|
+
const qtLibDotD = require('qtools-library-dot-d');
|
|
222
|
+
const myLibrary = qtLibDotD({ libraryName: 'myLibrary' });
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Async Pipeline Integration (Recommended)
|
|
226
|
+
|
|
227
|
+
```javascript
|
|
228
|
+
const {pipeRunner, taskListPlus} = new require('qtools-asynchronous-pipe-plus')();
|
|
229
|
+
|
|
230
|
+
const moduleFunction = ({ config, services }, callback) => {
|
|
231
|
+
const myLibraryDotD = qtLibDotD({ libraryName: 'MyLibrary' });
|
|
232
|
+
const taskList = new taskListPlus();
|
|
233
|
+
|
|
234
|
+
// Set library path
|
|
235
|
+
taskList.push((args, next) => {
|
|
236
|
+
const { myLibraryDotD } = args;
|
|
237
|
+
myLibraryDotD.setLibraryPath(
|
|
238
|
+
path.join(__dirname, 'modules.d'),
|
|
239
|
+
(err, libraryPath) => next(err, { ...args, libraryPath })
|
|
240
|
+
);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// Load modules
|
|
244
|
+
taskList.push((args, next) => {
|
|
245
|
+
const { myLibraryDotD } = args;
|
|
246
|
+
const passThroughParameters = { config, services };
|
|
247
|
+
|
|
248
|
+
myLibraryDotD.loadModules({ passThroughParameters }, (err, result) => {
|
|
249
|
+
next(err, { ...args, result });
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
// Execute pipeline
|
|
254
|
+
pipeRunner(taskList.getList(), { myLibraryDotD }, (err, args) => {
|
|
255
|
+
if (err) return callback(err);
|
|
256
|
+
|
|
257
|
+
myLibraryDotD.seal();
|
|
258
|
+
callback(null, myLibraryDotD);
|
|
259
|
+
});
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
// Usage in parent application with init activation:
|
|
263
|
+
const parentApp = ({ dependencies }, callback) => {
|
|
264
|
+
moduleFunction({ config, services }, (err, { myLibraryDotD }) => {
|
|
265
|
+
if (err) return callback(err);
|
|
266
|
+
|
|
267
|
+
// Activate all modules that have init functions
|
|
268
|
+
// or, activate specific ones with specific arguments
|
|
269
|
+
Object.keys(myLibraryDotD).forEach(name => {
|
|
270
|
+
if (myLibraryDotD[name].init) {
|
|
271
|
+
myLibraryDotD[name].init(); //could pass in args
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
callback(null, { myLibraryDotD });
|
|
276
|
+
});
|
|
277
|
+
};
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Simple Setup (Legacy)
|
|
281
|
+
|
|
282
|
+
```javascript
|
|
283
|
+
// Synchronous
|
|
284
|
+
myLibrary.setLibraryPath('./modules.d');
|
|
285
|
+
myLibrary.loadModules({ passThroughParameters });
|
|
286
|
+
|
|
287
|
+
// Asynchronous
|
|
288
|
+
myLibrary.setLibraryPath('./modules.d', (err, path) => {
|
|
289
|
+
if (err) return callback(err);
|
|
290
|
+
myLibrary.loadModules({ passThroughParameters }, callback);
|
|
291
|
+
});
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
## Module Structure Patterns
|
|
295
|
+
|
|
296
|
+
### Function Registration Module
|
|
297
|
+
|
|
298
|
+
```javascript
|
|
299
|
+
const moduleName = __filename.replace(__dirname + '/', '').replace(/.js$/, '');
|
|
300
|
+
|
|
301
|
+
const moduleFunction = ({ moduleName } = {}) => ({ dotD, passThroughParameters } = {}) => {
|
|
302
|
+
const { config, logger } = passThroughParameters;
|
|
303
|
+
|
|
304
|
+
const workingFunction = (/* your parameters */) => {
|
|
305
|
+
// Your module logic here
|
|
306
|
+
return result;
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
// Register the function
|
|
310
|
+
dotD.library.add(moduleName, workingFunction);
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
module.exports = moduleFunction({ moduleName });
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### Initialization Script Module
|
|
317
|
+
|
|
318
|
+
```javascript
|
|
319
|
+
const moduleName = __filename.replace(__dirname + '/', '').replace(/.js$/, '');
|
|
320
|
+
|
|
321
|
+
const moduleFunction = ({ dotD: libraryDotD, passThroughParameters }) => {
|
|
322
|
+
const { xLog, getConfig } = process.global;
|
|
323
|
+
const localConfig = getConfig(moduleName);
|
|
324
|
+
|
|
325
|
+
const { config, services } = passThroughParameters;
|
|
326
|
+
|
|
327
|
+
// Do initialization work
|
|
328
|
+
const result = initializeService(config);
|
|
329
|
+
|
|
330
|
+
// Track what was done
|
|
331
|
+
libraryDotD.logList.push(`${moduleName}: ${result.status}`);
|
|
332
|
+
|
|
333
|
+
// Return result (optional)
|
|
334
|
+
return result;
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
module.exports = moduleFunction;
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
## Pattern Selection Guidelines
|
|
341
|
+
|
|
342
|
+
**Use Function Registration Pattern when:**
|
|
343
|
+
|
|
344
|
+
- You need callable functions/methods on the library object
|
|
345
|
+
- Building a traditional API or service layer
|
|
346
|
+
- Modules provide stateless utility functions
|
|
347
|
+
- You want `myLibrary.functionName()` style access
|
|
348
|
+
|
|
349
|
+
**Use Initialization Script Pattern when:**
|
|
350
|
+
|
|
351
|
+
- Setting up persistent services (file watchers, database connections)
|
|
352
|
+
- Configuring application routes or middleware
|
|
353
|
+
- One-time setup tasks during application startup
|
|
354
|
+
- Building plugin architectures where modules do setup work
|
|
355
|
+
- Working with qtools-asynchronous-pipe-plus pipelines
|
|
356
|
+
|
|
357
|
+
## Benefits Over Traditional Approaches
|
|
358
|
+
|
|
359
|
+
| Traditional | dotD Libraries |
|
|
360
|
+
| --------------------- | --------------------------- |
|
|
361
|
+
| Manual imports | Automatic discovery |
|
|
362
|
+
| Tight coupling | Loose coupling |
|
|
363
|
+
| Hard to extend | Easy to extend |
|
|
364
|
+
| Configuration in code | Configuration through files |
|
|
365
|
+
| Monolithic | Modular |
|
|
366
|
+
| Static structure | Dynamic structure |
|
|
367
|
+
|
|
368
|
+
## Getting Started
|
|
369
|
+
|
|
370
|
+
1. **Install the dependency:**
|
|
371
|
+
|
|
372
|
+
```bash
|
|
373
|
+
npm install qtools-library-dot-d
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
2. **Create your dotD directory:**
|
|
377
|
+
|
|
378
|
+
```bash
|
|
379
|
+
mkdir myModules.d
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
3. **Choose your pattern and create modules:**
|
|
383
|
+
|
|
384
|
+
- For function registration: Follow the classic pattern
|
|
385
|
+
- For initialization scripts: Follow the modern pattern
|
|
386
|
+
- Access shared resources via `passThroughParameters`
|
|
387
|
+
|
|
388
|
+
## Advanced Features
|
|
389
|
+
|
|
390
|
+
- **Async module loading** for modules that need initialization
|
|
391
|
+
- **Error handling** during module loading with detailed reporting
|
|
392
|
+
- **Library introspection** with `toString()` and `qtdProcessLog()`
|
|
393
|
+
- **Multiple libraries** in the same application
|
|
394
|
+
- **Nested module structures** for complex applications
|
|
395
|
+
- **Pipeline integration** with qtools-asynchronous-pipe-plus
|
|
396
|
+
- **Logging and tracking** via `dotD.logList` for debugging
|
|
397
|
+
|
|
398
|
+
## Integration with Larger Systems
|
|
399
|
+
|
|
400
|
+
The dotD pattern works particularly well in:
|
|
401
|
+
|
|
402
|
+
- **Web frameworks** (Express, Koa) for route organization
|
|
403
|
+
- **API systems** for endpoint management
|
|
404
|
+
- **CLI tools** for command organization
|
|
405
|
+
- **Microservices** for feature modularity
|
|
406
|
+
- **Plugin systems** for extensible applications
|
|
407
|
+
- **Queue systems** for dynamic job processor loading
|
|
408
|
+
- **File processing pipelines** for modular processing sources
|
|
409
|
+
|
|
410
|
+
This pattern is used extensively in the qtools ecosystem for building scalable, maintainable Node.js applications.
|
|
411
|
+
|
|
412
|
+
## HISTORY
|
|
413
|
+
|
|
414
|
+
v1.10 - there was a reason I did not return the exception list. Undone.
|
|
415
|
+
v1.09 - fixed broken error handling when library file path is bad; added demonstration application
|
|
416
|
+
v1.08 - updated README (several times)
|
|
417
|
+
v1.05 - cleaned up toString() report, returns exception list (or err if callback) if needed, added qtools-functional-library
|
|
418
|
+
v1.04 - Now tolerates (and reports) directories and files that are not modules.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qtools-library-dot-d",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.11",
|
|
4
4
|
"description": "Scan a directory for JS modules that can execute and addMe() that attaches it to some form of name/value list, eg, a Map() with functions as values or an xPressJS route/endpoint list.",
|
|
5
5
|
"main": "qtools-library-dot-d.js",
|
|
6
6
|
"scripts": {
|
package/qtools-library-dot-d.js
CHANGED
|
@@ -14,7 +14,7 @@ const qt = require('qtools-functional-library');
|
|
|
14
14
|
//START OF moduleFunction() ============================================================
|
|
15
15
|
const moduleFunction =
|
|
16
16
|
({ moduleName } = {}) =>
|
|
17
|
-
({ libraryName='libraryDotD' }) => {
|
|
17
|
+
({ libraryName = 'libraryDotD' }) => {
|
|
18
18
|
// ======================================================================================
|
|
19
19
|
// SETTERS
|
|
20
20
|
|
|
@@ -47,7 +47,7 @@ const moduleFunction =
|
|
|
47
47
|
require(item.endpointPath)(item.moduleArgs); //calls module with dotD object, including library.add()
|
|
48
48
|
} catch (err) {
|
|
49
49
|
item.exceptionList.push(
|
|
50
|
-
|
|
50
|
+
`ignoring invlaid file: ${path.basename(item.endpointPath)}: ${err.toString().split('\n').qtFirst('n/a')}`,
|
|
51
51
|
);
|
|
52
52
|
}
|
|
53
53
|
|
|
@@ -207,17 +207,10 @@ const moduleFunction =
|
|
|
207
207
|
// INIT AND EXECUTE THE PIPELINE
|
|
208
208
|
|
|
209
209
|
if (typeof callback == 'function') {
|
|
210
|
-
|
|
211
|
-
callback(exceptionList);
|
|
212
|
-
} else {
|
|
213
|
-
callback();
|
|
214
|
-
}
|
|
210
|
+
callback();
|
|
215
211
|
} else {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
} else {
|
|
219
|
-
return;
|
|
220
|
-
}}
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
221
214
|
};
|
|
222
215
|
|
|
223
216
|
const externalElements = {
|