@superhero/core 1.8.5 → 4.0.0-beta.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.
Files changed (175) hide show
  1. package/README.md +108 -739
  2. package/index.js +654 -54
  3. package/index.test.js +94 -0
  4. package/package.json +17 -31
  5. package/worker.js +12 -0
  6. package/LICENCE +0 -20
  7. package/bootstrap/config.js +0 -7
  8. package/bootstrap/index.js +0 -19
  9. package/bootstrap/locator.js +0 -16
  10. package/cli/config.js +0 -7
  11. package/cli/index.js +0 -49
  12. package/cli/locator.js +0 -17
  13. package/composer/bootstrap/error/schema-not-resolvable.js +0 -10
  14. package/composer/bootstrap/index.js +0 -62
  15. package/composer/bootstrap/locator.js +0 -20
  16. package/composer/config.js +0 -49
  17. package/composer/error/filter-is-not-honering-contract.js +0 -13
  18. package/composer/error/invalid-attribute.js +0 -13
  19. package/composer/error/invalid-collection.js +0 -13
  20. package/composer/error/invalid-schema.js +0 -13
  21. package/composer/error/schema-not-found.js +0 -13
  22. package/composer/error/validator-is-not-honering-contract.js +0 -13
  23. package/composer/error/validator-not-found.js +0 -13
  24. package/composer/filter/boolean/index.js +0 -44
  25. package/composer/filter/boolean/locator.js +0 -11
  26. package/composer/filter/decimal/index.js +0 -38
  27. package/composer/filter/decimal/locator.js +0 -11
  28. package/composer/filter/index.js +0 -10
  29. package/composer/filter/integer/index.js +0 -38
  30. package/composer/filter/integer/locator.js +0 -11
  31. package/composer/filter/json/index.js +0 -23
  32. package/composer/filter/json/locator.js +0 -11
  33. package/composer/filter/schema/error/missing-schema-definition.js +0 -13
  34. package/composer/filter/schema/index.js +0 -49
  35. package/composer/filter/schema/locator.js +0 -17
  36. package/composer/filter/string/index.js +0 -50
  37. package/composer/filter/string/locator.js +0 -11
  38. package/composer/filter/timestamp/index.js +0 -45
  39. package/composer/filter/timestamp/locator.js +0 -11
  40. package/composer/index.js +0 -180
  41. package/composer/locator.js +0 -17
  42. package/composer/validator/boolean/error/invalid.js +0 -13
  43. package/composer/validator/boolean/index.js +0 -17
  44. package/composer/validator/boolean/locator.js +0 -11
  45. package/composer/validator/decimal/error/invalid.js +0 -13
  46. package/composer/validator/decimal/index.js +0 -59
  47. package/composer/validator/decimal/locator.js +0 -11
  48. package/composer/validator/index.js +0 -10
  49. package/composer/validator/integer/error/invalid.js +0 -13
  50. package/composer/validator/integer/index.js +0 -65
  51. package/composer/validator/integer/locator.js +0 -11
  52. package/composer/validator/json/error/invalid.js +0 -13
  53. package/composer/validator/json/index.js +0 -23
  54. package/composer/validator/json/locator.js +0 -11
  55. package/composer/validator/schema/error/invalid.js +0 -13
  56. package/composer/validator/schema/index.js +0 -13
  57. package/composer/validator/schema/locator.js +0 -11
  58. package/composer/validator/string/error/invalid.js +0 -13
  59. package/composer/validator/string/index.js +0 -59
  60. package/composer/validator/string/locator.js +0 -11
  61. package/composer/validator/timestamp/error/invalid.js +0 -13
  62. package/composer/validator/timestamp/index.js +0 -54
  63. package/composer/validator/timestamp/locator.js +0 -11
  64. package/configuration/config.js +0 -7
  65. package/configuration/index.js +0 -23
  66. package/configuration/locator.js +0 -22
  67. package/console/config.js +0 -15
  68. package/console/index.js +0 -23
  69. package/console/locator.js +0 -21
  70. package/console/observer/error/config.js +0 -7
  71. package/console/observer/error/index.js +0 -14
  72. package/console/observer/error/locator.js +0 -17
  73. package/console/observer/info/config.js +0 -7
  74. package/console/observer/info/index.js +0 -14
  75. package/console/observer/info/locator.js +0 -17
  76. package/console/observer/warning/config.js +0 -7
  77. package/console/observer/warning/index.js +0 -14
  78. package/console/observer/warning/locator.js +0 -17
  79. package/core/config.js +0 -21
  80. package/core/error.js +0 -10
  81. package/deepcopy/config.js +0 -7
  82. package/deepcopy/error/failed-to-fast-copy.js +0 -10
  83. package/deepcopy/index.js +0 -18
  84. package/deepcopy/locator.js +0 -11
  85. package/deepfind/config.js +0 -7
  86. package/deepfind/index.js +0 -11
  87. package/deepfind/locator.js +0 -11
  88. package/deepfreeze/config.js +0 -7
  89. package/deepfreeze/index.js +0 -20
  90. package/deepfreeze/locator.js +0 -11
  91. package/deepmerge/config.js +0 -7
  92. package/deepmerge/index.js +0 -42
  93. package/deepmerge/locator.js +0 -11
  94. package/eventbus/bootstrap/error/observer-contract-not-honered.js +0 -10
  95. package/eventbus/bootstrap/index.js +0 -33
  96. package/eventbus/bootstrap/locator.js +0 -20
  97. package/eventbus/config.js +0 -17
  98. package/eventbus/index.js +0 -21
  99. package/eventbus/locator.js +0 -25
  100. package/eventbus/observer.js +0 -9
  101. package/factory.js +0 -55
  102. package/http/request/config.js +0 -7
  103. package/http/request/locator.js +0 -21
  104. package/http/server/config.js +0 -23
  105. package/http/server/dispatcher/chain/error/dispatcher-chain-ended.js +0 -10
  106. package/http/server/dispatcher/chain/index.js +0 -41
  107. package/http/server/dispatcher/chain/locator.js +0 -20
  108. package/http/server/dispatcher/collection/builder/error/dispatcher-can-not-be-resolved.js +0 -10
  109. package/http/server/dispatcher/collection/builder/error/not-honering-dispatcher-contract.js +0 -10
  110. package/http/server/dispatcher/collection/builder/index.js +0 -56
  111. package/http/server/dispatcher/collection/builder/locator.js +0 -20
  112. package/http/server/dispatcher/error/bad-gateway.js +0 -12
  113. package/http/server/dispatcher/error/bad-request.js +0 -12
  114. package/http/server/dispatcher/error/conflict.js +0 -12
  115. package/http/server/dispatcher/error/forbidden.js +0 -12
  116. package/http/server/dispatcher/error/gateway-timeout.js +0 -12
  117. package/http/server/dispatcher/error/index.js +0 -10
  118. package/http/server/dispatcher/error/method-not-allowed.js +0 -12
  119. package/http/server/dispatcher/error/not-implemented.js +0 -12
  120. package/http/server/dispatcher/error/page-not-found.js +0 -12
  121. package/http/server/dispatcher/error/request-timeout.js +0 -12
  122. package/http/server/dispatcher/error/server-error.js +0 -12
  123. package/http/server/dispatcher/error/service-unavailable.js +0 -12
  124. package/http/server/dispatcher/error/unauthorized.js +0 -12
  125. package/http/server/dispatcher/index.js +0 -26
  126. package/http/server/dispatcher/rest.js +0 -75
  127. package/http/server/error/no-endpoint-defined-in-route.js +0 -10
  128. package/http/server/error/view-contract-not-honered.js +0 -10
  129. package/http/server/index.js +0 -195
  130. package/http/server/locator.js +0 -34
  131. package/http/server/request/builder/index.js +0 -53
  132. package/http/server/request/builder/locator.js +0 -20
  133. package/http/server/route/builder/error/dto-invalid-reference.js +0 -10
  134. package/http/server/route/builder/error/routes-invalid-type.js +0 -10
  135. package/http/server/route/builder/index.js +0 -88
  136. package/http/server/route/builder/locator.js +0 -20
  137. package/http/server/session/builder/index.js +0 -34
  138. package/http/server/session/builder/locator.js +0 -11
  139. package/http/server/view/index.js +0 -12
  140. package/http/server/view/json/index.js +0 -17
  141. package/http/server/view/json/locator.js +0 -11
  142. package/http/server/view/locator.js +0 -11
  143. package/http/server/view/stream/index.js +0 -10
  144. package/http/server/view/stream/locator.js +0 -11
  145. package/http/server/view/text/index.js +0 -15
  146. package/http/server/view/text/locator.js +0 -11
  147. package/locator/constituent.js +0 -26
  148. package/locator/error/locator-not-implemented.js +0 -10
  149. package/locator/error/service-undefined.js +0 -10
  150. package/locator/index.js +0 -29
  151. package/path/config.js +0 -7
  152. package/path/index.js +0 -63
  153. package/path/locator.js +0 -11
  154. package/process/bootstrap/index.js +0 -31
  155. package/process/bootstrap/locator.js +0 -17
  156. package/process/config.js +0 -12
  157. package/process/index.js +0 -9
  158. package/process/locator.js +0 -11
  159. package/test/api/config.js +0 -43
  160. package/test/api/endpoint/append-calculation.js +0 -41
  161. package/test/api/endpoint/create-calculation.js +0 -18
  162. package/test/api/middleware/authentication.js +0 -27
  163. package/test/calculator/calculation.js +0 -29
  164. package/test/calculator/config.js +0 -14
  165. package/test/calculator/error/calculation-could-not-be-found.js +0 -13
  166. package/test/calculator/error/invalid-calculation-type.js +0 -13
  167. package/test/calculator/index.js +0 -67
  168. package/test/calculator/locator.js +0 -20
  169. package/test/init.js +0 -2
  170. package/test/logger/config.js +0 -15
  171. package/test/logger/index.js +0 -17
  172. package/test/logger/locator.js +0 -20
  173. package/test/mocha.opts +0 -4
  174. package/test/test.calculation.js +0 -58
  175. package/test/test.logger.js +0 -35
