@teqfw/di 0.33.0 → 0.35.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/PHILOSOPHY.md ADDED
@@ -0,0 +1,175 @@
1
+ # Philosophy of the TeqFW Platform
2
+
3
+ **The philosophy of Tequila Framework (TeqFW)** is my personal approach to organizing web application development. I,
4
+ Alex Gusev, have shaped this approach based on my own experience, which focuses on **modular monoliths** with a single
5
+ database. This document reflects that specific context and does not aim to be universal.
6
+
7
+ Some of the principles presented may be applicable more broadly, while others may be irrelevant (or even
8
+ counterproductive) outside monolithic architectures. It is important to keep this limitation in mind when interpreting
9
+ the material.
10
+
11
+ The document is intended to provide cognitive context for both human and artificial intelligences. It addresses both
12
+ specific aspects of web development and more general software architecture issues, emphasizing the reduction of
13
+ excessive complexity, improved structuring, and adaptability to changes.
14
+
15
+ **Tequila Framework (TeqFW)** is not a finished product but an evolving experimental platform. It serves as a testbed
16
+ for the principles outlined here and is actively used in development.
17
+
18
+ ## Core Principles of TeqFW
19
+
20
+ 1. **Use a Unified Development Language:** JavaScript (ES6+) is used on both the client and server sides, ensuring code
21
+ integrity, reducing duplication, and lowering cognitive load.
22
+
23
+ 2. **Enable Late Binding for Flexibility:** Dynamic dependency management through an object container and ES6 module
24
+ namespaces. This reduces tight coupling between modules, simplifies system expansion, and makes the code more
25
+ adaptable.
26
+
27
+ 3. **Design for Evolutionary Code Resilience:** Code is designed with inevitable changes in mind to minimize adaptation
28
+ costs and facilitate expansion without modifying existing components.
29
+
30
+ 4. **Separate Data and Logic Functionally:** Isolation of data structures (DTO) and logic handlers. This approach makes
31
+ code easier to test, maintain, and reuse.
32
+
33
+ 5. **Use Namespaces for Structure and Isolation:** Each type of entity—npm packages, ES6 modules, database tables, CLI
34
+ commands, or configurations—has its own namespace. This ensures clear project structure, reduces conflicts, and
35
+ simplifies code navigation.
36
+
37
+ 6. **Favor Pure JavaScript Without Compilation:** Using modern JavaScript (ES6+) without TypeScript or version
38
+ downgrading. The code remains transparent and accessible, simplifying maintenance, library integration, and speeding
39
+ up development.
40
+
41
+ 7. **Optimize Code and Docs for LLMs:** Code and documentation are organized to be easily analyzed and supplemented by
42
+ language models. Clear structure, standardized templates, and predictable conventions simplify automation and code
43
+ generation.
44
+
45
+ ## Principles in Detail
46
+
47
+ ### Use a Unified Development Language
48
+
49
+ Using modern JavaScript (ES6+) at all application levels eliminates the need to switch between different languages and
50
+ simplifies knowledge sharing among developers. This is especially important in small teams and projects with limited
51
+ resources, where minimizing cognitive load accelerates work.
52
+
53
+ Browsers support only JavaScript (among high-level languages), and thanks to Node.js, it has become widespread in server
54
+ development. This enables writing **isomorphic code** that can be reused on both client and server sides, reducing logic
55
+ duplication.
56
+
57
+ A unified language simplifies code maintenance and accelerates the onboarding of new developers into the project.
58
+
59
+ ### Enable Late Binding for Flexibility
60
+
61
+ Late binding ensures architectural flexibility by allowing dynamic dependency management without tight coupling between
62
+ modules. Instead of direct imports, ES6 module namespaces and an object container are used to handle instantiation and
63
+ component replacement at runtime.
64
+
65
+ This approach reduces the risk of application "breakage" due to changes, simplifies system expansion, and makes the code
66
+ more adaptable. Components can be replaced without deep refactoring, and the dependency mechanism remains transparent
67
+ and predictable.
68
+
69
+ Late binding also improves testability: modules can be replaced with stubs or alternative implementations, making it
70
+ easier to isolate tests. In team development, this simplifies understanding of complex dependencies and makes system
71
+ maintenance more manageable.
72
+
73
+ ### Design for Evolutionary Code Resilience
74
+
75
+ Code is designed to adapt to inevitable changes in requirements, APIs, and data with minimal overhead. This is achieved
76
+ through approaches that allow **expanding functionality without significant modifications to existing code**.
77
+
78
+ Key techniques:
79
+
80
+ - **Flexible input data processing.** Using function argument destructuring and the "ignore unknown attributes"
81
+ principle in data structures allows adding new parameters and properties without modifying existing handlers.
82
+ - **Clear interaction contracts.** Separating interfaces from implementations reduces the impact of changes while
83
+ maintaining system predictability.
84
+ - **Late binding.** Components depend on abstractions rather than specific implementations, enabling code adaptation
85
+ without directly modifying dependencies (see **Enable Late Binding for Flexibility** principle).
86
+
87
+ These methods make the code less fragile and allow the system to evolve while reducing complexity and refactoring
88
+ volume.
89
+
90
+ ### Separate Data and Logic Functionally
91
+
92
+ Code is divided into **data structures (DTO) and logic handlers**, eliminating state within handlers and making them
93
+ independent of data. DTOs contain all necessary information and are passed between handlers that perform operations on
94
+ them.
95
+
96
+ This approach offers several key advantages:
97
+
98
+ - **Handlers can be singletons.** Since they do not store state, they are execution-context-independent and can be
99
+ shared across the entire application.
100
+ - **The program consists of data processing nodes.** The code is structured as a set of functions that receive data,
101
+ process it, and pass it on.
102
+ - **Changeability through pure logic.** Logic remains separate from data structures, allowing modifications without
103
+ affecting handlers and vice versa.
104
+ - **Minimized side effects.** Handlers do not depend on global state, making the system more predictable.
105
+
106
+ ### Use Namespaces for Structure and Isolation
107
+
108
+ Namespaces ensure a clear project structure and prevent conflicts by allowing each entity to reserve its name and build
109
+ its own hierarchy. This principle applies at all levels:
110
+
111
+ - **Packages and modules.** npm packages and ES6 modules are organized into predictable namespaces, avoiding dependency
112
+ conflicts.
113
+ - **Files and classes.** File and class names reflect their purpose and relationships with other components, simplifying
114
+ project navigation and structure.
115
+ - **Database tables.** Table names are structured to avoid collisions and logically group data.
116
+ - **Endpoints and APIs.** Namespaces are used in routing and APIs, ensuring consistent addressing.
117
+ - **Configurations and CLI commands.** Settings and commands are organized hierarchically to prevent duplication.
118
+
119
+ Code is designed to operate in an environment with other code. Each unit within its namespace reserves a name and builds
120
+ a downward hierarchy, creating a predictable interaction structure.
121
+
122
+ ### Favor Pure JavaScript Without Compilation
123
+
124
+ Tequila Framework uses modern JavaScript (ES6+) without version downgrading or strict TypeScript typing. The code
125
+ remains in its original form, making it transparent, accessible, and easy to maintain.
126
+
127
+ Key characteristics:
128
+
129
+ - **No compilation.** Developers work with pure JavaScript without intermediate transformations, speeding up debugging
130
+ and simplifying maintenance.
131
+ - **JSDoc instead of TypeScript.** JSDoc annotations allow IDEs to understand data structures and provide
132
+ autocompletion, maintaining flexibility without strict typing.
133
+ - **Maximum compatibility.** The code easily integrates with any libraries and tools, as it does not require adaptation
134
+ to strict type contracts.
135
+ - **Fast development.** Changes are immediately reflected in the code without requiring a rebuild, increasing
136
+ development speed.
137
+
138
+ ### Optimize Code and Docs for LLMs
139
+
140
+ Architecture, code, and documentation are designed for easy analysis and use by language models (LLM). This improves the
141
+ efficiency of **automatic code completion, generation of template solutions, and integration with AI tools**.
142
+
143
+ For this, the following practices are applied:
144
+
145
+ - **Predictable project structure.** Clear file organization, logical naming, and standardized conventions.
146
+ - **Unified code templates.** Repetitive structures and a predictable format help models understand and supplement the
147
+ code.
148
+ - **Optimized abstraction depth.** Code is organized to maintain modularity while avoiding excessive nesting.
149
+ - **Automated annotations.** JSDoc and standardized comments ensure precise code generation and documentation.
150
+
151
+ This approach allows LLM agents to:
152
+
153
+ - Quickly analyze code and suggest corrections.
154
+ - Automatically supplement documentation and comment code.
155
+ - Generate new modules according to the project's architectural standards.
156
+ - Simplify CI/CD integration by checking code compliance with style and conventions.
157
+
158
+ LLMs become part of the development process, helping not only with writing code but also keeping it up to date, reducing
159
+ developers' routine workload.
160
+
161
+ ## Conclusion
162
+
163
+ The principles outlined in this document form an approach to **modular monolith** development, focused on
164
+ predictability, structure, and adaptability. They enable building architectures where code remains flexible,
165
+ transparent, and easily extensible.
166
+
167
+ These ideas do not require complex theoretical justifications or significant time investments for validation. They aim
168
+ to simplify integration, reduce unnecessary complexity, and enhance the potential for automation. **Standardized
169
+ structures, predictable namespaces, and development without transpilation** create an environment where the code is
170
+ understandable both for developers and language models (LLM).
171
+
172
+ The **Tequila Framework (TeqFW)** platform demonstrates these principles in action. While still evolving, it already
173
+ supports real-world development. This approach may serve not only as a foundation for proprietary tools, but also as
174
+ inspiration for rethinking conventional software architecture, prioritizing clarity, modularity, and adaptability over
175
+ unnecessary complexity.
package/README.md CHANGED
@@ -8,16 +8,33 @@ objects with minimal manual configuration. It integrates smoothly in both browse
8
8
  flexibility, modularity, and easier testing for your applications.
