@teqfw/di 0.21.0 → 0.21.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/RELEASE.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # @teqfw/di releases
2
2
 
3
+ ## 0.21.1
4
+
5
+ * Fix the dependency key signature in `TeqFw_Di_Container_A_Parser_Chunk_Def`.
6
+
3
7
  ## 0.21.0
4
8
 
5
9
  * Restructured modules in the package.
package/docs/README.md ADDED
@@ -0,0 +1,6 @@
1
+ # Documentation for @teqfw/di
2
+
3
+ Publish updates to https://teqfw.github.io/di/:
4
+ ```shell
5
+ $ npm run deploy
6
+ ```
package/docs/app.vue ADDED
@@ -0,0 +1,5 @@
1
+ <template>
2
+ <NuxtLayout>
3
+ <NuxtPage/>
4
+ </NuxtLayout>
5
+ </template>
@@ -0,0 +1,51 @@
1
+ @import "vars.css";
2
+
3
+ HTML {
4
+ font-family: 'Inter', sans-serif;
5
+ offset-anchor: 1cm 2cm;
6
+ }
7
+
8
+ BODY {
9
+ font-size: 1rem;
10
+ margin: 0;
11
+ padding: 0;
12
+ }
13
+
14
+ H1 {
15
+ font-size: 2.25rem;
16
+ }
17
+
18
+ H2 {
19
+ font-size: 1.5rem;
20
+ }
21
+
22
+ H3 {
23
+ font-size: 1.25rem;
24
+ }
25
+
26
+ H4 {
27
+ font-size: 1rem;
28
+ }
29
+
30
+ A {
31
+ /*color: var(--color-set-green);*/
32
+ /*color: #0E6758;*/
33
+ color: #0A9466;
34
+ font-weight: bold;
35
+ text-decoration: none;
36
+ }
37
+
38
+ A:visited {
39
+ text-decoration: none;
40
+ }
41
+
42
+ P {
43
+ text-align: justify;
44
+ }
45
+
46
+ PRE {
47
+ background-color: var(--color-set-black);
48
+ color: var(--color-set-green);
49
+ font-size: 0.75rem;
50
+ padding: 5px;
51
+ }
@@ -0,0 +1,6 @@
1
+ :root {
2
+ --color-set-black: #16181F;
3
+ /*--color-set-green: #00FF57;*/
4
+ /*--color-set-green: #114A47;*/
5
+ --color-set-green: #07B469;
6
+ }
@@ -0,0 +1,47 @@
1
+ <script setup>
2
+
3
+ </script>
4
+
5
+ <template>
6
+ <footer>
7
+ <div>&copy; 2023 Wiredgeese</div>
8
+ <div class="v-line">|</div>
9
+ <div><a href="mailto:info@wiredgeese.com">info@wiredgeese.com</a></div>
10
+ <div class="v-line">|</div>
11
+ <div><a href="https://t.me/wiredgeese">@wiredgeese</a></div>
12
+ </footer>
13
+ </template>
14
+
15
+ <style>
16
+ FOOTER {
17
+ background-color: var(--color-set-black);
18
+ color: var(--color-set-green);
19
+ display: flex;
20
+ justify-content: center; /* Distribute space between elements */
21
+ align-items: center; /* Center items vertically */
22
+ margin-top: 50px;
23
+ padding: 5px;
24
+ text-align: center;
25
+ z-index: 1000;
26
+ }
27
+
28
+ FOOTER A {
29
+ font-weight: normal;
30
+ color: var(--color-set-green);
31
+ }
32
+
33
+ FOOTER .v-line {
34
+ padding-left: 5px;
35
+ padding-right: 5px;
36
+ }
37
+
38
+ @media only screen and (max-width: 800px) {
39
+ FOOTER {
40
+ flex-direction: column;
41
+ }
42
+
43
+ FOOTER .v-line {
44
+ display: none;
45
+ }
46
+ }
47
+ </style>
@@ -0,0 +1,79 @@
1
+ <script setup>
2
+
3
+ </script>
4
+
5
+ <template>
6
+ <header>
7
+ <div class="h-left">
8
+ <div class="img-container">
9
+ <img src="/favicon.ico" alt="logo">
10
+ </div>
11
+ <h4>TeqFW</h4>
12
+ </div>
13
+ <div class="h-center">
14
+ <h3>Dependency Injection</h3>
15
+ </div>
16
+ <div class="h-right">
17
+ <div class="img-container">
18
+ <a href="https://www.npmjs.com/package/@teqfw/di">
19
+ <img src="/img/npm.png" alt="npm"/>
20
+ </a>
21
+ </div>
22
+ <div class="img-container">
23
+ <a href="https://github.com/teqfw/di">
24
+ <img src="/img/github.svg" alt="GitHub"/>
25
+ </a>
26
+ </div>
27
+ </div>
28
+ </header>
29
+ </template>
30
+
31
+ <style>
32
+ HEADER {
33
+ background-color: var(--color-set-black);
34
+ color: var(--color-set-green);
35
+ position: sticky;
36
+ top: 0;
37
+ z-index: 1000;;
38
+ }
39
+
40
+ HEADER, .h-left, .h-center, .h-right {
41
+ align-items: center;
42
+ display: flex;
43
+ justify-content: space-between;
44
+ }
45
+
46
+ HEADER H3, HEADER H4 {
47
+ margin-block: 10px 10px;
48
+ }
49
+
50
+ .h-left {
51
+ margin-left: 20px;
52
+ }
53
+
54
+ .h-right {
55
+ margin-right: 20px;
56
+ }
57
+
58
+ .img-container {
59
+ /* border: 2px solid var(--color-set-green); */
60
+ margin: 5px;
61
+ border-radius: 50%;
62
+ height: 40px;
63
+ overflow: hidden;
64
+ width: 40px;
65
+ }
66
+
67
+ .img-container IMG {
68
+ width: 100%;
69
+ height: 100%;
70
+ object-fit: cover;
71
+ }
72
+
73
+ /* === Styles for mobiles === */
74
+ @media only screen and (max-width: 800px) {
75
+ .h-center H3 {
76
+ display: none;
77
+ }
78
+ }
79
+ </style>
@@ -0,0 +1,272 @@
1
+ # @teqfw/di
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)).
10
+
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
+ // backend style
24
+ import something from '@vendor/package/src/Module.js';
25
+
26
+ // frontend style
27
+ import something from 'https://domain.com/@vendor/package/src/Module.js';
28
+ ```
29
+
30
+ The inversion of control (IoC) design pattern came to the rescue. In this pattern, any software object with external
31
+ dependencies provides a mechanism for obtaining these dependencies. The external environment, whether it's a test unit
32
+ or an object container, is responsible for creating these dependencies and providing them to the software object.
33
+
34
+ ```javascript
35
+ // constructor-based injection
36
+ class Service {
37
+ constructor(config, logger) {}
38
+ }
39
+ ```
40
+
41
+ If all dependencies are added to software objects through a similar mechanism, there is no need to use static imports in
42
+ the source code itself. Now, they can be used without any changes, both on the front end and on the back end.
43
+
44
+ ## Object Container
45
+
46
+ Many programming languages implement the Dependency Injection pattern. In this pattern, an application typically
47
+ utilizes an object container, responsible for creating all the application's objects and their dependencies. The
48
+ `@teqfw/di` package provides precisely such an object container (`src/Container.js`). This object container is
49
+ initialized and configured at the outset of application execution, after which it assumes responsibility for creating
50
+ the remaining application objects:
51
+
52
+ ```javascript
53
+ import Container from '@teqfw/di';
54
+
55
+ const container = new Container();
56
+ const resolver = container.getResolver();
57
+ resolver.addNamespaceRoot('App_', pathApp);
58
+ resolver.addNamespaceRoot('Sample_Lib_', pathLib);
59
+ const app = await container.get('App_Main$');
60
+ ```
61
+
62
+ Since JavaScript does not have its own namespaces, similar to packages in Java and namespaces in PHP, the experience of
63
+ [Zend 1](https://framework.zend.com/manual/2.4/en/migration/namespacing-old-classes.html) is used as the basis for
64
+ identifiers.
65
+
66
+ ## Namespaces
67
+
68
+ The primary purpose of namespaces is to address code elements within an application. In JavaScript (JS) applications,
69
+ code is organized into npm packages, within which the sources reside in files and directories. Each npm package and its
70
+ root directory can be linked to a namespace:
71
+
72
+ ```
73
+ Vendor_Package_ => /home/user/app/node_modules/@vendor/package/src/....
74
+ ```
75
+
76
+ This way, you can reference any ES6 module in any npm package:
77
+
78
+ ```
79
+ Venodr_Package_Shared_Dto_Service_Save
80
+ => /home/user/app/node_modules/@vendor/package/src/Shared/Dto/Service/Save.js
81
+ ```
82
+
83
+ Depending on the execution environment, the mapping may be different:
84
+
85
+ ```
86
+ Vendor_Package_ => /home/user/app/node_modules/@vendor/package/src // Linux style
87
+ Vendor_Package_ => C:\projects\app\node_modules\@vendor\package\src // Windows style
88
+ Vendor_Package_ => https://unpkg.com/@vendor/package/src // Web style
89
+ ```
90
+
91
+ The source code employs namespaces to reference dependencies, while the object container utilizes a resolver to
92
+ translate the namespace into the corresponding path to the source code file, contingent upon the runtime environment:
93
+
94
+ ```
95
+ Venodr_Package_Shared_Dto_Service_Save
96
+ => /home/user/app/node_modules/@vendor/package/src/Shared/Dto/Service/Save.js
97
+ Venodr_Package_Shared_Dto_Service_Save
98
+ => C:\projects\app\node_modules\@vendor\package\src\Shared\Dto\Service\Save.js
99
+ Venodr_Package_Shared_Dto_Service_Save
100
+ => https://unpkg.com/@vendor/package/src/Shared/Dto/Service/Save.js
101
+ ```
102
+
103
+ ## Dependency Specification
104
+
105
+ JavaScript lacks reflection capabilities similar to Java or PHP. Consequently, to enable the object container to
106
+ comprehend the necessary dependencies for creating a specific object, a distinct convention is employed - a dependency
107
+ specification. A dependency specification is an object where each key represents the identifier of the required
108
+ dependency:
109
+
110
+ ```javascript
111
+ class Service {
112
+ constructor(
113
+ {
114
+ App_Config: config,
115
+ App_Logger: logger
116
+ }
117
+ ) {}
118
+ }
119
+ ```
120
+
121
+ In the object container, the required object is created as follows:
122
+
123
+ ```javascript
124
+ const App_Config = await container.get('App_Config');
125
+ const App_Logger = await container.get('App_Logger');
126
+ const spec = {App_Config, App_Logger};
127
+ const obj = new Service(spec);
128
+ ```
129
+
130
+ If dependencies are injected into a factory function, it appears as follows:
131
+
132
+ ```javascript
133
+ function Factory({App_Config: config, App_Logger: logger}) {
134
+ // perform operations with dependencies and compose the result
135
+ return res;
136
+ }
137
+ ```
138
+
139
+ ## Es6 export
140
+
141
+ In ES6+, a distinct building block in application development is the act
142
+ of [exporting](https://flancer32.com/es6-export-as-code-brick-b33a8efb3510):
143
+
144
+ ```javascript
145
+ export {
146
+ obj1 as default, obj2, obj3
147
+ };
148
+ ```
149
+
150
+ Static linking through imports is performed at the level of these building blocks:
151
+
152
+ ````javascript
153
+ import obj1 from './mod.js';
154
+ import {obj2} from './mod.js';
155
+ ````
156
+
157
+ This implies that the dependency identifier must have the capability to reference not only the ES6 module itself but
158
+ also a specific export within it, as illustrated in this example:
159
+
160
+ ```javascript
161
+ const exp = 'Vendor_Package_Module.export';
162
+ const def = 'Vendor_Package_Module.default';
163
+ const obj2 = 'Vendor_Package_Module.obj2';
164
+ ```
165
+
166
+ In this case, the dependency declaration in a constructor or factory function could look like this:
167
+
168
+ ```javascript
169
+ class Service {
170
+ constructor(
171
+ {
172
+ 'App_Config.default': config,
173
+ 'App_Util.logger': logger
174
+ }
175
+ ) {}
176
+ }
177
+ ```
178
+
179
+ ## Late binding
180
+
181
+ The object container links objects not at the source code level but in runtime mode. In my applications, I have
182
+ encountered two particularly useful runtime object lifecycles:
183
+
184
+ * **Singleton**: It exists in a single instance within the application.
185
+ * **Instance**: A new object is created each time.
186
+
187
+ Since any string can be used as an object key in a dependency specification, various formats can be devised to specify
188
+ the lifecycle of the required dependency. I have personally chosen the following format:
189
+
190
+ ```javascript
191
+ const asIs = 'Vendor_Package_Module.default';
192
+ const asSingleton = 'Vendor_Package_Module.default$';
193
+ const asInstance = 'Vendor_Package_Module.default$$';
194
+ ```
195
+
196
+ In principle, each package can have its own format for describing the dependencies it uses internally. The
197
+ `TeqFw_Di_Container_Parser` object is responsible for applying the appropriate format within the required namespace.
198
+
199
+ ## Transforming the Result
200
+
201
+ Here are the steps for the object container:
202
+
203
+ ![processing steps](./img/teqfw_di_container_steps.png)
204
+
205
+ There are two stages involved here:
206
+
207
+ * **Preprocessing**: the modification of the dependency identifier
208
+ * **Postprocessing**: the modification of the created object
209
+
210
+ ### Preprocessing
211
+
212
+ At times, situations may arise, especially when utilizing various extensions of the core functionality, where it becomes
213
+ necessary to redefine certain objects within the application. For such scenarios, `@teqfw/di` includes a preprocessor:
214
+
215
+ ```javascript
216
+ /** @type {TeqFw_Di_Api_Container_PreProcessor} */
217
+ const pre = container.getPreProcessor();
218
+ ```
219
+
220
+ You can add handlers (chunks) to the preprocessor that are capable of modifying the initial `depId`:
221
+
222
+ ```javascript
223
+ /** @type {TeqFw_Di_Api_Container_PreProcessor_Chunk} */
224
+ const replace = new ReplaceChunk(); // some implementation of the interface
225
+ pre.addChunk(replace);
226
+ ```
227
+
228
+ The preprocessor calls the handlers sequentially and can, for example, replace a dependency from the base npm package
229
+ (`App_Base_Mod_Api_Service_Auth`) with another dependency from one of the npm packages (`Auth_Password_Mod_Service` or
230
+ `OAuth2_Mod_Service`), depending on the npm packages included in the application compilation.
231
+
232
+ By using such replacements, you can implement the core functionality in one npm package, while in other npm packages,
233
+ you can implement the additional functionality required by the core package.
234
+
235
+ ### Postprocessing
236
+
237
+ Since the container creates all objects in the application, it can also perform additional actions on newly created
238
+ objects, such as adding extra functionality to them in the form of a wrapper.
239
+
240
+ `@teqfw/di` enables you to add individual handlers to the post-processing stage and modify the result. For example, you
241
+ can wrap a finished object or perform various operations on it:
242
+
243
+ ```javascript
244
+ // ./PostChunk.js
245
+ /**
246
+ * @implements TeqFw_Di_Api_Container_PostProcessor_Chunk
247
+ */
248
+ export default {
249
+ modify: function (obj, originalId, stack) {
250
+ if (originalId.wrappers.indexOf('proxy') !== -1)
251
+ return new Proxy(obj, {
252
+ get: async function (base, name) { /* do something */ }
253
+ });
254
+ else return obj;
255
+ }
256
+ };
257
+ ```
258
+
259
+ ```javascript
260
+ // ./main.js
261
+ import postChunk from './PostChunk.mjs';
262
+
263
+ container.getPostProcessor().addChunk(postChunk);
264
+ ```
265
+
266
+ ## Resume
267
+
268
+ `@teqfw/di` offers Dependency Injection for regular JavaScript with minimal manual configuration, supporting both
269
+ browser and Node.js environments. Its use of late binding and an object container in JavaScript applications, along with
270
+ the ability to modify the behavior of created objects (via pseudo-interfaces and wrappers), allows you to apply
271
+ architectural solutions from other languages (such as Java, PHP, C#) and fully harness the capabilities of npm packages
272
+ and ES6 modules in JavaScript applications, particularly in the Node.js environment.
@@ -0,0 +1,20 @@
1
+ ---
2
+ title: 'Title of the page'
3
+ description: 'meta description of the page'
4
+ ---
5
+
6
+ # TEST
7
+
8
+ Any text is going here...
9
+
10
+ ## H2
11
+
12
+ Any text is going here...
13
+
14
+ ### H3
15
+
16
+ Any text is going here...
17
+
18
+ #### H4
19
+
20
+ Any text is going here...
@@ -0,0 +1,83 @@
1
+ <script setup>
2
+ onMounted(() => {
3
+ // FUNCS
4
+ /**
5
+ * Scroll down the page on hashtag to display text under header block.
6
+ */
7
+ function scrollToChapter() {
8
+ const hash = window.location.hash;
9
+ if (hash) {
10
+ const target = document.querySelector(hash);
11
+ if (target) {
12
+ window.scrollTo({
13
+ top: target.offsetTop - 50,
14
+ // behavior: 'smooth' // Для плавной прокрутки
15
+ });
16
+ }
17
+ }
18
+ }
19
+
20
+ // MAIN
21
+ window.addEventListener('hashchange', scrollToChapter);
22
+ scrollToChapter();
23
+ });
24
+
25
+ useHead(() => ({
26
+ link: [
27
+ {rel: 'icon', type: 'image/x-icon', href: '/favicon.ico'},
28
+ ]
29
+ }));
30
+
31
+ </script>
32
+
33
+ <template>
34
+ <div class="app-frame">
35
+ <AppHeader/>
36
+ <div class="app-main">
37
+ <div class="app-left"></div>
38
+ <main class="app-center">
39
+ <slot/>
40
+ </main>
41
+ <div class="app-right"></div>
42
+ </div>
43
+ <AppFooter/>
44
+ </div>
45
+ </template>
46
+
47
+ <style>
48
+ .app-frame {
49
+ display: flex;
50
+ flex-direction: column;
51
+ min-height: 100vh;
52
+ }
53
+
54
+ .app-main {
55
+ display: flex;
56
+ }
57
+
58
+ .app-left, .app-center, .app-right {
59
+ flex: 1;
60
+ }
61
+
62
+ .app-center {
63
+ margin: 0;
64
+ max-width: 800px;
65
+ padding-left: 5px;
66
+ padding-right: 5px;
67
+ }
68
+
69
+ .app-center IMG {
70
+ width: 100%;
71
+ }
72
+
73
+ /* === Styles for mobiles === */
74
+ @media only screen and (max-width: 800px) {
75
+ .app-center {
76
+ max-width: calc(100vw - 10px);
77
+ }
78
+
79
+ PRE {
80
+ max-width: calc(100vw - 10px);
81
+ }
82
+ }
83
+ </style>
@@ -0,0 +1,11 @@
1
+ // https://nuxt.com/docs/api/configuration/nuxt-config
2
+ export default defineNuxtConfig({
3
+ app: {baseURL: '/'},
4
+ content: {},
5
+ css: ['~/assets/css/layout.css'],
6
+ devtools: {enabled: false},
7
+ experimental: {payloadExtraction: false},
8
+ googleFonts: {families: {Inter: true},},
9
+ gtag: {id: 'AW-11336737732'},
10
+ modules: ['@nuxt/content', '@nuxtjs/google-fonts', 'nuxt-gtag'],
11
+ });