@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.
Files changed (42) hide show
  1. package/README.md +88 -220
  2. package/RELEASE.md +9 -0
  3. package/bin/release/clean.sh +1 -0
  4. package/dist/esm.js +1 -0
  5. package/dist/umd.js +1 -0
  6. package/package.json +7 -10
  7. package/rollup.config.js +21 -0
  8. package/src/Api/Container/Resolver.js +16 -0
  9. package/src/Api/Container.js +2 -2
  10. package/src/Container/A/Composer.js +1 -2
  11. package/src/Container/A/Parser/Chunk/Def.js +25 -30
  12. package/src/Container/A/Parser/Chunk/V02X.js +70 -0
  13. package/src/Container/Resolver.js +18 -3
  14. package/src/Container.js +6 -6
  15. package/src/Defs.js +6 -11
  16. package/dist/di.cjs.js +0 -205
  17. package/dist/di.esm.js +0 -206
  18. package/docs/README.md +0 -6
  19. package/docs/app.vue +0 -5
  20. package/docs/assets/css/layout.css +0 -51
  21. package/docs/assets/css/vars.css +0 -6
  22. package/docs/components/AppFooter.vue +0 -47
  23. package/docs/components/AppHeader.vue +0 -79
  24. package/docs/content/index.md +0 -272
  25. package/docs/content/test.md +0 -20
  26. package/docs/layouts/default.vue +0 -83
  27. package/docs/nuxt.config.mjs +0 -11
  28. package/docs/package-lock.json +0 -14873
  29. package/docs/package.json +0 -25
  30. package/docs/pages/[...slug].vue +0 -6
  31. package/docs/pages/index.vue +0 -6
  32. package/docs/public/.nojekyll +0 -0
  33. package/docs/public/CNAME +0 -1
  34. package/docs/public/favicon.ico +0 -0
  35. package/docs/public/img/github.svg +0 -1
  36. package/docs/public/img/npm.png +0 -0
  37. package/docs/public/img/teqfw_di_container_steps.png +0 -0
  38. package/docs/server/tsconfig.json +0 -3
  39. package/docs/tsconfig.json +0 -4
  40. package/index.cjs +0 -5
  41. package/index.mjs +0 -6
  42. 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 _browser_ and _Node.js_
4
- applications. This library exclusively supports ES6 modules. The primary objective of this library is late binding with
5
- _minimal manual configuration_ for the container. All linking instructions are encapsulated within the dependency
6
- identifiers and source path resolver. Additionally, the container offers the capability to modify object identifiers
7
- (_preprocessing_) and the created objects (_postprocessing_). These features enable you to more comprehensively
8
- distribute the necessary functionality across npm packages and reuse npm packages in different projects, following a '
9
- _modular monolith_' architecture (see the [sample](https://github.com/flancer64/demo-di-app)).
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
- If all dependencies are added to software objects through a similar mechanism, there is no need to use static imports in
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
- ## Object Container
28
+ ```
29
+ ./src/
30
+ ./Service/
31
+ ./Customer.js
32
+ ./Sale.js
33
+ ./Config.js
34
+ ./Logger.js
35
+ ./Main.js
36
+ ```
42
37
 
