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,197 @@
1
+ export class FormValidation {
2
+ constructor() {
3
+ this.fieldRules = [];
4
+ this.singleError = false;
5
+ this.validators = {
6
+ 'required': async (data, field) => {
7
+ if (!(field in data)) {
8
+ return false;
9
+ }
10
+ const value = data[field];
11
+ if (typeof value !== 'string') {
12
+ return false;
13
+ }
14
+ return value.trim().length > 0;
15
+ },
16
+ 'number': async (data, field) => {
17
+ const value = data[field];
18
+ if (typeof value !== 'string') {
19
+ return false;
20
+ }
21
+ return /^-?\d+$/.test(value);
22
+ },
23
+ 'float': async (data, field) => {
24
+ const value = data[field];
25
+ if (typeof value !== 'string') {
26
+ return false;
27
+ }
28
+ return /^-?\d+\.\d+$/.test(value);
29
+ },
30
+ 'numeric': async (data, field, arg, rules) => {
31
+ return await this.validators['number'](data, field, arg, rules) || await this.validators['float'](data, field, arg, rules);
32
+ },
33
+ 'min': async (data, field, arg, rules) => {
34
+ const value = data[field];
35
+ if (typeof value !== 'string') {
36
+ return false;
37
+ }
38
+ if (await this.validators['numeric'](data, field, arg, rules)) {
39
+ return parseFloat(value) >= arg;
40
+ }
41
+ return false;
42
+ },
43
+ 'max': async (data, field, arg, rules) => {
44
+ const value = data[field];
45
+ if (typeof value !== 'string') {
46
+ return false;
47
+ }
48
+ if (await this.validators['numeric'](data, field, arg, rules)) {
49
+ return parseFloat(value) <= arg;
50
+ }
51
+ return false;
52
+ },
53
+ 'minLength': async (data, field, arg) => {
54
+ const value = data[field];
55
+ if (typeof value !== 'string') {
56
+ return false;
57
+ }
58
+ return value.length >= arg;
59
+ },
60
+ 'maxLength': async (data, field, arg) => {
61
+ const value = data[field];
62
+ if (typeof value !== 'string') {
63
+ return false;
64
+ }
65
+ return value.length <= arg;
66
+ },
67
+ 'alphanumeric': async (data, field) => {
68
+ const value = data[field];
69
+ if (typeof value !== 'string') {
70
+ return false;
71
+ }
72
+ return /^[a-zA-Z0-9]+$/.test(value);
73
+ },
74
+ 'validEmail': async (data, field) => {
75
+ const value = data[field];
76
+ if (typeof value !== 'string') {
77
+ return false;
78
+ }
79
+ return /^(?=.{1,254}$)(?=.{1,64}@)[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+(\.[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+)*@[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$/.test(value);
80
+ }
81
+ };
82
+ this.decorators = {
83
+ 'required': (fieldHuman) => {
84
+ return `${fieldHuman} is required`;
85
+ },
86
+ 'number': (fieldHuman) => {
87
+ return `${fieldHuman} has to be a whole number`;
88
+ },
89
+ 'float': (fieldHuman) => {
90
+ return `${fieldHuman} has to be a decimal number`;
91
+ },
92
+ 'numeric': (fieldHuman) => {
93
+ return `${fieldHuman} has to contain a numeric value`;
94
+ },
95
+ 'min': (fieldHuman, data, field, arg) => {
96
+ return `${fieldHuman} has to be a value greater than ${arg}`;
97
+ },
98
+ 'max': (fieldHuman, data, field, arg) => {
99
+ return `${fieldHuman} has to be a value lower than ${arg}`;
100
+ },
101
+ 'minLength': (fieldHuman, data, field, arg) => {
102
+ return `${fieldHuman} has to contain at least ${arg} characters`;
103
+ },
104
+ 'maxLength': (fieldHuman, data, field, arg) => {
105
+ return `${fieldHuman} has to contain no more than ${arg} characters`;
106
+ },
107
+ 'alphanumeric': (fieldHuman) => {
108
+ return `${fieldHuman} can contain only letter and numbers`;
109
+ },
110
+ 'validEmail': () => {
111
+ return `Please enter a valid email address`;
112
+ }
113
+ };
114
+ }
115
+ addRule(fieldName, nameHumanReadable, rules) {
116
+ const rule = {
117
+ field: [fieldName, nameHumanReadable],
118
+ rules
119
+ };
120
+ this.fieldRules.push(rule);
121
+ }
122
+ registerValidator(name, validator, decorator) {
123
+ this.validators[name] = validator;
124
+ if (typeof decorator === 'function') {
125
+ this.decorators[name] = decorator;
126
+ }
127
+ }
128
+ registerDecorator(name, decorator) {
129
+ this.decorators[name] = decorator;
130
+ }
131
+ async validate(data) {
132
+ const result = {
133
+ valid: true,
134
+ errors: {}
135
+ };
136
+ for (let i = 0; i < this.fieldRules.length; i++) {
137
+ const entry = this.fieldRules[i];
138
+ const isRequired = entry.rules.includes('required');
139
+ const value = data[entry.field[0]];
140
+ const possiblyValidDataExists = typeof value === 'string';
141
+ const isContent = !isRequired && (!possiblyValidDataExists || value.trim().length === 0);
142
+ if (!isContent) {
143
+ for (let j = 0; j < entry.rules.length; j++) {
144
+ const rule = entry.rules[j];
145
+ if (typeof rule === 'function') {
146
+ const valid = await rule.apply(this, [data, entry.field[0], 0, entry.rules]);
147
+ if (!valid) {
148
+ await this.addError(result.errors, data, entry.field, 'callback');
149
+ }
150
+ }
151
+ else {
152
+ if (typeof rule === 'string') {
153
+ if (this.validators[rule]) {
154
+ const valid = await this.validators[rule].apply(this, [data, entry.field[0], 0, entry.rules]);
155
+ if (!valid) {
156
+ await this.addError(result.errors, data, entry.field, rule);
157
+ }
158
+ }
159
+ }
160
+ else {
161
+ const validatorName = rule[0];
162
+ const arg = rule[1];
163
+ if (this.validators[validatorName]) {
164
+ const valid = await this.validators[validatorName].apply(this, [data, entry.field[0], arg, entry.rules]);
165
+ if (!valid) {
166
+ await this.addError(result.errors, data, entry.field, validatorName, arg);
167
+ }
168
+ }
169
+ }
170
+ }
171
+ }
172
+ }
173
+ }
174
+ result.valid = Object.keys(result.errors).length == 0;
175
+ return result;
176
+ }
177
+ async addError(errors, data, field, rule, arg) {
178
+ let errorMessage = '';
179
+ if (this.decorators[rule]) {
180
+ errorMessage = await this.decorators[rule](field[1], data, field[0], arg);
181
+ }
182
+ else {
183
+ errorMessage = rule;
184
+ }
185
+ if (!this.singleError) {
186
+ if (!errors[field[0]]) {
187
+ errors[field[0]] = [];
188
+ }
189
+ errors[field[0]].push(errorMessage);
190
+ }
191
+ else {
192
+ if (!errors[field[0]]) {
193
+ errors[field[0]] = errorMessage;
194
+ }
195
+ }
196
+ }
197
+ }
@@ -0,0 +1,11 @@
1
+ import { HelperDelegate } from "handlebars";
2
+ import { default as HandlebarsInstance } from 'handlebars';
3
+ import { LooseObject } from "../Types.js";
4
+ export declare class Handlebars {
5
+ readonly instance: typeof HandlebarsInstance;
6
+ readonly helpers: Record<string, HelperDelegate>;
7
+ register(name: string, helper: HelperDelegate): void;
8
+ loadHelpers(path: string): Promise<void>;
9
+ applyTo(handlebarsInstance: typeof HandlebarsInstance): void;
10
+ compile(html: string, data: LooseObject): string;
11
+ }
@@ -0,0 +1,34 @@
1
+ import { default as HandlebarsInstance } from 'handlebars';
2
+ export class Handlebars {
3
+ constructor() {
4
+ this.instance = HandlebarsInstance;
5
+ this.helpers = {};
6
+ }
7
+ register(name, helper) {
8
+ this.helpers[name] = helper;
9
+ this.instance.registerHelper(name, helper);
10
+ }
11
+ async loadHelpers(path) {
12
+ try {
13
+ const helpers = await import(path);
14
+ if (!('default' in helpers)) {
15
+ throw new Error('File has no default export, expected default: Record<string, HelperDelegate>');
16
+ }
17
+ for (const name in helpers.default) {
18
+ this.register(name, helpers.default[name]);
19
+ }
20
+ }
21
+ catch (e) {
22
+ throw new Error(e.message);
23
+ }
24
+ }
25
+ applyTo(handlebarsInstance) {
26
+ for (const name in this.helpers) {
27
+ handlebarsInstance.registerHelper(name, this.helpers[name]);
28
+ }
29
+ }
30
+ compile(html, data) {
31
+ const template = this.instance.compile(html);
32
+ return template(data);
33
+ }
34
+ }
@@ -0,0 +1,21 @@
1
+ import { IncomingMessage, ServerResponse } from "node:http";
2
+ import { PostedDataDecoded, RequestBodyFile, RequestCallback, RequestMethod } from "../Types.js";
3
+ import { Application } from "./Application.js";
4
+ export declare class Request {
5
+ private app;
6
+ constructor(app: Application);
7
+ pageNotFoundCallback: RequestCallback;
8
+ private readonly handlers;
9
+ on(methods: RequestMethod | Array<RequestMethod>, pattern: string | RegExp | Array<string | RegExp>, callback: RequestCallback, scope?: any, isStaticAsset?: boolean): void;
10
+ private getHandler;
11
+ handle(request: IncomingMessage, response: ServerResponse): Promise<void>;
12
+ private extractURIArguments;
13
+ private patternToSegments;
14
+ private parseBody;
15
+ private dataRaw;
16
+ redirect(response: ServerResponse, to: string, statusCode?: number): void;
17
+ loadHandlers(basePath?: string): Promise<void>;
18
+ static queryStringDecode(queryString: string, initialValue?: PostedDataDecoded, trimValues?: boolean): PostedDataDecoded;
19
+ static parseBodyMultipart(bodyRaw: string, boundary: string): PostedDataDecoded;
20
+ static multipartBodyFiles(bodyRaw: string, boundary: string): Record<string, RequestBodyFile>;
21
+ }
@@ -0,0 +1,356 @@
1
+ import { mergeDeep, queryStringDecode, queryStringDecodedSetValue } from "../Util.js";
2
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
3
+ import path from "node:path";
4
+ import { Buffer } from "node:buffer";
5
+ import { Document } from "./Document.js";
6
+ export class Request {
7
+ constructor(app) {
8
+ this.pageNotFoundCallback = async ({ response }) => {
9
+ response.statusCode = 404;
10
+ response.write('Page not found');
11
+ response.end();
12
+ };
13
+ this.handlers = [];
14
+ this.app = app;
15
+ }
16
+ on(methods, pattern, callback, scope, isStaticAsset = false) {
17
+ if (!(methods instanceof Array)) {
18
+ methods = [methods];
19
+ }
20
+ if (scope === undefined) {
21
+ scope = this.app;
22
+ }
23
+ if (pattern instanceof Array) {
24
+ pattern.forEach((p) => {
25
+ this.on(methods, p, callback, scope);
26
+ });
27
+ return;
28
+ }
29
+ const match = (typeof pattern === 'string' ? this.patternToSegments(pattern) : pattern);
30
+ const handler = {
31
+ match,
32
+ methods,
33
+ callback,
34
+ scope,
35
+ staticAsset: isStaticAsset
36
+ };
37
+ this.handlers.push(handler);
38
+ this.handlers.sort((a, b) => {
39
+ const valA = a.match instanceof RegExp ? 1 : 0;
40
+ const valB = b.match instanceof RegExp ? 1 : 0;
41
+ return valA - valB;
42
+ });
43
+ }
44
+ getHandler(uri, method) {
45
+ const segments = uri.split('/');
46
+ let possible = this.handlers.filter((handler) => {
47
+ return handler.methods.includes(method) && (handler.match instanceof RegExp || handler.match.length === segments.length);
48
+ });
49
+ for (let i = 0; i < segments.length; i++) {
50
+ possible = possible.filter((handler) => {
51
+ if (handler.match instanceof RegExp) {
52
+ return handler.match.test(uri);
53
+ }
54
+ else {
55
+ const pattern = handler.match[i].pattern;
56
+ if (typeof pattern === 'string') {
57
+ return pattern === segments[i];
58
+ }
59
+ return pattern.test(segments[i]);
60
+ }
61
+ });
62
+ }
63
+ if (possible.length === 0) {
64
+ return null;
65
+ }
66
+ if (possible.length > 1) {
67
+ console.warn(`Multiple request handlers for ${uri}`);
68
+ }
69
+ possible.sort((a, b) => {
70
+ if (a.match instanceof Array && b.match instanceof Array) {
71
+ for (let i = a.match.length - 1; i > -1; i--) {
72
+ const aVal = a.match[i].pattern instanceof RegExp ? 1 : 0;
73
+ const bVal = b.match[i].pattern instanceof RegExp ? 1 : 0;
74
+ if (aVal != bVal) {
75
+ return aVal - bVal;
76
+ }
77
+ }
78
+ return 0;
79
+ }
80
+ const aVal = a.match instanceof RegExp ? 1 : 0;
81
+ const bVal = b.match instanceof RegExp ? 1 : 0;
82
+ return aVal - bVal;
83
+ });
84
+ return possible[0];
85
+ }
86
+ async handle(request, response) {
87
+ const requestMethod = request.method;
88
+ let uri = request.url || '/';
89
+ if (this.app.config.url.removeTrailingSlash && uri.length > 1 && uri.endsWith('/')) {
90
+ uri = uri.substring(0, uri.length - 1);
91
+ }
92
+ let getArgs = {};
93
+ if (uri.indexOf('?') > -1) {
94
+ const uriParts = uri.split('?');
95
+ uri = uriParts[0];
96
+ getArgs = queryStringDecode(uriParts[1]);
97
+ }
98
+ const handler = this.getHandler(uri, requestMethod);
99
+ const context = {
100
+ request,
101
+ response,
102
+ handler,
103
+ args: {},
104
+ data: {},
105
+ getArgs,
106
+ cookies: this.app.cookies.parse(request),
107
+ isAjax: request.headers['x-requested-with'] == 'xmlhttprequest',
108
+ respondWith: function (data) {
109
+ if (typeof data === 'string' || Buffer.isBuffer(data)) {
110
+ response.write(data);
111
+ }
112
+ else if (typeof data === 'number') {
113
+ response.write(data.toString());
114
+ }
115
+ else if (data instanceof Document) {
116
+ response.setHeader('Content-Type', 'text/html');
117
+ response.write(data.toString());
118
+ }
119
+ else if (data === undefined || data === null) {
120
+ response.write('');
121
+ }
122
+ else {
123
+ response.setHeader('Content-Type', 'application/json');
124
+ response.write(JSON.stringify(data, null, 4));
125
+ }
126
+ },
127
+ redirect: (to, statusCode = 302) => {
128
+ this.redirect(response, to, statusCode);
129
+ },
130
+ show404: async () => {
131
+ await this.pageNotFoundCallback.apply(this.app, [context]);
132
+ this.app.emit('pageNotFound', context);
133
+ }
134
+ };
135
+ if (handler !== null) {
136
+ if (!handler.staticAsset) {
137
+ const results = await this.app.emit('beforeRequestHandler', context);
138
+ if (results.includes(false)) {
139
+ context.response.end();
140
+ return;
141
+ }
142
+ try {
143
+ await this.parseBody(context);
144
+ }
145
+ catch (e) {
146
+ console.error(`Error parsing request body: ${e.message}`);
147
+ }
148
+ const URIArgs = this.extractURIArguments(uri, handler.match);
149
+ context.args = URIArgs;
150
+ }
151
+ try {
152
+ const response = await handler.callback.apply(handler.scope, [context]);
153
+ if (!context.response.headersSent) {
154
+ context.respondWith(response);
155
+ }
156
+ }
157
+ catch (e) {
158
+ console.log('Error executing request handler ', e, handler.callback.toString());
159
+ }
160
+ if (!handler.staticAsset) {
161
+ await this.app.emit('afterRequestHandler', context);
162
+ }
163
+ }
164
+ else {
165
+ let staticAsset = false;
166
+ if (this.app.config.url.isAsset(context.request.url || '')) {
167
+ const basePath = context.request.url?.startsWith('/assets/ts/') ? './' : '../';
168
+ const assetPath = path.resolve(basePath + context.request.url);
169
+ if (existsSync(assetPath)) {
170
+ await this.app.emit('beforeAssetAccess', context);
171
+ const extension = (context.request.url || '').split('.').pop();
172
+ if (extension) {
173
+ const contentType = this.app.contentType(extension);
174
+ if (contentType) {
175
+ response.setHeader('Content-Type', contentType);
176
+ }
177
+ }
178
+ response.write(readFileSync(assetPath));
179
+ staticAsset = true;
180
+ await this.app.emit('afterAssetAccess', context);
181
+ }
182
+ }
183
+ if (!staticAsset) {
184
+ await context.show404();
185
+ }
186
+ }
187
+ response.end();
188
+ }
189
+ extractURIArguments(uri, match) {
190
+ if (match instanceof RegExp) {
191
+ const matches = match.exec(uri);
192
+ if (matches) {
193
+ return {
194
+ matches
195
+ };
196
+ }
197
+ else {
198
+ return {};
199
+ }
200
+ }
201
+ const uriArgs = {};
202
+ const segments = uri.split('/');
203
+ match.forEach((segmentPattern, i) => {
204
+ if (segmentPattern.name) {
205
+ uriArgs[segmentPattern.name] = segmentPattern.type === 'number' ? parseInt(segments[i]) : segments[i];
206
+ }
207
+ });
208
+ return uriArgs;
209
+ }
210
+ patternToSegments(pattern) {
211
+ const segments = [];
212
+ const segmentsIn = pattern.split('/');
213
+ segmentsIn.forEach((segmentIn) => {
214
+ const named = /^\([^\/]+\)$/.test(segmentIn);
215
+ const segmentPattern = {
216
+ pattern: segmentIn,
217
+ type: 'string'
218
+ };
219
+ if (named) {
220
+ const nameParts = /^\(([^\/:\)]+)/.exec(segmentIn);
221
+ const isNumber = /:num\)$/.test(segmentIn);
222
+ if (nameParts) {
223
+ segmentPattern.name = nameParts[1];
224
+ if (isNumber) {
225
+ segmentPattern.pattern = /^(\d+)$/;
226
+ segmentPattern.type = 'number';
227
+ }
228
+ else {
229
+ segmentPattern.pattern = /^([^\/]+)$/;
230
+ segmentPattern.type = 'string';
231
+ }
232
+ }
233
+ else {
234
+ console.warn(`Invalid URI segment pattern ${segmentIn} in URI ${pattern}`);
235
+ }
236
+ }
237
+ segments.push(segmentPattern);
238
+ });
239
+ return segments;
240
+ }
241
+ async parseBody(ctx) {
242
+ if (ctx.request.headers['content-type']) {
243
+ ctx.bodyRaw = await this.dataRaw(ctx.request);
244
+ if (ctx.request.headers['content-type'].indexOf('urlencoded') > -1) {
245
+ ctx.body = queryStringDecode(ctx.bodyRaw.toString('utf-8'));
246
+ }
247
+ else if (ctx.request.headers['content-type'].indexOf('multipart/form-data') > -1) {
248
+ let boundary = /^multipart\/form-data; boundary=(.+)$/.exec(ctx.request.headers['content-type']);
249
+ if (boundary) {
250
+ boundary = `--${boundary[1]}`;
251
+ ctx.body = Request.parseBodyMultipart(ctx.bodyRaw.toString('utf-8'), boundary);
252
+ ctx.files = Request.multipartBodyFiles(ctx.bodyRaw.toString('binary'), boundary);
253
+ }
254
+ }
255
+ else if (ctx.request.headers['content-type'].indexOf('application/json') > -1) {
256
+ try {
257
+ ctx.body = JSON.parse(ctx.bodyRaw.toString());
258
+ }
259
+ catch (e) {
260
+ ctx.body = undefined;
261
+ }
262
+ }
263
+ }
264
+ return;
265
+ }
266
+ dataRaw(request) {
267
+ const chunks = [];
268
+ return new Promise((resolve, reject) => {
269
+ request.on('data', (chunk) => {
270
+ chunks.push(chunk);
271
+ });
272
+ request.on('close', () => {
273
+ const size = chunks.reduce((prev, curr) => {
274
+ return prev + curr.length;
275
+ }, 0);
276
+ const data = Buffer.concat(chunks, size);
277
+ resolve(data);
278
+ });
279
+ request.on('error', (e) => {
280
+ reject(e);
281
+ });
282
+ });
283
+ }
284
+ redirect(response, to, statusCode = 302) {
285
+ response.setHeader('Location', to);
286
+ response.writeHead(statusCode);
287
+ }
288
+ async loadHandlers(basePath) {
289
+ let routesPath;
290
+ if (basePath) {
291
+ routesPath = basePath;
292
+ }
293
+ else {
294
+ routesPath = path.resolve((this.app.config.runtime === 'Node.js' ? '../build/' : './') + this.app.config.routes.path);
295
+ }
296
+ const files = readdirSync(routesPath);
297
+ for (let i = 0; i < files.length; i++) {
298
+ const file = files[i];
299
+ if (!(file.endsWith('.js') || file.endsWith('.ts'))) {
300
+ continue;
301
+ }
302
+ const filePath = path.resolve(routesPath + '/' + file);
303
+ const isDirectory = statSync(filePath).isDirectory();
304
+ if (isDirectory) {
305
+ await this.loadHandlers(filePath);
306
+ }
307
+ else {
308
+ const fn = (await import('file:///' + filePath)).default;
309
+ if (typeof fn === 'function') {
310
+ fn(this.app);
311
+ }
312
+ }
313
+ }
314
+ return;
315
+ }
316
+ static queryStringDecode(queryString, initialValue = {}, trimValues = true) {
317
+ return queryStringDecode(queryString, initialValue, trimValues);
318
+ }
319
+ static parseBodyMultipart(bodyRaw, boundary) {
320
+ const pairsRaw = bodyRaw.split(boundary);
321
+ const pairs = pairsRaw.map((pair) => {
322
+ const parts = /Content-Disposition: form-data; name="([^\r\n"]+)"\r?\n\r?\n([^$]+)/m.exec(pair);
323
+ if (parts) {
324
+ return {
325
+ key: parts[1],
326
+ value: parts[2]
327
+ };
328
+ }
329
+ return null;
330
+ });
331
+ const urlEncoded = pairs.reduce((prev, curr) => {
332
+ if (curr !== null) {
333
+ prev.push(`${curr.key}=${encodeURIComponent(curr.value.replaceAll('&', '%26'))}`);
334
+ }
335
+ return prev;
336
+ }, []).join('&');
337
+ return queryStringDecode(urlEncoded);
338
+ }
339
+ static multipartBodyFiles(bodyRaw, boundary) {
340
+ let files = {};
341
+ const pairsRaw = bodyRaw.split(boundary);
342
+ pairsRaw.map((pair) => {
343
+ const parts = /Content-Disposition: form-data; name="(.+?)"; filename="(.+?)"\r\nContent-Type: (.*)\r\n\r\n([\s\S]+)$/m.exec(pair);
344
+ if (parts) {
345
+ const file = {
346
+ data: Buffer.from(parts[4].substring(0, parts[4].length - 2).trim(), 'binary'),
347
+ fileName: parts[2],
348
+ type: parts[3]
349
+ };
350
+ files = mergeDeep(files, queryStringDecodedSetValue(parts[1], file));
351
+ }
352
+ return null;
353
+ });
354
+ return files;
355
+ }
356
+ }
@@ -0,0 +1,23 @@
1
+ import { LooseObject, SessionEntry } from '../Types.js';
2
+ import { Application } from './Application.js';
3
+ export declare class Session {
4
+ application: Application;
5
+ enabled: boolean;
6
+ sessions: {
7
+ [key: string]: SessionEntry;
8
+ };
9
+ constructor(app: Application);
10
+ start(): void;
11
+ stop(): void;
12
+ private sessionInit;
13
+ private generateId;
14
+ private garbageCollect;
15
+ setValue(sessionId: string | undefined, key: string, value: any): void;
16
+ getValue<T>(sessionId: string | undefined, key: string): T | null;
17
+ getClear<T>(sessionId: string | undefined, key: string): T | null;
18
+ removeValue(sessionId: string | undefined, key: string): void;
19
+ clear(sessionId: string | undefined): void;
20
+ extract(sessionId: string | undefined, keys: Array<string | {
21
+ [keyInSession: string]: string;
22
+ }>): LooseObject;
23
+ }