structured-fw 0.7.2

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 (129) hide show
  1. package/Config.ts +47 -0
  2. package/LICENSE +21 -0
  3. package/README.md +332 -0
  4. package/app/Types.ts +1 -0
  5. package/app/models/README.md +9 -0
  6. package/app/routes/README.md +19 -0
  7. package/app/views/README.md +1 -0
  8. package/app/views/layout.html +1 -0
  9. package/bin/structured +114 -0
  10. package/build/Config.d.ts +2 -0
  11. package/build/Config.js +31 -0
  12. package/build/app/Types.d.ts +1 -0
  13. package/build/app/Types.js +1 -0
  14. package/build/app/models/Users.d.ts +0 -0
  15. package/build/app/models/Users.js +1 -0
  16. package/build/app/routes/Auth.d.ts +0 -0
  17. package/build/app/routes/Auth.js +1 -0
  18. package/build/app/routes/Test.d.ts +2 -0
  19. package/build/app/routes/Test.js +101 -0
  20. package/build/app/routes/Todo.d.ts +0 -0
  21. package/build/app/routes/Todo.js +1 -0
  22. package/build/app/routes/Upload.d.ts +0 -0
  23. package/build/app/routes/Upload.js +1 -0
  24. package/build/app/routes/Validation.d.ts +2 -0
  25. package/build/app/routes/Validation.js +34 -0
  26. package/build/app/views/components/ClientImport/ClientImport.client.d.ts +2 -0
  27. package/build/app/views/components/ClientImport/ClientImport.client.js +4 -0
  28. package/build/app/views/components/ClientImport/Export.d.ts +1 -0
  29. package/build/app/views/components/ClientImport/Export.js +1 -0
  30. package/build/app/views/components/Conditionals/Conditionals.client.d.ts +2 -0
  31. package/build/app/views/components/Conditionals/Conditionals.client.js +43 -0
  32. package/build/app/views/components/FormTest/FormTestNested/FormTestNested.d.ts +8 -0
  33. package/build/app/views/components/FormTest/FormTestNested/FormTestNested.js +7 -0
  34. package/build/app/views/components/ModelsTest/ModelsTest.client.d.ts +2 -0
  35. package/build/app/views/components/ModelsTest/ModelsTest.client.js +5 -0
  36. package/build/app/views/components/MultipartForm/MultipartForm.client.d.ts +0 -0
  37. package/build/app/views/components/MultipartForm/MultipartForm.client.js +1 -0
  38. package/build/app/views/components/PassObject/PassObject.d.ts +10 -0
  39. package/build/app/views/components/PassObject/PassObject.js +10 -0
  40. package/build/app/views/components/PassObject/ReceiveObj/ReceiveObj.d.ts +6 -0
  41. package/build/app/views/components/PassObject/ReceiveObj/ReceiveObj.js +6 -0
  42. package/build/app/views/components/RedrawAbort/RedrawAbort.client.d.ts +2 -0
  43. package/build/app/views/components/RedrawAbort/RedrawAbort.client.js +6 -0
  44. package/build/app/views/components/RedrawAbort/RedrawAbort.d.ts +8 -0
  45. package/build/app/views/components/RedrawAbort/RedrawAbort.js +8 -0
  46. package/build/app/views/components/ServerSideContext/ServerSideContext.d.ts +7 -0
  47. package/build/app/views/components/ServerSideContext/ServerSideContext.js +10 -0
  48. package/build/assets/ts/Export.d.ts +1 -0
  49. package/build/assets/ts/Export.js +1 -0
  50. package/build/index.d.ts +1 -0
  51. package/build/index.js +3 -0
  52. package/build/system/Helpers.d.ts +3 -0
  53. package/build/system/Helpers.js +72 -0
  54. package/build/system/Symbols.d.ts +3 -0
  55. package/build/system/Symbols.js +3 -0
  56. package/build/system/Types.d.ts +171 -0
  57. package/build/system/Types.js +1 -0
  58. package/build/system/Util.d.ts +20 -0
  59. package/build/system/Util.js +336 -0
  60. package/build/system/client/App.d.ts +7 -0
  61. package/build/system/client/App.js +8 -0
  62. package/build/system/client/Client.d.ts +6 -0
  63. package/build/system/client/Client.js +9 -0
  64. package/build/system/client/ClientComponent.d.ts +68 -0
  65. package/build/system/client/ClientComponent.js +734 -0
  66. package/build/system/client/DataStore.d.ts +22 -0
  67. package/build/system/client/DataStore.js +64 -0
  68. package/build/system/client/DataStoreView.d.ts +19 -0
  69. package/build/system/client/DataStoreView.js +56 -0
  70. package/build/system/client/EventEmitter.d.ts +7 -0
  71. package/build/system/client/EventEmitter.js +31 -0
  72. package/build/system/client/Net.d.ts +13 -0
  73. package/build/system/client/Net.js +39 -0
  74. package/build/system/client/NetRequest.d.ts +13 -0
  75. package/build/system/client/NetRequest.js +45 -0
  76. package/build/system/server/Application.d.ts +31 -0
  77. package/build/system/server/Application.js +171 -0
  78. package/build/system/server/Component.d.ts +27 -0
  79. package/build/system/server/Component.js +249 -0
  80. package/build/system/server/Components.d.ts +12 -0
  81. package/build/system/server/Components.js +77 -0
  82. package/build/system/server/Cookies.d.ts +6 -0
  83. package/build/system/server/Cookies.js +19 -0
  84. package/build/system/server/Document.d.ts +24 -0
  85. package/build/system/server/Document.js +107 -0
  86. package/build/system/server/DocumentHead.d.ts +32 -0
  87. package/build/system/server/DocumentHead.js +118 -0
  88. package/build/system/server/FormValidation.d.ts +16 -0
  89. package/build/system/server/FormValidation.js +197 -0
  90. package/build/system/server/Handlebars.d.ts +11 -0
  91. package/build/system/server/Handlebars.js +34 -0
  92. package/build/system/server/Request.d.ts +21 -0
  93. package/build/system/server/Request.js +356 -0
  94. package/build/system/server/Session.d.ts +23 -0
  95. package/build/system/server/Session.js +114 -0
  96. package/build/system/server/dom/DOMFragment.d.ts +4 -0
  97. package/build/system/server/dom/DOMFragment.js +6 -0
  98. package/build/system/server/dom/DOMNode.d.ts +31 -0
  99. package/build/system/server/dom/DOMNode.js +110 -0
  100. package/build/system/server/dom/HTMLParser.d.ts +21 -0
  101. package/build/system/server/dom/HTMLParser.js +204 -0
  102. package/index.ts +4 -0
  103. package/package.json +31 -0
  104. package/system/Helpers.ts +97 -0
  105. package/system/Symbols.ts +6 -0
  106. package/system/Types.ts +234 -0
  107. package/system/Util.ts +488 -0
  108. package/system/client/App.ts +11 -0
  109. package/system/client/Client.ts +9 -0
  110. package/system/client/ClientComponent.ts +1117 -0
  111. package/system/client/DataStore.ts +101 -0
  112. package/system/client/DataStoreView.ts +82 -0
  113. package/system/client/EventEmitter.ts +38 -0
  114. package/system/client/Net.ts +58 -0
  115. package/system/client/NetRequest.ts +64 -0
  116. package/system/server/Application.ts +230 -0
  117. package/system/server/Component.ts +404 -0
  118. package/system/server/Components.ts +111 -0
  119. package/system/server/Cookies.ts +29 -0
  120. package/system/server/Document.ts +163 -0
  121. package/system/server/DocumentHead.ts +150 -0
  122. package/system/server/FormValidation.ts +231 -0
  123. package/system/server/Handlebars.ts +51 -0
  124. package/system/server/Request.ts +497 -0
  125. package/system/server/Session.ts +151 -0
  126. package/system/server/dom/DOMFragment.ts +7 -0
  127. package/system/server/dom/DOMNode.ts +140 -0
  128. package/system/server/dom/HTMLParser.ts +238 -0
  129. package/tsconfig.json +35 -0
