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,249 @@
1
+ import { Document } from './Document.js';
2
+ import { attributeValueFromString, attributeValueToString, objectEach, toCamelCase } from '../Util.js';
3
+ import { DOMFragment } from './dom/DOMFragment.js';
4
+ export class Component {
5
+ constructor(name, node, parent, autoInit = true) {
6
+ this.children = [];
7
+ this.path = [];
8
+ this.attributesRaw = {};
9
+ this.attributes = {};
10
+ this.data = {};
11
+ this.name = name;
12
+ if (name === 'root') {
13
+ this.dom = new DOMFragment();
14
+ this.path.push('');
15
+ this.isRoot = true;
16
+ }
17
+ else {
18
+ this.dom = node || new DOMFragment();
19
+ if (parent) {
20
+ this.path = parent.path.concat(this.name);
21
+ }
22
+ this.isRoot = false;
23
+ }
24
+ if (this instanceof Document) {
25
+ this.document = this;
26
+ }
27
+ else {
28
+ if (!(parent instanceof Component)) {
29
+ console.error('Component initialized without a parent');
30
+ }
31
+ this.document = parent.document;
32
+ }
33
+ this.parent = parent || null;
34
+ this.id = '';
35
+ const component = parent === undefined ? false : this.document.application.components.getByName(this.name);
36
+ if (component) {
37
+ this.entry = component;
38
+ if (autoInit) {
39
+ this.init(component.html);
40
+ }
41
+ }
42
+ else {
43
+ this.entry = null;
44
+ }
45
+ }
46
+ async init(html, data) {
47
+ this.initAttributesData();
48
+ this.dom.tagName = this.entry?.renderTagName || 'div';
49
+ this.dom.innerHTML = html;
50
+ this.setAttributes(this.attributesRaw, '', false);
51
+ if (this.entry !== null && this.entry.initializer !== undefined && typeof this.document.initializers[this.name] === 'undefined') {
52
+ this.document.initializers[this.name] = this.entry.initializer;
53
+ }
54
+ this.dom.setAttribute(`data-${this.document.application.config.components.componentNameAttribute}`, this.name);
55
+ if (typeof this.attributes.componentId !== 'string') {
56
+ this.id = this.document.allocateId(this);
57
+ this.dom.setAttribute('data-component-id', this.id);
58
+ }
59
+ else {
60
+ this.id = this.attributes.componentId;
61
+ }
62
+ const exportedContextData = this.document.application.exportedRequestContextData.reduce((prev, field) => {
63
+ if (!this.document.ctx) {
64
+ return prev;
65
+ }
66
+ if (field in this.document.ctx.data) {
67
+ prev[field] = this.document.ctx.data[field];
68
+ }
69
+ return prev;
70
+ }, {});
71
+ objectEach(exportedContextData, (key, val) => {
72
+ if (this.document.application.exportedRequestContextData.includes(key)) {
73
+ this.setAttributes({ [key]: val }, 'data-', true);
74
+ }
75
+ });
76
+ this.data = exportedContextData;
77
+ if (this.entry !== null &&
78
+ typeof this.entry.module !== 'undefined' &&
79
+ typeof this.entry.module.deferred === 'function' &&
80
+ this.entry.module.deferred(this.attributes, this.document.ctx, this.document.application) &&
81
+ this.attributes.deferred !== false) {
82
+ this.setAttributes({ deferred: true }, 'data-', true);
83
+ return;
84
+ }
85
+ if (typeof this.attributes.use === 'string' && this.parent !== null) {
86
+ this.attributes = Object.assign(this.importedParentData(this.parent.data) || {}, this.attributes);
87
+ }
88
+ if (data === undefined) {
89
+ if (this.entry && this.entry.module) {
90
+ this.data = Object.assign(this.data, await this.entry.module.getData(this.attributes, this.document.ctx, this.document.application, this) || {});
91
+ }
92
+ else {
93
+ this.data = Object.assign(exportedContextData, this.attributes);
94
+ }
95
+ }
96
+ else {
97
+ this.data = Object.assign(exportedContextData, data, this.attributes);
98
+ }
99
+ this.fillData(this.data);
100
+ if (this.entry === null || this.entry.exportData) {
101
+ this.setAttributes(this.data, 'data-');
102
+ }
103
+ else if (this.entry) {
104
+ if (this.entry.exportFields) {
105
+ this.setAttributes(this.entry.exportFields.reduce((prev, field) => {
106
+ if (this.data[field] !== undefined) {
107
+ prev[field] = this.data[field];
108
+ }
109
+ return prev;
110
+ }, {}), 'data-');
111
+ }
112
+ if (this.entry.attributes) {
113
+ this.setAttributes(this.entry.attributes, '', false);
114
+ }
115
+ }
116
+ await this.initChildren();
117
+ if (this.isRoot) {
118
+ const dataIf = this.dom.queryByHasAttribute('data-if');
119
+ for (let i = 0; i < dataIf.length; i++) {
120
+ dataIf[i].style.display = 'none';
121
+ }
122
+ }
123
+ }
124
+ setAttributes(attributes, prefix = '', encode = true) {
125
+ if (typeof attributes === 'object' && attributes !== null) {
126
+ for (const attr in attributes) {
127
+ const encoded = typeof attributes[attr] === 'string' && attributes[attr].indexOf('base64:') === 0;
128
+ const value = (encode && !encoded) ? attributeValueToString(attr, attributes[attr]) : attributes[attr];
129
+ this.dom.setAttribute(prefix + attr, value);
130
+ }
131
+ }
132
+ }
133
+ async initChildren(passData) {
134
+ const componentTags = this.document.application.components.componentNames;
135
+ const childNodes = this.dom.queryByTagName(...componentTags);
136
+ for (let i = 0; i < childNodes.length; i++) {
137
+ const childNode = childNodes[i];
138
+ const component = this.document.application.components.getByName(childNode.tagName);
139
+ if (component) {
140
+ const child = new Component(component.name, childNode, this, false);
141
+ await child.init(childNode.outerHTML, passData);
142
+ this.children.push(child);
143
+ }
144
+ }
145
+ }
146
+ importedParentData(parentData) {
147
+ if (!this.parent) {
148
+ return {};
149
+ }
150
+ const data = {};
151
+ if (typeof this.attributes.use !== 'string') {
152
+ return data;
153
+ }
154
+ const usePaths = this.attributes.use.split(',').map((key) => {
155
+ return key.split(/\.|\[(\d+)\]/).filter((s) => { return s !== undefined && s.length > 0; }).map((s) => {
156
+ return /^\d+$/.test(s) ? parseInt(s) : s;
157
+ });
158
+ });
159
+ usePaths.forEach((dataPath) => {
160
+ let dataCurrent = parentData;
161
+ for (let i = 0; i < dataPath.length; i++) {
162
+ const segment = dataPath[i];
163
+ if (typeof dataCurrent[segment] === 'undefined') {
164
+ dataCurrent = undefined;
165
+ break;
166
+ }
167
+ dataCurrent = dataCurrent[segment];
168
+ }
169
+ const dataKey = dataPath[dataPath.length - 1];
170
+ data[dataKey] = dataCurrent;
171
+ });
172
+ if (usePaths.length == 1 && typeof usePaths[0][usePaths[0].length - 1] === 'number') {
173
+ return data[usePaths[0][usePaths[0].length - 1]];
174
+ }
175
+ return data;
176
+ }
177
+ initAttributesData(domNode) {
178
+ if (domNode === undefined) {
179
+ domNode = this.dom;
180
+ }
181
+ for (let i = 0; i < domNode.attributes.length; i++) {
182
+ const attrNameRaw = domNode.attributes[i].name;
183
+ const attrNameUnprefixed = this.attributeUnpreffixed(attrNameRaw);
184
+ if (attrNameUnprefixed.indexOf('data-') === 0) {
185
+ const attrDataType = this.attributeDataType(attrNameRaw);
186
+ const dataDecoded = attributeValueFromString(domNode.attributes[i].value.toString());
187
+ const valueEncoded = typeof dataDecoded === 'object';
188
+ let value = valueEncoded ? dataDecoded.value : dataDecoded;
189
+ const key = valueEncoded ? dataDecoded.key : toCamelCase(attrNameUnprefixed.substring(5));
190
+ if (!valueEncoded) {
191
+ if (typeof value === 'string') {
192
+ if (attrDataType === 'number') {
193
+ value = parseFloat(value);
194
+ }
195
+ else if (attrDataType === 'boolean') {
196
+ value = value === 'true' || value === '1';
197
+ }
198
+ else if (attrDataType === 'object') {
199
+ if (typeof value === 'string') {
200
+ if (value.trim().length > 1) {
201
+ value = JSON.parse(value);
202
+ }
203
+ else {
204
+ value = null;
205
+ }
206
+ }
207
+ }
208
+ }
209
+ const attrData = attributeValueToString(key, value);
210
+ domNode.setAttribute(attrNameRaw, attrData);
211
+ }
212
+ this.attributes[key] = value;
213
+ }
214
+ this.attributesRaw[attrNameRaw] = domNode.attributes[i].value;
215
+ }
216
+ }
217
+ attributePreffix(attrName) {
218
+ const index = attrName.indexOf(':');
219
+ if (index < 0) {
220
+ return null;
221
+ }
222
+ return attrName.substring(0, index);
223
+ }
224
+ attributeDataType(attrName) {
225
+ const prefix = this.attributePreffix(attrName);
226
+ if (prefix === 'string' ||
227
+ prefix === 'number' ||
228
+ prefix === 'object' ||
229
+ prefix === 'boolean') {
230
+ return prefix;
231
+ }
232
+ return 'any';
233
+ }
234
+ attributeUnpreffixed(attrName) {
235
+ const index = attrName.indexOf(':');
236
+ if (index < 0) {
237
+ return attrName;
238
+ }
239
+ return attrName.substring(index + 1);
240
+ }
241
+ fillData(data) {
242
+ if (this.entry && this.entry.static === true) {
243
+ this.dom.innerHTML = this.entry.html;
244
+ return;
245
+ }
246
+ const html = this.entry ? this.entry.html : this.dom.innerHTML;
247
+ this.dom.innerHTML = this.document.application.handlebars.compile(html, data);
248
+ }
249
+ }
@@ -0,0 +1,12 @@
1
+ import { ComponentEntry, StructuredConfig } from '../Types';
2
+ import { Application } from './Application.js';
3
+ export declare class Components {
4
+ config: StructuredConfig;
5
+ private readonly components;
6
+ componentNames: Array<string>;
7
+ constructor(app: Application);
8
+ loadComponents(relativeToPath?: string): void;
9
+ getByName(name: string): null | ComponentEntry;
10
+ private loadHTML;
11
+ private stripComments;
12
+ }
@@ -0,0 +1,77 @@
1
+ import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
2
+ import * as path from 'node:path';
3
+ export class Components {
4
+ constructor(app) {
5
+ this.components = {};
6
+ this.componentNames = [];
7
+ this.config = app.config;
8
+ }
9
+ loadComponents(relativeToPath) {
10
+ if (relativeToPath === undefined) {
11
+ relativeToPath = path.resolve((this.config.runtime === 'Node.js' ? '../' : './') + this.config.components.path);
12
+ }
13
+ const components = readdirSync(relativeToPath);
14
+ components.forEach(async (component) => {
15
+ const absolutePath = relativeToPath + '/' + component;
16
+ const isDirectory = statSync(absolutePath).isDirectory();
17
+ if (isDirectory) {
18
+ this.loadComponents(absolutePath);
19
+ }
20
+ else {
21
+ if (component.endsWith('.html') || component.endsWith('.hbs')) {
22
+ const componentNameParts = component.split('.');
23
+ const componentName = componentNameParts.slice(0, componentNameParts.length - 1).join('.');
24
+ const pathAbsolute = relativeToPath || '';
25
+ const pathRelative = path.relative(this.config.runtime === 'Node.js' ? '../' : './', pathAbsolute);
26
+ const pathBuild = path.resolve('./' + pathRelative);
27
+ const pathRelativeToViews = path.relative(`./${this.config.components.path}`, pathRelative);
28
+ const pathHTML = `${pathAbsolute}/${component}`;
29
+ const jsServerPath = `${pathBuild}/${componentName}.${this.config.runtime === 'Node.js' ? 'js' : 'ts'}`;
30
+ const hasServerJS = existsSync(jsServerPath);
31
+ const jsClientPath = `${pathBuild}/${componentName}.client.${this.config.runtime === 'Node.js' ? 'js' : 'ts'}`;
32
+ const hasClientJS = existsSync(jsClientPath);
33
+ const entry = {
34
+ name: componentName,
35
+ path: {
36
+ absolute: pathAbsolute,
37
+ relative: pathRelative,
38
+ relativeToViews: `${pathRelativeToViews}/${component}`,
39
+ build: pathBuild,
40
+ html: pathHTML,
41
+ jsClient: hasClientJS ? jsClientPath : undefined,
42
+ jsServer: hasServerJS ? jsServerPath : undefined
43
+ },
44
+ hasJS: existsSync(jsServerPath),
45
+ html: this.loadHTML(absolutePath),
46
+ exportData: false,
47
+ static: false
48
+ };
49
+ if (hasClientJS) {
50
+ const initializer = await import('file:///' + jsClientPath);
51
+ entry.initializer = initializer.init;
52
+ }
53
+ if (hasServerJS) {
54
+ const componentConstructor = await import('file:///' + entry.path.jsServer);
55
+ entry.module = new componentConstructor.default();
56
+ entry.renderTagName = entry.module?.tagName || 'div';
57
+ entry.exportData = typeof entry.module?.exportData === 'boolean' ? entry.module.exportData : false;
58
+ entry.exportFields = entry.module?.exportFields;
59
+ entry.attributes = entry.module?.attributes;
60
+ entry.static = typeof entry.module?.static === 'boolean' ? entry.module.static : false;
61
+ }
62
+ this.components[componentName.toUpperCase()] = entry;
63
+ this.componentNames.push(entry.name);
64
+ }
65
+ }
66
+ });
67
+ }
68
+ getByName(name) {
69
+ return this.components[name.toUpperCase()] || null;
70
+ }
71
+ loadHTML(path) {
72
+ return this.stripComments(readFileSync(path).toString());
73
+ }
74
+ stripComments(html) {
75
+ return html.replaceAll(/<!--(?!-?>)(?!.*--!>)(?!.*<!--(?!>)).*?(?<!<!-)-->/g, '');
76
+ }
77
+ }
@@ -0,0 +1,6 @@
1
+ import { IncomingMessage, ServerResponse } from "node:http";
2
+ import { LooseObject } from "../Types.js";
3
+ export declare class Cookies {
4
+ parse(request: IncomingMessage): LooseObject;
5
+ set(response: ServerResponse, name: string, value: string | number, lifetimeSeconds: number, path?: string, sameSite?: 'Strict' | 'Lax' | 'None', domain?: string): void;
6
+ }
@@ -0,0 +1,19 @@
1
+ export class Cookies {
2
+ parse(request) {
3
+ if (!request.headers.cookie) {
4
+ return {};
5
+ }
6
+ const cookieString = request.headers.cookie;
7
+ const cookiePairs = cookieString.split(';');
8
+ const cookies = {};
9
+ cookiePairs.forEach((cookiePair) => {
10
+ const parts = cookiePair.trim().split('=');
11
+ cookies[parts.shift() || ''] = parts.join('=');
12
+ });
13
+ return cookies;
14
+ }
15
+ set(response, name, value, lifetimeSeconds, path = '/', sameSite = 'Strict', domain) {
16
+ const expiresAt = lifetimeSeconds > 0 ? new Date(new Date().getTime() + lifetimeSeconds * 1000).toUTCString() : 0;
17
+ response.appendHeader('Set-Cookie', `${name}=${value}; Expires=${expiresAt}; Path=${path}; SameSite=${sameSite}${domain ? `; domain=${domain}` : ''}`);
18
+ }
19
+ }
@@ -0,0 +1,24 @@
1
+ import { ServerResponse } from 'node:http';
2
+ import { Initializers, LooseObject, RequestContext } from '../../system/Types.js';
3
+ import { Application } from './Application.js';
4
+ import { DocumentHead } from './DocumentHead.js';
5
+ import { Component } from './Component.js';
6
+ export declare class Document extends Component {
7
+ head: DocumentHead;
8
+ language: string;
9
+ application: Application;
10
+ initializers: Initializers;
11
+ initializersInitialized: boolean;
12
+ componentIds: Array<string>;
13
+ ctx: undefined | RequestContext;
14
+ appendHTML: string;
15
+ constructor(app: Application, title: string, ctx?: RequestContext);
16
+ push(response: ServerResponse): void;
17
+ body(): string;
18
+ initInitializers(): Record<string, string>;
19
+ private initClientConfig;
20
+ toString(): string;
21
+ allocateId(component: Component): string;
22
+ loadView(pathRelative: string, data?: LooseObject): Promise<boolean>;
23
+ loadComponent(componentName: string, data?: LooseObject): Promise<void>;
24
+ }
@@ -0,0 +1,107 @@
1
+ import { Md5 } from 'ts-md5';
2
+ import { DocumentHead } from './DocumentHead.js';
3
+ import { Component } from './Component.js';
4
+ import { attributeValueToString, randomString } from '../Util.js';
5
+ import path from 'node:path';
6
+ import { existsSync, readFileSync } from 'node:fs';
7
+ export class Document extends Component {
8
+ constructor(app, title, ctx) {
9
+ super('root');
10
+ this.language = 'en';
11
+ this.initializers = {};
12
+ this.initializersInitialized = false;
13
+ this.componentIds = [];
14
+ this.appendHTML = '';
15
+ this.application = app;
16
+ this.ctx = ctx;
17
+ this.document = this;
18
+ this.head = new DocumentHead(title);
19
+ this.head.addJS('/assets/client-js/client/Client.js', 0, { type: 'module' });
20
+ this.application.emit('documentCreated', this);
21
+ }
22
+ push(response) {
23
+ const resourcesJS = this.head.js.map((resource) => {
24
+ return `<${resource.path}>; rel=${this.application.config.http.linkHeaderRel}; as=script; crossorigin=anonymous`;
25
+ });
26
+ const resourcesCSS = this.head.css.map((resource) => {
27
+ return `<${resource.path}>; rel=${this.application.config.http.linkHeaderRel}; as=style; crossorigin=anonymous`;
28
+ });
29
+ const value = resourcesCSS.concat(resourcesJS).join(', ');
30
+ response.setHeader('Link', value);
31
+ }
32
+ body() {
33
+ return this.dom.innerHTML + '\n' + this.appendHTML;
34
+ }
35
+ initInitializers() {
36
+ const initializers = {};
37
+ for (const name in this.initializers) {
38
+ initializers[name] = this.initializers[name].toString();
39
+ }
40
+ const initializersString = '<script type="application/javascript">window.initializers = ' + JSON.stringify(initializers) + '</script>';
41
+ this.head.add(initializersString);
42
+ this.initializersInitialized = true;
43
+ return initializers;
44
+ }
45
+ initClientConfig() {
46
+ const clientConf = {
47
+ componentRender: this.application.config.url.componentRender,
48
+ componentNameAttribute: this.application.config.components.componentNameAttribute
49
+ };
50
+ const clientConfString = `<script type="application/javascript">window.structuredClientConfig = ${JSON.stringify(clientConf)}</script>`;
51
+ this.head.add(clientConfString);
52
+ }
53
+ toString() {
54
+ if (!this.initializersInitialized) {
55
+ this.initInitializers();
56
+ this.initClientConfig();
57
+ }
58
+ return `<!DOCTYPE html>
59
+ <html lang="${this.language}">
60
+ ${this.head.toString()}
61
+ <body>
62
+ ${this.body()}
63
+ </body>
64
+ </html>`;
65
+ }
66
+ allocateId(component) {
67
+ if (!this.componentIds) {
68
+ this.componentIds = [];
69
+ }
70
+ let id = Md5.hashStr(`${component.name}:${'id' in component.attributes ? component.attributes.id : `${component.path.join('/')}:${JSON.stringify(component.attributesRaw)}`}`);
71
+ if (this.componentIds.includes(id)) {
72
+ let current = component.parent;
73
+ do {
74
+ if (current === null || current.isRoot) {
75
+ console.error(`Could not define an unique ID for component ${component.name}, path: ${component.path}`);
76
+ id = randomString(16);
77
+ }
78
+ else {
79
+ id += '-' + Md5.hashStr(current.dom.outerHTML);
80
+ }
81
+ current = current?.parent || null;
82
+ } while (this.componentIds.includes(id));
83
+ }
84
+ this.componentIds.push(id);
85
+ return id;
86
+ }
87
+ async loadView(pathRelative, data) {
88
+ const viewPath = path.resolve('../' + this.application.config.components.path + '/' + pathRelative + (pathRelative.endsWith('.html') ? '' : '.html'));
89
+ if (!existsSync(viewPath)) {
90
+ console.warn(`Couldn't load document ${this.document.head.title}: ${viewPath}`);
91
+ return false;
92
+ }
93
+ const html = readFileSync(viewPath).toString();
94
+ await this.init(html, data);
95
+ return true;
96
+ }
97
+ async loadComponent(componentName, data) {
98
+ const componentEntry = this.document.application.components.getByName(componentName);
99
+ if (componentEntry) {
100
+ const dataString = data === undefined ? '' : Object.keys(data).reduce((prev, key) => {
101
+ prev.push(`data-${key}="${attributeValueToString(key, data[key])}"`);
102
+ return prev;
103
+ }, []).join(' ');
104
+ await this.init(`<${componentName} ${dataString}></${componentName}>`, data);
105
+ }
106
+ }
107
+ }
@@ -0,0 +1,32 @@
1
+ import { DocumentResource } from '../Types';
2
+ export declare class DocumentHead {
3
+ title: string;
4
+ js: Array<DocumentResource>;
5
+ css: Array<DocumentResource>;
6
+ custom: Array<string>;
7
+ charset: string;
8
+ favicon: {
9
+ image: string | null;
10
+ type: string;
11
+ };
12
+ constructor(title: string);
13
+ setTitle(title: string): void;
14
+ add(str: string): void;
15
+ remove(str: string): void;
16
+ addJS(path: string, priority?: number, attributes?: {
17
+ [attributeName: string]: string | null;
18
+ }): DocumentResource;
19
+ addCSS(path: string, priority?: number, attributes?: {
20
+ [attributeName: string]: string | null;
21
+ }): DocumentResource;
22
+ removeJS(path: string): void;
23
+ removeCSS(path: string): void;
24
+ private toResource;
25
+ private attributesString;
26
+ toString(): string;
27
+ setFavicon(faviconPath: string | {
28
+ image: string | null;
29
+ type: string;
30
+ }): void;
31
+ private faviconType;
32
+ }
@@ -0,0 +1,118 @@
1
+ export class DocumentHead {
2
+ constructor(title) {
3
+ this.js = [];
4
+ this.css = [];
5
+ this.custom = [];
6
+ this.charset = 'UTF-8';
7
+ this.favicon = {
8
+ image: null,
9
+ type: 'image/png'
10
+ };
11
+ this.title = title;
12
+ }
13
+ setTitle(title) {
14
+ this.title = title;
15
+ }
16
+ add(str) {
17
+ this.custom.push(str);
18
+ }
19
+ remove(str) {
20
+ this.custom = this.custom.filter((strExisting) => {
21
+ return strExisting !== str;
22
+ });
23
+ }
24
+ addJS(path, priority = 0, attributes = {}) {
25
+ const resource = this.toResource(path, priority, attributes);
26
+ this.js.push(resource);
27
+ return resource;
28
+ }
29
+ addCSS(path, priority = 0, attributes = {}) {
30
+ const resource = this.toResource(path, priority, attributes);
31
+ this.css.push(resource);
32
+ return resource;
33
+ }
34
+ removeJS(path) {
35
+ const index = this.js.findIndex((resource) => {
36
+ return resource.path == path;
37
+ });
38
+ this.js.splice(index, 1);
39
+ }
40
+ removeCSS(path) {
41
+ const index = this.css.findIndex((resource) => {
42
+ return resource.path == path;
43
+ });
44
+ this.css.splice(index, 1);
45
+ }
46
+ toResource(path, priority = 0, attributes = {}) {
47
+ return {
48
+ path,
49
+ priority,
50
+ attributes
51
+ };
52
+ }
53
+ attributesString(resource) {
54
+ let attributesString = '';
55
+ for (const attributeName in resource.attributes) {
56
+ const val = resource.attributes[attributeName];
57
+ if (val === null) {
58
+ attributesString += ` ${attributeName}`;
59
+ }
60
+ else {
61
+ attributesString += ` ${attributeName}="${val}"`;
62
+ }
63
+ }
64
+ return attributesString;
65
+ }
66
+ toString() {
67
+ const css = this.css.reduce((prev, curr) => {
68
+ return prev + '\n' + `<link rel="stylesheet" href="${curr.path}"${this.attributesString(curr)}>`;
69
+ }, '');
70
+ const js = this.js.reduce((prev, curr) => {
71
+ return prev + '\n' + `<script src="${curr.path}"${this.attributesString(curr)}></script>`;
72
+ }, '');
73
+ const custom = this.custom.reduce((prev, curr) => {
74
+ return prev + '\n' + curr;
75
+ }, '');
76
+ return `<head>
77
+ <meta charset="${this.charset}">
78
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
79
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
80
+ <title>${this.title}</title>
81
+ <link rel="icon" type="${this.favicon.type}" href="${this.favicon.image}">
82
+ ${css}
83
+ ${js}
84
+ ${custom}
85
+ </head>`;
86
+ }
87
+ setFavicon(faviconPath) {
88
+ if (typeof faviconPath === 'string') {
89
+ this.favicon = {
90
+ image: faviconPath,
91
+ type: this.faviconType(faviconPath)
92
+ };
93
+ return;
94
+ }
95
+ if (faviconPath.type === '') {
96
+ faviconPath.type = faviconPath.image ? this.faviconType(faviconPath.image) : 'image/png';
97
+ }
98
+ this.favicon = faviconPath;
99
+ }
100
+ faviconType(file) {
101
+ let ext = /\.([^.]+)$/.exec(file);
102
+ let type = 'image/png';
103
+ if (ext !== null) {
104
+ ext = ext[1].toLowerCase();
105
+ const types = {
106
+ 'png': 'image/png',
107
+ 'jpg': 'image/jpeg',
108
+ 'jpeg': 'image/jpeg',
109
+ 'gif': 'image/gif',
110
+ 'ico': 'image/x-icon'
111
+ };
112
+ if (types[ext]) {
113
+ type = types[ext];
114
+ }
115
+ }
116
+ return type;
117
+ }
118
+ }
@@ -0,0 +1,16 @@
1
+ import { FormValidationEntry, PostedDataDecoded, ValidationResult, ValidationRuleWithArguments, ValidatorErrorDecorator, ValidatorFunction } from '../Types';
2
+ export declare class FormValidation {
3
+ fieldRules: Array<FormValidationEntry>;
4
+ singleError: boolean;
5
+ validators: {
6
+ [name: string]: ValidatorFunction;
7
+ };
8
+ decorators: {
9
+ [validatorName: string]: ValidatorErrorDecorator;
10
+ };
11
+ addRule(fieldName: string, nameHumanReadable: string, rules: Array<string | ValidationRuleWithArguments | ValidatorFunction>): void;
12
+ registerValidator(name: string, validator: ValidatorFunction, decorator?: ValidatorErrorDecorator): void;
13
+ registerDecorator(name: string, decorator: ValidatorErrorDecorator): void;
14
+ validate(data: PostedDataDecoded): Promise<ValidationResult>;
15
+ private addError;
16
+ }