structured-fw 1.4.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 +64 -23
- 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 +3 -2
- package/build/system/server/Application.js +19 -7
- package/build/system/server/Component.js +15 -7
- package/build/system/server/Components.js +3 -2
- package/build/system/server/Document.d.ts +1 -1
- 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.d.ts +2 -2
- package/build/system/server/Layout.js +4 -0
- package/build/system/server/Request.d.ts +1 -11
- package/build/system/server/Request.js +12 -269
- package/build/system/server/RequestContext.d.ts +43 -0
- package/build/system/server/RequestContext.js +322 -0
- 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/build/system/types/component.types.d.ts +1 -1
- package/build/system/types/request.types.d.ts +1 -19
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -29,6 +29,13 @@ npm init -y
|
|
|
29
29
|
npm install @types/node
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
+
## Set type to "module" in package.json
|
|
33
|
+
```
|
|
34
|
+
...
|
|
35
|
+
"type": "module",
|
|
36
|
+
...
|
|
37
|
+
```
|
|
38
|
+
|
|
32
39
|
*If you have TypeScript installed globally then you can skip the following*\
|
|
33
40
|
`npm install --save-dev typescript`
|
|
34
41
|
|
|
@@ -127,13 +134,14 @@ new Application(config);
|
|
|
127
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
|
|
128
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)
|
|
129
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
|
|
130
138
|
- `afterRoutes` - runs after all routes are loaded from `StructuredConfig.routes.path`. Callback receives no arguments
|
|
131
139
|
- `beforeComponentsLoad` - runs before components are loaded from `StructuredConfig.components.path`. Callback receives no arguments
|
|
132
140
|
- `afterComponentsLoaded` - runs after all components are loaded from `StructuredConfig.components.path`. Callback receives instance of Components as the first argument
|
|
133
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(...)`
|
|
134
142
|
- `beforeAssetAccess` - runs when assets are being accessed, before response is sent. Callback receives `RequestContext` as the first argument
|
|
135
143
|
- `afterAssetAccess` - runs when assets are being accessed, after response is sent. Callback receives `RequestContext` as the first argument
|
|
136
|
-
- `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
|
|
137
145
|
- **Callback to any of the `ApplicationEvents` is expected to be an async function**
|
|
138
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:
|
|
139
147
|
- numeric values -> `number`
|
|
@@ -258,44 +266,67 @@ Route file name has no effect on how the route (request handler) behaves, the on
|
|
|
258
266
|
RequestContext is created for every received request. It contains all the data related to the request, as well as data you include in `RequestContextData`.
|
|
259
267
|
All request handlers receive a `RequestContext` as the first argument. Your component server side part also receives `RequestContext` as the second argument.
|
|
260
268
|
```
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
269
|
+
class RequestContext<Body extends LooseObject | undefined = LooseObject> = {
|
|
270
|
+
app: Application;
|
|
271
|
+
uri: string;
|
|
272
|
+
|
|
273
|
+
request: IncomingMessage;
|
|
274
|
+
response: ServerResponse;
|
|
266
275
|
|
|
267
|
-
|
|
276
|
+
// captured URI arguments
|
|
277
|
+
// for example if the requested URI was /users/8 and the pattern was /users/(userId:num)
|
|
278
|
+
// args will be { userId: 8 }
|
|
279
|
+
args: URIArguments;
|
|
280
|
+
|
|
281
|
+
// request cookies parsed into an object
|
|
282
|
+
cookies: Record<string, string>;
|
|
268
283
|
|
|
269
284
|
// POSTed data, parsed to object
|
|
270
|
-
body?: LooseObject
|
|
285
|
+
body?: LooseObject;
|
|
271
286
|
|
|
272
|
-
bodyRaw?: Buffer
|
|
287
|
+
bodyRaw?: Buffer;
|
|
273
288
|
|
|
274
289
|
// files extracted from request body
|
|
275
|
-
files?: Record<string, RequestBodyRecordValue
|
|
290
|
+
files?: Record<string, RequestBodyRecordValue>;
|
|
276
291
|
|
|
277
292
|
// user defined data
|
|
278
|
-
data: RequestContextData
|
|
293
|
+
data: RequestContextData;
|
|
279
294
|
|
|
280
|
-
// if session is started and user has visited any page
|
|
281
|
-
sessionId?: string
|
|
295
|
+
// defined if session is started and user has visited any page
|
|
296
|
+
sessionId?: string;
|
|
282
297
|
|
|
283
|
-
// true if x-requested-with header is
|
|
284
|
-
isAjax: boolean
|
|
298
|
+
// true if x-requested-with header value is 'xmlhttprequest'
|
|
299
|
+
isAjax: () => boolean;
|
|
285
300
|
|
|
286
301
|
// time when request was received (unix timestamp in milliseconds)
|
|
287
|
-
timeStart: number
|
|
302
|
+
timeStart: number;
|
|
303
|
+
|
|
304
|
+
// URL GET arguments, for example if the URI isss /users?id=8
|
|
305
|
+
// getArgs would be { id: '8' }
|
|
306
|
+
getArgs: PostedDataDecoded;
|
|
307
|
+
|
|
308
|
+
// create a Document and load given component
|
|
309
|
+
createDocument: (title: string, component: string, data?: LooseObject) => Promise<Document>;
|
|
288
310
|
|
|
289
|
-
//
|
|
290
|
-
|
|
311
|
+
// create a Document using provided layout
|
|
312
|
+
layoutDocument: layoutDocument(
|
|
313
|
+
layout: Layout,
|
|
314
|
+
title: string,
|
|
315
|
+
component: string,
|
|
316
|
+
data?: LooseObject,
|
|
317
|
+
attributes?: Record<string, string>
|
|
318
|
+
) => Promise<Document>;
|
|
291
319
|
|
|
292
320
|
// send given data as a response
|
|
293
|
-
respondWith: (data: any) => Promise<void
|
|
321
|
+
respondWith: (data: any) => Promise<void>;
|
|
294
322
|
|
|
295
|
-
// redirect to given
|
|
296
|
-
redirect: (to: string, statusCode?: number) => void
|
|
323
|
+
// redirect to given URI, with given statusCode (default 302)
|
|
324
|
+
redirect: (to: string, statusCode?: number) => void;
|
|
297
325
|
|
|
298
|
-
// show a 404
|
|
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'); })
|
|
299
330
|
show404: () => Promise<void>
|
|
300
331
|
}
|
|
301
332
|
```
|
|
@@ -371,6 +402,15 @@ app.request.on('GET', '/home', async (ctx) => {
|
|
|
371
402
|
});
|
|
372
403
|
```
|
|
373
404
|
|
|
405
|
+
Above code could be simplified using RequestContext.createDocument method as:
|
|
406
|
+
```
|
|
407
|
+
app.request.on('GET', '/home', async (ctx) => {
|
|
408
|
+
return await ctx.createDocument('Home', 'Home');
|
|
409
|
+
});
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
Much shorter, but slightly confusing due to bad example (both arguments being 'Home'). First argument is the document title, second argument is the name of the component you want to load.
|
|
413
|
+
|
|
374
414
|
> [!TIP]
|
|
375
415
|
> Since version 0.8.4 Document extends EventEmitter, and "componentCreated" event is emitted whenever a component instance is created within the Document.\
|
|
376
416
|
> This makes the following possible:
|
|
@@ -445,7 +485,8 @@ That was the simplest possible example, let's make it more interesting by adding
|
|
|
445
485
|
Create a new file `/app/views/HelloWorld/HelloWorld.ts` (server side component code):
|
|
446
486
|
```
|
|
447
487
|
import { Application } from 'structured-fw/Application';
|
|
448
|
-
import { ComponentScaffold
|
|
488
|
+
import { ComponentScaffold } from 'structured-fw/Types';
|
|
489
|
+
import { RequestContext } from 'structured-fw/RequestContext';
|
|
449
490
|
|
|
450
491
|
type ComponentInput = {
|
|
451
492
|
name: string,
|
|
@@ -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;
|
|
@@ -2,13 +2,13 @@ import { Server } from 'node:http';
|
|
|
2
2
|
import { ApplicationEvents } from '../types/application.types.js';
|
|
3
3
|
import { StructuredConfig } from '../types/structured.types.js';
|
|
4
4
|
import { LooseObject } from '../types/general.types.js';
|
|
5
|
-
import { RequestContext } from "../types/request.types.js";
|
|
6
5
|
import { Document } from './Document.js';
|
|
7
6
|
import { Components } from './Components.js';
|
|
8
7
|
import { Session } from './Session.js';
|
|
9
8
|
import { Request } from './Request.js';
|
|
10
9
|
import { Handlebars } from './Handlebars.js';
|
|
11
10
|
import { Cookies } from './Cookies.js';
|
|
11
|
+
import { RequestContext } from './RequestContext.js';
|
|
12
12
|
export declare class Application {
|
|
13
13
|
readonly config: StructuredConfig;
|
|
14
14
|
initialized: boolean;
|
|
@@ -25,13 +25,14 @@ 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;
|
|
32
32
|
contentType(extension: string): string | false;
|
|
33
33
|
registerPlugin<Opt extends Readonly<LooseObject>>(callback: (app: Application, options: Opt) => void | Promise<void>, opts: NoInfer<Opt>): Promise<void>;
|
|
34
34
|
private respondWithComponent;
|
|
35
|
+
directory(uri?: string): string;
|
|
35
36
|
memoryUsage(): NodeJS.MemoryUsage;
|
|
36
37
|
printMemoryUsage(): void;
|
|
37
38
|
}
|
|
@@ -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);
|
|
@@ -167,6 +172,13 @@ export class Application {
|
|
|
167
172
|
}
|
|
168
173
|
return false;
|
|
169
174
|
}
|
|
175
|
+
directory(uri) {
|
|
176
|
+
const dir = path.resolve('../');
|
|
177
|
+
if (typeof uri === 'string') {
|
|
178
|
+
return path.resolve(dir, uri.startsWith('/') ? uri.substring(1) : uri);
|
|
179
|
+
}
|
|
180
|
+
return dir;
|
|
181
|
+
}
|
|
170
182
|
memoryUsage() {
|
|
171
183
|
return process.memoryUsage();
|
|
172
184
|
}
|
|
@@ -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) {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { ServerResponse } from 'node:http';
|
|
2
2
|
import { LooseObject } from '../types/general.types.js';
|
|
3
3
|
import { Initializers } from '../types/component.types.js';
|
|
4
|
-
import { RequestContext } from "../types/request.types.js";
|
|
5
4
|
import { Application } from './Application.js';
|
|
6
5
|
import { DocumentHead } from './DocumentHead.js';
|
|
7
6
|
import { Component } from './Component.js';
|
|
7
|
+
import { RequestContext } from './RequestContext.js';
|
|
8
8
|
export declare class Document extends Component<{
|
|
9
9
|
'componentCreated': Component;
|
|
10
10
|
'beforeRender': void;
|
|
@@ -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) {
|