@teqfw/di 0.31.0 → 0.32.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.
- package/RELEASE.md +10 -2
- package/package.json +5 -5
- package/rollup.config.js +1 -1
- package/src/Api/Container.js +7 -0
- package/src/Container/A/Composer.js +5 -5
- package/src/Container/A/Parser/Chunk/Def.js +25 -21
- package/src/Container/A/Parser/Chunk/V02X.js +29 -34
- package/src/Container.js +38 -2
- package/src/Defs.js +5 -4
- package/src/DepId.js +18 -7
package/RELEASE.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @teqfw/di releases
|
|
2
2
|
|
|
3
|
+
## 0.32.0 - added support for the `node:` prefix
|
|
4
|
+
|
|
5
|
+
- **Added ability** to manually register objects in the container (`register`).
|
|
6
|
+
- **Optimized dependency parsing**, added support for the `node:` prefix.
|
|
7
|
+
- **Updated singleton handling**, fixed issues with `Defs.LS`.
|
|
8
|
+
- **Added protection against object modification**, freezing objects when possible (`Object.freeze`).
|
|
9
|
+
- **Updated dependencies** (Mocha, Rollup, Rollup plugins).
|
|
10
|
+
|
|
3
11
|
## 0.31.0
|
|
4
12
|
|
|
5
13
|
* Added optional `stack` parameter to the `get` method for improved dependency tracking and debugging.
|
|
@@ -70,6 +78,6 @@
|
|
|
70
78
|
## 0.8.0
|
|
71
79
|
|
|
72
80
|
* docs for plugin's teq-descriptor (see in `main` branch);
|
|
73
|
-
* use object notation instead of array notation in namespace replacement statements of
|
|
74
|
-
|
|
81
|
+
* use object notation instead of array notation in namespace replacement statements of teq-descriptor (
|
|
82
|
+
`@teqfw/di.replace` node format is changed in `./teqfw.json`);
|
|
75
83
|
* array is used as a container for upline dependencies in the 'SpecProxy' (object was);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@teqfw/di",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.0",
|
|
4
4
|
"description": "Dependency Injection container for ES6 modules that works in both browser and Node.js apps.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dependency injection",
|
|
@@ -31,10 +31,10 @@
|
|
|
31
31
|
"test": "mocha --recursive './test/**/*.test.mjs'"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"@rollup/plugin-node-resolve": "^
|
|
35
|
-
"mocha": "^
|
|
36
|
-
"rollup": "^
|
|
37
|
-
"rollup
|
|
34
|
+
"@rollup/plugin-node-resolve": "^16.0.1",
|
|
35
|
+
"mocha": "^11.1.0",
|
|
36
|
+
"rollup": "^4.36.0",
|
|
37
|
+
"@rollup/plugin-terser": "^0.4.4"
|
|
38
38
|
},
|
|
39
39
|
"mocha": {
|
|
40
40
|
"spec": "./test/**/*.test.mjs",
|
package/rollup.config.js
CHANGED
package/src/Api/Container.js
CHANGED
|
@@ -34,6 +34,13 @@ export default class TeqFw_Di_Api_Container {
|
|
|
34
34
|
*/
|
|
35
35
|
getResolver() {};
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Registers an object (module, singleton, factory, or prototype) by dependency ID.
|
|
39
|
+
* @param {string} depId
|
|
40
|
+
* @param {Object} obj
|
|
41
|
+
*/
|
|
42
|
+
register(depId, obj) {};
|
|
43
|
+
|
|
37
44
|
/**
|
|
38
45
|
* Enable disable debug output for the object composition process.
|
|
39
46
|
* @param {boolean} data
|
|
@@ -29,17 +29,17 @@ export default class TeqFw_Di_Container_A_Composer {
|
|
|
29
29
|
* @returns {Promise<*>}
|
|
30
30
|
*/
|
|
31
31
|
this.create = async function (depId, module, stack, container) {
|
|
32
|
-
if (stack.includes(depId.
|
|
33
|
-
throw new Error(`Circular dependency for '${depId.
|
|
32
|
+
if (stack.includes(depId.origin))
|
|
33
|
+
throw new Error(`Circular dependency for '${depId.origin}'. Parents are: ${JSON.stringify(stack)}`);
|
|
34
34
|
if (depId.exportName) {
|
|
35
35
|
// use export from the es6-module
|
|
36
|
-
const stackNew = [...stack, depId.
|
|
36
|
+
const stackNew = [...stack, depId.origin];
|
|
37
37
|
const {[depId.exportName]: exp} = module;
|
|
38
|
-
if (depId.composition === Defs.
|
|
38
|
+
if (depId.composition === Defs.CF) {
|
|
39
39
|
if (typeof exp === 'function') {
|
|
40
40
|
// create deps for factory function
|
|
41
41
|
const deps = specParser(exp);
|
|
42
|
-
if (deps.length) log(`Deps for object '${depId.
|
|
42
|
+
if (deps.length) log(`Deps for object '${depId.origin}' are: ${JSON.stringify(deps)}`);
|
|
43
43
|
const spec = {};
|
|
44
44
|
for (const dep of deps)
|
|
45
45
|
spec[dep] = await container.compose(dep, stackNew);
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Default parser for object keys in format:
|
|
3
3
|
* - Ns_Module.export$$(post)
|
|
4
|
+
* - node:package.export$$(post)
|
|
5
|
+
* - node:@scope/package.export$$(post)
|
|
4
6
|
*
|
|
5
7
|
* @namespace TeqFw_Di_Container_A_Parser_Chunk_Def
|
|
6
8
|
*/
|
|
@@ -8,8 +10,8 @@ import Dto from '../../../../DepId.js';
|
|
|
8
10
|
import Defs from '../../../../Defs.js';
|
|
9
11
|
|
|
10
12
|
// VARS
|
|
11
|
-
/** @type {RegExp} expression for default object key
|
|
12
|
-
const REGEXP = /^((([A-
|
|
13
|
+
/** @type {RegExp} expression for default object key */
|
|
14
|
+
const REGEXP = /^(node:)?((@?[A-Za-z0-9_\-]+\/?[A-Za-z0-9_\-]*))((\.)?([A-Za-z0-9_]*)((\$)?(\$)?)?)?(\(([A-Za-z0-9_,]*)\))?$/;
|
|
13
15
|
|
|
14
16
|
/**
|
|
15
17
|
* @implements TeqFw_Di_Api_Container_Parser_Chunk
|
|
@@ -17,49 +19,51 @@ const REGEXP = /^((([A-Z])[A-Za-z0-9_]*)((\.)?([A-Za-z0-9_]*)((\$)?(\$)?)?)?(\((
|
|
|
17
19
|
export default class TeqFw_Di_Container_A_Parser_Chunk_Def {
|
|
18
20
|
|
|
19
21
|
canParse(depId) {
|
|
20
|
-
// default parser always
|
|
22
|
+
// default parser always tries to parse the depId
|
|
21
23
|
return true;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
parse(objectKey) {
|
|
25
27
|
const res = new Dto();
|
|
26
|
-
res.
|
|
28
|
+
res.origin = objectKey;
|
|
27
29
|
const parts = REGEXP.exec(objectKey);
|
|
28
30
|
if (parts) {
|
|
29
|
-
res.
|
|
31
|
+
res.isNodeModule = Boolean(parts[1]); // Detect 'node:' prefix
|
|
32
|
+
res.moduleName = parts[2].replace(/^node:/, ''); // Remove 'node:' prefix
|
|
33
|
+
|
|
30
34
|
if (parts[5] === '.') {
|
|
31
|
-
// Ns_Module.export
|
|
35
|
+
// Ns_Module.export or node:package.export
|
|
32
36
|
if ((parts[7] === '$') || (parts[7] === '$$')) {
|
|
33
|
-
|
|
34
|
-
res.composition = Defs.COMP_F;
|
|
37
|
+
res.composition = Defs.CF;
|
|
35
38
|
res.exportName = parts[6];
|
|
36
|
-
res.life = (parts[7] === '$') ? Defs.
|
|
39
|
+
res.life = (parts[7] === '$') ? Defs.LS : Defs.LI;
|
|
37
40
|
} else {
|
|
38
|
-
res.composition = Defs.
|
|
39
|
-
res.life = Defs.
|
|
40
|
-
// res.exportName = (parts[6]) ? parts[6] : 'default';
|
|
41
|
+
res.composition = Defs.CA;
|
|
42
|
+
res.life = Defs.LS;
|
|
41
43
|
res.exportName = (parts[6] !== '') ? parts[6] : 'default';
|
|
42
44
|
}
|
|
43
45
|
} else if ((parts[7] === '$') || parts[7] === '$$') {
|
|
44
|
-
// Ns_Module$$
|
|
45
|
-
res.composition = Defs.
|
|
46
|
+
// Ns_Module$$ or node:package$$
|
|
47
|
+
res.composition = Defs.CF;
|
|
46
48
|
res.exportName = 'default';
|
|
47
|
-
res.life = (parts[7] === '$') ? Defs.
|
|
49
|
+
res.life = (parts[7] === '$') ? Defs.LS : Defs.LI;
|
|
48
50
|
} else {
|
|
49
|
-
// Ns_Module (
|
|
51
|
+
// Ns_Module or node:package (ES6 module)
|
|
50
52
|
res.composition = undefined;
|
|
51
53
|
res.exportName = undefined;
|
|
52
54
|
res.life = undefined;
|
|
53
55
|
}
|
|
54
|
-
|
|
56
|
+
|
|
57
|
+
// Wrappers handling
|
|
55
58
|
if (parts[11]) {
|
|
56
59
|
res.wrappers = parts[11].split(',');
|
|
57
60
|
}
|
|
58
61
|
}
|
|
59
62
|
|
|
60
|
-
//
|
|
61
|
-
if ((res.composition === Defs.
|
|
62
|
-
throw new Error(`Export is not a function and should be used as a singleton only: '${res.
|
|
63
|
+
// Ensure singletons for non-factory exports
|
|
64
|
+
if ((res.composition === Defs.CA) && (res.life === Defs.LI))
|
|
65
|
+
throw new Error(`Export is not a function and should be used as a singleton only: '${res.origin}'.`);
|
|
66
|
+
|
|
63
67
|
return res;
|
|
64
68
|
}
|
|
65
|
-
}
|
|
69
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Default parser for object keys in format:
|
|
3
3
|
* - Ns_Module[.|#]export$[F|A][S|I]
|
|
4
|
+
* - node:package[.|#]export$[F|A][S|I]
|
|
4
5
|
*
|
|
5
6
|
* @namespace TeqFw_Di_Container_A_Parser_Chunk_V02X
|
|
6
7
|
*/
|
|
@@ -8,8 +9,8 @@ import Dto from '../../../../DepId.js';
|
|
|
8
9
|
import Defs from '../../../../Defs.js';
|
|
9
10
|
|
|
10
11
|
// VARS
|
|
11
|
-
/** @type {RegExp} expression for default object key
|
|
12
|
-
const REGEXP = /^((([A-Z])[A-Za-z0-9_]*)((#|\.)?([A-Za-z0-9_]*)((\$)([F|A])?([S|I])?)?)
|
|
12
|
+
/** @type {RegExp} expression for default object key */
|
|
13
|
+
const REGEXP = /^(node:)?((([A-Z])[A-Za-z0-9_]*|[a-z][a-z0-9\-]*))((#|\.)?([A-Za-z0-9_]*)((\$)([F|A])?([S|I])?)?)?$/;
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* @implements TeqFw_Di_Api_Container_Parser_Chunk
|
|
@@ -17,54 +18,48 @@ const REGEXP = /^((([A-Z])[A-Za-z0-9_]*)((#|\.)?([A-Za-z0-9_]*)((\$)([F|A])?([S|
|
|
|
17
18
|
export default class TeqFw_Di_Container_A_Parser_Chunk_V02X {
|
|
18
19
|
|
|
19
20
|
canParse(depId) {
|
|
20
|
-
// default parser always
|
|
21
|
+
// default parser always tries to parse the depId
|
|
21
22
|
return true;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
parse(objectKey) {
|
|
25
26
|
const res = new Dto();
|
|
26
|
-
res.
|
|
27
|
+
res.origin = objectKey;
|
|
27
28
|
const parts = REGEXP.exec(objectKey);
|
|
28
29
|
if (parts) {
|
|
29
|
-
res.
|
|
30
|
-
|
|
31
|
-
// App_Service.export...
|
|
32
|
-
if (parts[8] === '$') {
|
|
33
|
-
// App_Service.export$...
|
|
34
|
-
res.composition = Defs.COMP_F;
|
|
35
|
-
res.exportName = parts[6];
|
|
36
|
-
res.life = (parts[10] === Defs.LIFE_I)
|
|
37
|
-
? Defs.LIFE_I : Defs.LIFE_S;
|
|
38
|
-
} else {
|
|
39
|
-
res.composition = ((parts[8] === undefined) || (parts[8] === Defs.COMP_A))
|
|
40
|
-
? Defs.COMP_A : Defs.COMP_F;
|
|
41
|
-
res.exportName = parts[6];
|
|
42
|
-
res.life = ((parts[8] === undefined) || (parts[10] === Defs.LIFE_S))
|
|
43
|
-
? Defs.LIFE_S : Defs.LIFE_I;
|
|
44
|
-
}
|
|
30
|
+
res.isNodeModule = Boolean(parts[1]); // Check if it starts with 'node:'
|
|
31
|
+
res.moduleName = parts[2].replace(/^node:/, ''); // Remove 'node:' if present
|
|
45
32
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
res.life = (parts[10] === Defs.LIFE_S) ? Defs.LIFE_S : Defs.LIFE_I;
|
|
33
|
+
if (parts[6] === '.') {
|
|
34
|
+
// App_Service.export or node:package.export
|
|
35
|
+
if (parts[9] === '$') {
|
|
36
|
+
// App_Service.export$ or node:package.export$
|
|
37
|
+
res.composition = Defs.CF;
|
|
38
|
+
res.exportName = parts[7];
|
|
39
|
+
res.life = (parts[11] === Defs.LI) ? Defs.LI : Defs.LS;
|
|
54
40
|
} else {
|
|
55
|
-
res.
|
|
41
|
+
res.composition = (!parts[9] || parts[9] === Defs.CA) ? Defs.CA : Defs.CF;
|
|
42
|
+
res.exportName = parts[7];
|
|
43
|
+
res.life = (!parts[9] || parts[11] === Defs.LS) ? Defs.LS : Defs.LI;
|
|
56
44
|
}
|
|
45
|
+
} else if (parts[9] === '$') {
|
|
46
|
+
// App_Logger$FS or node:package$
|
|
47
|
+
res.composition = (!parts[10] || parts[10] === Defs.CF) ? Defs.CF : Defs.CA;
|
|
48
|
+
res.exportName = 'default';
|
|
49
|
+
res.life = parts[11] ? (parts[11] === Defs.LS ? Defs.LS : Defs.LI) : (res.composition === Defs.CF ? Defs.LS : Defs.LI);
|
|
57
50
|
} else {
|
|
58
|
-
// App_Service (
|
|
51
|
+
// App_Service or node:package (ES6 module)
|
|
59
52
|
res.composition = undefined;
|
|
60
53
|
res.exportName = undefined;
|
|
61
54
|
res.life = undefined;
|
|
62
55
|
}
|
|
63
56
|
}
|
|
64
57
|
|
|
65
|
-
//
|
|
66
|
-
if (
|
|
67
|
-
throw new Error(`Export is not a function and should be used as a singleton only: '${res.
|
|
58
|
+
// Enforce singleton for non-factory exports
|
|
59
|
+
if (res.composition === Defs.CA && res.life === Defs.LI) {
|
|
60
|
+
throw new Error(`Export is not a function and should be used as a singleton only: '${res.origin}'.`);
|
|
61
|
+
}
|
|
62
|
+
|
|
68
63
|
return res;
|
|
69
64
|
}
|
|
70
|
-
}
|
|
65
|
+
}
|
package/src/Container.js
CHANGED
|
@@ -19,6 +19,21 @@ function getSingletonId(key) {
|
|
|
19
19
|
return `${key.moduleName}#${key.exportName}`;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Determines if an object, function, or primitive can be safely frozen.
|
|
24
|
+
* @param {*} value - The value to check.
|
|
25
|
+
* @returns {boolean} - Returns true if the value can be safely frozen.
|
|
26
|
+
*/
|
|
27
|
+
function canBeFrozen(value) {
|
|
28
|
+
// Primitives (except objects and functions) cannot be frozen
|
|
29
|
+
if (value === null || typeof value !== 'object' && typeof value !== 'function') return false;
|
|
30
|
+
// // ES modules cannot be frozen
|
|
31
|
+
if (Object.prototype.toString.call(value) === '[object Module]') return false;
|
|
32
|
+
// check is Object is already frozen
|
|
33
|
+
return !Object.isFrozen(value);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
22
37
|
// MAIN
|
|
23
38
|
/**
|
|
24
39
|
* @implements TeqFw_Di_Api_Container
|
|
@@ -80,7 +95,7 @@ export default class TeqFw_Di_Container {
|
|
|
80
95
|
// modify original key according to some rules (replacements, etc.)
|
|
81
96
|
const key = _preProcessor.modify(parsed, stack);
|
|
82
97
|
// return existing singleton
|
|
83
|
-
if (key.life === Defs.
|
|
98
|
+
if (key.life === Defs.LS) {
|
|
84
99
|
const singleId = getSingletonId(key);
|
|
85
100
|
if (_regSingles[singleId]) {
|
|
86
101
|
log(`Existing singleton '${singleId}' is returned.`);
|
|
@@ -111,11 +126,13 @@ export default class TeqFw_Di_Container {
|
|
|
111
126
|
}
|
|
112
127
|
// create object using the composer then modify it in post-processor
|
|
113
128
|
let res = await _composer.create(key, module, stack, this);
|
|
129
|
+
// freeze the result to prevent modifications (TODO: should we have configuration for the feature?)
|
|
130
|
+
if (canBeFrozen(res)) Object.freeze(res);
|
|
114
131
|
res = await _postProcessor.modify(res, key, stack);
|
|
115
132
|
log(`Object '${depId}' is created.`);
|
|
116
133
|
|
|
117
134
|
// save singletons
|
|
118
|
-
if (key.life === Defs.
|
|
135
|
+
if (key.life === Defs.LS) {
|
|
119
136
|
const singleId = getSingletonId(key);
|
|
120
137
|
_regSingles[singleId] = res;
|
|
121
138
|
log(`Object '${depId}' is saved as singleton.`);
|
|
@@ -128,8 +145,27 @@ export default class TeqFw_Di_Container {
|
|
|
128
145
|
this.getPreProcessor = () => _preProcessor;
|
|
129
146
|
|
|
130
147
|
this.getPostProcessor = () => _postProcessor;
|
|
148
|
+
|
|
131
149
|
this.getResolver = () => _resolver;
|
|
132
150
|
|
|
151
|
+
/**
|
|
152
|
+
* Register new object in the Container.
|
|
153
|
+
* @param {string} depId
|
|
154
|
+
* @param {Object} obj
|
|
155
|
+
*/
|
|
156
|
+
this.register = function (depId, obj) {
|
|
157
|
+
if (!depId || !obj) throw new Error('depId and object are required');
|
|
158
|
+
const key = _parser.parse(depId);
|
|
159
|
+
if (key.life === Defs.LS) {
|
|
160
|
+
const singleId = getSingletonId(key);
|
|
161
|
+
_regSingles[singleId] = obj;
|
|
162
|
+
log(`Object '${depId}' is registered manually as singleton.`);
|
|
163
|
+
} else {
|
|
164
|
+
// TODO: factory function also should be added manually
|
|
165
|
+
throw new Error(`Only singletons can be registered manually. Given: ${depId}`);
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
|
|
133
169
|
this.setDebug = function (data) {
|
|
134
170
|
_debug = data;
|
|
135
171
|
_composer.setDebug(data);
|
package/src/Defs.js
CHANGED
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
* @namespace TeqFw_Di_Defs
|
|
4
4
|
*/
|
|
5
5
|
export default {
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
CA: 'A', // composition: as-is
|
|
7
|
+
CF: 'F', // composition: factory
|
|
8
|
+
// TODO: we don't need an access to the container itself.
|
|
8
9
|
ID: 'container', // default ID for container itself
|
|
9
10
|
ID_FQN: 'TeqFw_Di_Container$', // default Full Qualified Name for container itself
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
LI: 'I', // lifestyle: instance
|
|
12
|
+
LS: 'S', // lifestyle: singleton
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Return 'true' if function is a class definition.
|
package/src/DepId.js
CHANGED
|
@@ -1,33 +1,44 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* DTO representing a dependency identifier in the container.
|
|
3
3
|
* @namespace TeqFw_Di_DepId
|
|
4
4
|
*/
|
|
5
5
|
export default class TeqFw_Di_DepId {
|
|
6
6
|
/**
|
|
7
|
-
* The name of an export
|
|
7
|
+
* The name of an export from the module.
|
|
8
|
+
* Example: 'default', 'logger', 'DbClient'.
|
|
8
9
|
* @type {string}
|
|
9
10
|
*/
|
|
10
11
|
exportName;
|
|
11
12
|
/**
|
|
12
|
-
*
|
|
13
|
+
* Defines how the export should be used:
|
|
14
|
+
* - 'F' (Factory): The export is a factory function, call it to get an instance.
|
|
15
|
+
* - 'A' (As-Is): The export is returned as-is, without calling.
|
|
16
|
+
* Example: 'F', 'A'.
|
|
13
17
|
* @type {string}
|
|
14
18
|
*/
|
|
15
19
|
composition;
|
|
16
20
|
/**
|
|
17
|
-
*
|
|
21
|
+
* Defines the lifecycle of the resolved dependency:
|
|
22
|
+
* - 'S' (Singleton): A single instance is created and reused.
|
|
23
|
+
* - 'I' (Instance): A new instance is created on each request.
|
|
24
|
+
* Example: 'S', 'I'.
|
|
18
25
|
* @type {string}
|
|
19
26
|
*/
|
|
20
27
|
life;
|
|
21
28
|
/**
|
|
22
|
-
*
|
|
29
|
+
* ES6 module identifier, which can be transformed into a file path.
|
|
30
|
+
* This value is processed by the Resolver to determine the module's location.
|
|
31
|
+
* Example: 'TeqFw_Core_Shared_Api_Logger'.
|
|
23
32
|
* @type {string}
|
|
24
33
|
*/
|
|
25
34
|
moduleName;
|
|
26
35
|
/**
|
|
27
|
-
*
|
|
36
|
+
* The original identifier string provided to the container.
|
|
37
|
+
* This is the unprocessed dependency key.
|
|
38
|
+
* Example: 'TeqFw_Core_Shared_Api_Logger$$'.
|
|
28
39
|
* @type {string}
|
|
29
40
|
*/
|
|
30
|
-
|
|
41
|
+
origin;
|
|
31
42
|
/**
|
|
32
43
|
* List of wrappers to decorate the result.
|
|
33
44
|
* @type {string[]}
|