structured-fw 1.5.0 → 1.6.0
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.
- package/README.md +6 -4
- package/build/system/EventEmitter.js +4 -6
- package/build/system/StructuredError.d.ts +8 -0
- package/build/system/StructuredError.js +40 -0
- package/build/system/bin/structured.js +1 -2
- package/build/system/client/ClientApplication.js +3 -2
- package/build/system/client/ClientComponent.js +21 -13
- package/build/system/client/DataStore.js +2 -4
- package/build/system/client/DataStoreView.js +3 -1
- package/build/system/client/NetRequest.js +7 -2
- package/build/system/server/Application.d.ts +1 -1
- package/build/system/server/Application.js +12 -7
- package/build/system/server/Component.js +15 -7
- package/build/system/server/Components.js +3 -2
- package/build/system/server/Document.js +10 -7
- package/build/system/server/DocumentHead.js +9 -8
- package/build/system/server/FormValidation.js +121 -123
- package/build/system/server/Handlebars.js +2 -4
- package/build/system/server/Layout.js +4 -0
- package/build/system/server/Request.d.ts +0 -2
- package/build/system/server/Request.js +15 -7
- package/build/system/server/RequestContext.d.ts +7 -5
- package/build/system/server/RequestContext.js +54 -39
- package/build/system/server/Session.js +3 -2
- package/build/system/server/dom/DOMNode.js +11 -7
- package/build/system/server/dom/HTMLParser.js +10 -8
- package/build/system/types/application.types.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -134,13 +134,14 @@ new Application(config);
|
|
|
134
134
|
- `serverStarted` - executed once the built-in http server is started and running. Callback receives Server (exported from node:http) instance as the first argument
|
|
135
135
|
- `beforeRequestHandler` - runs before any request handler (route) is executed. Callback receives `RequestContext` as the first argument. Useful for example to set `RequestContext.data: RequestContextData` (user defined data, to make it available to routes and components)
|
|
136
136
|
- `afterRequestHandler` - runs after any request handler (route) is executed. Callback receives `RequestContext` as the first argument
|
|
137
|
+
- `requestHandleError` - runs if there were errors while serving the request. Callback's result is sent as a response - a good use case is showing a "server error" page. Callback receives `RequestContext` as the first argument
|
|
137
138
|
- `afterRoutes` - runs after all routes are loaded from `StructuredConfig.routes.path`. Callback receives no arguments
|
|
138
139
|
- `beforeComponentsLoad` - runs before components are loaded from `StructuredConfig.components.path`. Callback receives no arguments
|
|
139
140
|
- `afterComponentsLoaded` - runs after all components are loaded from `StructuredConfig.components.path`. Callback receives instance of Components as the first argument
|
|
140
141
|
- `documentCreated` - runs whenever an instance of a [Document](#document) is created. Callback receives the Document instance as the first argument. You will often use this, for example if you want to include a CSS file to all pages `Document.head.addCSS(...)`
|
|
141
142
|
- `beforeAssetAccess` - runs when assets are being accessed, before response is sent. Callback receives `RequestContext` as the first argument
|
|
142
143
|
- `afterAssetAccess` - runs when assets are being accessed, after response is sent. Callback receives `RequestContext` as the first argument
|
|
143
|
-
- `pageNotFound` - runs when a request is received for which there is no registered request handler (route), and the requested URL is not an asset. Callback receives `RequestContext` as the first argument
|
|
144
|
+
- `pageNotFound` - runs when a request is received for which there is no registered request handler (route), and the requested URL is not an asset. Callback's result is sent as a response - a good use case is showing a 404 page. Callback receives `RequestContext` as the first argument
|
|
144
145
|
- **Callback to any of the `ApplicationEvents` is expected to be an async function**
|
|
145
146
|
- `importEnv<T extends LooseObject>(smartPrimitives: boolean = true): T` - import ENV variables that start with `StructuredConfig.envPrefix`_ (if envPrefix is omitted from config, all ENV variables are returned). It is a generic method so that you can specify the expected return type. If `smartPrimitives = true` importEnv will convert the ENV values to type it feels is appropriate:
|
|
146
147
|
- numeric values -> `number`
|
|
@@ -322,9 +323,10 @@ class RequestContext<Body extends LooseObject | undefined = LooseObject> = {
|
|
|
322
323
|
// redirect to given URI, with given statusCode (default 302)
|
|
323
324
|
redirect: (to: string, statusCode?: number) => void;
|
|
324
325
|
|
|
325
|
-
// show a
|
|
326
|
-
//
|
|
327
|
-
//
|
|
326
|
+
// show a blank page and send a 404 status code
|
|
327
|
+
// to show a custom page, text, json, etc... register an event handler for pageNotFound event
|
|
328
|
+
// for example:
|
|
329
|
+
// app.on('pageNotFound', (ctx) => { return await ctx.createDocument('Page not found', 'NotFound'); })
|
|
328
330
|
show404: () => Promise<void>
|
|
329
331
|
}
|
|
330
332
|
```
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
export class EventEmitter {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
this.eventQueue = [];
|
|
7
|
-
}
|
|
2
|
+
listeners = {};
|
|
3
|
+
destroyed = false;
|
|
4
|
+
ready = false;
|
|
5
|
+
eventQueue = [];
|
|
8
6
|
on(eventName, callback) {
|
|
9
7
|
if (this.destroyed) {
|
|
10
8
|
return;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare class StructuredError extends Error {
|
|
2
|
+
causedBy: StructuredError | null;
|
|
3
|
+
stack: string | undefined;
|
|
4
|
+
constructor(message: string | Error, cause: StructuredError | Error | null);
|
|
5
|
+
toString(depth?: number): string;
|
|
6
|
+
log(depth?: number): void;
|
|
7
|
+
private formatLines;
|
|
8
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export class StructuredError extends Error {
|
|
2
|
+
causedBy;
|
|
3
|
+
stack;
|
|
4
|
+
constructor(message, cause) {
|
|
5
|
+
super(typeof message === 'string' ? message : message.message);
|
|
6
|
+
if (message instanceof Error) {
|
|
7
|
+
this.stack = message.stack;
|
|
8
|
+
}
|
|
9
|
+
if (cause === null) {
|
|
10
|
+
this.causedBy = null;
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
this.causedBy = cause instanceof StructuredError ? cause : new StructuredError(cause, null);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
toString(depth = 0) {
|
|
17
|
+
const errorParts = [this.message];
|
|
18
|
+
if (this.stack) {
|
|
19
|
+
errorParts.push('Stack trace:');
|
|
20
|
+
errorParts.push(this.stack);
|
|
21
|
+
}
|
|
22
|
+
if (this.causedBy !== null) {
|
|
23
|
+
errorParts.push('Originated from:');
|
|
24
|
+
errorParts.push(this.causedBy.toString(depth + 1));
|
|
25
|
+
}
|
|
26
|
+
return this.formatLines(errorParts.join('\n'), depth);
|
|
27
|
+
}
|
|
28
|
+
log(depth = 0) {
|
|
29
|
+
console.error(this.formatLines(this.toString(), depth));
|
|
30
|
+
}
|
|
31
|
+
formatLines(text, depth) {
|
|
32
|
+
const linesFormatted = [];
|
|
33
|
+
const lines = text.split('\n');
|
|
34
|
+
const prepend = ' '.repeat(depth);
|
|
35
|
+
lines.forEach((line) => {
|
|
36
|
+
linesFormatted.push(prepend + line);
|
|
37
|
+
});
|
|
38
|
+
return linesFormatted.join('\n');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -64,11 +64,10 @@ function createTsconfig() {
|
|
|
64
64
|
"moduleResolution": "node16",
|
|
65
65
|
"outDir": "./build",
|
|
66
66
|
"module": "Node16",
|
|
67
|
-
"target": "
|
|
67
|
+
"target": "es2022",
|
|
68
68
|
"allowSyntheticDefaultImports": true,
|
|
69
69
|
"preserveSymlinks": true,
|
|
70
70
|
"removeComments": true,
|
|
71
|
-
"baseUrl": ".",
|
|
72
71
|
"rootDir": ".",
|
|
73
72
|
paths
|
|
74
73
|
},
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { DataStore } from './DataStore.js';
|
|
2
2
|
import { ClientComponent } from './ClientComponent.js';
|
|
3
3
|
export class ClientApplication {
|
|
4
|
+
root;
|
|
5
|
+
store = new DataStore();
|
|
6
|
+
initializers = {};
|
|
4
7
|
constructor() {
|
|
5
|
-
this.store = new DataStore();
|
|
6
|
-
this.initializers = {};
|
|
7
8
|
this.loadInitializers();
|
|
8
9
|
this.root = new ClientComponent(null, 'root', document.body, this);
|
|
9
10
|
}
|
|
@@ -4,21 +4,29 @@ import { Net } from './Net.js';
|
|
|
4
4
|
import { NetRequest } from './NetRequest.js';
|
|
5
5
|
import { EventEmitter } from '../EventEmitter.js';
|
|
6
6
|
export class ClientComponent extends EventEmitter {
|
|
7
|
+
name;
|
|
8
|
+
children = [];
|
|
9
|
+
parent;
|
|
10
|
+
domNode;
|
|
11
|
+
isRoot;
|
|
12
|
+
root;
|
|
13
|
+
store;
|
|
14
|
+
app;
|
|
15
|
+
net = new Net();
|
|
16
|
+
initializerExecuted = false;
|
|
17
|
+
fn;
|
|
18
|
+
destroyed = false;
|
|
19
|
+
redrawRequest = null;
|
|
20
|
+
bound = [];
|
|
21
|
+
conditionals = [];
|
|
22
|
+
conditionalCallbacks = {};
|
|
23
|
+
conditionalClassNames = [];
|
|
24
|
+
refs = {};
|
|
25
|
+
refsArray = {};
|
|
26
|
+
isReady = false;
|
|
27
|
+
data = {};
|
|
7
28
|
constructor(parent, name, domNode, app) {
|
|
8
29
|
super();
|
|
9
|
-
this.children = [];
|
|
10
|
-
this.net = new Net();
|
|
11
|
-
this.initializerExecuted = false;
|
|
12
|
-
this.destroyed = false;
|
|
13
|
-
this.redrawRequest = null;
|
|
14
|
-
this.bound = [];
|
|
15
|
-
this.conditionals = [];
|
|
16
|
-
this.conditionalCallbacks = {};
|
|
17
|
-
this.conditionalClassNames = [];
|
|
18
|
-
this.refs = {};
|
|
19
|
-
this.refsArray = {};
|
|
20
|
-
this.isReady = false;
|
|
21
|
-
this.data = {};
|
|
22
30
|
this.name = name;
|
|
23
31
|
this.domNode = domNode;
|
|
24
32
|
if (parent === null) {
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { equalDeep } from '../Util.js';
|
|
2
2
|
export class DataStore {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
this.changeListeners = {};
|
|
6
|
-
}
|
|
3
|
+
data = {};
|
|
4
|
+
changeListeners = {};
|
|
7
5
|
set(component, key, val, force = false, triggerListeners = true) {
|
|
8
6
|
const componentId = component.getData('componentId');
|
|
9
7
|
const oldValue = this.get(componentId, key);
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
export class NetRequest {
|
|
2
|
+
xhr = new XMLHttpRequest();
|
|
3
|
+
method;
|
|
4
|
+
url;
|
|
5
|
+
headers;
|
|
6
|
+
responseType;
|
|
7
|
+
body;
|
|
8
|
+
requestSent = false;
|
|
2
9
|
constructor(method, url, headers = {}, responseType = 'text', body) {
|
|
3
|
-
this.xhr = new XMLHttpRequest();
|
|
4
|
-
this.requestSent = false;
|
|
5
10
|
this.method = method;
|
|
6
11
|
this.url = url;
|
|
7
12
|
this.headers = headers;
|
|
@@ -25,7 +25,7 @@ export declare class Application {
|
|
|
25
25
|
constructor(config: StructuredConfig);
|
|
26
26
|
init(): Promise<void>;
|
|
27
27
|
private start;
|
|
28
|
-
on<E extends ApplicationEvents>(evt: E, callback: (payload: E extends 'beforeRequestHandler' | 'afterRequestHandler' | 'beforeAssetAccess' | 'afterAssetAccess' | 'pageNotFound' ? RequestContext : E extends 'documentCreated' ? Document : E extends 'afterComponentsLoaded' ? Components : E extends 'serverStarted' ? Server : undefined) => void): void;
|
|
28
|
+
on<E extends ApplicationEvents>(evt: E, callback: (payload: E extends 'beforeRequestHandler' | 'afterRequestHandler' | 'beforeAssetAccess' | 'afterAssetAccess' | 'pageNotFound' | 'requestHandleError' ? RequestContext<RequestContextData> : E extends 'documentCreated' ? Document : E extends 'afterComponentsLoaded' ? Components : E extends 'serverStarted' ? Server : undefined) => void): void;
|
|
29
29
|
emit(eventName: ApplicationEvents, payload?: any): Promise<Array<any>>;
|
|
30
30
|
importEnv<T extends LooseObject>(smartPrimitives?: boolean): T;
|
|
31
31
|
exportContextFields(...fields: Array<keyof RequestContextData>): void;
|
|
@@ -11,14 +11,19 @@ import { Request } from './Request.js';
|
|
|
11
11
|
import { Handlebars } from './Handlebars.js';
|
|
12
12
|
import { Cookies } from './Cookies.js';
|
|
13
13
|
export class Application {
|
|
14
|
+
config;
|
|
15
|
+
initialized = false;
|
|
16
|
+
server = null;
|
|
17
|
+
listening = false;
|
|
18
|
+
eventEmitter = new EventEmitter();
|
|
19
|
+
cookies;
|
|
20
|
+
session;
|
|
21
|
+
request;
|
|
22
|
+
components;
|
|
23
|
+
handlebars = new Handlebars();
|
|
24
|
+
exportedRequestContextData = [];
|
|
25
|
+
data = {};
|
|
14
26
|
constructor(config) {
|
|
15
|
-
this.initialized = false;
|
|
16
|
-
this.server = null;
|
|
17
|
-
this.listening = false;
|
|
18
|
-
this.eventEmitter = new EventEmitter();
|
|
19
|
-
this.handlebars = new Handlebars();
|
|
20
|
-
this.exportedRequestContextData = [];
|
|
21
|
-
this.data = {};
|
|
22
27
|
this.config = config;
|
|
23
28
|
this.cookies = new Cookies();
|
|
24
29
|
this.session = new Session(this);
|
|
@@ -2,14 +2,22 @@ import { Document } from './Document.js';
|
|
|
2
2
|
import { attributeValueFromString, attributeValueToString, objectEach, toCamelCase } from '../Util.js';
|
|
3
3
|
import { DOMFragment } from './dom/DOMFragment.js';
|
|
4
4
|
import { EventEmitter } from '../EventEmitter.js';
|
|
5
|
+
import { StructuredError } from '../StructuredError.js';
|
|
5
6
|
export class Component extends EventEmitter {
|
|
7
|
+
id;
|
|
8
|
+
name;
|
|
9
|
+
document;
|
|
10
|
+
parent;
|
|
11
|
+
children = [];
|
|
12
|
+
path = [];
|
|
13
|
+
attributesRaw = {};
|
|
14
|
+
attributes = {};
|
|
15
|
+
dom;
|
|
16
|
+
data = {};
|
|
17
|
+
entry;
|
|
18
|
+
isRoot;
|
|
6
19
|
constructor(name, node, parent, autoInit = true) {
|
|
7
20
|
super();
|
|
8
|
-
this.children = [];
|
|
9
|
-
this.path = [];
|
|
10
|
-
this.attributesRaw = {};
|
|
11
|
-
this.attributes = {};
|
|
12
|
-
this.data = {};
|
|
13
21
|
const isDocument = this instanceof Document;
|
|
14
22
|
this.name = name;
|
|
15
23
|
this.emitterReady();
|
|
@@ -96,7 +104,7 @@ export class Component extends EventEmitter {
|
|
|
96
104
|
await this.entry.serverPart.getData(Object.assign(importedParentData, this.attributes, data || {}), this.document.ctx, this.document.application, this) : {}) || {};
|
|
97
105
|
}
|
|
98
106
|
catch (e) {
|
|
99
|
-
throw new
|
|
107
|
+
throw new StructuredError(`Error executing getData in component ${this.name}`, e);
|
|
100
108
|
}
|
|
101
109
|
if (data === undefined) {
|
|
102
110
|
if (this.entry && this.entry.hasServerPart) {
|
|
@@ -271,7 +279,7 @@ export class Component extends EventEmitter {
|
|
|
271
279
|
this.dom.innerHTML = this.document.application.handlebars.compile(html, data);
|
|
272
280
|
}
|
|
273
281
|
catch (e) {
|
|
274
|
-
throw new
|
|
282
|
+
throw new StructuredError(`Error compiling Handlebars template in component ${this.name}`, e);
|
|
275
283
|
}
|
|
276
284
|
}
|
|
277
285
|
}
|
|
@@ -2,9 +2,10 @@ import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
|
2
2
|
import * as path from 'node:path';
|
|
3
3
|
import { stripBOM } from '../Util.js';
|
|
4
4
|
export class Components {
|
|
5
|
+
config;
|
|
6
|
+
components = {};
|
|
7
|
+
componentNames = [];
|
|
5
8
|
constructor(app) {
|
|
6
|
-
this.components = {};
|
|
7
|
-
this.componentNames = [];
|
|
8
9
|
this.config = app.config;
|
|
9
10
|
}
|
|
10
11
|
loadComponents(relativeToPath) {
|
|
@@ -5,15 +5,18 @@ import path from 'node:path';
|
|
|
5
5
|
import { existsSync, readFileSync } from 'node:fs';
|
|
6
6
|
import { randomUUID } from 'node:crypto';
|
|
7
7
|
export class Document extends Component {
|
|
8
|
+
head;
|
|
9
|
+
language = 'en';
|
|
10
|
+
application;
|
|
11
|
+
htmlTagAttributes = {};
|
|
12
|
+
bodyTagAttributes = {};
|
|
13
|
+
initializers = {};
|
|
14
|
+
initializersInitialized = false;
|
|
15
|
+
componentIds = [];
|
|
16
|
+
ctx;
|
|
17
|
+
appendHTML = '';
|
|
8
18
|
constructor(app, title, ctx) {
|
|
9
19
|
super('root');
|
|
10
|
-
this.language = 'en';
|
|
11
|
-
this.htmlTagAttributes = {};
|
|
12
|
-
this.bodyTagAttributes = {};
|
|
13
|
-
this.initializers = {};
|
|
14
|
-
this.initializersInitialized = false;
|
|
15
|
-
this.componentIds = [];
|
|
16
|
-
this.appendHTML = '';
|
|
17
20
|
this.application = app;
|
|
18
21
|
this.ctx = ctx;
|
|
19
22
|
this.document = this;
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
export class DocumentHead {
|
|
2
|
+
title;
|
|
3
|
+
js = [];
|
|
4
|
+
css = [];
|
|
5
|
+
custom = [];
|
|
6
|
+
charset = 'UTF-8';
|
|
7
|
+
favicon = {
|
|
8
|
+
image: null,
|
|
9
|
+
type: 'image/png'
|
|
10
|
+
};
|
|
2
11
|
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
12
|
this.title = title;
|
|
12
13
|
}
|
|
13
14
|
setTitle(title) {
|
|
@@ -1,132 +1,130 @@
|
|
|
1
1
|
export class FormValidation {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
2
|
+
fieldRules = [];
|
|
3
|
+
singleError = false;
|
|
4
|
+
customValidators = [];
|
|
5
|
+
validators = {
|
|
6
|
+
'required': async (data, field) => {
|
|
7
|
+
if (!(field in data)) {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
const value = data[field];
|
|
11
|
+
if (value === null ||
|
|
12
|
+
value === undefined ||
|
|
13
|
+
(typeof value === 'string' && value.trim().length === 0)) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
return true;
|
|
17
|
+
},
|
|
18
|
+
'number': async (data, field) => {
|
|
19
|
+
const value = data[field];
|
|
20
|
+
if (typeof value === 'number') {
|
|
17
21
|
return true;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const value = data[field];
|
|
21
|
-
if (typeof value === 'number') {
|
|
22
|
-
return true;
|
|
23
|
-
}
|
|
24
|
-
if (typeof value !== 'string') {
|
|
25
|
-
return false;
|
|
26
|
-
}
|
|
27
|
-
return /^-?\d+$/.test(value);
|
|
28
|
-
},
|
|
29
|
-
'float': async (data, field) => {
|
|
30
|
-
const value = data[field];
|
|
31
|
-
if (typeof value === 'number') {
|
|
32
|
-
return true;
|
|
33
|
-
}
|
|
34
|
-
if (typeof value !== 'string') {
|
|
35
|
-
return false;
|
|
36
|
-
}
|
|
37
|
-
return /^-?\d+\.\d+$/.test(value);
|
|
38
|
-
},
|
|
39
|
-
'numeric': async (data, field, arg, rules) => {
|
|
40
|
-
return await this.validators['number'](data, field, arg, rules) || await this.validators['float'](data, field, arg, rules);
|
|
41
|
-
},
|
|
42
|
-
'min': async (data, field, arg, rules) => {
|
|
43
|
-
const value = data[field];
|
|
44
|
-
if (typeof value === 'number') {
|
|
45
|
-
return value >= arg;
|
|
46
|
-
}
|
|
47
|
-
if (typeof value !== 'string') {
|
|
48
|
-
return false;
|
|
49
|
-
}
|
|
50
|
-
if (await this.validators['numeric'](data, field, arg, rules)) {
|
|
51
|
-
return parseFloat(value) >= arg;
|
|
52
|
-
}
|
|
22
|
+
}
|
|
23
|
+
if (typeof value !== 'string') {
|
|
53
24
|
return false;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (await this.validators['numeric'](data, field, arg, rules)) {
|
|
64
|
-
return parseFloat(value) <= arg;
|
|
65
|
-
}
|
|
25
|
+
}
|
|
26
|
+
return /^-?\d+$/.test(value);
|
|
27
|
+
},
|
|
28
|
+
'float': async (data, field) => {
|
|
29
|
+
const value = data[field];
|
|
30
|
+
if (typeof value === 'number') {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
if (typeof value !== 'string') {
|
|
66
34
|
return false;
|
|
67
|
-
},
|
|
68
|
-
'minLength': async (data, field, arg) => {
|
|
69
|
-
const value = data[field];
|
|
70
|
-
if (typeof value !== 'string') {
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
return value.length >= arg;
|
|
74
|
-
},
|
|
75
|
-
'maxLength': async (data, field, arg) => {
|
|
76
|
-
const value = data[field];
|
|
77
|
-
if (typeof value !== 'string') {
|
|
78
|
-
return false;
|
|
79
|
-
}
|
|
80
|
-
return value.length <= arg;
|
|
81
|
-
},
|
|
82
|
-
'alphanumeric': async (data, field) => {
|
|
83
|
-
const value = data[field];
|
|
84
|
-
if (typeof value !== 'string') {
|
|
85
|
-
return false;
|
|
86
|
-
}
|
|
87
|
-
return /^[a-zA-Z0-9]+$/.test(value);
|
|
88
|
-
},
|
|
89
|
-
'validEmail': async (data, field) => {
|
|
90
|
-
const value = data[field];
|
|
91
|
-
if (typeof value !== 'string') {
|
|
92
|
-
return false;
|
|
93
|
-
}
|
|
94
|
-
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);
|
|
95
35
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
return `${fieldHuman} has to be a decimal number`;
|
|
106
|
-
},
|
|
107
|
-
'numeric': (fieldHuman) => {
|
|
108
|
-
return `${fieldHuman} has to contain a numeric value`;
|
|
109
|
-
},
|
|
110
|
-
'min': (fieldHuman, data, field, arg) => {
|
|
111
|
-
return `${fieldHuman} has to be a value greater than ${arg}`;
|
|
112
|
-
},
|
|
113
|
-
'max': (fieldHuman, data, field, arg) => {
|
|
114
|
-
return `${fieldHuman} has to be a value lower than ${arg}`;
|
|
115
|
-
},
|
|
116
|
-
'minLength': (fieldHuman, data, field, arg) => {
|
|
117
|
-
return `${fieldHuman} has to contain at least ${arg} characters`;
|
|
118
|
-
},
|
|
119
|
-
'maxLength': (fieldHuman, data, field, arg) => {
|
|
120
|
-
return `${fieldHuman} has to contain no more than ${arg} characters`;
|
|
121
|
-
},
|
|
122
|
-
'alphanumeric': (fieldHuman) => {
|
|
123
|
-
return `${fieldHuman} can contain only letter and numbers`;
|
|
124
|
-
},
|
|
125
|
-
'validEmail': () => {
|
|
126
|
-
return `Please enter a valid email address`;
|
|
36
|
+
return /^-?\d+\.\d+$/.test(value);
|
|
37
|
+
},
|
|
38
|
+
'numeric': async (data, field, arg, rules) => {
|
|
39
|
+
return await this.validators['number'](data, field, arg, rules) || await this.validators['float'](data, field, arg, rules);
|
|
40
|
+
},
|
|
41
|
+
'min': async (data, field, arg, rules) => {
|
|
42
|
+
const value = data[field];
|
|
43
|
+
if (typeof value === 'number') {
|
|
44
|
+
return value >= arg;
|
|
127
45
|
}
|
|
128
|
-
|
|
129
|
-
|
|
46
|
+
if (typeof value !== 'string') {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
if (await this.validators['numeric'](data, field, arg, rules)) {
|
|
50
|
+
return parseFloat(value) >= arg;
|
|
51
|
+
}
|
|
52
|
+
return false;
|
|
53
|
+
},
|
|
54
|
+
'max': async (data, field, arg, rules) => {
|
|
55
|
+
const value = data[field];
|
|
56
|
+
if (typeof value === 'number') {
|
|
57
|
+
return value <= arg;
|
|
58
|
+
}
|
|
59
|
+
if (typeof value !== 'string') {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
if (await this.validators['numeric'](data, field, arg, rules)) {
|
|
63
|
+
return parseFloat(value) <= arg;
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
},
|
|
67
|
+
'minLength': async (data, field, arg) => {
|
|
68
|
+
const value = data[field];
|
|
69
|
+
if (typeof value !== 'string') {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
return value.length >= arg;
|
|
73
|
+
},
|
|
74
|
+
'maxLength': async (data, field, arg) => {
|
|
75
|
+
const value = data[field];
|
|
76
|
+
if (typeof value !== 'string') {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
return value.length <= arg;
|
|
80
|
+
},
|
|
81
|
+
'alphanumeric': async (data, field) => {
|
|
82
|
+
const value = data[field];
|
|
83
|
+
if (typeof value !== 'string') {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
return /^[a-zA-Z0-9]+$/.test(value);
|
|
87
|
+
},
|
|
88
|
+
'validEmail': async (data, field) => {
|
|
89
|
+
const value = data[field];
|
|
90
|
+
if (typeof value !== 'string') {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
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);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
decorators = {
|
|
97
|
+
'required': (fieldHuman) => {
|
|
98
|
+
return `${fieldHuman} is required`;
|
|
99
|
+
},
|
|
100
|
+
'number': (fieldHuman) => {
|
|
101
|
+
return `${fieldHuman} has to be a whole number`;
|
|
102
|
+
},
|
|
103
|
+
'float': (fieldHuman) => {
|
|
104
|
+
return `${fieldHuman} has to be a decimal number`;
|
|
105
|
+
},
|
|
106
|
+
'numeric': (fieldHuman) => {
|
|
107
|
+
return `${fieldHuman} has to contain a numeric value`;
|
|
108
|
+
},
|
|
109
|
+
'min': (fieldHuman, data, field, arg) => {
|
|
110
|
+
return `${fieldHuman} has to be a value greater than ${arg}`;
|
|
111
|
+
},
|
|
112
|
+
'max': (fieldHuman, data, field, arg) => {
|
|
113
|
+
return `${fieldHuman} has to be a value lower than ${arg}`;
|
|
114
|
+
},
|
|
115
|
+
'minLength': (fieldHuman, data, field, arg) => {
|
|
116
|
+
return `${fieldHuman} has to contain at least ${arg} characters`;
|
|
117
|
+
},
|
|
118
|
+
'maxLength': (fieldHuman, data, field, arg) => {
|
|
119
|
+
return `${fieldHuman} has to contain no more than ${arg} characters`;
|
|
120
|
+
},
|
|
121
|
+
'alphanumeric': (fieldHuman) => {
|
|
122
|
+
return `${fieldHuman} can contain only letter and numbers`;
|
|
123
|
+
},
|
|
124
|
+
'validEmail': () => {
|
|
125
|
+
return `Please enter a valid email address`;
|
|
126
|
+
}
|
|
127
|
+
};
|
|
130
128
|
addRule(fieldName, nameHumanReadable, rules) {
|
|
131
129
|
const rule = {
|
|
132
130
|
field: [fieldName, nameHumanReadable],
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { default as HandlebarsInstance } from 'handlebars';
|
|
2
2
|
export class Handlebars {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
this.helpers = {};
|
|
6
|
-
}
|
|
3
|
+
instance = HandlebarsInstance;
|
|
4
|
+
helpers = {};
|
|
7
5
|
register(name, helper) {
|
|
8
6
|
this.helpers[name] = helper;
|
|
9
7
|
this.instance.registerHelper(name, helper);
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { Component } from "./Component.js";
|
|
2
2
|
import { Document } from "./Document.js";
|
|
3
3
|
export class Layout {
|
|
4
|
+
layoutComponent;
|
|
5
|
+
app;
|
|
6
|
+
language;
|
|
7
|
+
attributes;
|
|
4
8
|
constructor(app, layoutComponent, language = 'en', attributes) {
|
|
5
9
|
this.app = app;
|
|
6
10
|
this.layoutComponent = layoutComponent;
|
|
@@ -2,10 +2,8 @@ import { IncomingMessage, ServerResponse } from "node:http";
|
|
|
2
2
|
import { LooseObject } from '../types/general.types.js';
|
|
3
3
|
import { RequestMethod, RequestCallback } from "../types/request.types.js";
|
|
4
4
|
import { Application } from "./Application.js";
|
|
5
|
-
import { Document } from "./Document.js";
|
|
6
5
|
export declare class Request {
|
|
7
6
|
private app;
|
|
8
|
-
pageNotFoundCallback: RequestCallback<void | Document, LooseObject | undefined>;
|
|
9
7
|
constructor(app: Application);
|
|
10
8
|
private readonly handlers;
|
|
11
9
|
on<R extends any, Body extends LooseObject | undefined = LooseObject>(methods: RequestMethod | Array<RequestMethod>, pattern: string | RegExp | Array<string | RegExp>, callback: RequestCallback<R, Body>, scope?: any, isStaticAsset?: boolean): void;
|
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { RequestContext } from "./RequestContext.js";
|
|
4
|
+
import { StructuredError } from "../StructuredError.js";
|
|
4
5
|
export class Request {
|
|
6
|
+
app;
|
|
5
7
|
constructor(app) {
|
|
6
|
-
this.pageNotFoundCallback = async ({ response }) => {
|
|
7
|
-
response.statusCode = 404;
|
|
8
|
-
response.write('Page not found');
|
|
9
|
-
response.end();
|
|
10
|
-
};
|
|
11
|
-
this.handlers = [];
|
|
12
8
|
this.app = app;
|
|
13
9
|
}
|
|
10
|
+
handlers = [];
|
|
14
11
|
on(methods, pattern, callback, scope, isStaticAsset = false) {
|
|
15
12
|
if (!(methods instanceof Array)) {
|
|
16
13
|
methods = [methods];
|
|
@@ -92,7 +89,18 @@ export class Request {
|
|
|
92
89
|
uri = uriParts[0];
|
|
93
90
|
}
|
|
94
91
|
const handler = this.getHandler(uri, requestMethod);
|
|
95
|
-
|
|
92
|
+
try {
|
|
93
|
+
const ctx = new RequestContext(this.app, request, response, handler);
|
|
94
|
+
await ctx.exec();
|
|
95
|
+
}
|
|
96
|
+
catch (e) {
|
|
97
|
+
if (e instanceof StructuredError) {
|
|
98
|
+
e.log();
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
throw e;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
96
104
|
}
|
|
97
105
|
patternToSegments(pattern) {
|
|
98
106
|
const segments = [];
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
-
import { LooseObject, PostedDataDecoded, RequestBodyRecordValue,
|
|
2
|
+
import { LooseObject, PostedDataDecoded, RequestBodyRecordValue, RequestHandler, URIArguments } from "../Types.js";
|
|
3
3
|
import { Application } from "./Application.js";
|
|
4
4
|
import { Document } from "./Document.js";
|
|
5
5
|
import { Layout } from "./Layout.js";
|
|
6
6
|
export declare class RequestContext<Body extends LooseObject | undefined = LooseObject> {
|
|
7
|
+
private executionStartedAt;
|
|
8
|
+
private executionCompletedAt;
|
|
7
9
|
readonly app: Application;
|
|
8
10
|
uri: string;
|
|
9
|
-
private readonly pageNotFoundCallback;
|
|
10
11
|
private readonly handler;
|
|
11
12
|
readonly request: IncomingMessage;
|
|
12
13
|
readonly response: ServerResponse;
|
|
@@ -20,8 +21,8 @@ export declare class RequestContext<Body extends LooseObject | undefined = Loose
|
|
|
20
21
|
getArgs: PostedDataDecoded;
|
|
21
22
|
readonly timeStart: number;
|
|
22
23
|
private streamingData;
|
|
23
|
-
constructor(app: Application, request: IncomingMessage, response: ServerResponse, handler: RequestHandler | null
|
|
24
|
-
|
|
24
|
+
constructor(app: Application, request: IncomingMessage, response: ServerResponse, handler: RequestHandler | null);
|
|
25
|
+
exec(): Promise<void>;
|
|
25
26
|
respondWith(data: any): Promise<void>;
|
|
26
27
|
private sendResponse;
|
|
27
28
|
createDocument(title: string, component: string, data?: LooseObject): Promise<Document>;
|
|
@@ -37,5 +38,6 @@ export declare class RequestContext<Body extends LooseObject | undefined = Loose
|
|
|
37
38
|
private extractURIArguments;
|
|
38
39
|
private handle;
|
|
39
40
|
isAjax(): boolean;
|
|
40
|
-
|
|
41
|
+
duration(): number;
|
|
42
|
+
complete(): boolean;
|
|
41
43
|
}
|
|
@@ -3,13 +3,26 @@ import { mergeDeep, queryStringDecode, queryStringDecodedSetValue } from "../Uti
|
|
|
3
3
|
import { Document } from "./Document.js";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { existsSync, readFileSync, ReadStream } from "node:fs";
|
|
6
|
+
import { StructuredError } from "../StructuredError.js";
|
|
6
7
|
export class RequestContext {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
executionStartedAt = null;
|
|
9
|
+
executionCompletedAt = null;
|
|
10
|
+
app;
|
|
11
|
+
uri;
|
|
12
|
+
handler;
|
|
13
|
+
request;
|
|
14
|
+
response;
|
|
15
|
+
args = {};
|
|
16
|
+
cookies = {};
|
|
17
|
+
body;
|
|
18
|
+
bodyRaw;
|
|
19
|
+
files;
|
|
20
|
+
data = {};
|
|
21
|
+
sessionId;
|
|
22
|
+
getArgs = {};
|
|
23
|
+
timeStart;
|
|
24
|
+
streamingData = false;
|
|
25
|
+
constructor(app, request, response, handler) {
|
|
13
26
|
this.timeStart = Date.now();
|
|
14
27
|
this.uri = request.url || '/';
|
|
15
28
|
this.app = app;
|
|
@@ -17,21 +30,31 @@ export class RequestContext {
|
|
|
17
30
|
this.response = response;
|
|
18
31
|
this.handler = handler;
|
|
19
32
|
this.body = undefined;
|
|
20
|
-
this.pageNotFoundCallback = pageNotFoundCallback;
|
|
21
|
-
this.exec();
|
|
22
33
|
}
|
|
23
34
|
async exec() {
|
|
24
|
-
this.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
35
|
+
if (this.executionStartedAt !== null) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
this.executionStartedAt = Date.now();
|
|
39
|
+
try {
|
|
40
|
+
this.initGetArgs();
|
|
41
|
+
this.parseCookies();
|
|
42
|
+
if (this.handler) {
|
|
28
43
|
await this.parseBody();
|
|
29
44
|
}
|
|
30
|
-
|
|
31
|
-
|
|
45
|
+
await this.handle();
|
|
46
|
+
this.executionCompletedAt = Date.now();
|
|
47
|
+
}
|
|
48
|
+
catch (e) {
|
|
49
|
+
const res = await this.app.emit('requestHandleError', this);
|
|
50
|
+
if (res.length > 0) {
|
|
51
|
+
if (!!res[0]) {
|
|
52
|
+
await this.respondWith(res[0]);
|
|
53
|
+
}
|
|
32
54
|
}
|
|
55
|
+
this.response.end();
|
|
56
|
+
throw new StructuredError(`Error in request to ${this.uri}`, e);
|
|
33
57
|
}
|
|
34
|
-
await this.handle();
|
|
35
58
|
}
|
|
36
59
|
async respondWith(data) {
|
|
37
60
|
if (typeof data === 'string' || Buffer.isBuffer(data)) {
|
|
@@ -91,11 +114,10 @@ export class RequestContext {
|
|
|
91
114
|
return await layout.document(this, title, component, data, attributes);
|
|
92
115
|
}
|
|
93
116
|
async show404() {
|
|
94
|
-
this.app.emit('pageNotFound', this);
|
|
95
117
|
this.response.statusCode = 404;
|
|
96
|
-
const res = await this.
|
|
97
|
-
if (res
|
|
98
|
-
await this.respondWith(res);
|
|
118
|
+
const res = await this.app.emit('pageNotFound', this);
|
|
119
|
+
if (res.length > 0 && !!res[0]) {
|
|
120
|
+
await this.respondWith(res[0]);
|
|
99
121
|
}
|
|
100
122
|
}
|
|
101
123
|
initGetArgs() {
|
|
@@ -139,11 +161,7 @@ export class RequestContext {
|
|
|
139
161
|
this.body = queryStringDecode(bodyRaw);
|
|
140
162
|
}
|
|
141
163
|
catch (e) {
|
|
142
|
-
|
|
143
|
-
Raw data:
|
|
144
|
-
${bodyRaw}
|
|
145
|
-
|
|
146
|
-
`);
|
|
164
|
+
throw new StructuredError(`Error parsing urlencoded request body, raw data: ${bodyRaw}`, e);
|
|
147
165
|
}
|
|
148
166
|
}
|
|
149
167
|
else if (this.request.headers['content-type'].indexOf('multipart/form-data') > -1) {
|
|
@@ -154,21 +172,13 @@ export class RequestContext {
|
|
|
154
172
|
this.body = this.parseBodyMultipart(this.bodyRaw.toString('utf-8'), boundary);
|
|
155
173
|
}
|
|
156
174
|
catch (e) {
|
|
157
|
-
|
|
158
|
-
Raw data:
|
|
159
|
-
${this.bodyRaw.toString('utf-8')}
|
|
160
|
-
|
|
161
|
-
`);
|
|
175
|
+
throw new StructuredError(`Error parsing multipart request body, raw data: ${this.bodyRaw.toString('utf-8')}`, e);
|
|
162
176
|
}
|
|
163
177
|
try {
|
|
164
178
|
this.files = this.multipartBodyFiles(this.bodyRaw.toString('binary'), boundary);
|
|
165
179
|
}
|
|
166
180
|
catch (e) {
|
|
167
|
-
|
|
168
|
-
Raw data:
|
|
169
|
-
${this.bodyRaw.toString('utf-8')}
|
|
170
|
-
|
|
171
|
-
`);
|
|
181
|
+
throw new StructuredError(`Error parsing multipart request body files, raw data ${this.bodyRaw.toString('utf-8')}`, e);
|
|
172
182
|
}
|
|
173
183
|
}
|
|
174
184
|
}
|
|
@@ -177,7 +187,7 @@ export class RequestContext {
|
|
|
177
187
|
this.body = JSON.parse(this.bodyRaw.toString());
|
|
178
188
|
}
|
|
179
189
|
catch (e) {
|
|
180
|
-
this.
|
|
190
|
+
throw new StructuredError(`Error parsing JSON request body, raw data: ${this.bodyRaw.toString('utf-8')}`, e);
|
|
181
191
|
}
|
|
182
192
|
}
|
|
183
193
|
}
|
|
@@ -263,7 +273,7 @@ export class RequestContext {
|
|
|
263
273
|
}
|
|
264
274
|
}
|
|
265
275
|
catch (e) {
|
|
266
|
-
|
|
276
|
+
throw new StructuredError(`Error executing request handler ${this.handler.callback.name}`, e);
|
|
267
277
|
}
|
|
268
278
|
if (!this.handler.staticAsset) {
|
|
269
279
|
await this.app.emit('afterRequestHandler', this);
|
|
@@ -300,8 +310,13 @@ export class RequestContext {
|
|
|
300
310
|
isAjax() {
|
|
301
311
|
return this.request.headers['x-requested-with'] == 'xmlhttprequest';
|
|
302
312
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
313
|
+
duration() {
|
|
314
|
+
if (this.executionStartedAt === null || this.executionCompletedAt === null) {
|
|
315
|
+
return 0;
|
|
316
|
+
}
|
|
317
|
+
return this.executionCompletedAt - this.executionStartedAt;
|
|
318
|
+
}
|
|
319
|
+
complete() {
|
|
320
|
+
return this.executionCompletedAt !== null;
|
|
306
321
|
}
|
|
307
322
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { randomString } from '../Util.js';
|
|
2
2
|
export class Session {
|
|
3
|
+
application;
|
|
4
|
+
enabled = false;
|
|
5
|
+
sessions = {};
|
|
3
6
|
constructor(app) {
|
|
4
|
-
this.enabled = false;
|
|
5
|
-
this.sessions = {};
|
|
6
7
|
this.application = app;
|
|
7
8
|
this.application.on('beforeRequestHandler', async (ctx) => {
|
|
8
9
|
if (this.enabled) {
|
|
@@ -7,14 +7,18 @@ export const recognizedHTMLTags = [
|
|
|
7
7
|
'svg', 'g', 'text', 'path', 'circle', 'clipPath', 'defs', 'ellipse', 'rect', 'polygon', 'image', 'style',
|
|
8
8
|
];
|
|
9
9
|
export class DOMNode {
|
|
10
|
+
tagName;
|
|
11
|
+
root;
|
|
12
|
+
parentNode = null;
|
|
13
|
+
children = [];
|
|
14
|
+
isRoot;
|
|
15
|
+
attributes = [];
|
|
16
|
+
attributeMap = {};
|
|
17
|
+
style = {};
|
|
18
|
+
selfClosing;
|
|
19
|
+
explicitSelfClosing = false;
|
|
20
|
+
potentialComponentChildren = [];
|
|
10
21
|
constructor(root, parentNode, tagName) {
|
|
11
|
-
this.parentNode = null;
|
|
12
|
-
this.children = [];
|
|
13
|
-
this.attributes = [];
|
|
14
|
-
this.attributeMap = {};
|
|
15
|
-
this.style = {};
|
|
16
|
-
this.explicitSelfClosing = false;
|
|
17
|
-
this.potentialComponentChildren = [];
|
|
18
22
|
this.root = root === null ? this : root;
|
|
19
23
|
this.isRoot = root === null;
|
|
20
24
|
this.parentNode = parentNode;
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { DOMFragment } from "./DOMFragment.js";
|
|
2
2
|
import { DOMNode } from "./DOMNode.js";
|
|
3
3
|
export class HTMLParser {
|
|
4
|
+
html;
|
|
5
|
+
offset = 0;
|
|
6
|
+
context;
|
|
7
|
+
state = 'idle';
|
|
8
|
+
tokenCurrent = '';
|
|
9
|
+
fragment = new DOMFragment();
|
|
10
|
+
explicitSelfClosing = false;
|
|
11
|
+
attributeOpenQuote = '"';
|
|
12
|
+
attributeNameCurrent = '';
|
|
13
|
+
attributeContext = null;
|
|
4
14
|
constructor(html) {
|
|
5
|
-
this.offset = 0;
|
|
6
|
-
this.state = 'idle';
|
|
7
|
-
this.tokenCurrent = '';
|
|
8
|
-
this.fragment = new DOMFragment();
|
|
9
|
-
this.explicitSelfClosing = false;
|
|
10
|
-
this.attributeOpenQuote = '"';
|
|
11
|
-
this.attributeNameCurrent = '';
|
|
12
|
-
this.attributeContext = null;
|
|
13
15
|
this.html = html;
|
|
14
16
|
this.context = this.fragment;
|
|
15
17
|
while (this.parse()) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export type ApplicationEvents = 'serverStarted' | 'beforeRequestHandler' | 'afterRequestHandler' | 'beforeRoutes' | 'afterRoutes' | 'beforeComponentsLoad' | 'afterComponentsLoaded' | 'documentCreated' | 'beforeAssetAccess' | 'afterAssetAccess' | 'pageNotFound';
|
|
1
|
+
export type ApplicationEvents = 'serverStarted' | 'beforeRequestHandler' | 'afterRequestHandler' | 'requestHandleError' | 'beforeRoutes' | 'afterRoutes' | 'beforeComponentsLoad' | 'afterComponentsLoaded' | 'documentCreated' | 'beforeAssetAccess' | 'afterAssetAccess' | 'pageNotFound';
|
package/package.json
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"license": "MIT",
|
|
20
20
|
"type": "module",
|
|
21
21
|
"main": "build/index",
|
|
22
|
-
"version": "1.
|
|
22
|
+
"version": "1.6.0",
|
|
23
23
|
"scripts": {
|
|
24
24
|
"develop": "tsc --watch",
|
|
25
25
|
"startDev": "cd build && nodemon --watch '../app/**/*' --watch '../build/**/*' -e js,html,hbs,css index.js",
|