43
- Many programming languages implement the Dependency Injection pattern. In this pattern, an application typically
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
- ```javascript
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_', pathApp);
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
- Since JavaScript does not have its own namespaces, similar to packages in Java and namespaces in PHP, the experience of
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
- If dependencies are injected into a factory function, it appears as follows:
51
+ ## The main benefits
124
52
 
125
- ```javascript
126
- function Factory({App_Config: config, App_Logger: logger}) {
127
- // perform operations with dependencies and compose the result
128
- return res;
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
- ## Es6 export
60
+ ## Installation
133
61
 
134
- In ES6+, a distinct building block in application development is the act
135
- of [exporting](https://flancer32.com/es6-export-as-code-brick-b33a8efb3510):
62
+ NodeJS:
136
63
 
137
- ```javascript
138
- export {
139
- obj1 as default, obj2, obj3
140
- };
64
+ ```shell
65
+ $ npm i --save @teqfw/di
141
66
  ```
142
67
 
143
- Static linking through imports is performed at the level of these building blocks:
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
- * **Singleton**: It exists in a single instance within the application.
178
- * **Instance**: A new object is created each time.
70
+ ```html
179
71
 
180
- Since any string can be used as an object key in a dependency specification, various formats can be devised to specify
181
- the lifecycle of the required dependency. I have personally chosen the following format:
72
+ <script type="module">
73
+ import {default as Container} from 'https://cdn.jsdelivr.net/npm/@teqfw/di@latest/+esm';
182
74
 
183
- ```javascript
184
- const asIs = 'Vendor_Package_Module.default';
185
- const asSingleton = 'Vendor_Package_Module.default$';
186
- const asInstance = 'Vendor_Package_Module.default$$';
75
+ /** @type {TeqFw_Di_Container} */
76
+ const container = new Container();
77
+ ...
78
+ </script>
187
79
  ```
188
80
 
189
- In principle, each package can have its own format for describing the dependencies it uses internally. The
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
- ## Transforming the Result
83
+ ```html
193
84
 
194
- Here are the steps for the object container:
195
-
196
- ![processing steps](https://raw.githubusercontent.com/teqfw/di/main/doc/img/teqfw_di_container_steps.png)
197
-
198
- There are two stages involved here:
199
-
200
- * **Preprocessing**: the modification of the dependency identifier
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
- You can add handlers (chunks) to the preprocessor that are capable of modifying the initial `depId`:
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
- The preprocessor calls the handlers sequentially and can, for example, replace a dependency from the base npm package
222
- (`App_Base_Mod_Api_Service_Auth`) with another dependency from one of the npm packages (`Auth_Password_Mod_Service` or
223
- `OAuth2_Mod_Service`), depending on the npm packages included in the application compilation.
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
- By using such replacements, you can implement the core functionality in one npm package, while in other npm packages,
226
- you can implement the additional functionality required by the core package.
227
-
228
- ### Postprocessing
229
-
230
- Since the container creates all objects in the application, it can also perform additional actions on newly created
231
- objects, such as adding extra functionality to them in the form of a wrapper.
232
-
233
- `@teqfw/di` enables you to add individual handlers to the post-processing stage and modify the result. For example, you
234
- can wrap a finished object or perform various operations on it:
235
-
236
- ```javascript
237
- // ./PostChunk.js
238
- /**
239
- * @implements TeqFw_Di_Api_Container_PostProcessor_Chunk
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
- container.getPostProcessor().addChunk(postChunk);
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`.
@@ -6,6 +6,7 @@ DIR_ROOT=${DIR_ROOT:-$(cd "$(dirname "$0")/../../" && pwd)}
6
6
 
7
7
  rm -fr "${DIR_ROOT}/demo/"
8
8
  rm -fr "${DIR_ROOT}/doc/"
9
+ rm -fr "${DIR_ROOT}/docs/"
9
10
  rm -fr "${DIR_ROOT}/example/"
10
11
  rm -fr "${DIR_ROOT}/node_modules/"
11
12
  rm -fr "${DIR_ROOT}/test/"
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.21.1",
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
- "build": "rm -fr ./dist/ && webpack build",
30
+ "rollup": "rollup -c",
31
31
  "test": "mocha --recursive './test/**/*.test.mjs'"
32
32
  },
33
33
  "devDependencies": {
34
- "babel-eslint": "latest",
35
- "eslint": "latest",
36
- "esm": "latest",
37
- "mocha": "latest",
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
  }
@@ -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
+ }
@@ -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 {TeqFw_Di_Container_Resolver} data
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.COMPOSE_FACTORY) {
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
- * - Vnd_Pkg_Prj_Mod$FA
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[.|#]export$[F|A][S|I]) */
12
- const REGEXP = /^((([A-Z])[A-Za-z0-9_]*)((#|\.)?([A-Za-z0-9_]*)((\$)([F|A])?([S|I])?)?)?)$/;
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
- // App_Service.export...
32
- if (parts[8] === '$') {
33
- // App_Service.export$...
34
- res.composition = Defs.COMPOSE_FACTORY;
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[10] === Defs.LIFE_INSTANCE)
37
- ? Defs.LIFE_INSTANCE : Defs.LIFE_SINGLETON;
36
+ res.life = (parts[7] === '$') ? Defs.LIFE_S : Defs.LIFE_I;
38
37
  } else {
39
- res.composition = ((parts[8] === undefined) || (parts[8] === Defs.COMPOSE_AS_IS))
40
- ? Defs.COMPOSE_AS_IS : Defs.COMPOSE_FACTORY;
41
- res.exportName = parts[6];
42
- res.life = ((parts[8] === undefined) || (parts[10] === Defs.LIFE_SINGLETON))
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
- } else if (parts[8] === '$') {
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
- if (parts[10]) {
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
- // App_Service
59
- res.composition = Defs.COMPOSE_AS_IS;
60
- res.exportName = 'default';
61
- res.life = Defs.LIFE_SINGLETON;
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.COMPOSE_AS_IS) && (res.life === Defs.LIFE_INSTANCE))
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
  }