@@ -0,0 +1,101 @@
1
+ import { AsteriskAny, StoreChangeCallback } from '../Types.js';
2
+ import { equalDeep } from '../Util.js';
3
+ import { ClientComponent } from './ClientComponent.js';
4
+
5
+
6
+ export class DataStore {
7
+
8
+ protected data: {
9
+ [componentId: string]: {
10
+ [key: string]: any;
11
+ };
12
+ } = {};
13
+
14
+ protected changeListeners: {
15
+ [componentId: string]: {
16
+ [key: string]: Array<StoreChangeCallback>;
17
+ };
18
+ } = {};
19
+
20
+ // return self to allow chained calls to set
21
+ public set(component: ClientComponent, key: string, val: any, force: boolean = false, triggerListeners: boolean = true): DataStore {
22
+ const componentId = component.getData<string>('componentId');
23
+
24
+ const oldValue = this.get(componentId, key);
25
+
26
+ if (! force && equalDeep({ value: oldValue }, { value: val })) {
27
+ return this;
28
+ }
29
+
30
+ if (!this.data[componentId]) {
31
+ this.data[componentId] = {};
32
+ }
33
+
34
+ this.data[componentId][key] = val;
35
+
36
+ if (triggerListeners) {
37
+ if (this.changeListeners[componentId] && (this.changeListeners[componentId][key] || this.changeListeners[componentId]['*'])) {
38
+ // there are change listeners, call them
39
+ (this.changeListeners[componentId][key] || []).concat(this.changeListeners[componentId]['*'] || []).forEach((cb) => {
40
+ cb.apply(component, [key, val, oldValue, componentId]);
41
+ });
42
+ }
43
+ }
44
+
45
+ return this;
46
+ }
47
+
48
+ public get(componentId: string, key?: string): any {
49
+ if (!this.data[componentId]) {
50
+ return undefined;
51
+ }
52
+ if (typeof key !== 'string') {
53
+ return this.data[componentId];
54
+ }
55
+ return this.data[componentId][key];
56
+ }
57
+
58
+ public hasKey(componentId: string, key: string): boolean {
59
+ if (! (componentId in this.data)) {
60
+ return false;
61
+ }
62
+
63
+ return key in this.data[componentId];
64
+ }
65
+
66
+ // clear data for given componentId
67
+ public clear(componentId: string): void {
68
+ this.data[componentId] = {};
69
+ }
70
+
71
+ // clear data and unbind onChange listeners for given componentId
72
+ public destroy(componentId: string): void {
73
+ this.unbindAll(componentId);
74
+ this.clear(componentId);
75
+ }
76
+
77
+ // add callback to be called when a given key's value is changed
78
+ // if key === '*' then it will be called when any of the key's values is changed
79
+ public onChange(componentId: string, key: string | AsteriskAny, callback: StoreChangeCallback): DataStore {
80
+ if (! (componentId in this.changeListeners)) {
81
+ this.changeListeners[componentId] = {};
82
+ }
83
+ if (! (key in this.changeListeners[componentId])) {
84
+ this.changeListeners[componentId][key] = [];
85
+ }
86
+
87
+ this.changeListeners[componentId][key].push(callback);
88
+
89
+ return this;
90
+ }
91
+
92
+ // return all on change callbacks for given component
93
+ public onChangeCallbacks(componentId: string): Record<string, Array<StoreChangeCallback>> {
94
+ return this.changeListeners[componentId];
95
+ }
96
+
97
+ // unbind all onChange listeners for given component id
98
+ private unbindAll(componentId: string): void {
99
+ delete this.changeListeners[componentId];
100
+ }
101
+ }
@@ -0,0 +1,82 @@
1
+ import { AsteriskAny, StoreChangeCallback } from '../Types.js';
2
+ import { ClientComponent } from './ClientComponent.js';
3
+ import { DataStore } from './DataStore.js';
4
+
5
+ // Simplifies the use of data store
6
+ // it is initialized with component ID and global store so that from component
7
+ // one can set/get a value without having to pass in a component id
8
+
9
+ export class DataStoreView {
10
+
11
+ private store: DataStore;
12
+ private component: ClientComponent;
13
+ private destroyed = false;
14
+
15
+ constructor(store: DataStore, component: ClientComponent) {
16
+ this.store = store;
17
+ this.component = component;
18
+ }
19
+
20
+ public set(key: string, val: any, force: boolean = false, triggerListeners: boolean = true): DataStoreView {
21
+ if (! this.destroyed) {
22
+ this.store.set(this.component, key, val, force, triggerListeners);
23
+ }
24
+ return this;
25
+ }
26
+
27
+ public get<T>(key: string): T|undefined {
28
+ if (this.destroyed) {return undefined;}
29
+ return this.store.get(this.componentId(), key);
30
+ }
31
+
32
+ public toggle(key: string): void {
33
+ this.set(key, !this.get(key));
34
+ }
35
+
36
+ public keys(): Array<string> {
37
+ if (this.destroyed) {return [];}
38
+ return Object.keys(this.store.get(this.componentId()));
39
+ }
40
+
41
+ // import this.component.data to store
42
+ // existing keys are skipped unless force = true
43
+ public import(fields?: Array<string>, force: boolean = false, triggerListeners: boolean = true): void {
44
+ const fieldsImported = Array.isArray(fields) ? fields : Object.keys(this.component.getData());
45
+ fieldsImported.forEach((field) => {
46
+ // skip existing key, unless force = true
47
+ if (force || ! this.store.hasKey(this.componentId(), field)) {
48
+ this.set(field, this.component.getData(field), force, triggerListeners);
49
+ }
50
+ });
51
+ }
52
+
53
+ // clear data for owner component
54
+ public clear(): void {
55
+ this.store.clear(this.componentId());
56
+ }
57
+
58
+ // clear data and unbind onChange listeners for owner component
59
+ // mark this instance as destroyed so it no longer accepts any input
60
+ public destroy(): void {
61
+ this.store.destroy(this.componentId());
62
+ this.destroyed = true;
63
+ }
64
+
65
+ // return owner component id
66
+ private componentId(): string {
67
+ return this.component.getData<string>('componentId');
68
+ }
69
+
70
+ // add callback to be called when a given key's value is changed
71
+ // if key === '*' then it will be called when any of the key's values is changed
72
+ public onChange(key: string | AsteriskAny, callback: StoreChangeCallback): DataStoreView {
73
+ if (this.destroyed) {return this;}
74
+ this.store.onChange(this.componentId(), key, callback);
75
+ return this;
76
+ }
77
+
78
+ // return all onChange listeners for the owner component
79
+ public onChangeCallbacks(): Record<string, Array<StoreChangeCallback>> {
80
+ return this.store.onChangeCallbacks(this.componentId());
81
+ }
82
+ }
@@ -0,0 +1,38 @@
1
+ import { EventEmitterCallback } from "../Types.js";
2
+
3
+ export class EventEmitter {
4
+ protected listeners: Record<string, Array<EventEmitterCallback>> = {}
5
+
6
+ // add event listener
7
+ public on(eventName: string, callback: EventEmitterCallback): void {
8
+ if (! Array.isArray(this.listeners[eventName])) {
9
+ this.listeners[eventName] = [];
10
+ }
11
+
12
+ this.listeners[eventName].push(callback);
13
+ }
14
+
15
+ // emit event with given payload
16
+ public emit(eventName: string, payload?: any): void {
17
+ if (Array.isArray(this.listeners[eventName])) {
18
+ this.listeners[eventName].forEach((callback) => {
19
+ callback(payload);
20
+ });
21
+ }
22
+ }
23
+
24
+ // remove event listener
25
+ public unbind(eventName: string, callback: EventEmitterCallback): void {
26
+ if (Array.isArray(this.listeners[eventName])) {
27
+ while (true) {
28
+ const index = this.listeners[eventName].indexOf(callback);
29
+ if (index > -1) {
30
+ this.listeners[eventName].splice(index, 1);
31
+ } else {
32
+ // callback not found, all removed
33
+ break;
34
+ }
35
+ }
36
+ }
37
+ }
38
+ }
@@ -0,0 +1,58 @@
1
+ import { IncomingHttpHeaders } from 'node:http';
2
+ import { LooseObject, RequestMethod } from '../Types.js';
3
+ import { NetRequest } from './NetRequest.js';
4
+
5
+
6
+ export class Net {
7
+
8
+ // Make a HTTP request
9
+ public async request(method: RequestMethod, url: string, headers: IncomingHttpHeaders = {}, body?: any, responseType: XMLHttpRequestResponseType = 'text'): Promise<string> {
10
+ const request = new NetRequest(method, url, headers, responseType);
11
+ return request.send(body);
12
+ }
13
+
14
+ public async get(url: string, headers: IncomingHttpHeaders = {}): Promise<string> {
15
+ return this.request('GET', url, headers);
16
+ }
17
+
18
+ public async delete(url: string, headers: IncomingHttpHeaders = {}): Promise<string> {
19
+ return this.request('DELETE', url, headers);
20
+ }
21
+
22
+ public async post(url: string, data: any, headers: IncomingHttpHeaders = {}): Promise<string> {
23
+ if (typeof data === 'object' && !headers['content-type'] && !(data instanceof FormData)) {
24
+ // if data is object and no content/type header is specified default to application/json
25
+ headers['content-type'] = 'application/json';
26
+ // convert data to JSON
27
+ data = JSON.stringify(data);
28
+ }
29
+ return await this.request('POST', url, headers, data);
30
+ }
31
+
32
+ public async put(url: string, data: any, headers: IncomingHttpHeaders = {}): Promise<string> {
33
+ if (typeof data === 'object' && !headers['content-type']) {
34
+ // if data is object and no content/type header is specified default to application/json
35
+ headers['content-type'] = 'application/json';
36
+ // convert data to JSON
37
+ data = JSON.stringify(data);
38
+ }
39
+ return this.request('PUT', url, headers, data);
40
+ }
41
+
42
+ public async getJSON<T>(url: string, headers?: IncomingHttpHeaders): Promise<T> {
43
+ return JSON.parse(await this.get(url, headers));
44
+ }
45
+
46
+ public async postJSON<T>(url: string, data: LooseObject, headers?: IncomingHttpHeaders): Promise<T> {
47
+ return JSON.parse(await this.post(url, data, headers));
48
+ }
49
+
50
+ public async deleteJSON<T>(url: string, headers?: IncomingHttpHeaders): Promise<T> {
51
+ return JSON.parse(await this.delete(url, headers));
52
+ }
53
+
54
+ public async putJSON<T>(url: string, data: LooseObject, headers?: IncomingHttpHeaders): Promise<T> {
55
+ return JSON.parse(await this.put(url, data, headers));
56
+ }
57
+
58
+ }
@@ -0,0 +1,64 @@
1
+ import { IncomingHttpHeaders } from 'node:http';
2
+ import { RequestMethod } from '../Types.js';
3
+
4
+
5
+ export class NetRequest {
6
+ xhr: XMLHttpRequest = new XMLHttpRequest();
7
+ method: RequestMethod;
8
+ url: string;
9
+ headers: IncomingHttpHeaders;
10
+ responseType: XMLHttpRequestResponseType;
11
+ body: any;
12
+ requestSent: boolean = false;
13
+
14
+ constructor(method: RequestMethod, url: string, headers: IncomingHttpHeaders = {}, responseType: XMLHttpRequestResponseType = 'text', body?: any) {
15
+ this.method = method;
16
+ this.url = url;
17
+ this.headers = headers;
18
+ this.responseType = responseType;
19
+ this.body = body;
20
+
21
+ this.xhr.open(this.method, this.url);
22
+ this.xhr.responseType = this.responseType;
23
+
24
+
25
+ // set the X-Requested-With: xmlhttprequest header if not set by user
26
+ if (!('x-requested-with' in headers)) {
27
+ headers['x-requested-with'] = 'xmlhttprequest';
28
+ }
29
+
30
+ // set request headers
31
+ for (const header in headers) {
32
+ const headerValue = headers[header];
33
+ if (typeof headerValue === 'string') {
34
+ this.xhr.setRequestHeader(header, headerValue);
35
+ } else {
36
+ console.warn('Only string header values are supported');
37
+ }
38
+ }
39
+ }
40
+
41
+ public async send(body?: any): Promise<string> {
42
+ if (this.requestSent) { return ''; }
43
+ this.requestSent = true;
44
+ if (typeof body !== 'undefined') {
45
+ this.body = body;
46
+ }
47
+
48
+ return new Promise((resolve, reject) => {
49
+ // listen for state change
50
+ this.xhr.onreadystatechange = () => {
51
+ if (this.xhr.readyState == 4) {
52
+ // got the response
53
+ resolve(this.xhr.responseText);
54
+ }
55
+ };
56
+ // reject on error
57
+ this.xhr.onerror = (err) => {
58
+ reject(err);
59
+ };
60
+
61
+ this.xhr.send(body);
62
+ });
63
+ }
64
+ }
@@ -0,0 +1,230 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import { createServer, Server } from 'node:http';
4
+ import * as path from 'node:path';
5
+ import * as mime from 'mime-types';
6
+ import { ApplicationEvents, LooseObject, RequestBodyArguments, RequestCallback, RequestContext, StructuredConfig } from '../Types';
7
+ import { Document } from './Document.js';
8
+ import { Components } from './Components.js';
9
+ import { Session } from './Session.js';
10
+ import { toSnakeCase } from '../Util.js';
11
+ import { Request } from './Request.js';
12
+ import { Handlebars } from './Handlebars.js';
13
+ import { Cookies } from './Cookies.js';
14
+ import { RequestContextData } from '../../app/Types.js';
15
+
16
+ export class Application {
17
+ readonly config: StructuredConfig;
18
+
19
+ server: null|Server = null;
20
+ listening: boolean = false;
21
+
22
+ private readonly eventEmitter: EventEmitter = new EventEmitter();
23
+
24
+ readonly cookies: Cookies;
25
+ readonly session: Session;
26
+ readonly request: Request;
27
+ readonly components: Components;
28
+
29
+ // handlebars helpers manager
30
+ readonly handlebars: Handlebars = new Handlebars();
31
+
32
+ // fields from RequestContext.data to be exported for all components
33
+ readonly exportedRequestContextData: Array<keyof RequestContextData> = [];
34
+
35
+ constructor(config: StructuredConfig) {
36
+ this.config = config;
37
+
38
+ this.cookies = new Cookies();
39
+ this.session = new Session(this);
40
+ this.request = new Request(this);
41
+ this.components = new Components(this);
42
+
43
+ // enable sessions
44
+ this.session.start();
45
+
46
+ if (this.config.autoInit) {
47
+ this.init();
48
+ }
49
+ }
50
+
51
+ public async init(): Promise<void> {
52
+
53
+ // max listeners per event
54
+ this.eventEmitter.setMaxListeners(10);
55
+
56
+ // load handlebars helpers
57
+ try {
58
+ await this.handlebars.loadHelpers('../Helpers.js');
59
+ } catch(e) {
60
+ console.error(e.message);
61
+ }
62
+
63
+ await this.emit('beforeComponentLoad');
64
+ this.components.loadComponents();
65
+ await this.emit('afterComponentLoad');
66
+
67
+
68
+ await this.emit('beforeRoutes');
69
+ await this.request.loadHandlers();
70
+ await this.emit('afterRoutes');
71
+
72
+ if (this.config.url.componentRender !== false) {
73
+ // special request handler, executed when ClientComponent.redraw is called
74
+ this.request.on('POST', `${this.config.url.componentRender}`, async (ctx) => {
75
+ const input = ctx.body as unknown as {
76
+ component: string,
77
+ attributes: RequestBodyArguments,
78
+ unwrap?: boolean
79
+ };
80
+
81
+ await this.respondWithComponent(ctx, input.component, input.attributes || undefined, typeof input.unwrap === 'boolean' ? input.unwrap : true);
82
+ });
83
+ }
84
+
85
+ // special request handler, serve the client side JS
86
+ this.request.on('GET', /^\/assets\/client-js/, async ({ request, response }) => {
87
+ const uri = request.url?.substring(18) as string;
88
+ const filePath = path.resolve('./system/', uri);
89
+ if (existsSync(filePath)) {
90
+ response.setHeader('Content-Type', 'application/javascript');
91
+ response.write(readFileSync(filePath));
92
+ response.end();
93
+ } else {
94
+ response.statusCode = 404;
95
+ }
96
+ return;
97
+ }, this, true);
98
+
99
+ await this.start();
100
+ }
101
+
102
+ // start the http server
103
+ public start(): Promise<void> {
104
+ return new Promise((resolve, reject) => {
105
+ this.server = createServer((req, res) => {
106
+ this.request.handle(req, res);
107
+ });
108
+ this.server.listen(this.config.http.port, this.config.http.host || '127.0.0.1', async () => {
109
+ const address = (this.config.http.host !== undefined ? this.config.http.host : '') + ':' + this.config.http.port;
110
+ await this.emit('serverStarted');
111
+ console.log(`Server started on ${address}`);
112
+ resolve();
113
+ });
114
+ });
115
+ }
116
+
117
+ // add event listener
118
+ public on(evt: ApplicationEvents, callback: RequestCallback|((payload?: any) => void)): void {
119
+ this.eventEmitter.on(evt, callback);
120
+ }
121
+
122
+ // emit an event on Application
123
+ // this will run all event listeners attached to given eventName
124
+ // providing the payload as the first argument
125
+ // returns an array of all resolved values, any rejected promise values are discarded
126
+ public async emit(eventName: ApplicationEvents, payload?: any): Promise<Array<any>> {
127
+ const promises: Array<Promise<any>> = [];
128
+ const listeners = this.eventEmitter.rawListeners(eventName);
129
+ for (let i = 0; i < listeners.length; i++) {
130
+ promises.push(listeners[i](payload));
131
+ }
132
+ const results = await Promise.allSettled(promises);
133
+ return results.filter((res) => {
134
+ return res.status === 'fulfilled';
135
+ }).map((res) => {
136
+ return res.value;
137
+ });
138
+ }
139
+
140
+ // load envirnment variables
141
+ // if this.config.envPrefix is a string, load all ENV variables starting with [envPrefix]_
142
+ // the method is generic, so user can define the expected return type
143
+ public importEnv<T extends LooseObject>(smartPrimitives: boolean = true): T {
144
+ const values: LooseObject = {}
145
+ const usePrefix = typeof this.config.envPrefix === 'string';
146
+ const prefixLength = usePrefix ? this.config.envPrefix.length : 0;
147
+ for (const key in process.env) {
148
+ if (! usePrefix || key.startsWith(this.config.envPrefix)) {
149
+ // import
150
+ let value: any = process.env[key];
151
+ const keyWithoutPrefix = key.substring(prefixLength + 1);
152
+
153
+ if (smartPrimitives) {
154
+ if (value === 'undefined') {
155
+ value = undefined;
156
+ } else if (value === 'null') {
157
+ value = null;
158
+ } else if (value === 'true') {
159
+ value = true;
160
+ } else if (value === 'false') {
161
+ value = false;
162
+ } else if (/^-?\d+$/.test(value)) {
163
+ value = parseInt(value);
164
+ } else if (/^\d+\.\d+$/.test(value)) {
165
+ value = parseFloat(value);
166
+ }
167
+ }
168
+
169
+ values[keyWithoutPrefix] = value;
170
+ }
171
+ }
172
+ return values as T;
173
+ }
174
+
175
+ // export given fields to all components
176
+ public exportContextFields(...fields: Array<keyof RequestContextData>): void {
177
+ fields.forEach((field) => {
178
+ if (! this.exportedRequestContextData.includes(field)) {
179
+ this.exportedRequestContextData.push(field);
180
+ }
181
+ });
182
+ }
183
+
184
+ // given file extension (or file name), returns the appropriate content-type
185
+ public contentType(extension: string): string|false {
186
+ return mime.contentType(extension);
187
+ }
188
+
189
+ // renders a component with give data and sends it as a response
190
+ private async respondWithComponent(ctx: RequestContext, componentName: string, attributes: RequestBodyArguments, unwrap: boolean = true): Promise<boolean> {
191
+ const component = this.components.getByName(componentName);
192
+ if (component) {
193
+ const document = new Document(this, '', ctx);
194
+ const data: LooseObject = attributes;
195
+ await document.loadComponent(component.name, data);
196
+
197
+ const exportedData = component.exportData ? document.data : (component.exportFields ? component.exportFields.reduce((prev, curr) => {
198
+ prev[curr] = document.children[0].data[curr];
199
+ return prev;
200
+ }, {} as LooseObject) : {});
201
+
202
+ ctx.respondWith({
203
+ html: document.children[0].dom[unwrap ? 'innerHTML' : 'outerHTML'],
204
+ initializers: document.initInitializers(),
205
+ data: exportedData
206
+ });
207
+
208
+ return true;
209
+ }
210
+ return false;
211
+ }
212
+
213
+ public memoryUsage(): NodeJS.MemoryUsage {
214
+ return process.memoryUsage();
215
+ }
216
+
217
+ public printMemoryUsage(): void {
218
+ const usage = this.memoryUsage();
219
+ let total = 0;
220
+ const totals = Object.keys(usage).reduce((prev, key: keyof NodeJS.MemoryUsage) => {
221
+ const usedMb = usage[key] / 1000000;
222
+ prev[toSnakeCase(key).replaceAll('_', ' ')] = [parseFloat(usedMb.toFixed(1)), 'Mb'];
223
+ total += usedMb;
224
+ return prev;
225
+ }, {} as LooseObject);
226
+ totals.total = [parseFloat(total.toFixed(1)), 'Mb'];
227
+ console.table(totals);
228
+ }
229
+
230
+ }