@teqfw/di 0.21.1 → 0.30.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/README.md +88 -220
- package/RELEASE.md +9 -0
- package/bin/release/clean.sh +1 -0
- package/dist/esm.js +1 -0
- package/dist/umd.js +1 -0
- package/package.json +7 -10
- package/rollup.config.js +21 -0
- package/src/Api/Container/Resolver.js +16 -0
- package/src/Api/Container.js +2 -2
- package/src/Container/A/Composer.js +1 -2
- package/src/Container/A/Parser/Chunk/Def.js +25 -30
- package/src/Container/A/Parser/Chunk/V02X.js +70 -0
- package/src/Container/Resolver.js +18 -3
- package/src/Container.js +6 -6
- package/src/Defs.js +6 -11
- package/dist/di.cjs.js +0 -205
- package/dist/di.esm.js +0 -206
- package/docs/README.md +0 -6
- package/docs/app.vue +0 -5
- package/docs/assets/css/layout.css +0 -51
- package/docs/assets/css/vars.css +0 -6
- package/docs/components/AppFooter.vue +0 -47
- package/docs/components/AppHeader.vue +0 -79
- package/docs/content/index.md +0 -272
- package/docs/content/test.md +0 -20
- package/docs/layouts/default.vue +0 -83
- package/docs/nuxt.config.mjs +0 -11
- package/docs/package-lock.json +0 -14873
- package/docs/package.json +0 -25
- package/docs/pages/[...slug].vue +0 -6
- package/docs/pages/index.vue +0 -6
- package/docs/public/.nojekyll +0 -0
- package/docs/public/CNAME +0 -1
- package/docs/public/favicon.ico +0 -0
- package/docs/public/img/github.svg +0 -1
- package/docs/public/img/npm.png +0 -0
- package/docs/public/img/teqfw_di_container_steps.png +0 -0
- package/docs/server/tsconfig.json +0 -3
- package/docs/tsconfig.json +0 -4
- package/index.cjs +0 -5
- package/index.mjs +0 -6
- package/webpack.config.mjs +0 -15
package/README.md
CHANGED
|
@@ -1,259 +1,127 @@
|
|
|
1
1
|
# @teqfw/di
|
|
2
2
|
|
|
3
|
-
A Dependency Injection container for regular JavaScript is provided, which can be used in both
|
|
4
|
-
applications
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
A Dependency Injection container for regular JavaScript is provided, which can be used in both browser and Node.js
|
|
4
|
+
applications with JS, and in Node.js only with TS.
|
|
5
|
+
|
|
6
|
+
**This library exclusively supports ES6 modules.**
|
|
7
|
+
|
|
8
|
+
The primary objective of this library is the late binding for code objects with minimal manual configuration for the
|
|
9
|
+
object container. All linking instructions are encapsulated within the dependency identifiers and used in constructors
|
|
10
|
+
or factory functions (the constructor injection scheme):
|
|
11
|
+
|
|
12
|
+
```js
|
|
13
|
+
export default class App_Main {
|
|
14
|
+
constructor(
|
|
15
|
+
{
|
|
16
|
+
App_Config$: config,
|
|
17
|
+
App_Logger$: logger,
|
|
18
|
+
App_Service_Customer$: servCustomer,
|
|
19
|
+
App_Service_Sale$: servSale,
|
|
20
|
+
}
|
|
21
|
+
) { ... }
|
|
10
22
|
|
|
11
|
-
## Inversion of Control
|
|
12
|
-
|
|
13
|
-
The primary motivation for creating this library was the concept that JavaScript is a language in which the entire
|
|
14
|
-
application can be written, both on the front end and the back end. The idea was to enable the use of the same
|
|
15
|
-
JavaScript code seamlessly on both the front end and the back end without requiring any changes, including additional
|
|
16
|
-
transpilation.
|
|
17
|
-
|
|
18
|
-
The main challenge encountered along this path was static importing. When the entire application can fit into a single
|
|
19
|
-
npm package, all sources can be linked to each other through relative paths. However, if the sources are distributed
|
|
20
|
-
across different npm packages, addressing them becomes problematic:
|
|
21
|
-
|
|
22
|
-
```javascript
|
|
23
|
-
import something from '@vendor/package/src/Module.js'; // backend style
|
|
24
|
-
import something from 'https://domain.com/@vendor/package/src/Module.js'; // frontend style
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
The inversion of control (IoC) design pattern came to the rescue. In this pattern, any software object with external
|
|
28
|
-
dependencies provides a mechanism for obtaining these dependencies. The external environment, whether it's a test unit
|
|
29
|
-
or an object container, is responsible for creating these dependencies and providing them to the software object.
|
|
30
|
-
|
|
31
|
-
```javascript
|
|
32
|
-
// constructor-based injection
|
|
33
|
-
class Service {
|
|
34
|
-
constructor(config, logger) {}
|
|
35
23
|
}
|
|
36
24
|
```
|
|
37
25
|
|
|
38
|
-
|
|
39
|
-
the source code itself. Now, they can be used without any changes, both on the front end and on the back end.
|
|
26
|
+
The files corresponded to this case:
|
|
40
27
|
|
|
41
|
-
|
|
28
|
+
```
|
|
29
|
+
./src/
|
|
30
|
+
./Service/
|
|
31
|
+
./Customer.js
|
|
32
|
+
./Sale.js
|
|
33
|
+
./Config.js
|
|
34
|
+
./Logger.js
|
|
35
|
+
./Main.js
|
|
36
|
+
```
|
|
42
37
|
|
|
43
|
-
|
|
44
|
-
utilizes an object container, responsible for creating all the application's objects and their dependencies. The
|
|
45
|
-
`@teqfw/di` package provides precisely such an object container (`src/Container.js`). This object container is
|
|
46
|
-
initialized and configured at the outset of application execution, after which it assumes responsibility for creating
|
|
47
|
-
the remaining application objects:
|
|
38
|
+
Just set up a mapping rules for the container:
|
|
48
39
|
|
|
49
|
-
```
|
|
40
|
+
```js
|
|
50
41
|
import Container from '@teqfw/di';
|
|
51
42
|
|
|
52
43
|
const container = new Container();
|
|
53
44
|
const resolver = container.getResolver();
|
|
54
|
-
resolver.addNamespaceRoot('App_',
|
|
55
|
-
resolver.addNamespaceRoot('Sample_Lib_', pathLib);
|
|
45
|
+
resolver.addNamespaceRoot('App_', '/path/to/src'); // or 'https://cdn.jsdelivr.net/npm/@vendor/pkg@latest/src'
|
|
56
46
|
const app = await container.get('App_Main$');
|
|
57
47
|
```
|
|
58
48
|
|
|
59
|
-
|
|
60
|
-
[Zend 1](https://framework.zend.com/manual/2.4/en/migration/namespacing-old-classes.html) is used as the basis for
|
|
61
|
-
identifiers.
|
|
62
|
-
|
|
63
|
-
## Namespaces
|
|
64
|
-
|
|
65
|
-
The primary purpose of namespaces is to address code elements within an application. In JavaScript (JS) applications,
|
|
66
|
-
code is organized into npm packages, within which the sources reside in files and directories. Each npm package and its
|
|
67
|
-
root directory can be linked to a namespace:
|
|
68
|
-
|
|
69
|
-
```
|
|
70
|
-
Vendor_Package_ => /home/user/app/node_modules/@vendor/package/src/....
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
This way, you can reference any ES6 module in any npm package:
|
|
74
|
-
|
|
75
|
-
```
|
|
76
|
-
Venodr_Package_Shared_Dto_Service_Save => /home/user/app/node_modules/@vendor/package/src/Shared/Dto/Service/Save.js
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
Depending on the execution environment, the mapping may be different:
|
|
80
|
-
|
|
81
|
-
```
|
|
82
|
-
Vendor_Package_ => /home/user/app/node_modules/@vendor/package/src // Linux style
|
|
83
|
-
Vendor_Package_ => C:\projects\app\node_modules\@vendor\package\src // Window style
|
|
84
|
-
Vendor_Package_ => https://unpkg.com/@vendor/package/src // Web style
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
The source code employs namespaces to reference dependencies, while the object container utilizes a resolver to
|
|
88
|
-
translate the namespace into the corresponding path to the source code file, contingent upon the runtime environment:
|
|
89
|
-
|
|
90
|
-
```
|
|
91
|
-
Venodr_Package_Shared_Dto_Service_Save => /home/user/app/node_modules/@vendor/package/src/Shared/Dto/Service/Save.js
|
|
92
|
-
Venodr_Package_Shared_Dto_Service_Save => C:\projects\app\node_modules\@vendor\package\src\Shared\Dto\Service\Save.js
|
|
93
|
-
Venodr_Package_Shared_Dto_Service_Save => https://unpkg.com/@vendor/package/src/Shared/Dto/Service/Save.js
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
## Dependency Specification
|
|
97
|
-
|
|
98
|
-
JavaScript lacks reflection capabilities similar to Java or PHP. Consequently, to enable the object container to
|
|
99
|
-
comprehend the necessary dependencies for creating a specific object, a distinct convention is employed - a dependency
|
|
100
|
-
specification. A dependency specification is an object where each key represents the identifier of the required
|
|
101
|
-
dependency:
|
|
102
|
-
|
|
103
|
-
```javascript
|
|
104
|
-
class Service {
|
|
105
|
-
constructor(
|
|
106
|
-
{
|
|
107
|
-
App_Config: config,
|
|
108
|
-
App_Logger: logger
|
|
109
|
-
}
|
|
110
|
-
) {}
|
|
111
|
-
}
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
In the object container, the required object is created as follows:
|
|
115
|
-
|
|
116
|
-
```javascript
|
|
117
|
-
const App_Config = await container.get('App_Config');
|
|
118
|
-
const App_Logger = await container.get('App_Logger');
|
|
119
|
-
const spec = {App_Config, App_Logger};
|
|
120
|
-
const obj = new Service(spec);
|
|
121
|
-
```
|
|
49
|
+
That's all.
|
|
122
50
|
|
|
123
|
-
|
|
51
|
+
## The main benefits
|
|
124
52
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
53
|
+
* **Late Binding**: Enjoy all the typical advantages of late binding during runtime, including flexibility, testability,
|
|
54
|
+
modularity, manageability, and clear separation of concerns.
|
|
55
|
+
* **Integration with ES6 Modules**: Seamlessly integrate singletons and instances based on ES6 module exports.
|
|
56
|
+
* **Interface Usage in Vanilla JS**: Utilize "interfaces" in standard JavaScript, along with dependency substitution (
|
|
57
|
+
preprocessing).
|
|
58
|
+
* **Object Wrapping**: Add wrappers to created objects (postprocessing) for enhanced functionality.
|
|
131
59
|
|
|
132
|
-
##
|
|
60
|
+
## Installation
|
|
133
61
|
|
|
134
|
-
|
|
135
|
-
of [exporting](https://flancer32.com/es6-export-as-code-brick-b33a8efb3510):
|
|
62
|
+
NodeJS:
|
|
136
63
|
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
obj1 as default, obj2, obj3
|
|
140
|
-
};
|
|
64
|
+
```shell
|
|
65
|
+
$ npm i --save @teqfw/di
|
|
141
66
|
```
|
|
142
67
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
````javascript
|
|
146
|
-
import obj1 from './mod.js';
|
|
147
|
-
import {obj2} from './mod.js';
|
|
148
|
-
````
|
|
149
|
-
|
|
150
|
-
This implies that the dependency identifier must have the capability to reference not only the ES6 module itself but
|
|
151
|
-
also a specific export within it, as illustrated in this example:
|
|
152
|
-
|
|
153
|
-
```javascript
|
|
154
|
-
const exp = 'Vendor_Package_Module.export';
|
|
155
|
-
const def = 'Vendor_Package_Module.default';
|
|
156
|
-
const obj2 = 'Vendor_Package_Module.obj2';
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
In this case, the dependency declaration in a constructor or factory function could look like this:
|
|
160
|
-
|
|
161
|
-
```javascript
|
|
162
|
-
class Service {
|
|
163
|
-
constructor(
|
|
164
|
-
{
|
|
165
|
-
'App_Config.default': config,
|
|
166
|
-
'App_Util.logger': logger
|
|
167
|
-
}
|
|
168
|
-
) {}
|
|
169
|
-
}
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
## Late binding
|
|
173
|
-
|
|
174
|
-
The object container links objects not at the source code level but in runtime mode. In my applications, I have
|
|
175
|
-
encountered two particularly useful runtime object lifecycles:
|
|
68
|
+
Web as ESM (~5Kb):
|
|
176
69
|
|
|
177
|
-
|
|
178
|
-
* **Instance**: A new object is created each time.
|
|
70
|
+
```html
|
|
179
71
|
|
|
180
|
-
|
|
181
|
-
|
|
72
|
+
<script type="module">
|
|
73
|
+
import {default as Container} from 'https://cdn.jsdelivr.net/npm/@teqfw/di@latest/+esm';
|
|
182
74
|
|
|
183
|
-
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
75
|
+
/** @type {TeqFw_Di_Container} */
|
|
76
|
+
const container = new Container();
|
|
77
|
+
...
|
|
78
|
+
</script>
|
|
187
79
|
```
|
|
188
80
|
|
|
189
|
-
|
|
190
|
-
`TeqFw_Di_Container_Parser` object is responsible for applying the appropriate format within the required namespace.
|
|
81
|
+
Web as UMD (~5Kb):
|
|
191
82
|
|
|
192
|
-
|
|
83
|
+
```html
|
|
193
84
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
* **Postprocessing**: the modification of the created object
|
|
202
|
-
|
|
203
|
-
### Preprocessing
|
|
204
|
-
|
|
205
|
-
At times, situations may arise, especially when utilizing various extensions of the core functionality, where it becomes
|
|
206
|
-
necessary to redefine certain objects within the application. For such scenarios, `@teqfw/di` includes a preprocessor:
|
|
207
|
-
|
|
208
|
-
```javascript
|
|
209
|
-
/** @type {TeqFw_Di_Api_Container_PreProcessor} */
|
|
210
|
-
const pre = container.getPreProcessor();
|
|
85
|
+
<script src="https://cdn.jsdelivr.net/npm/@teqfw/di@latest/dist/umd.js"></script>
|
|
86
|
+
<script>
|
|
87
|
+
const {default: Container} = window.TeqFw_Di_Container;
|
|
88
|
+
/** @type {TeqFw_Di_Container} */
|
|
89
|
+
const container = new Container();
|
|
90
|
+
...
|
|
91
|
+
</script>
|
|
211
92
|
```
|
|
212
93
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
```javascript
|
|
216
|
-
/** @type {TeqFw_Di_Api_Container_PreProcessor_Chunk} */
|
|
217
|
-
const replace = new ReplaceChunk(); // some implementation of the interface
|
|
218
|
-
pre.addChunk(replace);
|
|
219
|
-
```
|
|
94
|
+
## Types of Dependency ID
|
|
220
95
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
`
|
|
96
|
+
* `App_Service`=> `import * as Service from './App/Service.js'` as ES Module
|
|
97
|
+
* `App_Service.default` => `import {default} from './App/Service.js'` default export as-is
|
|
98
|
+
* `App_Service.name` => `import {name} from './App/Service.js'` named export as-is
|
|
99
|
+
* `App_Service$` => `import {default} from './App/Service.js'; return res ?? (res = default({...}));` as singleton for
|
|
100
|
+
container
|
|
101
|
+
* `App_Service$$` => `import {default} from './App/Service.js'; return new default({...})` as instance for every
|
|
102
|
+
dep
|
|
103
|
+
* `App_Service.name$` => `import {name} from './App/Service.js'; return res ?? (res = name({...}));` as singleton
|
|
104
|
+
* `App_Service.name$$` => `import {name} from './App/Service.js'; const res = new name({...})` as instance
|
|
105
|
+
* `...(proxy,factory,...)`: add custom wrappers on postprocessing
|
|
224
106
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
*/
|
|
241
|
-
export default {
|
|
242
|
-
modify: function (obj, originalId, stack) {
|
|
243
|
-
if (originalId.wrappers.indexOf('proxy') !== -1)
|
|
244
|
-
return new Proxy(obj, {
|
|
245
|
-
get: async function (base, name) { /* do something */ }
|
|
246
|
-
});
|
|
247
|
-
else return obj;
|
|
107
|
+
```js
|
|
108
|
+
export default class App_Main {
|
|
109
|
+
constructor(
|
|
110
|
+
{
|
|
111
|
+
App_Service: EsModule,
|
|
112
|
+
'App_Service.default': defaultExportAsIs,
|
|
113
|
+
'App_Service.name': namedExportAsIs,
|
|
114
|
+
App_Service$: defaultExportAsSingleton,
|
|
115
|
+
App_Service$$: defaultExportAsInstance,
|
|
116
|
+
'App_Service.name$': namedExportAsSingleton,
|
|
117
|
+
'App_Service.name$$': namedExportAsInstance,
|
|
118
|
+
'App_Service.name(factory)': factoryToCreateInstancesFromNamedExport,
|
|
119
|
+
}
|
|
120
|
+
) {
|
|
121
|
+
const {default as SrvDef, name as SrvName} = EsModule; // deconstruct the module and access the exports
|
|
248
122
|
}
|
|
249
|
-
};
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
```javascript
|
|
253
|
-
// ./main.js
|
|
254
|
-
import postChunk from './PostChunk.mjs';
|
|
255
123
|
|
|
256
|
-
|
|
124
|
+
}
|
|
257
125
|
```
|
|
258
126
|
|
|
259
127
|
## Resume
|
package/RELEASE.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# @teqfw/di releases
|
|
2
2
|
|
|
3
|
+
## 0.30.0
|
|
4
|
+
|
|
5
|
+
* New format of the depId for default parser (`Ns_Module.export$$(post)`).
|
|
6
|
+
* The rollup is added.
|
|
7
|
+
|
|
8
|
+
## 0.22.0
|
|
9
|
+
|
|
10
|
+
* Add Windows paths to the Resolver.
|
|
11
|
+
|
|
3
12
|
## 0.21.1
|
|
4
13
|
|
|
5
14
|
* Fix the dependency key signature in `TeqFw_Di_Container_A_Parser_Chunk_Def`.
|
package/bin/release/clean.sh
CHANGED
package/dist/esm.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var e={COMP_A:"A",COMP_F:"F",ID:"container",ID_FQN:"TeqFw_Di_Container$",LIFE_I:"I",LIFE_S:"S",isClass(e){const t=Object.getOwnPropertyDescriptor(e,"prototype");return t&&!t.writable}};const t=/(function)*\s*\w*\s*\(\s*\{([^\}]*)\}/s,o=/constructor\s*\(\s*\{([^\}]*)\}/s;function s(e){const t=[];try{const o=new Function(`{${e}}`,"return");o(new Proxy({},{get:(e,o)=>t.push(o)}))}catch(t){throw new Error(`Cannot analyze the deps specification:${e}\n\nPlease, be sure that spec does not contain extra ')' in a comments.\n\nError: ${t}`)}return t}function n(n){return"function"==typeof n?e.isClass(n)?function(e){const t=[],n=e.toString(),r=o.exec(n);return r&&t.push(...s(r[1])),t}(n):function(e){const o=[],n=e.toString(),r=t.exec(n);return r&&o.push(...s(r[2])),o}(n):[]}class r{constructor(){let t=!1;this.create=async function(o,s,r,i){if(r.includes(o.value))throw new Error(`Circular dependency for '${o.value}'. Parents are: ${JSON.stringify(r)}`);if(o.exportName){const a=[...r,o.value],{[o.exportName]:u}=s;if(o.composition===e.COMP_F){if("function"==typeof u){const s=n(u);s.length&&(c=`Deps for object '${o.value}' are: ${JSON.stringify(s)}`,t&&console.log(c));const r={};for(const e of s)r[e]=await i.compose(e,a);const l=e.isClass(u)?new u(r):u(r);return l instanceof Promise?await l:l}return Object.assign({},u)}return u}return s;var c},this.setDebug=function(e){t=e}}}class i{exportName;composition;life;moduleName;value;wrappers=[]}const c=/^((([A-Z])[A-Za-z0-9_]*)((\.)?([A-Za-z0-9_]*)((\$)?(\$)?)?)?(\(([A-Za-z0-9_,]*)\))?)$/;class a{canParse(e){return!0}parse(t){const o=new i;o.value=t;const s=c.exec(t);if(s&&(o.moduleName=s[2],"."===s[5]?"$"===s[7]||"$$"===s[7]?(o.composition=e.COMP_F,o.exportName=s[6],o.life="$"===s[7]?e.LIFE_S:e.LIFE_I):(o.composition=e.COMP_A,o.life=e.LIFE_S,o.exportName=""!==s[6]?s[6]:"default"):"$"===s[7]||"$$"===s[7]?(o.composition=e.COMP_F,o.exportName="default",o.life="$"===s[7]?e.LIFE_S:e.LIFE_I):(o.composition=void 0,o.exportName=void 0,o.life=void 0),s[11]&&(o.wrappers=s[11].split(","))),o.composition===e.COMP_A&&o.life===e.LIFE_I)throw new Error(`Export is not a function and should be used as a singleton only: '${o.value}'.`);return o}}class u{constructor(){let e=new a;const t=[];this.addChunk=function(e){t.push(e)},this.parse=function(o){let s;for(const e of t)if(e.canParse(o)){s=e.parse(o);break}return s||(s=e?.parse(o)),s},this.setDefaultChunk=function(t){e=t}}}class l{constructor(){const e=[];this.addChunk=function(t){e.push(t)},this.modify=function(t,o){let s=t;for(const n of e)s=n.modify(s,t,o);return s}}}class f{constructor(){const e=[];this.addChunk=function(t){e.push(t)},this.modify=async function(t,o,s){let n=t;for(const t of e)n=t.modify(n,o,s),n instanceof Promise&&(n=await n);return n}}}const p="ext",m="ns",d="root";class h{constructor(){const e={};let t=!1,o=[],s="/";this.addNamespaceRoot=function(s,n,r){const i=(t?n.replace(/^\\/,""):n).replace(/\\/g,"/"),c=t?`file://${i}`:i;e[s]={[p]:r??"js",[m]:s,[d]:c},o=Object.keys(e).sort(((e,t)=>t.localeCompare(e)))},this.resolve=function(t){let n,r,i;for(i of o)if(t.startsWith(i)){n=e[i][d],r=e[i].ext;break}if(n&&r){let e=t.replace(i,"");0===e.indexOf("_")&&(e=e.replace("_",""));const o=e.replaceAll("_",s);return`${n}${s}${o}.${r}`}return t},this.setWindowsEnv=function(e=!0){t=e,s=e?"\\":"/"}}}function $(e){return`${e.moduleName}#${e.exportName}`}class w{constructor(){let t=new r,o=!1,s=new u,n=new l,i=new f;const c={},a={};let p=new h;function m(){o&&console.log(...arguments)}this.get=async function(e){return this.compose(e,[])},this.compose=async function(o,r=[]){if(m(`Object '${o}' is requested.`),o===e.ID||o===e.ID_FQN)return m("Container itself is returned."),a[e.ID];const u=s.parse(o),l=n.modify(u,r);if(l.life===e.LIFE_S){const e=$(l);if(a[e])return m(`Existing singleton '${e}' is returned.`),a[e]}let f;c[l.moduleName]||(m(`ES6 module '${l.moduleName}' is not resolved yet`),c[l.moduleName]=p.resolve(l.moduleName));const d=c[l.moduleName];try{f=await import(d),m(`ES6 module '${l.moduleName}' is loaded from '${d}'.`)}catch(e){throw console.error(e?.message,`Object key: "${o}".`,`Path: "${d}".`,`Stack: ${JSON.stringify(r)}`),e}let h=await t.create(l,f,r,this);if(h=await i.modify(h,l,r),m(`Object '${o}' is created.`),l.life===e.LIFE_S){const e=$(l);a[e]=h,m(`Object '${o}' is saved as singleton.`)}return h},this.getParser=()=>s,this.getPreProcessor=()=>n,this.getPostProcessor=()=>i,this.getResolver=()=>p,this.setDebug=function(e){o=e,t.setDebug(e)},this.setParser=e=>s=e,this.setPreProcessor=e=>n=e,this.setPostProcessor=e=>i=e,this.setResolver=e=>p=e,a[e.ID]=this}}export{w as default};
|
package/dist/umd.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).TeqFw_Di_Container=t()}(this,(function(){"use strict";var e={COMP_A:"A",COMP_F:"F",ID:"container",ID_FQN:"TeqFw_Di_Container$",LIFE_I:"I",LIFE_S:"S",isClass(e){const t=Object.getOwnPropertyDescriptor(e,"prototype");return t&&!t.writable}};const t=/(function)*\s*\w*\s*\(\s*\{([^\}]*)\}/s,o=/constructor\s*\(\s*\{([^\}]*)\}/s;function n(e){const t=[];try{const o=new Function(`{${e}}`,"return");o(new Proxy({},{get:(e,o)=>t.push(o)}))}catch(t){throw new Error(`Cannot analyze the deps specification:${e}\n\nPlease, be sure that spec does not contain extra ')' in a comments.\n\nError: ${t}`)}return t}function s(s){return"function"==typeof s?e.isClass(s)?function(e){const t=[],s=e.toString(),r=o.exec(s);return r&&t.push(...n(r[1])),t}(s):function(e){const o=[],s=e.toString(),r=t.exec(s);return r&&o.push(...n(r[2])),o}(s):[]}class r{constructor(){let t=!1;this.create=async function(o,n,r,i){if(r.includes(o.value))throw new Error(`Circular dependency for '${o.value}'. Parents are: ${JSON.stringify(r)}`);if(o.exportName){const a=[...r,o.value],{[o.exportName]:u}=n;if(o.composition===e.COMP_F){if("function"==typeof u){const n=s(u);n.length&&(c=`Deps for object '${o.value}' are: ${JSON.stringify(n)}`,t&&console.log(c));const r={};for(const e of n)r[e]=await i.compose(e,a);const l=e.isClass(u)?new u(r):u(r);return l instanceof Promise?await l:l}return Object.assign({},u)}return u}return n;var c},this.setDebug=function(e){t=e}}}class i{exportName;composition;life;moduleName;value;wrappers=[]}const c=/^((([A-Z])[A-Za-z0-9_]*)((\.)?([A-Za-z0-9_]*)((\$)?(\$)?)?)?(\(([A-Za-z0-9_,]*)\))?)$/;class a{canParse(e){return!0}parse(t){const o=new i;o.value=t;const n=c.exec(t);if(n&&(o.moduleName=n[2],"."===n[5]?"$"===n[7]||"$$"===n[7]?(o.composition=e.COMP_F,o.exportName=n[6],o.life="$"===n[7]?e.LIFE_S:e.LIFE_I):(o.composition=e.COMP_A,o.life=e.LIFE_S,o.exportName=""!==n[6]?n[6]:"default"):"$"===n[7]||"$$"===n[7]?(o.composition=e.COMP_F,o.exportName="default",o.life="$"===n[7]?e.LIFE_S:e.LIFE_I):(o.composition=void 0,o.exportName=void 0,o.life=void 0),n[11]&&(o.wrappers=n[11].split(","))),o.composition===e.COMP_A&&o.life===e.LIFE_I)throw new Error(`Export is not a function and should be used as a singleton only: '${o.value}'.`);return o}}class u{constructor(){let e=new a;const t=[];this.addChunk=function(e){t.push(e)},this.parse=function(o){let n;for(const e of t)if(e.canParse(o)){n=e.parse(o);break}return n||(n=e?.parse(o)),n},this.setDefaultChunk=function(t){e=t}}}class l{constructor(){const e=[];this.addChunk=function(t){e.push(t)},this.modify=function(t,o){let n=t;for(const s of e)n=s.modify(n,t,o);return n}}}class f{constructor(){const e=[];this.addChunk=function(t){e.push(t)},this.modify=async function(t,o,n){let s=t;for(const t of e)s=t.modify(s,o,n),s instanceof Promise&&(s=await s);return s}}}const p="ext",d="ns",m="root";class h{constructor(){const e={};let t=!1,o=[],n="/";this.addNamespaceRoot=function(n,s,r){const i=(t?s.replace(/^\\/,""):s).replace(/\\/g,"/"),c=t?`file://${i}`:i;e[n]={[p]:r??"js",[d]:n,[m]:c},o=Object.keys(e).sort(((e,t)=>t.localeCompare(e)))},this.resolve=function(t){let s,r,i;for(i of o)if(t.startsWith(i)){s=e[i][m],r=e[i].ext;break}if(s&&r){let e=t.replace(i,"");0===e.indexOf("_")&&(e=e.replace("_",""));const o=e.replaceAll("_",n);return`${s}${n}${o}.${r}`}return t},this.setWindowsEnv=function(e=!0){t=e,n=e?"\\":"/"}}}function $(e){return`${e.moduleName}#${e.exportName}`}return class{constructor(){let t=new r,o=!1,n=new u,s=new l,i=new f;const c={},a={};let p=new h;function d(){o&&console.log(...arguments)}this.get=async function(e){return this.compose(e,[])},this.compose=async function(o,r=[]){if(d(`Object '${o}' is requested.`),o===e.ID||o===e.ID_FQN)return d("Container itself is returned."),a[e.ID];const u=n.parse(o),l=s.modify(u,r);if(l.life===e.LIFE_S){const e=$(l);if(a[e])return d(`Existing singleton '${e}' is returned.`),a[e]}let f;c[l.moduleName]||(d(`ES6 module '${l.moduleName}' is not resolved yet`),c[l.moduleName]=p.resolve(l.moduleName));const m=c[l.moduleName];try{f=await import(m),d(`ES6 module '${l.moduleName}' is loaded from '${m}'.`)}catch(e){throw console.error(e?.message,`Object key: "${o}".`,`Path: "${m}".`,`Stack: ${JSON.stringify(r)}`),e}let h=await t.create(l,f,r,this);if(h=await i.modify(h,l,r),d(`Object '${o}' is created.`),l.life===e.LIFE_S){const e=$(l);a[e]=h,d(`Object '${o}' is saved as singleton.`)}return h},this.getParser=()=>n,this.getPreProcessor=()=>s,this.getPostProcessor=()=>i,this.getResolver=()=>p,this.setDebug=function(e){o=e,t.setDebug(e)},this.setParser=e=>n=e,this.setPreProcessor=e=>s=e,this.setPostProcessor=e=>i=e,this.setResolver=e=>p=e,a[e.ID]=this}}}));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@teqfw/di",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.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",
|
|
@@ -27,19 +27,16 @@
|
|
|
27
27
|
"url": "git+https://github.com/teqfw/di.git"
|
|
28
28
|
},
|
|
29
29
|
"scripts": {
|
|
30
|
-
"
|
|
30
|
+
"rollup": "rollup -c",
|
|
31
31
|
"test": "mocha --recursive './test/**/*.test.mjs'"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"webpack": "latest",
|
|
39
|
-
"webpack-cli": "latest"
|
|
34
|
+
"@rollup/plugin-node-resolve": "^15.2.3",
|
|
35
|
+
"mocha": "^10.7.0",
|
|
36
|
+
"rollup": "^2.79.1",
|
|
37
|
+
"rollup-plugin-terser": "^7.0.2"
|
|
40
38
|
},
|
|
41
|
-
"mocha":{
|
|
42
|
-
"require": "esm",
|
|
39
|
+
"mocha": {
|
|
43
40
|
"spec": "./test/**/*.test.mjs",
|
|
44
41
|
"timeout": 5000
|
|
45
42
|
}
|
package/rollup.config.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import resolve from '@rollup/plugin-node-resolve';
|
|
2
|
+
import {terser} from 'rollup-plugin-terser';
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
input: 'src/Container.js',
|
|
6
|
+
output: [
|
|
7
|
+
{
|
|
8
|
+
file: 'dist/esm.js',
|
|
9
|
+
format: 'es'
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
file: 'dist/umd.js',
|
|
13
|
+
format: 'umd',
|
|
14
|
+
name: 'TeqFw_Di_Container',
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
plugins: [
|
|
18
|
+
resolve(),
|
|
19
|
+
terser()
|
|
20
|
+
]
|
|
21
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interface for the resolver to map a module name of the dependency ID into the path to the sources.
|
|
3
|
+
*
|
|
4
|
+
* This is not executable code, it is just for documentation purposes (similar to .h files in the C/C++ language).
|
|
5
|
+
* @interface
|
|
6
|
+
*/
|
|
7
|
+
export default class TeqFw_Di_Api_Container_Resolver {
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Convert the module name to the path of the source files .
|
|
11
|
+
* @param {string} moduleName 'Vendor_Package_Module'
|
|
12
|
+
* @return {string} '/home/user/app/node_modules/@vendor/package/src/Module.js'
|
|
13
|
+
*/
|
|
14
|
+
resolve(moduleName) {}
|
|
15
|
+
|
|
16
|
+
}
|
package/src/Api/Container.js
CHANGED
|
@@ -29,7 +29,7 @@ export default class TeqFw_Di_Api_Container {
|
|
|
29
29
|
getPostProcessor() {};
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
|
-
* @return {TeqFw_Di_Container_Resolver}
|
|
32
|
+
* @return {TeqFw_Di_Container_Resolver} - the default resolver
|
|
33
33
|
*/
|
|
34
34
|
getResolver() {};
|
|
35
35
|
|
|
@@ -55,7 +55,7 @@ export default class TeqFw_Di_Api_Container {
|
|
|
55
55
|
setPostProcessor(data) {};
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
|
-
* @param {
|
|
58
|
+
* @param {TeqFw_Di_Api_Container_Resolver} data
|
|
59
59
|
*/
|
|
60
60
|
setResolver(data) {};
|
|
61
61
|
};
|
|
@@ -35,7 +35,7 @@ export default class TeqFw_Di_Container_A_Composer {
|
|
|
35
35
|
// use export from the es6-module
|
|
36
36
|
const stackNew = [...stack, depId.value];
|
|
37
37
|
const {[depId.exportName]: exp} = module;
|
|
38
|
-
if (depId.composition === Defs.
|
|
38
|
+
if (depId.composition === Defs.COMP_F) {
|
|
39
39
|
if (typeof exp === 'function') {
|
|
40
40
|
// create deps for factory function
|
|
41
41
|
const deps = specParser(exp);
|
|
@@ -56,7 +56,6 @@ export default class TeqFw_Di_Container_A_Composer {
|
|
|
56
56
|
// just return the export (w/o factory function)
|
|
57
57
|
return exp;
|
|
58
58
|
} else {
|
|
59
|
-
// TODO: this is almost useless option
|
|
60
59
|
return module;
|
|
61
60
|
}
|
|
62
61
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Default parser for object keys in format:
|
|
3
|
-
* -
|
|
3
|
+
* - Ns_Module.export$$(post)
|
|
4
4
|
*
|
|
5
5
|
* @namespace TeqFw_Di_Container_A_Parser_Chunk_Def
|
|
6
6
|
*/
|
|
@@ -8,8 +8,8 @@ import Dto from '../../../../DepId.js';
|
|
|
8
8
|
import Defs from '../../../../Defs.js';
|
|
9
9
|
|
|
10
10
|
// VARS
|
|
11
|
-
/** @type {RegExp} expression for default object key (Ns_Module
|
|
12
|
-
const REGEXP = /^((([A-Z])[A-Za-z0-9_]*)((
|
|
11
|
+
/** @type {RegExp} expression for default object key (Ns_Module.export$$(post)) */
|
|
12
|
+
const REGEXP = /^((([A-Z])[A-Za-z0-9_]*)((\.)?([A-Za-z0-9_]*)((\$)?(\$)?)?)?(\(([A-Za-z0-9_,]*)\))?)$/;
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* @implements TeqFw_Di_Api_Container_Parser_Chunk
|
|
@@ -28,42 +28,37 @@ export default class TeqFw_Di_Container_A_Parser_Chunk_Def {
|
|
|
28
28
|
if (parts) {
|
|
29
29
|
res.moduleName = parts[2];
|
|
30
30
|
if (parts[5] === '.') {
|
|
31
|
-
//
|
|
32
|
-
if (parts[
|
|
33
|
-
//
|
|
34
|
-
res.composition = Defs.
|
|
31
|
+
// Ns_Module.export...
|
|
32
|
+
if ((parts[7] === '$') || (parts[7] === '$$')) {
|
|
33
|
+
// Ns_Module.export$...
|
|
34
|
+
res.composition = Defs.COMP_F;
|
|
35
35
|
res.exportName = parts[6];
|
|
36
|
-
res.life = (parts[
|
|
37
|
-
? Defs.LIFE_INSTANCE : Defs.LIFE_SINGLETON;
|
|
36
|
+
res.life = (parts[7] === '$') ? Defs.LIFE_S : Defs.LIFE_I;
|
|
38
37
|
} else {
|
|
39
|
-
res.composition =
|
|
40
|
-
|
|
41
|
-
res.exportName = parts[6];
|
|
42
|
-
res.
|
|
43
|
-
? Defs.LIFE_SINGLETON : Defs.LIFE_INSTANCE;
|
|
38
|
+
res.composition = Defs.COMP_A;
|
|
39
|
+
res.life = Defs.LIFE_S;
|
|
40
|
+
// res.exportName = (parts[6]) ? parts[6] : 'default';
|
|
41
|
+
res.exportName = (parts[6] !== '') ? parts[6] : 'default';
|
|
44
42
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
// App_Logger$FS
|
|
49
|
-
res.composition = ((parts[9] === undefined) || (parts[9] === Defs.COMPOSE_FACTORY))
|
|
50
|
-
? Defs.COMPOSE_FACTORY : Defs.COMPOSE_AS_IS;
|
|
43
|
+
} else if ((parts[7] === '$') || parts[7] === '$$') {
|
|
44
|
+
// Ns_Module$$
|
|
45
|
+
res.composition = Defs.COMP_F;
|
|
51
46
|
res.exportName = 'default';
|
|
52
|
-
|
|
53
|
-
res.life = (parts[10] === Defs.LIFE_SINGLETON) ? Defs.LIFE_SINGLETON : Defs.LIFE_INSTANCE;
|
|
54
|
-
} else {
|
|
55
|
-
res.life = (res.composition === Defs.COMPOSE_FACTORY) ? Defs.LIFE_SINGLETON : Defs.LIFE_INSTANCE;
|
|
56
|
-
}
|
|
47
|
+
res.life = (parts[7] === '$') ? Defs.LIFE_S : Defs.LIFE_I;
|
|
57
48
|
} else {
|
|
58
|
-
//
|
|
59
|
-
res.composition =
|
|
60
|
-
res.exportName =
|
|
61
|
-
res.life =
|
|
49
|
+
// Ns_Module (es6 module)
|
|
50
|
+
res.composition = undefined;
|
|
51
|
+
res.exportName = undefined;
|
|
52
|
+
res.life = undefined;
|
|
53
|
+
}
|
|
54
|
+
// wrappers
|
|
55
|
+
if (parts[11]) {
|
|
56
|
+
res.wrappers = parts[11].split(',');
|
|
62
57
|
}
|
|
63
58
|
}
|
|
64
59
|
|
|
65
60
|
// we should always use singletons for as-is exports
|
|
66
|
-
if ((res.composition === Defs.
|
|
61
|
+
if ((res.composition === Defs.COMP_A) && (res.life === Defs.LIFE_I))
|
|
67
62
|
throw new Error(`Export is not a function and should be used as a singleton only: '${res.value}'.`);
|
|
68
63
|
return res;
|
|
69
64
|
}
|