package/README.md CHANGED
@@ -1,813 +1,182 @@
1
- # Core
2
-
3
- Licence: [MIT](https://opensource.org/licenses/MIT)
4
-
5
- ---
6
-
7
- [![npm version](https://badge.fury.io/js/%40superhero%2Fcore.svg)](https://badge.fury.io/js/%40superhero%2Fcore)
8
-
9
- - A core framework I use when developing in [nodejs](https://nodejs.org/en/docs/).
10
- - I built the framework to help me build applications after a reactive [domain driven design (DDD)](https://en.wikipedia.org/wiki/Domain-driven_design) approch.
11
- - The framework is designed to have little to no production dependencies.
12
- - The framework offers solutions for different topics, not necessarily an http server.
13
- - The vision of the framework is to offer a code structure to the developer that will help segregate responsibilities in projects through a [SOLID](https://en.wikipedia.org/wiki/SOLID) [OOP](https://en.wikipedia.org/wiki/Object-oriented_programming) approach.
14
-
15
- ## Addons
16
-
17
- Consider extending the framework with one or multiple plugins where the extra functionality is required:
18
- *Please note; you should use matching versions for core and the plugins by major and future releases*
19
-
20
- - [core-handlebars](http://github.com/superhero/js.core.handlebars)
21
- - [core-resource](http://github.com/superhero/js.core.resource)
22
- - [core-websocket](http://github.com/superhero/js.core.websocket)
23
-
24
- ## Install
25
-
26
- `npm install @superhero/core`
27
-
28
- ...or just set the dependency in your `package.json` file:
29
-
30
- ```json
31
- {
32
- "dependencies":
33
- {
34
- "@superhero/core": "*"
35
- }
36
- }
37
- ```
38
-
39
- ## Example Application
40
-
41
- An example application to get started, covering some recommended "best practices" regarding testing and docs generation.
42
-
43
- The application is a calculator with very basic functionality. The aim with this application is not to make a good calculator. The aim is to show you, who is reading this, how different parts of the framework works, or are intended to work.
44
-
45
- ### Example Application › File structure
46
-
47
- ```
48
- super-duper-app
49
- ├── docs
50
- ├── src
51
- │ ├── api
52
- │ │ ├── endpoint
53
- │ │ │ ├── append-calculation.js
54
- │ │ │ └── create-calculation.js
55
- │ │ ├── middleware
56
- │ │ │ └── authentication.js
57
- │ │ └── config.js
58
- │ ├── calculator
59
- │ │ ├── error
60
- │ │ | ├── calculation-could-not-be-found.js
61
- │ │ | └── invalid-calculation-type.js
62
- │ │ ├── calculation.js
63
- │ │ ├── config.js
64
- │ │ ├── index.js
65
- │ │ └── locator.js
66
- │ ├── logger
67
- │ │ ├── config.js
68
- │ │ ├── index.js
69
- │ │ └── locator.js
70
- │ └── index.js
71
- ├── test
72
- │ ├── init.js
73
- │ ├── mocha.opts
74
- │ ├── test.calculations.js
75
- │ └── test.logger.js
76
- ├── .gitignore
77
- ├── package.json
78
- └── README.md
79
- ```
80
-
81
- The file structure is here divided to only 3 modules
82
-
83
- - One called `api` *- that's responsible for the endpoints.*
84
- - A second one called `calculator` *- that is responsible for domain logic.*
85
- - The third one called `logger` *- an infrastructure layer that will log events to the console.*
86
-
87
- #### `.gitignore`
88
-
89
- ```
90
- docs/generated
91
- npm-debug.log
92
- node_modules
93
- package-lock.json
94
- .nyc_output
95
- ```
96
-
97
- Set up a `.gitignore` file to ignore some auto-generated files to keep a clean repository.
98
-
99
- #### `package.json`
100
-
101
- ```js
102
- {
103
- "name": "Super Duper App",
104
- "version": "0.0.1",
105
- "description": "An example application of the superhero/core framework",
106
- "repository": "https://github.com/...",
107
- "license": "MIT",
108
- "main": "src/index.js",
109
- "author": {
110
- "name": "Padawan",
111
- "email": "padawan@example.com"
112
- },
113
- "scripts": {
114
- "docs-coverage": "nyc mocha './test/test.*.js' --opts ./test/mocha.opts && nyc report --reporter=html --report-dir=./docs/generated/coverage",
115
- "docs-tests": "mocha './test/test.*.js' --opts ./test/mocha.opts --reporter mochawesome --reporter-options reportDir=docs/generated/test,reportFilename=index,showHooks=always",
116
- "test": "nyc mocha './test/test.*.js' --opts ./test/mocha.opts",
117
- "start": "node ./src/index.js"
118
- },
119
- "dependencies": {
120
- "@superhero/core": "*"
121
- },
122
- "devDependencies": {
123
- "mocha": "5.1.0",
124
- "mochawesome": "3.0.2",
125
- "chai": "4.1.2",
126
- "nyc": "11.7.1"
127
- }
128
- }
129
- ```
130
-
131
- Our [`package.json`](https://docs.npmjs.com/files/package.json) file will dictate what dependencies we use. This example application will go through test cases, why we have a few `devDependencies` defined.
132
-
133
- #### `index.js`
134
-
135
- ```js
136
- const
137
- CoreFactory = require('@superhero/core/factory'),
138
- coreFactory = new CoreFactory,
139
- core = coreFactory.create()
140
-
141
- core.add('api')
142
- core.add('calculator')
143
- core.add('logger')
144
- core.add('http/server')
145
-
146
- core.load()
147
-
148
- core.locate('bootstrap').bootstrap().then(() =>
149
- core.locate('http/server').listen(process.env.HTTP_PORT))
150
- ```
151
-
152
- We start of by creating a core factory that will create the core object, central to our application. The core is designed to keep track of a global state related to it's context. You can create multiple cores if necessary, but normally it makes sens to only use one. This example will only use one core.
153
-
154
- The next step is to add services that we will use in relation to the core context. Here we add `api` and `calculator` that exists in the file tree previously defined. You also notice that we add the service `http/server` that does not exist in the file tree we defined. The `http/server` service is located in the core, but may not always be necessary to load depending on the scope of your application, so you need to add the `http/server` service when you want to set up an http server.
155
- The framework will try to add services depending on a hierarchy of paths.
156
-
157
- - First it will try to load in relation to your `main script`
158
- - next it attempts by dependency defined in your `package.json` file
159
- - and finally it will attempt to load related to the core library of existing services.
160
-
161
- *This hierarchy allows you, as a developer, to overwrite anything in the framework with custom logic.*
162
-
163
- Next we load the core! This will eager load all the services previously added to the context.
164
-
165
- By bootstrapping the core, we run a few scripts that needs to run for some modules to function properly. As a developer you can hook into this process in the modules you write.
166
-
167
- And in the end, after we have added, loaded and bootstrapped the modules related to the context, we tell the server to listen to a specific port for network activity.
168
-
169
- ### Api
170
1
 
171
- #### `src/api/config.js`
2
+ # Core
172
3
 
173
- ```js
174
- module.exports =
175
- {
176
- http:
177
- {
178
- server:
179
- {
180
- routes:
181
- {
182
- 'create-calculation':
183
- {
184
- url : '/calculations',
185
- method : 'post',
186
- endpoint: 'api/endpoint/create-calculation',
187
- view : 'http/server/view/json'
188
- },
189
- 'authentication':
190
- {
191
- middleware :
192
- [
193
- 'api/middleware/authentication'
194
- ]
195
- },
196
- 'append-calculation':
197
- {
198
- url : '/calculations/.+',
199
- method : 'put',
200
- endpoint: 'api/endpoint/append-calculation',
201
- view : 'http/server/view/json',
202
- dto :
203
- {
204
- 'id' : { 'url' : 2 },
205
- 'type' : { 'body' : 'type' },
206
- 'value' : { 'body' : 'value' }
207
- }
208
- }
209
- }
210
- }
211
- },
212
- authentication:
213
- {
214
- apikey : 'ABC123456789'
215
- }
216
- }
217
- ```
4
+ A framework designed to support application clustering, configuration management, and service location.
218
5
 
219
- I have here chosen to set up a folder structure with one module called `api`. This module will contain all the endpoints. As such, the config file of this module will specify the router setting for these endpoints.
6
+ ### OBS!
220
7
 
221
- I set up two explicit routes: `create-calculation` and `append-calculation`, and one middleware route: `authentication`.
8
+ _Current version is in beta release_
222
9
 
223
- - The **action** attribute declares on what **url pathname** the route will be valid for.
224
- - The **method** attribute declares on what **url method** the route will be valid for
10
+ ---
225
11
 
226
- The middleware route does not have an action or method constraint specified, so it's considered valid, but it does not have an endpoint specified; declaring it not unterminated. When a route is valid, but unterminated, then it will be merged together with every other valid route specified until one that is terminated appears, eg: one that has declared an endpoint.
12
+ ## Features
227
13
 
228
- #### `src/api/endpoint/create-calculation.js`
14
+ - **Cluster Support**: Scale horizontally using the `cluster` module with worker management.
15
+ - **Graceful Shutdown**: Handles system signals (`SIGINT`, `SIGTERM`) and unexpected errors (`unhandledRejection`, `uncaughtException`) to support a graceful shutdown.
16
+ - **Service Locator**: Locate and initialize services based on configuration.
17
+ - **Bootstrap Support**: Initialize resources and services based on configurations.
18
+ - **Configuration Manager**: Manage configurations for different environments or branches.
229
19
 
230
- ```js
231
- const Dispatcher = require('@superhero/core/http/server/dispatcher')
20
+ ---
232
21
 
233
- /**
234
- * @extends {@superhero/core/http/server/dispatcher}
235
- */
236
- class CreateCalculationEndpoint extends Dispatcher
237
- {
238
- dispatch()
239
- {
240
- const
241
- calculator = this.locator.locate('calculator'),
242
- calculationId = calculator.createCalculation()
243
-
244
- this.view.body.id = calculationId
245
- }
246
- }
22
+ ## Installation
247
23
 
248
- module.exports = CreateCalculationEndpoint
24
+ ```bash
25
+ npm install @superhero/core
249
26
  ```
250
27
 
251
- The `create calculation` endpoint is here defined.
252
-
253
- - First we locate the `calculator` service.
254
- - Next we create a calculation
255
- - And finally we populate the `view model` with the returning calculation id
256
-
257
- ***OBS! it's possible to define the `dispatch` method as `async`. The framework will `await` for the method to finish***
258
-
259
- #### `src/api/endpoint/append-calculation.js`
260
-
261
- ```js
262
- const
263
- Dispatcher = require('@superhero/core/http/server/dispatcher'),
264
- PageNotFoundError = require('@superhero/core/http/server/dispatcher/error/page-not-found'),
265
- BadRequestError = require('@superhero/core/http/server/dispatcher/error/bad-request')
266
-
267
- /**
268
- * @extends {@superhero/core/http/server/dispatcher}
269
- */
270
- class AppendCalculationEndpoint extends Dispatcher
271
- {
272
- dispatch()
273
- {
274
- const
275
- calculator = this.locator.locate('calculator'),
276
- composer = this.locator.locate('composer'),
277
- calculation = composer.compose('calculation', this.route.dto),
278
- result = calculator.appendToCalculation(calculation)
279
-
280
- this.view.body.result = result
281
- }
282
-
283
- onError(error)
284
- {
285
- switch(error)
286
- {
287
- case 'E_CALCULATION_COULD_NOT_BE_FOUND':
288
- throw new PageNotFoundError('Calculation could not be found')
289
-
290
- case 'E_INVALID_CALCULATION_TYPE':
291
- throw new BadRequestError(`Unrecognized type: "${this.route.dto.type}"`)
292
-
293
- case 'E_COMPOSER_INVALID_ATTRIBUTE':
294
- throw new BadRequestError(error.message)
295
-
296
- default:
297
- throw error
298
- }
299
- }
300
- }
28
+ ---
301
29
 
302
- module.exports = AppendCalculationEndpoint
303
- ```
30
+ ## Usage
304
31
 
305
- The `append calculation` endpoint is here defined.
32
+ ### Basic Example
306
33
 
307
- - We start by showing you how to access data in the request through the `dto` *(Data Transfer Object)* located on the route.
308
- - Next we locate the `calculator` service.
309
- - Followed by appending a calculation to the stored calculated sum.
310
- - And finally we populate the `view model` with the result of the calculation.
34
+ ```javascript
35
+ import Core from '@superhero/core'
311
36
 
312
- Apart from the `dispatch` method, this time, we also define an `onError` method. This is the method that will be called if an error is thrown in the `dispatch` method. The first parameter to the `onError` method is the error that was thrown in the `dispatch` method.
37
+ const core = new Core()
313
38
 
314
- #### `src/api/middleware/authentication.js`
39
+ // Add configuration paths
40
+ await core.add('./path/to/config.json')
315
41
 
316
- ```js
317
- const
318
- Dispatcher = require('@superhero/core/http/server/dispatcher'),
319
- Unauthorized = require('@superhero/core/http/server/dispatcher/error/unauthorized')
42
+ // Bootstrap core
43
+ await core.bootstrap()
320
44
 
321
- /**
322
- * @extends {@superhero/core/http/server/dispatcher}
323
- */
324
- class AuthenticationMiddleware extends Dispatcher
325
- {
326
- async dispatch(next)
327
- {
328
- const
329
- configuration = this.locator.locate('configuration'),
330
- apikey = this.request.headers['api-key']
331
-
332
- if(apikey === configuration.find('authentication.apikey'))
333
- {
334
- await next()
335
- }
336
- else
337
- {
338
- throw new Unauthorized('You are not authorized to access the requested resource')
339
- }
340
- }
341
- }
45
+ // Locate a service
46
+ const myService = core.locate('myService')
47
+ console.log(myService)
342
48
 
343
- module.exports = AuthenticationMiddleware
49
+ // Graceful shutdown
50
+ await core.destruct()
344
51
  ```
345
52
 
346
- This middleware is used for authentication. It's a simple implementation, one should look at using a more robust solution, involving an ACL, when creating a solution for production environment. This example serves a purpose to show a good use-case for when to apply a middleware, not how best practice regarding authentication is defined.
53
+ ### Clustering Example
347
54
 
348
- **OBS!** The post handling (after the `next` callback has been called) will be handled in reversed order.
55
+ ```javascript
56
+ import Core from '@superhero/core'
349
57
 
350
- ```
351
- Middleware
352
- ↓ ↑
353
- Middleware
354
- ↓ ↑
355
- Endpoint
356
- ```
58
+ const core = new Core()
357
59
 
358
- ### Calculator
60
+ // Cluster into 4 workers
61
+ await core.cluster(4)
359
62
 
360
- #### `src/calculator/calculation.js`
63
+ // Add configuration paths
64
+ await core.add('./path/to/config.js')
361
65
 
362
- ```js
363
- /**
364
- * @typedef {Object} CalculatorCalculationDto
365
- * @property {number} id
366
- * @property {string} type
367
- * @property {number} value
368
- */
369
- const dto =
370
- {
371
- 'id':
372
- {
373
- 'type' : 'integer',
374
- 'unsigned': true
375
- },
376
- 'type':
377
- {
378
- 'type': 'string',
379
- 'enum':
380
- [
381
- 'addition',
382
- 'subtraction'
383
- ]
384
- },
385
- 'value':
386
- {
387
- 'type': 'decimal'
388
- }
389
- }
66
+ // Bootstrap core
67
+ await core.bootstrap()
390
68
 
391
- module.exports = dto
69
+ // Destruct core when done
70
+ await core.destruct()
392
71
  ```
393
72
 
394
- Defining a JSON schema for a dto; calculation. It's a good praxis to also define the type in "jsdoc", as seen above.
395
-
396
- A table over validation and filtration rules follows...
397
-
398
- | | boolean | decimal | integer | json | schema | string | timestamp |
399
- |-------------|-----------|-----------|-----------|-----------|-----------|-----------|-----------|
400
- | default | * | * | * | * | * | * | * |
401
- | collection | boolean | boolean | boolean | boolean | boolean | boolean | boolean |
402
- | optional | boolean | boolean | boolean | boolean | boolean | boolean | boolean |
403
- | nullable | boolean | boolean | boolean | boolean | boolean | boolean | boolean |
404
- | unsigned | - | boolean | boolean | - | - | - | - |
405
- | min | - | number | number | - | - | number | timestamp |
406
- | max | - | number | number | - | - | number | timestamp |
407
- | gt | - | number | number | - | - | number | timestamp |
408
- | lt | - | number | number | - | - | number | timestamp |
409
- | enum | array | array | array | - | - | array | array |
410
- | uppercase | - | - | - | - | - | boolean | - |
411
- | lowercase | - | - | - | - | - | boolean | - |
412
- | not-empty | - | - | - | - | - | boolean | - |
413
- | stringified | - | - | - | boolean | - | - | - |
414
- | indentation | - | - | - | number | - | - | - |
415
- | schema | - | - | - | - | string | - | - |
416
-
417
- #### `src/calculator/config.js`
418
-
419
- ```js
420
- module.exports =
421
- {
422
- composer:
423
- {
424
- schema:
425
- {
426
- 'calculation' : __dirname + '/calculation'
427
- }
428
- },
429
- locator:
430
- {
431
- 'calculator' : __dirname
432
- }
433
- }
434
- ```
435
-
436
- In the `calculator config` we define where a path to a module is located, and where the locator can find a constituent locator to locate the service through.
73
+ ---
437
74
 
438
- #### `src/calculator/index.js`
75
+ ## Configuration
439
76
 
440
- ```js
441
- const
442
- CalculationCouldNotBeFoundError = require('./error/calculation-could-not-be-found'),
443
- InvalidCalculationTypeError = require('./error/invalid-calculation-type')
77
+ The core reads configurations from JSON files and supports environment-specific overrides. Example structure:
444
78
 
445
- /**
446
- * Calculator service, manages calculations
447
- */
448
- class Calculator
79
+ `config.json`:
80
+ ```json
449
81
  {
450
- /**
451
- * @param {@superhero/eventbus} eventbus
452
- */
453
- constructor(eventbus)
454
- {
455
- this.eventbus = eventbus
456
- this.calculations = []
457
- }
458
-
459
- /**
460
- * @returns {number} the id of the created calculation
461
- */
462
- createCalculation()
463
- {
464
- const id = this.calculations.push(0)
465
- this.eventbus.emit('calculator.calculation-created', { id })
466
- return id
467
- }
468
-
469
- /**
470
- * @throws {E_CALCULATION_COULD_NOT_BE_FOUND}
471
- * @throws {E_INVALID_CALCULATION_TYPE}
472
- *
473
- * @param {CalculatorCalculation} dto
474
- *
475
- * @returns {number} the result of the calculation
476
- */
477
- appendToCalculation({ id, type, value })
478
- {
479
- if(id < 1
480
- || id > this.calculations.length)
481
- {
482
- throw new CalculationCouldNotBeFoundError(`Id out of range: "${id}/${this.calculations.length}"`)
483
- }
484
-
485
- switch(type)
486
- {
487
- case 'addition':
488
- {
489
- const result = this.calculations[id - 1] += value
490
- this.eventbus.emit('calculator.calculation-appended', { id, type, result })
491
- return result
492
- }
493
- case 'subtraction':
494
- {
495
- const result = this.calculations[id - 1] -= value
496
- this.eventbus.emit('calculator.calculation-appended', { id, type, result })
497
- return result
498
- }
499
- default:
500
- {
501
- throw new InvalidCalculationTypeError(`Unrecognized type used for calculation: "${type}"`)
502
- }
503
- }
504
- }
82
+ "bootstrap": { "myService": true },
83
+ "locator": { "myService": "./path/to/myService.js" }
505
84
  }
506
-
507
- module.exports = Calculator
508
85
  ```
509
86
 
510
- The calculator service is here defined. Good practice dictate that we should define isolated errors for everything that can go wrong. On top of the service we require these errors to have access to the type and to be able to trow when needed.
511
-
512
- It's a simple service that creates a calculation and allows to append an additional calculation to an already created calculation.
513
-
514
- #### `src/calculator/locator.js`
515
-
516
- ```js
517
- const
518
- Calculator = require('.'),
519
- LocatorConstituent = require('@superhero/core/locator/constituent')
520
-
521
- /**
522
- * @extends {@superhero/core/locator/constituent}
523
- */
524
- class CalculatorLocator extends LocatorConstituent
87
+ `config-dev.json`:
88
+ ```json
525
89
  {
526
- /**
527
- * @returns {Calculator}
528
- */
529
- locate()
530
- {
531
- const eventbus = this.locator.locate('eventbus')
532
- return new Calculator(eventbus)
533
- }
90
+ "myService": { "debug": true }
534
91
  }
535
-
536
- module.exports = CalculatorLocator
537
92
  ```
538
93
 
539
- The locator is responsible for dependency injection related to the service it creates.
540
-
541
- #### `src/calculator/error/calculation-could-not-be-found.js`
542
-
543
- ```js
544
- /**
545
- * @extends {Error}
546
- */
547
- class CalculationCouldNotBeFoundError extends Error
548
- {
549
- constructor(...args)
550
- {
551
- super(...args)
552
- this.code = 'E_CALCULATION_COULD_NOT_BE_FOUND'
553
- }
554
- }
94
+ ---
555
95
 
556
- module.exports = CalculationCouldNotBeFoundError
557
- ```
96
+ ## Testing
558
97
 
559
- A specific error with a specific error code; specifying what specific type of error is thrown.
98
+ ### Prerequisites
560
99
 
561
- #### `src/calculator/error/invalid-calculation-type.js`
100
+ Ensure the required dependencies are installed:
562
101
 
563
- ```js
564
- /**
565
- * @extends {Error}
566
- */
567
- class InvalidCalculationTypeError extends Error
568
- {
569
- constructor(...args)
570
- {
571
- super(...args)
572
- this.code = 'E_INVALID_CALCULATION_TYPE'
573
- }
574
- }
575
-
576
- module.exports = InvalidCalculationTypeError
102
+ ```bash
103
+ npm install
577
104
  ```
578
105
 
579
- Another specific error...
580
-
581
- ### Logger
106
+ ### Run Tests
582
107
 
583
- #### `src/logger/config.js`
584
-
585
- ```js
586
- module.exports =
587
- {
588
- eventbus:
589
- {
590
- observers:
591
- {
592
- 'calculator.calculation-created' : [ 'logger' ],
593
- 'calculator.calculation-appended' : [ 'logger' ]
594
- }
595
- },
596
- locator:
597
- {
598
- 'logger' : __dirname
599
- }
600
- }
108
+ ```bash
109
+ npm test
601
110
  ```
602
111
 
603
- Attaching a logger observer to specific events. Notice that the `locator` describes where the service can be located from, then used in the `eventbus.observers` context.
112
+ ### Test Coverage
604
113
 
605
- #### `src/logger/index.js`
606
-
607
- ```js
608
- /**
609
- * @implements {@superhero/eventbus/observer}
610
- */
611
- class Logger
612
- {
613
- constructor(eventbus)
614
- {
615
- this.eventbus = eventbus
616
- }
617
-
618
- observe(event)
619
- {
620
- this.eventbus.emit('logger.logged-event', event)
621
- }
622
- }
623
-
624
- module.exports = Logger
625
114
  ```
115
+ ▶ @superhero/core
116
+ ✔ Plain core bootstrap (2.792737ms)
117
+ ✔ Bootstraps a service successfully (9.447829ms)
118
+ ✔ Bootstraps a service with a branch variable (4.01927ms)
119
+ ✔ Can cluster the core and bootstrap a service (479.701512ms)
120
+ ✔ @superhero/core (511.980508ms)
626
121
 
627
- The logger observer simply implements an interface to be recognized as an observer by the `eventbus`.
628
-
629
- After we have logged the event to the console, we emit an event broadcasting that we have done so. By doing so, we can hook up to this event in the future if we like. For instance, when you like to create a test for the method, we can listen to this event.
122
+ tests 4
123
+ pass 4
630
124
 
631
- #### `src/logger/locator.js`
632
-
633
- ```js
634
- const
635
- Logger = require('.'),
636
- LocatorConstituent = require('@superhero/core/locator/constituent')
637
-
638
- /**
639
- * @extends {@superhero/core/locator/constituent}
640
- */
641
- class LoggerLocator extends LocatorConstituent
642
- {
643
- /**
644
- * @returns {Logger}
645
- */
646
- locate()
647
- {
648
- const eventbus = this.locator.locate('eventbus')
649
- return new Logger(eventbus)
650
- }
651
- }
652
-
653
- module.exports = LoggerLocator
125
+ ---------------------------------------------------------------------------------------------------
126
+ file | line % | branch % | funcs % | uncovered lines
127
+ ---------------------------------------------------------------------------------------------------
128
+ index.js | 74.64 | 67.42 | 76.00 | 103-106 125-127 135-140 143-145 189-194 205-210 2…
129
+ index.test.js | 100.00 | 100.00 | 100.00 |
130
+ worker.js | 100.00 | 100.00 | 100.00 |
131
+ ---------------------------------------------------------------------------------------------------
132
+ all files | 77.97 | 70.41 | 78.95 |
133
+ ---------------------------------------------------------------------------------------------------
654
134
  ```
655
135
 
656
- The logger locator creates the logger for the `service locator`.
657
-
658
- ### Test
659
-
660
- #### `test/mocha.opts`
136
+ ---
661
137
 
662
- ```
663
- --require test/init.js
664
- --ui bdd
665
- --full-trace
666
- --timeout 5000
667
- ```
138
+ ## API
668
139
 
669
- There will probably be a lot of [settings you need to set for mocha](https://mochajs.org/api/mocha), sooner or later; just as well that we make it a praxis to define the options outside your `package.json` file.
140
+ ### Core
670
141
 
671
- #### `test/init.js`
142
+ #### Constructor
672
143
 
673
- ```js
674
- require.main.filename = __dirname + '/../src/index.js'
675
- require.main.dirname = __dirname + '/../src'
144
+ ```javascript
145
+ new Core(branch: string = undefined)
676
146
  ```
147
+ - `branch`: The configuration branch to load additional configurations (e.g., `dev` for `config-dev.json`).
677
148
 
678
- The init script must set some variables for the core to function as expected in testing.
679
-
680
- The port is by design set through the environment variable `HTTP_PORT`. While testing we can set the variable to what ever we like, and what ever is suitable for the local machine we are on.
681
-
682
- #### `test/test.calculations.js`
149
+ #### Methods
683
150
 
684
- ```js
685
- describe('Calculations', () =>
686
- {
687
- const
688
- expect = require('chai').expect,
689
- context = require('mochawesome/addContext')
690
-
691
- let core
692
-
693
- before((done) =>
694
- {
695
- const
696
- CoreFactory = require('@superhero/core/factory'),
697
- coreFactory = new CoreFactory
698
-
699
- core = coreFactory.create()
700
-
701
- core.add('api')
702
- core.add('calculator')
703
- core.add('logger')
704
- core.add('http/server')
705
-
706
- core.load()
707
-
708
- core.locate('bootstrap').bootstrap().then(() =>
709
- {
710
- core.locate('http/server').listen(9001)
711
- core.locate('http/server').onListening(done)
712
- })
713
- })
714
-
715
- after(() =>
716
- {
717
- core.locate('http/server').close()
718
- })
719
-
720
- it('A client can create a calculation', async function()
721
- {
722
- const configuration = core.locate('configuration')
723
- const httpRequest = core.locate('http/request')
724
- context(this, { title:'route', value:configuration.find('http.server.routes.create-calculation') })
725
- const response = await httpRequest.post('http://localhost:9001/calculations')
726
- expect(response.data.id).to.be.equal(1)
727
- })
728
-
729
- it('A client can append a calculation to the result of a former calculation if authentication Api-Key', async function()
730
- {
731
- const configuration = core.locate('configuration')
732
- const httpRequest = core.locate('http/request')
733
- context(this, { title:'route', value:configuration.find('http.server.routes.append-calculation') })
734
- const url = 'http://localhost:9001/calculations/1'
735
- const data = { id:1, type:'addition', value:100 }
736
- const response_unauthorized = await httpRequest.put({ url, data })
737
- expect(response_unauthorized.status).to.be.equal(401)
738
- const headers = { 'Api-Key':'ABC123456789' }
739
- const response_authorized = await httpRequest.put({ headers, url, data })
740
- expect(response_authorized.data.result).to.be.equal(data.value)
741
- })
742
- })
743
- ```
151
+ - **`add(configPaths: string | string[] | object): Promise<Core>`**
152
+ Add configuration paths to the core.
744
153
 
745
- #### `test/test.logger.js`
154
+ - **`bootstrap(freeze: boolean = true): Promise<Core>`**
155
+ Initialize the core and its dependencies.
746
156
 
747
- ```js
748
- describe('Logger', () =>
749
- {
750
- const
751
- expect = require('chai').expect,
752
- context = require('mochawesome/addContext')
753
-
754
- let core
755
-
756
- before((done) =>
757
- {
758
- const
759
- CoreFactory = require('@superhero/core/factory'),
760
- coreFactory = new CoreFactory
761
-
762
- core = coreFactory.create()
763
-
764
- core.add('api')
765
- core.add('calculator')
766
- core.add('logger')
767
- core.add('http/server')
768
-
769
- core.load()
770
-
771
- core.locate('bootstrap').bootstrap().then(done)
772
- })
773
-
774
- it('Events are logged to the console', function(done)
775
- {
776
- const configuration = core.locate('configuration')
777
- const eventbus = core.locate('eventbus')
778
- context(this, { title:'observers', value:configuration.find('http.eventbus.observers') })
779
- eventbus.once('logger.logged-event', () => done())
780
- eventbus.emit('calculator.calculation-created', 'test')
781
- })
782
- })
783
- ```
157
+ - **`cluster(forks: number, branch?: number, version?: number): Promise<number>`**
158
+ Start clustering with the specified number of workers.
784
159
 
785
- Finally I have designed a few simple tests that proves the pattern, and gives insight to the expected interface. Here I suggest using a [BDD](https://en.wikipedia.org/wiki/Behavior-driven_development) approach.
160
+ - **`destruct(): Promise<void>`**
161
+ Gracefully shutdown the core, its workers, and services.
786
162
 
787
- ### Npm scripts
163
+ #### Properties
788
164
 
789
- #### To run the application:
165
+ - **`basePath: string`**
166
+ The base path used to resolve file paths.
790
167
 
791
- ```
792
- npm install --production
793
- npm start
794
- ```
168
+ - **`branch: string`**
169
+ The branch used for environment-specific configurations.
795
170
 
796
- #### To test the application:
171
+ - **`workers: object`**
172
+ Access to clustered workers.
797
173
 
798
- ```
799
- npm install
800
- npm test
801
- ```
802
-
803
- #### For an auto-generated coverage report in html:
174
+ ---
804
175
 
805
- ```
806
- npm run docs-coverage
807
- ```
176
+ ## License
177
+ This project is licensed under the MIT License.
808
178
 
809
- #### For an auto-generated test report in html:
179
+ ---
810
180
 
811
- ```
812
- npm run docs-tests
813
- ```
181
+ ## Contributing
182
+ Feel free to submit issues or pull requests for improvements or additional features.