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,140 @@
1
+ import { HTMLParser } from "./HTMLParser.js";
2
+
3
+ type DOMNodeAttribute = { name: string, value: string | true };
4
+
5
+ type JSONNode = {
6
+ name: string,
7
+ children: Array<JSONNode>,
8
+ attributes: Record<string, DOMNodeAttribute>,
9
+ strings: Array<string>
10
+ }
11
+
12
+ export const selfClosingTags: ReadonlyArray<string> = ['br', 'hr', 'input', 'img', 'link', 'meta', 'source', 'embed', 'path', 'area'];
13
+
14
+ export class DOMNode {
15
+
16
+ tagName: string;
17
+
18
+ parentNode: DOMNode | null = null;
19
+ children: Array<DOMNode | string> = [];
20
+
21
+ attributes: Array<DOMNodeAttribute> = []
22
+ attributeMap: Record<string, DOMNodeAttribute> = {}
23
+
24
+ style: Partial<CSSStyleDeclaration> = {}
25
+
26
+ selfClosing: boolean;
27
+
28
+ constructor(tagName: string) {
29
+ this.tagName = tagName;
30
+ this.selfClosing = selfClosingTags.includes(tagName);
31
+ }
32
+
33
+ appendChild(node: DOMNode | string): void {
34
+ if (typeof node !== 'string') {
35
+ node.parentNode = this;
36
+ }
37
+ this.children.push(node);
38
+ }
39
+
40
+ setAttribute(attributeName: string, attributeValue: string | true): void {
41
+ const attributeExisting = this.attributeMap[attributeName];
42
+
43
+ if (! attributeExisting) {
44
+ const attribute = {
45
+ name: attributeName,
46
+ value: attributeValue
47
+ }
48
+ this.attributeMap[attributeName] = attribute;
49
+ this.attributes.push(attribute);
50
+ } else {
51
+ attributeExisting.value = attributeValue;
52
+ }
53
+ }
54
+
55
+ hasAttribute(attributeName: string): boolean {
56
+ return attributeName in this.attributeMap;
57
+ }
58
+
59
+ queryByTagName(...tagNames: Array<string>): Array<DOMNode> {
60
+ let nodes: Array<DOMNode> = [];
61
+
62
+ for (let i = 0; i < this.children.length; i++) {
63
+ const child = this.children[i];
64
+ if (typeof child === 'string') {continue;}
65
+ if (tagNames.includes(child.tagName)) {
66
+ nodes.push(child);
67
+ }
68
+
69
+ nodes = nodes.concat(child.queryByTagName(...tagNames));
70
+ }
71
+
72
+ return nodes;
73
+ }
74
+
75
+ queryByHasAttribute(...attributeNames: Array<string>): Array<DOMNode> {
76
+ let nodes: Array<DOMNode> = [];
77
+
78
+ for (let i = 0; i < this.children.length; i++) {
79
+ const child = this.children[i];
80
+ if (typeof child === 'string') {continue;}
81
+ if (attributeNames.some((attributeName) => {
82
+ return child.hasAttribute(attributeName)
83
+ })) {
84
+ nodes.push(child);
85
+ }
86
+
87
+ nodes = nodes.concat(child.queryByHasAttribute(...attributeNames));
88
+ }
89
+
90
+ return nodes;
91
+ }
92
+
93
+ get innerHTML(): string {
94
+ return this.children.reduce((html, child) => {
95
+ if (typeof child === 'string') {
96
+ return html += child;
97
+ } else {
98
+ return html += child.outerHTML;
99
+ }
100
+ }, '') as string;
101
+ }
102
+
103
+ set innerHTML(html: string) {
104
+ const fragment = new HTMLParser(html).dom();
105
+ this.children = fragment.children;
106
+ }
107
+
108
+ get outerHTML(): string {
109
+ const attributes = this.attributes.reduce((attributes, attribute) => {
110
+ attributes += ` ${attribute.name}${attribute.value === true ? '' : `="${attribute.value}"`}`;
111
+ return attributes;
112
+ }, '');
113
+
114
+ const style = Object.keys(this.style).reduce((style, styleName) => {
115
+ const styleValue = this.style[styleName as keyof CSSStyleDeclaration];
116
+ if (styleValue?.toString().trim().length === 0) {return style};
117
+ style += ` ${styleName}: ${styleValue};`;
118
+ return style;
119
+ }, '');
120
+
121
+
122
+ return `<${this.tagName}${attributes}${style.trim().length > 0 ? ` style="${style}"` : ''}>${this.selfClosing ? '' : `${this.innerHTML}</${this.tagName}>`}`;
123
+ }
124
+
125
+ toObject():JSONNode {
126
+ return {
127
+ name: this.tagName,
128
+ children: this.children.filter((child) => {
129
+ return typeof child !== 'string';
130
+ }).map((child) => {
131
+ return child.toObject()
132
+ }),
133
+ attributes: this.attributeMap,
134
+ strings: this.children.filter((child) => {
135
+ return typeof child === 'string';
136
+ })
137
+ }
138
+ }
139
+
140
+ }
@@ -0,0 +1,238 @@
1
+ import { DOMFragment } from "./DOMFragment.js";
2
+ import { DOMNode } from "./DOMNode.js";
3
+
4
+ export class HTMLParser {
5
+
6
+ private readonly html: string;
7
+
8
+ private offset: number = 0;
9
+ private context: DOMFragment | DOMNode;
10
+ private state: 'idle' | 'tagStart' | 'tagOpen' | 'tagClose' | 'attributeName' | 'attributeValueStart' | 'attributeValue' | 'attributeEnd' | 'text' = 'idle';
11
+
12
+ private tokenCurrent: string = '';
13
+
14
+ private fragment: DOMFragment = new DOMFragment();
15
+
16
+ private attributeOpenQuote: '"' | "'" = '"';
17
+ private attributeNameCurrent: string = '';
18
+ private attributeContext: DOMNode | null = null;
19
+
20
+ constructor(html: string) {
21
+ this.html = html;
22
+ this.context = this.fragment;
23
+ while (this.parse()) {
24
+ // console.log({ char: this.char(), state: this.state, context: this.context.tagName });
25
+ this.offset++;
26
+ }
27
+ }
28
+
29
+ private char(): string {
30
+ return this.html.charAt(this.offset);
31
+ }
32
+
33
+ private lastChar(): boolean {
34
+ return this.offset === this.html.length - 1;
35
+ }
36
+
37
+ public parse(): boolean {
38
+ if (this.offset >= this.html.length) {
39
+ // done
40
+ return false;
41
+ }
42
+
43
+ const char = this.char();
44
+ const charCode = char.charCodeAt(0);
45
+
46
+ if (this.state === 'idle') {
47
+ if (char === ' ') {return true;}
48
+ if (char === '<') {
49
+ this.state = 'tagStart';
50
+ this.tokenCurrent = '';
51
+ return true;
52
+ }
53
+
54
+ // text
55
+ this.state = 'text';
56
+ this.tokenCurrent = char;
57
+
58
+ } else if (this.state === 'tagStart') {
59
+ if (char === '/') {
60
+ this.state = 'tagClose';
61
+ return true;
62
+ }
63
+
64
+ if (this.isLetter(charCode)) {
65
+ this.state = 'tagOpen';
66
+ this.tokenCurrent = char;
67
+ return true;
68
+ }
69
+ } else if (this.state === 'tagOpen') {
70
+ // this state means we found "<" previously and we expect to find the tag name
71
+
72
+ if (char === '\n') {
73
+ return true;
74
+ }
75
+
76
+ if (char === '/') {
77
+ if (this.tokenCurrent.length === 0) {
78
+ throw this.error(`Unexpected tag closing sequence "</", expected opening tag`);
79
+ }
80
+ // ignore this one, it's a self closing tag, but we will expect to find ">" anyway
81
+ return true;
82
+ }
83
+
84
+ if (char === '>') {
85
+ if (this.tokenCurrent.length === 0) {
86
+ throw this.error(`Found an empty HTML tag <>`);
87
+ }
88
+ // opening tag end, create node and switch context to new node
89
+ const node = new DOMNode(this.tokenCurrent);
90
+ this.context.appendChild(node);
91
+ this.state = 'idle';
92
+ this.tokenCurrent = '';
93
+ if (! node.selfClosing) {
94
+ this.context = node;
95
+ }
96
+ this.attributeContext = node;
97
+ return true;
98
+ }
99
+
100
+ if (char === ' ') {
101
+ if (this.tokenCurrent.length === 0) {
102
+ return true;
103
+ }
104
+
105
+ // encountered space after opening tag name, could be a start of attribute name
106
+ this.state = 'attributeName';
107
+ const node = new DOMNode(this.tokenCurrent);
108
+ this.context.appendChild(node);
109
+ this.tokenCurrent = '';
110
+ if (! node.selfClosing) {
111
+ this.context = node;
112
+ }
113
+ this.attributeContext = node;
114
+ return true;
115
+ }
116
+
117
+ if (char !== '_' && ! this.isLetter(charCode) && (this.tokenCurrent.length > 0 && ! this.isNumber(charCode))) {
118
+ throw this.error(`Expected a-Z after HTML opening tag`);
119
+ }
120
+
121
+ this.tokenCurrent += char;
122
+ return true;
123
+ } else if(this.state === 'tagClose') {
124
+ if (char === '/') {
125
+ // slash before closing tag eg. <input name="..." />
126
+ return true;
127
+ }
128
+ if (char === '>') {
129
+ if (this.tokenCurrent !== this.context.tagName) {
130
+ throw this.error(`Found closing tag ${this.tokenCurrent}, expected ${this.context.tagName}`);
131
+ }
132
+ // tag closed, switch context to parent of the current context
133
+ this.context = this.context.parentNode || this.fragment;
134
+ this.state = 'idle';
135
+ }
136
+ this.tokenCurrent += char;
137
+ } else if (this.state === 'text') {
138
+ if (char === '<') {
139
+ // end text
140
+ this.state = 'tagStart';
141
+ this.context.appendChild(this.tokenCurrent);
142
+ this.tokenCurrent = '';
143
+ return true;
144
+ }
145
+ this.tokenCurrent += char;
146
+
147
+ if (this.lastChar() && this.tokenCurrent.length > 0) {
148
+ // text within node is handled when node closing tag is found
149
+ // this handles text that is a direct child of the fragment
150
+ this.context.appendChild(this.tokenCurrent);
151
+ }
152
+ } else if (this.state === 'attributeName') {
153
+ const boundsChar = char === ' ' || char === '\n' || char === '\t';
154
+ if (boundsChar || char === '=' || char === '>') {
155
+ // end of attribute name
156
+ if (char === '=') {
157
+ this.state = 'attributeValueStart';
158
+ } else if (char === '>') {
159
+ this.state = 'idle';
160
+ }
161
+ if (this.tokenCurrent.length > 0) {
162
+ if (this.attributeContext !== null && this.tokenCurrent.trim().length > 0) {
163
+ this.attributeContext.setAttribute(this.tokenCurrent, true);
164
+ }
165
+ this.attributeNameCurrent = this.tokenCurrent;
166
+ this.tokenCurrent = '';
167
+ return true;
168
+ }
169
+ }
170
+
171
+ if (! boundsChar) {
172
+ this.tokenCurrent += char;
173
+ }
174
+ } else if (this.state === 'attributeValueStart') {
175
+ if (char === '"' || char === "'") {
176
+ this.state = 'attributeValue';
177
+ this.attributeOpenQuote = char;
178
+ return true;
179
+ }
180
+ } else if (this.state === 'attributeValue') {
181
+
182
+ if (char === this.attributeOpenQuote) {
183
+ // attribute value ended
184
+ if (this.attributeContext) {
185
+ this.attributeContext.setAttribute(this.attributeNameCurrent, this.tokenCurrent);
186
+ }
187
+ this.tokenCurrent = '';
188
+ this.attributeNameCurrent = '';
189
+ this.state = 'attributeEnd';
190
+ return true;
191
+ }
192
+
193
+ this.tokenCurrent += char;
194
+ } else if (this.state === 'attributeEnd') {
195
+ if (char === '>') {
196
+ this.state = 'idle';
197
+ return true;
198
+ } if (char === ' ' || char === '\n') {
199
+ this.state = 'attributeName';
200
+ return true;
201
+ } else if (char === '/') {
202
+ return true;
203
+ } else {
204
+ throw this.error(`Unexpected character ${char} after attribute value`);
205
+ }
206
+ }
207
+
208
+ return true;
209
+
210
+ }
211
+
212
+ // returns current line
213
+ private line(): number {
214
+ return this.html.substring(0, this.offset).split('\n').length;
215
+ }
216
+
217
+ private isLetter(charCode: number): boolean {
218
+ const isLowerCase = charCode > 96 && charCode < 123;
219
+ if (isLowerCase) {return true;}
220
+ const isUpperCase = charCode > 64 && charCode < 91;
221
+ if (isUpperCase) {return true;}
222
+
223
+ return false;
224
+ }
225
+
226
+ private isNumber(charCode: number): boolean {
227
+ return charCode > 47 && charCode < 58;
228
+ }
229
+
230
+ public dom(): DOMFragment {
231
+ return this.fragment;
232
+ }
233
+
234
+ private error(message: string): Error {
235
+ return new Error(`HTMLParser: ${message}\nLine ${this.line()}\nHTML:\n${this.html}\n`);
236
+ }
237
+
238
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "compilerOptions": {
3
+ "noImplicitAny": true,
4
+ "strictNullChecks": true,
5
+ "strictPropertyInitialization": true,
6
+ "strictBindCallApply": true,
7
+ "noUnusedLocals": true,
8
+ "moduleResolution" : "Node",
9
+ "outDir": "./build",
10
+ "module": "ES2020",
11
+ "target": "ES2021",
12
+ "alwaysStrict": true,
13
+ "allowSyntheticDefaultImports": true, // albe to do import { default as varname } from 'module'
14
+ "declaration": true,
15
+ "isolatedDeclarations": true,
16
+ "noImplicitReturns": true,
17
+ "preserveSymlinks": true,
18
+ "removeComments": true,
19
+ "baseUrl": ".",
20
+ "rootDir": ".",
21
+ "paths": {
22
+ "/assets/ts/*": ["./assets/ts/*"],
23
+ "/assets/client-js/*": ["./system/*"],
24
+ "@structured/*": [
25
+ "./node_modules/structured/build/system/server/*",
26
+ "./node_modules/structured/build/system/client/*",
27
+ "./node_modules/structured/build/system/*",
28
+ "./system/server/*",
29
+ "./system/client/*",
30
+ "./system/*",
31
+ ]
32
+ }
33
+ },
34
+ "include": ["./system/**/*", "./app/**/*", "./assets/ts/**/*", "index.ts"]
35
+ }