9
9
 
10
10
  Unlike typical object containers, `@teqfw/di` requires no manual registration of objects, instead mapping dependency IDs
11
- directly to their source paths for greater simplicity.
11
+ directly to their source paths for greater simplicity. However, for advanced use cases—such as unit testing—it is
12
+ possible to explicitly register singleton objects using the `register(depId, obj)` method (available only in test mode).
13
+ This allows controlled substitution of dependencies without altering the main codebase.
12
14
 
13
15
  **This library is specifically optimized for ES6 modules, ensuring top performance and compatibility. It does not
14
16
  support CommonJS, AMD, UMD, or other module formats.**
15
17
 
18
+ To increase robustness, all instances created by the container are automatically **frozen** using `Object.freeze()`.
19
+ This guarantees immutability of the returned objects, helping prevent accidental modifications and ensuring predictable
20
+ behavior at runtime.
21
+
16
22
  While this library is primarily designed for JavaScript, it is also fully compatible with TypeScript. Developers can use
17
23
  TypeScript to compose dependency identifiers in the same way as in JavaScript. It is important to ensure that TypeScript
18
24
  transpiles the source code to ES6 modules for proper functionality. With this setup, TypeScript users can effectively
19
25
  leverage the benefits of this library without any additional configuration.
20
26
 
27
+ ---
28
+
29
+ ## Design Philosophy
30
+
31
+ This library is a component of the **TeqFW platform**, an experimental framework grounded in the principles of modular
32
+ monolith design, long-term maintainability, late binding, and immutability-first logic composition.
33
+
34
+ To explore the conceptual background, see: **[TeqFW Philosophy](./PHILOSOPHY.md)**.
35
+
36
+ ---
37
+
21
38
  ## Samples
22
39
 
23
40
  Explore `@teqfw/di` in action through the following demo applications:
@@ -36,6 +53,8 @@ Explore `@teqfw/di` in action through the following demo applications:
36
53
 
37
54
  These projects offer practical examples and insights into using `@teqfw/di` effectively!
38
55
 
56
+ ---
57
+
39
58
  ## Example of Typical Usage
40
59
 
41
60
  Using `@teqfw/di` typically involves a few simple steps: organizing the file structure, declaring dependencies,
@@ -58,7 +77,7 @@ as the container can be configured to work with any layout (e.g., within `/home/
58
77
 
59
78
  ### Step 2: Declare Dependencies
60
79
 
61
- In your code, declare dependencies by defining them as keys in the constructor. Dependency identifiers here follow a
80
+ In your code, declare dependencies by specifying them as keys in the constructor. Dependency identifiers here follow a
62
81
  namespace style similar to PHP Zend 1, which is used by default in this library. You can also implement a custom parser
63
82
  if you prefer a different naming convention or mapping strategy.
64
83
 
@@ -101,6 +120,26 @@ Finally, retrieve your main application instance. The container automatically in
101
120
  const app = await container.get('App_Main$');
102
121
  ```
103
122
 
123
+ ---
124
+
125
+ ## Test Mode Support
126
+
127
+ `@teqfw/di` supports a dedicated **test mode** to help with unit testing and dependency mocking.
128
+
129
+ When test mode is enabled via `container.enableTestMode()`, you can manually register singleton dependencies using the
130
+ `register(depId, obj)` method:
131
+
132
+ ```js
133
+ container.enableTestMode();
134
+ container.register('App_Service_Customer$', mockCustomerService);
135
+ ```
136
+
137
+ This makes it easy to substitute real implementations with mocks or stubs during tests, without affecting production
138
+ logic. Test mode safeguards this capability, ensuring that manual overrides are only permitted in designated test
139
+ environments.
140
+
141
+ ---
142
+
104
143
  ## Key Benefits
105
144
 
106
145
  `@teqfw/di` offers the core functionality of any object container — creating objects and injecting dependencies — with
@@ -117,6 +156,10 @@ Here’s what makes it stand out:
117
156
  - **Mapping IDs to Source Modules via Resolvers**: Thanks to resolvers, `@teqfw/di` lets you map dependency IDs to their
118
157
  source locations effortlessly. This makes the library adaptable to any project structure or file layout.
119
158
 
159
+ - **Immutable Objects for Safer Runtime**: All objects created by the container are frozen by default. This ensures that
160
+ dependencies cannot be modified once instantiated, eliminating unintended mutations and reinforcing modular,
161
+ predictable application behavior.
162
+
120
163
  - **Preprocessing for Enhanced Control**: Built-in preprocessing allows modifying dependencies at the time of creation,
121
164
  enabling local overrides or adjustments in functionality. This is especially useful for larger projects, where
122
165
  different teams may tailor dependencies to their specific requirements. The default preprocessing can also be replaced
@@ -132,6 +175,8 @@ Here’s what makes it stand out:
132
175
  These features make `@teqfw/di` a powerful, adaptable DI container that not only provides ready-to-use solutions but can
133
176
  be easily customized to meet unique project demands.
134
177
 
178
+ ---
179
+
135
180
  ## Installation
136
181
 
137
182
  ### For Node.js
@@ -220,6 +265,8 @@ Alternatively, you can use the UMD version in the browser (~5KB):
220
265
  With these steps, the container is configured to automatically resolve and inject dependencies based on your setup,
221
266
  whether in Node.js or in a browser environment.
222
267
 
268
+ ---
269
+
223
270
  ## Dependency ID Types
224
271
 
225
272
  `@teqfw/di` supports various dependency ID formats to match different import styles and object requirements. Here’s a
@@ -259,6 +306,8 @@ export default class App_Main {
259
306
  }
260
307
  ```
261
308
 
309
+ ---
310
+
262
311
  ## Summary
263
312
 
264
313
  `@teqfw/di` is a versatile and lightweight dependency injection container tailored for modern JavaScript applications.
@@ -266,8 +315,8 @@ With its flexible dependency mapping, customizable ID configurations, and suppor
266
315
  `@teqfw/di` empowers developers to build modular, testable, and scalable codebases.
267
316
 
268
317
  Whether you’re working in Node.js or a browser environment, `@teqfw/di` provides a solid foundation with built-in
269
- functionality that you can further adapt to fit your project’s unique requirements. You are encouraged to explore and
270
- extend this library as needed to create your ideal development environment.
318
+ functionality that you can further adapt to fit your project’s unique requirements. Feel free to explore and extend the
319
+ library as needed to create your ideal development environment.
271
320
 
272
321
  For any questions, feedback, or collaboration opportunities, please feel free to reach out through the following
273
322
  channels:
package/RELEASE.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # @teqfw/di releases
2
2
 
3
+ ## 0.35.0 – Support for Node.js Module Mocking in Test Mode
4
+
5
+ - DI container (test mode): extended support to register not only singleton instances but also mock implementations of
6
+ native Node.js modules, enabling more flexible and robust unit testing.
7
+ - Added missing property `TeqFw_Di_DepId.isNodeModule` to properly identify Node.js modules in the dependency resolution
8
+ process.
9
+ - Improved documentation (`README.md` and `PHILOSOPHY.md`) to clarify the design philosophy of TeqFW and explain the
10
+ immutability and test mode features in greater detail.
11
+
12
+ ## 0.34.0 – Strict Mode and Test Support
13
+
14
+ - Removed outdated interface `TeqFw_Di_Api_Container` and associated documentation.
15
+ - Unified the `get()` and `compose()` methods for dependency retrieval.
16
+ - Introduced strict mode by default: manual registration of dependencies is now **disabled** unless explicitly allowed.
17
+ - Added `enableTestMode()` method to the container, enabling safe, test-specific manual registration of singleton
18
+ dependencies.
19
+ - Improved error messages and safeguards against accidental overwrites in `register()`.
20
+ - Updated internal naming and documentation for better clarity and alignment with the platform's principles.
21
+ - Cleaned up ESLint and JSDoc inconsistencies in internal files.
22
+
3
23
  ## 0.33.0 – Cleanup and Enhancements
4
24
 
5
25
  - Removed deprecated documentation.
package/dist/esm.js CHANGED
@@ -1 +1 @@
1
- var e={CA:"A",CF:"F",ID:"container",ID_FQN:"TeqFw_Di_Container$",LI:"I",LS:"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.origin))throw new Error(`Circular dependency for '${o.origin}'. Parents are: ${JSON.stringify(r)}`);if(o.exportName){const a=[...r,o.origin],{[o.exportName]:u}=n;if(o.composition===e.CF){if("function"==typeof u){const n=s(u);n.length&&(c=`Deps for object '${o.origin}' 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;origin;wrappers=[]}const c=/^(node:)?(@?[A-Za-z0-9_-]+\/?[A-Za-z0-9_-]*)(([.#])?([A-Za-z0-9_]*)((\$)?(\$)?)?)?(\(([A-Za-z0-9_,]*)\))?$/;class a{canParse(){return!0}parse(t){const o=new i;o.origin=t;const n=c.exec(t);if(n&&(o.isNodeModule=Boolean(n[1]),o.moduleName=n[2].replace(/^node:/,""),"."===n[4]||"#"===n[4]?"$"===n[6]||"$$"===n[6]?(o.composition=e.CF,o.exportName=n[5],o.life="$"===n[6]?e.LS:e.LI):(o.composition=e.CA,o.life=e.LS,o.exportName=""!==n[5]?n[5]:"default"):"$"===n[6]||"$$"===n[6]?(o.composition=e.CF,o.exportName="default",o.life="$"===n[6]?e.LS:e.LI):(o.composition=void 0,o.exportName=void 0,o.life=void 0),n[10]&&(o.wrappers=n[10].split(","))),o.composition===e.CA&&o.life===e.LI)throw new Error(`Export is not a function and should be used as a singleton only: '${o.origin}'.`);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="root";class d{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]={ext:r??"js",ns:n,[p]: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][p],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 m(e){return`${e.moduleName}#${e.exportName}`}class h{constructor(){let t=new r,o=!1,n=new u,s=new l,i=new f;const c={},a={};let p=new d;function h(){o&&console.log(...arguments)}this.get=async function(e,t=[]){return this.compose(e,t)},this.compose=async function(o,r=[]){if(h(`Object '${o}' is requested.`),o===e.ID||o===e.ID_FQN)return h("Container itself is returned."),a[e.ID];const u=n.parse(o),l=s.modify(u,r);if(l.life===e.LS){const e=m(l);if(a[e])return h(`Existing singleton '${e}' is returned.`),a[e]}let f;c[l.moduleName]||(h(`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),h(`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 $=await t.create(l,f,r,this);var g;if(null===(g=$)||"object"!=typeof g&&"function"!=typeof g||"[object Module]"===Object.prototype.toString.call(g)||Object.isFrozen(g)||Object.freeze($),$=await i.modify($,l,r),h(`Object '${o}' is created.`),l.life===e.LS){const e=m(l);a[e]=$,h(`Object '${o}' is saved as singleton.`)}return $},this.getParser=()=>n,this.getPreProcessor=()=>s,this.getPostProcessor=()=>i,this.getResolver=()=>p,this.register=function(t,o){if(!t||!o)throw new Error("depId and object are required");const s=n.parse(t);if(s.life!==e.LS)throw new Error(`Only singletons can be registered manually. Given: ${t}`);{const e=m(s);a[e]=o,h(`Object '${t}' is registered manually as singleton.`)}},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}}export{h as default};
1
+ var e={CA:"A",CF:"F",ID:"container",ID_FQN:"TeqFw_Di_Container$",LI:"I",LS:"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.origin))throw new Error(`Circular dependency for '${o.origin}'. Parents are: ${JSON.stringify(r)}`);if(o.exportName){const a=[...r,o.origin],{[o.exportName]:l}=n;if(o.composition===e.CF){if("function"==typeof l){const n=s(l);n.length&&(c=`Deps for object '${o.origin}' are: ${JSON.stringify(n)}`,t&&console.log(c));const r={};for(const e of n)r[e]=await i.get(e,a);const u=e.isClass(l)?new l(r):l(r);return u instanceof Promise?await u:u}return Object.assign({},l)}return l}return n;var c},this.setDebug=function(e){t=e}}}class i{exportName;composition;isNodeModule;life;moduleName;origin;wrappers=[]}const c=/^(node:)?(@?[A-Za-z0-9_-]+\/?[A-Za-z0-9_-]*)(([.#])?([A-Za-z0-9_]*)((\$)?(\$)?)?)?(\(([A-Za-z0-9_,]*)\))?$/;class a{canParse(){return!0}parse(t){const o=new i;o.origin=t;const n=c.exec(t);if(n&&(o.isNodeModule=Boolean(n[1]),o.moduleName=n[2].replace(/^node:/,""),"."===n[4]||"#"===n[4]?"$"===n[6]||"$$"===n[6]?(o.composition=e.CF,o.exportName=n[5],o.life="$"===n[6]?e.LS:e.LI):(o.composition=e.CA,o.life=e.LS,o.exportName=""!==n[5]?n[5]:"default"):"$"===n[6]||"$$"===n[6]?(o.composition=e.CF,o.exportName="default",o.life="$"===n[6]?e.LS:e.LI):(o.composition=void 0,o.exportName=void 0,o.life=void 0),n[10]&&(o.wrappers=n[10].split(","))),o.composition===e.CA&&o.life===e.LI)throw new Error(`Export is not a function and should be used as a singleton only: '${o.origin}'.`);return o}}class l{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 u{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 d="ext",p="ns",h="root";class m{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]={[d]:r??"js",[p]:n,[h]: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][h],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 g(e){return`${e.moduleName}#${e.exportName}`}class ${constructor(){let t=new r,o=!1,n=new l,s=new f,i=new u,c=!1;const a={},d={},p={};let h=new m;function $(){o&&console.log(...arguments)}this.get=async function(o,r=[]){if($(`Object '${o}' is requested.`),o===e.ID||o===e.ID_FQN)return $("Container itself is returned."),d[e.ID];const l=n.parse(o),u=i.modify(l,r);if(u.life===e.LS){const e=g(u);if(d[e])return $(`Existing singleton '${e}' is returned.`),d[e]}if(u.isNodeModule&&c){const e=u.origin;if(p[e])return $(`Existing nodejs lib '${e}' is returned.`),p[e]}let f;a[u.moduleName]||($(`ES6 module '${u.moduleName}' is not resolved yet`),a[u.moduleName]=h.resolve(u.moduleName));const m=a[u.moduleName];try{f=await import(m),$(`ES6 module '${u.moduleName}' is loaded from '${m}'.`)}catch(e){throw console.error(e?.message,`Object key: "${o}".`,`Path: "${m}".`,`Stack: ${JSON.stringify(r)}`),e}let w=await t.create(u,f,r,this);var y;if(null===(y=w)||"object"!=typeof y&&"function"!=typeof y||"[object Module]"===Object.prototype.toString.call(y)||Object.isFrozen(y)||Object.freeze(w),w=await s.modify(w,u,r),$(`Object '${o}' is created.`),u.life===e.LS){const e=g(u);d[e]=w,$(`Object '${o}' is saved as singleton.`)}return w},this.enableTestMode=function(){c=!0,$("Test mode enabled")},this.getParser=()=>n,this.getPreProcessor=()=>i,this.getPostProcessor=()=>s,this.getResolver=()=>h,this.register=function(t,o){if(!c)throw new Error("Use enableTestMode() to allow it");if(!t||!o)throw new Error("Both params are required");const s=n.parse(t);if(s.life!==e.LS&&!s.isNodeModule)throw new Error(`Only node modules & singletons can be registered: '${t}'`);if(s.life===e.LS){const e=g(s);if(d[e])throw new Error(`'${t}' is already registered`);d[e]=o}else if(s.isNodeModule){const e=s.origin;if(p[e])throw new Error(`'${t}' is already registered`);p[e]=o}$(`'${t}' is registered`)},this.setDebug=function(e){o=e,t.setDebug(e)},this.setParser=e=>n=e,this.setPreProcessor=e=>i=e,this.setPostProcessor=e=>s=e,this.setResolver=e=>h=e,d[e.ID]=this}}export{$ as default};
package/dist/umd.js CHANGED
@@ -1 +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={CA:"A",CF:"F",ID:"container",ID_FQN:"TeqFw_Di_Container$",LI:"I",LS:"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.origin))throw new Error(`Circular dependency for '${o.origin}'. Parents are: ${JSON.stringify(r)}`);if(o.exportName){const a=[...r,o.origin],{[o.exportName]:u}=n;if(o.composition===e.CF){if("function"==typeof u){const n=s(u);n.length&&(c=`Deps for object '${o.origin}' 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;origin;wrappers=[]}const c=/^(node:)?(@?[A-Za-z0-9_-]+\/?[A-Za-z0-9_-]*)(([.#])?([A-Za-z0-9_]*)((\$)?(\$)?)?)?(\(([A-Za-z0-9_,]*)\))?$/;class a{canParse(){return!0}parse(t){const o=new i;o.origin=t;const n=c.exec(t);if(n&&(o.isNodeModule=Boolean(n[1]),o.moduleName=n[2].replace(/^node:/,""),"."===n[4]||"#"===n[4]?"$"===n[6]||"$$"===n[6]?(o.composition=e.CF,o.exportName=n[5],o.life="$"===n[6]?e.LS:e.LI):(o.composition=e.CA,o.life=e.LS,o.exportName=""!==n[5]?n[5]:"default"):"$"===n[6]||"$$"===n[6]?(o.composition=e.CF,o.exportName="default",o.life="$"===n[6]?e.LS:e.LI):(o.composition=void 0,o.exportName=void 0,o.life=void 0),n[10]&&(o.wrappers=n[10].split(","))),o.composition===e.CA&&o.life===e.LI)throw new Error(`Export is not a function and should be used as a singleton only: '${o.origin}'.`);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 d="root";class p{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]={ext:r??"js",ns:n,[d]: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][d],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 m(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 d=new p;function h(){o&&console.log(...arguments)}this.get=async function(e,t=[]){return this.compose(e,t)},this.compose=async function(o,r=[]){if(h(`Object '${o}' is requested.`),o===e.ID||o===e.ID_FQN)return h("Container itself is returned."),a[e.ID];const u=n.parse(o),l=s.modify(u,r);if(l.life===e.LS){const e=m(l);if(a[e])return h(`Existing singleton '${e}' is returned.`),a[e]}let f;c[l.moduleName]||(h(`ES6 module '${l.moduleName}' is not resolved yet`),c[l.moduleName]=d.resolve(l.moduleName));const p=c[l.moduleName];try{f=await import(p),h(`ES6 module '${l.moduleName}' is loaded from '${p}'.`)}catch(e){throw console.error(e?.message,`Object key: "${o}".`,`Path: "${p}".`,`Stack: ${JSON.stringify(r)}`),e}let g=await t.create(l,f,r,this);var $;if(null===($=g)||"object"!=typeof $&&"function"!=typeof $||"[object Module]"===Object.prototype.toString.call($)||Object.isFrozen($)||Object.freeze(g),g=await i.modify(g,l,r),h(`Object '${o}' is created.`),l.life===e.LS){const e=m(l);a[e]=g,h(`Object '${o}' is saved as singleton.`)}return g},this.getParser=()=>n,this.getPreProcessor=()=>s,this.getPostProcessor=()=>i,this.getResolver=()=>d,this.register=function(t,o){if(!t||!o)throw new Error("depId and object are required");const s=n.parse(t);if(s.life!==e.LS)throw new Error(`Only singletons can be registered manually. Given: ${t}`);{const e=m(s);a[e]=o,h(`Object '${t}' is registered manually as singleton.`)}},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=>d=e,a[e.ID]=this}}}));
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={CA:"A",CF:"F",ID:"container",ID_FQN:"TeqFw_Di_Container$",LI:"I",LS:"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.origin))throw new Error(`Circular dependency for '${o.origin}'. Parents are: ${JSON.stringify(r)}`);if(o.exportName){const a=[...r,o.origin],{[o.exportName]:l}=n;if(o.composition===e.CF){if("function"==typeof l){const n=s(l);n.length&&(c=`Deps for object '${o.origin}' are: ${JSON.stringify(n)}`,t&&console.log(c));const r={};for(const e of n)r[e]=await i.get(e,a);const u=e.isClass(l)?new l(r):l(r);return u instanceof Promise?await u:u}return Object.assign({},l)}return l}return n;var c},this.setDebug=function(e){t=e}}}class i{exportName;composition;isNodeModule;life;moduleName;origin;wrappers=[]}const c=/^(node:)?(@?[A-Za-z0-9_-]+\/?[A-Za-z0-9_-]*)(([.#])?([A-Za-z0-9_]*)((\$)?(\$)?)?)?(\(([A-Za-z0-9_,]*)\))?$/;class a{canParse(){return!0}parse(t){const o=new i;o.origin=t;const n=c.exec(t);if(n&&(o.isNodeModule=Boolean(n[1]),o.moduleName=n[2].replace(/^node:/,""),"."===n[4]||"#"===n[4]?"$"===n[6]||"$$"===n[6]?(o.composition=e.CF,o.exportName=n[5],o.life="$"===n[6]?e.LS:e.LI):(o.composition=e.CA,o.life=e.LS,o.exportName=""!==n[5]?n[5]:"default"):"$"===n[6]||"$$"===n[6]?(o.composition=e.CF,o.exportName="default",o.life="$"===n[6]?e.LS:e.LI):(o.composition=void 0,o.exportName=void 0,o.life=void 0),n[10]&&(o.wrappers=n[10].split(","))),o.composition===e.CA&&o.life===e.LI)throw new Error(`Export is not a function and should be used as a singleton only: '${o.origin}'.`);return o}}class l{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 u{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 d="ext",p="ns",h="root";class m{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]={[d]:r??"js",[p]:n,[h]: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][h],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 g(e){return`${e.moduleName}#${e.exportName}`}return class{constructor(){let t=new r,o=!1,n=new l,s=new f,i=new u,c=!1;const a={},d={},p={};let h=new m;function w(){o&&console.log(...arguments)}this.get=async function(o,r=[]){if(w(`Object '${o}' is requested.`),o===e.ID||o===e.ID_FQN)return w("Container itself is returned."),d[e.ID];const l=n.parse(o),u=i.modify(l,r);if(u.life===e.LS){const e=g(u);if(d[e])return w(`Existing singleton '${e}' is returned.`),d[e]}if(u.isNodeModule&&c){const e=u.origin;if(p[e])return w(`Existing nodejs lib '${e}' is returned.`),p[e]}let f;a[u.moduleName]||(w(`ES6 module '${u.moduleName}' is not resolved yet`),a[u.moduleName]=h.resolve(u.moduleName));const m=a[u.moduleName];try{f=await import(m),w(`ES6 module '${u.moduleName}' is loaded from '${m}'.`)}catch(e){throw console.error(e?.message,`Object key: "${o}".`,`Path: "${m}".`,`Stack: ${JSON.stringify(r)}`),e}let $=await t.create(u,f,r,this);var y;if(null===(y=$)||"object"!=typeof y&&"function"!=typeof y||"[object Module]"===Object.prototype.toString.call(y)||Object.isFrozen(y)||Object.freeze($),$=await s.modify($,u,r),w(`Object '${o}' is created.`),u.life===e.LS){const e=g(u);d[e]=$,w(`Object '${o}' is saved as singleton.`)}return $},this.enableTestMode=function(){c=!0,w("Test mode enabled")},this.getParser=()=>n,this.getPreProcessor=()=>i,this.getPostProcessor=()=>s,this.getResolver=()=>h,this.register=function(t,o){if(!c)throw new Error("Use enableTestMode() to allow it");if(!t||!o)throw new Error("Both params are required");const s=n.parse(t);if(s.life!==e.LS&&!s.isNodeModule)throw new Error(`Only node modules & singletons can be registered: '${t}'`);if(s.life===e.LS){const e=g(s);if(d[e])throw new Error(`'${t}' is already registered`);d[e]=o}else if(s.isNodeModule){const e=s.origin;if(p[e])throw new Error(`'${t}' is already registered`);p[e]=o}w(`'${t}' is registered`)},this.setDebug=function(e){o=e,t.setDebug(e)},this.setParser=e=>n=e,this.setPreProcessor=e=>i=e,this.setPostProcessor=e=>s=e,this.setResolver=e=>h=e,d[e.ID]=this}}}));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teqfw/di",
3
- "version": "0.33.0",
3
+ "version": "0.35.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",
@@ -23,7 +23,7 @@ export default class TeqFw_Di_Container_A_Composer {
23
23
  * Returns or creates and returns the requested object.
24
24
  *
25
25
  * @param {TeqFw_Di_DepId} depId
26
- * @param {Object} module - imported es6 module
26
+ * @param {object} module - imported es6 module
27
27
  * @param {string[]} stack - array of the parent objects IDs to prevent dependency loop
28
28
  * @param {TeqFw_Di_Container} container - to create dependencies for requested object
29
29
  * @returns {Promise<*>}
@@ -42,7 +42,7 @@ export default class TeqFw_Di_Container_A_Composer {
42
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
- spec[dep] = await container.compose(dep, stackNew);
45
+ spec[dep] = await container.get(dep, stackNew);
46
46
  // create a new object with the factory function
47
47
  const res = (Defs.isClass(exp)) ? new exp(spec) : exp(spec);
48
48
  if (res instanceof Promise)
package/src/Container.js CHANGED
@@ -1,5 +1,7 @@
1
1
  /**
2
2
  * The Object Container (composition root).
3
+ * We can use static imports in the Container.
4
+ *
3
5
  * @namespace TeqFw_Di_Container
4
6
  */
5
7
  import Composer from './Container/A/Composer.js';
@@ -33,11 +35,7 @@ function canBeFrozen(value) {
33
35
  return !Object.isFrozen(value);
34
36
  }
35
37
 
36
-
37
38
  // MAIN
38
- /**
39
- * @implements TeqFw_Di_Api_Container
40
- */
41
39
  export default class TeqFw_Di_Container {
42
40
 
43
41
  constructor() {
@@ -45,8 +43,9 @@ export default class TeqFw_Di_Container {
45
43
  let _composer = new Composer();
46
44
  let _debug = false;
47
45
  let _parser = new Parser();
48
- let _preProcessor = new PreProcessor();
49
46
  let _postProcessor = new PostProcessor();
47
+ let _preProcessor = new PreProcessor();
48
+ let _testMode = false;
50
49
 
51
50
  /**
52
51
  * Registry for paths for loaded es6 modules.
@@ -56,9 +55,15 @@ export default class TeqFw_Di_Container {
56
55
  const _regPaths = {};
57
56
  /**
58
57
  * Registry to store singletons.
59
- * @type {Object<string, *>}
58
+ * @type {Object<string, object>}
60
59
  */
61
60
  const _regSingles = {};
61
+ /**
62
+ * Registry to store mocks for Node.js libs in the Test mode.
63
+ * @type {Object<string, object>}
64
+ */
65
+ const _regTestNodeLibs = {};
66
+
62
67
  let _resolver = new Resolver();
63
68
 
64
69
  // FUNCS
@@ -69,30 +74,16 @@ export default class TeqFw_Di_Container {
69
74
 
70
75
  // INSTANCE METHODS
71
76
 
72
- this.get = async function (runtimeDepId, stack = []) {
73
- return this.compose(runtimeDepId, stack);
74
- };
75
-
76
- /**
77
- * This method is 'private' for the npm package. It is used in the Composer only.
78
- *
79
- * @param {string} depId runtime dependency ID
80
- * @param {string[]} stack set of the depId to detect circular dependencies
81
- * @returns {Promise<*>}
82
- */
83
- this.compose = async function (depId, stack = []) {
77
+ this.get = async function (depId, stack = []) {
84
78
  log(`Object '${depId}' is requested.`);
85
- // return container itself if requested
86
- if (
87
- (depId === Defs.ID) ||
88
- (depId === Defs.ID_FQN)
89
- ) {
79
+ // return the container itself if requested
80
+ if ((depId === Defs.ID) || (depId === Defs.ID_FQN)) {
90
81
  log('Container itself is returned.');
91
82
  return _regSingles[Defs.ID];
92
83
  }
93
84
  // parse the `objectKey` and get the structured DTO
94
85
  const parsed = _parser.parse(depId);
95
- // modify original key according to some rules (replacements, etc.)
86
+ // modify the original key according to some rules (replacements, etc.)
96
87
  const key = _preProcessor.modify(parsed, stack);
97
88
  // return existing singleton
98
89
  if (key.life === Defs.LS) {
@@ -102,10 +93,18 @@ export default class TeqFw_Di_Container {
102
93
  return _regSingles[singleId];
103
94
  }
104
95
  }
105
- // resolve path to es6 module if not resolved before
96
+ // return existing node lib in the Test mode
97
+ if (key.isNodeModule && _testMode) {
98
+ const nodeId = key.origin;
99
+ if (_regTestNodeLibs[nodeId]) {
100
+ log(`Existing nodejs lib '${nodeId}' is returned.`);
101
+ return _regTestNodeLibs[nodeId];
102
+ }
103
+ }
104
+ // resolve a path to es6 module if not resolved before
106
105
  if (!_regPaths[key.moduleName]) {
107
106
  log(`ES6 module '${key.moduleName}' is not resolved yet`);
108
- // convert module name to the path to es6-module file with a sources
107
+ // convert the module name to the path to an es6-module file with a source
109
108
  _regPaths[key.moduleName] = _resolver.resolve(key.moduleName);
110
109
  }
111
110
 
@@ -116,17 +115,12 @@ export default class TeqFw_Di_Container {
116
115
  module = await import(path);
117
116
  log(`ES6 module '${key.moduleName}' is loaded from '${path}'.`);
118
117
  } catch (e) {
119
- console.error(
120
- e?.message,
121
- `Object key: "${depId}".`,
122
- `Path: "${path}".`,
123
- `Stack: ${JSON.stringify(stack)}`
124
- );
118
+ console.error(e?.message, `Object key: "${depId}".`, `Path: "${path}".`, `Stack: ${JSON.stringify(stack)}`);
125
119
  throw e;
126
120
  }
127
- // create object using the composer then modify it in post-processor
121
+ // create an object using the composer, then modify it in post-processor
128
122
  let res = await _composer.create(key, module, stack, this);
129
- // freeze the result to prevent modifications (TODO: should we have configuration for the feature?)
123
+ // freeze the result to prevent modifications
130
124
  if (canBeFrozen(res)) Object.freeze(res);
131
125
  res = await _postProcessor.modify(res, key, stack);
132
126
  log(`Object '${depId}' is created.`);
@@ -140,6 +134,14 @@ export default class TeqFw_Di_Container {
140
134
  return res;
141
135
  };
142
136
 
137
+ /**
138
+ * Enables test mode, allowing manual singleton registration.
139
+ */
140
+ this.enableTestMode = function () {
141
+ _testMode = true;
142
+ log('Test mode enabled');
143
+ };
144
+
143
145
  this.getParser = () => _parser;
144
146
 
145
147
  this.getPreProcessor = () => _preProcessor;
@@ -149,21 +151,29 @@ export default class TeqFw_Di_Container {
149
151
  this.getResolver = () => _resolver;
150
152
 
151
153
  /**
152
- * Register new object in the Container.
153
- * @param {string} depId
154
- * @param {Object} obj
154
+ * Registers a new singleton object in the Container.
155
+ *
156
+ * @param {string} depId - Dependency identifier. Must be a singleton identifier.
157
+ * @param {object} obj - The object to register.
155
158
  */
156
159
  this.register = function (depId, obj) {
157
- if (!depId || !obj) throw new Error('depId and object are required');
160
+ if (!_testMode) throw new Error('Use enableTestMode() to allow it');
161
+
162
+ if (!depId || !obj) throw new Error('Both params are required');
163
+
158
164
  const key = _parser.parse(depId);
165
+ if ((key.life !== Defs.LS) && !key.isNodeModule) throw new Error(`Only node modules & singletons can be registered: '${depId}'`);
159
166
  if (key.life === Defs.LS) {
160
167
  const singleId = getSingletonId(key);
168
+ if (_regSingles[singleId]) throw new Error(`'${depId}' is already registered`);
161
169
  _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}`);
170
+ } else if (key.isNodeModule) {
171
+ const nodeId = key.origin;
172
+ if (_regTestNodeLibs[nodeId]) throw new Error(`'${depId}' is already registered`);
173
+ _regTestNodeLibs[nodeId] = obj;
166
174
  }
175
+
176
+ log(`'${depId}' is registered`);
167
177
  };
168
178
 
169
179
  this.setDebug = function (data) {
package/src/DepId.js CHANGED
@@ -17,6 +17,11 @@ export default class TeqFw_Di_DepId {
17
17
  * @type {string}
18
18
  */
19
19
  composition;
20
+ /**
21
+ * Defines if the dependency is a Node.js module.
22
+ * @type {boolean}
23
+ */
24
+ isNodeModule;
20
25
  /**
21
26
  * Defines the lifecycle of the resolved dependency:
22
27
  * - 'S' (Singleton): A single instance is created and reused.
@@ -1,69 +0,0 @@
1
- /**
2
- * Interface for the Object Container.
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 {
8
- /**
9
- * Gets or creates a runtime object by ID.
10
- *
11
- * @param {string} runtimeDepId - The ID of the runtime object.
12
- * @param {string[]} [stack]
13
- * @returns {Promise<*>} - A promise that resolves to the runtime object.
14
- */
15
- get(runtimeDepId, stack) {};
16
-
17
- /**
18
- * @returns {TeqFw_Di_Api_Container_Parser}
19
- */
20
- getParser() {};
21
-
22
- /**
23
- * @returns {TeqFw_Di_Api_Container_PreProcessor}
24
- */
25
- getPreProcessor() {};
26
-
27
- /**
28
- * @returns {TeqFw_Di_Api_Container_PostProcessor}
29
- */
30
- getPostProcessor() {};
31
-
32
- /**
33
- * @returns {TeqFw_Di_Container_Resolver} - the default resolver
34
- */
35
- getResolver() {};
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
-
44
- /**
45
- * Enable disable debug output for the object composition process.
46
- * @param {boolean} data
47
- */
48
- setDebug(data) {};
49
-
50
- /**
51
- * @param {TeqFw_Di_Api_Container_Parser} data
52
- */
53
- setParser(data) {};
54
-
55
- /**
56
- * @param {TeqFw_Di_Api_Container_PreProcessor} data
57
- */
58
- setPreProcessor(data) {};
59
-
60
- /**
61
- * @param {TeqFw_Di_Api_Container_PostProcessor} data
62
- */
63
- setPostProcessor(data) {};
64
-
65
- /**
66
- * @param {TeqFw_Di_Api_Container_Resolver} data
67
- */
68
- setResolver(data) {};
69
- };