@mojojs/core 1.26.18 → 2.0.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 +1 -1
- package/lib/app.d.ts +31 -16
- package/lib/app.js +181 -126
- package/lib/body/params.js +0 -1
- package/lib/body.d.ts +13 -5
- package/lib/body.js +47 -17
- package/lib/cache.d.ts +1 -2
- package/lib/cache.js +12 -21
- package/lib/cgi.d.ts +3 -3
- package/lib/cgi.js +16 -8
- package/lib/cli/cgi.d.ts +1 -1
- package/lib/cli/cgi.js +1 -2
- package/lib/cli/create-full-app.d.ts +1 -1
- package/lib/cli/create-full-app.js +27 -27
- package/lib/cli/create-lite-app.d.ts +1 -1
- package/lib/cli/create-lite-app.js +2 -3
- package/lib/cli/create-plugin.d.ts +1 -1
- package/lib/cli/create-plugin.js +25 -23
- package/lib/cli/eval.d.ts +1 -1
- package/lib/cli/eval.js +12 -5
- package/lib/cli/get.d.ts +1 -1
- package/lib/cli/get.js +30 -26
- package/lib/cli/repl.d.ts +1 -1
- package/lib/cli/repl.js +0 -1
- package/lib/cli/routes.d.ts +1 -1
- package/lib/cli/routes.js +10 -7
- package/lib/cli/server.d.ts +1 -1
- package/lib/cli/server.js +41 -24
- package/lib/cli/version.js +1 -2
- package/lib/cli.d.ts +1 -1
- package/lib/cli.js +25 -19
- package/lib/context.d.ts +33 -10
- package/lib/context.js +137 -32
- package/lib/core.d.ts +14 -13
- package/lib/core.js +12 -12
- package/lib/headers.js +5 -4
- package/lib/hooks.d.ts +1 -1
- package/lib/hooks.js +1 -5
- package/lib/logger.d.ts +2 -2
- package/lib/logger.js +28 -16
- package/lib/mime.js +10 -13
- package/lib/plugins/default-conditions.d.ts +1 -1
- package/lib/plugins/default-conditions.js +0 -1
- package/lib/plugins/default-helpers.d.ts +1 -1
- package/lib/plugins/default-helpers.js +28 -11
- package/lib/plugins/json-config.d.ts +1 -1
- package/lib/plugins/json-config.js +0 -1
- package/lib/plugins/mount.d.ts +1 -1
- package/lib/plugins/mount.js +0 -1
- package/lib/plugins/tmpl-engine.d.ts +1 -1
- package/lib/plugins/tmpl-engine.js +2 -5
- package/lib/plugins/yaml-config.d.ts +1 -1
- package/lib/plugins/yaml-config.js +1 -2
- package/lib/renderer.d.ts +1 -1
- package/lib/renderer.js +27 -30
- package/lib/router/pattern.d.ts +1 -1
- package/lib/router/pattern.js +25 -14
- package/lib/router/plan.d.ts +1 -1
- package/lib/router/plan.js +12 -15
- package/lib/router/route.d.ts +10 -3
- package/lib/router/route.js +44 -38
- package/lib/router.d.ts +4 -4
- package/lib/router.js +25 -26
- package/lib/server/cookie.d.ts +1 -1
- package/lib/server/cookie.js +3 -2
- package/lib/server/request.d.ts +3 -3
- package/lib/server/request.js +44 -17
- package/lib/server/response.d.ts +3 -3
- package/lib/server/response.js +20 -20
- package/lib/server.d.ts +7 -2
- package/lib/server.js +70 -20
- package/lib/session.d.ts +8 -3
- package/lib/session.js +47 -29
- package/lib/sse.d.ts +10 -0
- package/lib/sse.js +62 -0
- package/lib/static.d.ts +1 -1
- package/lib/static.js +27 -22
- package/lib/types.d.ts +13 -5
- package/lib/types.js +0 -1
- package/lib/user-agent/cookie-jar.d.ts +2 -2
- package/lib/user-agent/cookie-jar.js +6 -9
- package/lib/user-agent/mock.d.ts +4 -4
- package/lib/user-agent/mock.js +6 -7
- package/lib/user-agent/response.d.ts +12 -1
- package/lib/user-agent/response.js +36 -2
- package/lib/user-agent/test.d.ts +75 -5
- package/lib/user-agent/test.js +147 -13
- package/lib/user-agent/transport/http.d.ts +2 -2
- package/lib/user-agent/transport/http.js +7 -10
- package/lib/user-agent/transport/https.d.ts +2 -2
- package/lib/user-agent/transport/https.js +2 -6
- package/lib/user-agent/transport/ws.d.ts +2 -2
- package/lib/user-agent/transport/ws.js +2 -3
- package/lib/user-agent.d.ts +21 -18
- package/lib/user-agent.js +70 -49
- package/lib/util.d.ts +13 -0
- package/lib/util.js +35 -11
- package/lib/validator/result.d.ts +1 -1
- package/lib/validator/result.js +8 -1
- package/lib/validator.d.ts +2 -2
- package/lib/validator.js +3 -6
- package/lib/websocket.d.ts +2 -2
- package/lib/websocket.js +9 -1
- package/package.json +22 -29
- package/lib/app.js.map +0 -1
- package/lib/body/params.js.map +0 -1
- package/lib/body.js.map +0 -1
- package/lib/cache.js.map +0 -1
- package/lib/cgi.js.map +0 -1
- package/lib/cli/cgi.js.map +0 -1
- package/lib/cli/create-full-app.js.map +0 -1
- package/lib/cli/create-lite-app.js.map +0 -1
- package/lib/cli/create-plugin.js.map +0 -1
- package/lib/cli/eval.js.map +0 -1
- package/lib/cli/get.js.map +0 -1
- package/lib/cli/repl.js.map +0 -1
- package/lib/cli/routes.js.map +0 -1
- package/lib/cli/server.js.map +0 -1
- package/lib/cli/version.js.map +0 -1
- package/lib/cli.js.map +0 -1
- package/lib/context.js.map +0 -1
- package/lib/core.js.map +0 -1
- package/lib/headers.js.map +0 -1
- package/lib/hooks.js.map +0 -1
- package/lib/logger.js.map +0 -1
- package/lib/mime.js.map +0 -1
- package/lib/plugins/default-conditions.js.map +0 -1
- package/lib/plugins/default-helpers.js.map +0 -1
- package/lib/plugins/json-config.js.map +0 -1
- package/lib/plugins/mount.js.map +0 -1
- package/lib/plugins/tmpl-engine.js.map +0 -1
- package/lib/plugins/yaml-config.js.map +0 -1
- package/lib/renderer.js.map +0 -1
- package/lib/router/pattern.js.map +0 -1
- package/lib/router/plan.js.map +0 -1
- package/lib/router/route.js.map +0 -1
- package/lib/router.js.map +0 -1
- package/lib/server/cookie.js.map +0 -1
- package/lib/server/request.js.map +0 -1
- package/lib/server/response.js.map +0 -1
- package/lib/server.js.map +0 -1
- package/lib/session.js.map +0 -1
- package/lib/static.js.map +0 -1
- package/lib/types.js.map +0 -1
- package/lib/user-agent/cookie-jar.js.map +0 -1
- package/lib/user-agent/mock.js.map +0 -1
- package/lib/user-agent/response.js.map +0 -1
- package/lib/user-agent/test.js.map +0 -1
- package/lib/user-agent/transport/http.js.map +0 -1
- package/lib/user-agent/transport/https.js.map +0 -1
- package/lib/user-agent/transport/ws.js.map +0 -1
- package/lib/user-agent.js.map +0 -1
- package/lib/util.js.map +0 -1
- package/lib/validator/result.js.map +0 -1
- package/lib/validator.js.map +0 -1
- package/lib/websocket.js.map +0 -1
package/lib/context.d.ts
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import type { App } from './app.
|
|
2
|
-
import type { ChildLogger } from './logger.
|
|
3
|
-
import type { Plan } from './router/plan.
|
|
4
|
-
import type { ServerRequest } from './server/request.
|
|
5
|
-
import type { ServerResponse } from './server/response.
|
|
6
|
-
import type { BackendInfo, MojoAction, MojoModels, MojoRenderOptions, MojoURLOptions, ScopedNestedHelpers, SessionData, UploadOptions, ValidatorFunction } from './types.
|
|
7
|
-
import type { UserAgent } from './user-agent.
|
|
8
|
-
import type { WebSocket } from './websocket.
|
|
1
|
+
import type { App } from './app.ts';
|
|
2
|
+
import type { ChildLogger } from './logger.ts';
|
|
3
|
+
import type { Plan } from './router/plan.ts';
|
|
4
|
+
import type { ServerRequest } from './server/request.ts';
|
|
5
|
+
import type { ServerResponse } from './server/response.ts';
|
|
6
|
+
import type { BackendInfo, MojoAction, MojoModels, MojoRenderOptions, MojoURLOptions, ScopedNestedHelpers, SessionData, SSEEvent, UploadOptions, ValidatorFunction } from './types.ts';
|
|
7
|
+
import type { UserAgent } from './user-agent.ts';
|
|
8
|
+
import type { WebSocket } from './websocket.ts';
|
|
9
9
|
import type Path from '@mojojs/path';
|
|
10
10
|
import EventEmitter from 'node:events';
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
11
|
+
import { PassThrough } from 'node:stream';
|
|
12
|
+
import { Params } from './body/params.ts';
|
|
13
|
+
import { SafeString } from './util.ts';
|
|
13
14
|
type WebSocketHandler = (ws: WebSocket) => void | Promise<void>;
|
|
14
15
|
interface ContextEvents {
|
|
15
16
|
connection: (ws: WebSocket) => void;
|
|
@@ -105,6 +106,7 @@ declare class Context extends EventEmitter implements ContextEventEmitter {
|
|
|
105
106
|
_nestedHelpersCache: Record<string, ScopedNestedHelpers>;
|
|
106
107
|
_params: Params | undefined;
|
|
107
108
|
_session: Record<string, any> | undefined;
|
|
109
|
+
_sse: PassThrough | undefined;
|
|
108
110
|
_ws: WeakRef<WebSocket> | null;
|
|
109
111
|
constructor(app: App, req: ServerRequest, res: ServerResponse, backend: BackendInfo);
|
|
110
112
|
[EventEmitter.captureRejectionSymbol](error: Error): void;
|
|
@@ -126,6 +128,10 @@ declare class Context extends EventEmitter implements ContextEventEmitter {
|
|
|
126
128
|
* await ctx.contentFor('head', '<link rel="icon" href="/static/favicon.ico">');
|
|
127
129
|
*/
|
|
128
130
|
contentFor(name: string, content: string | SafeString | Promise<string | SafeString> | (() => Promise<string | SafeString>)): Promise<void>;
|
|
131
|
+
/**
|
|
132
|
+
* Finish Server-Sent Events response. Note that this method is EXPERIMENTAL and might change without warning!
|
|
133
|
+
*/
|
|
134
|
+
finishSSE(): void;
|
|
129
135
|
/**
|
|
130
136
|
* Data storage persistent only for the next request.
|
|
131
137
|
* @example
|
|
@@ -286,6 +292,23 @@ declare class Context extends EventEmitter implements ContextEventEmitter {
|
|
|
286
292
|
* Established WebSocket connection.
|
|
287
293
|
*/
|
|
288
294
|
get ws(): WebSocket | null;
|
|
295
|
+
/**
|
|
296
|
+
* Write Server-Sent Event, and wait for it to be written. Calling this method without an event will just send the
|
|
297
|
+
* response headers and allow for events to be written later. Note that this method is EXPERIMENTAL and might change
|
|
298
|
+
* without warning!
|
|
299
|
+
* @example
|
|
300
|
+
* // Send event with default "message" type
|
|
301
|
+
* await ctx.writeSSE({text: 'Hello World!'});
|
|
302
|
+
*
|
|
303
|
+
* // You can send comments in regular intervals to keep the connection alive
|
|
304
|
+
* await ctx.writeSSE({comment: 'Keep connection alive!'});
|
|
305
|
+
*
|
|
306
|
+
* // Send events with id and type
|
|
307
|
+
* await ctx.writeSSE({id: 23, type: 'greeting', text: 'Hello World!'});
|
|
308
|
+
* await ctx.writeSSE({id: 24, type: 'farewell', text: 'Goodbye World!'});
|
|
309
|
+
* ctx.finishSSE();
|
|
310
|
+
*/
|
|
311
|
+
writeSSE(event?: SSEEvent): Promise<void>;
|
|
289
312
|
_urlForPath(path: string, isWebSocket: boolean, options: MojoURLOptions): string;
|
|
290
313
|
}
|
|
291
314
|
export { Context };
|
package/lib/context.js
CHANGED
|
@@ -1,46 +1,110 @@
|
|
|
1
1
|
import EventEmitter from 'node:events';
|
|
2
2
|
import querystring from 'node:querystring';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { PassThrough } from 'node:stream';
|
|
4
|
+
import { Params } from "./body/params.js";
|
|
5
|
+
import { buildEvent } from "./sse.js";
|
|
6
|
+
import { SafeString } from "./util.js";
|
|
5
7
|
const ABSOLUTE = /^[a-zA-Z][a-zA-Z0-9]*:\/\//;
|
|
6
8
|
/**
|
|
7
9
|
* Context class.
|
|
8
10
|
*/
|
|
9
11
|
class Context extends EventEmitter {
|
|
12
|
+
/**
|
|
13
|
+
* Application this context belongs to.
|
|
14
|
+
*/
|
|
15
|
+
app;
|
|
16
|
+
/**
|
|
17
|
+
* Backend information for server that received the request.
|
|
18
|
+
* @example
|
|
19
|
+
* // Backend name (usually "server", "cgi", or "mock")
|
|
20
|
+
* const {name} = ctx.backend;
|
|
21
|
+
*
|
|
22
|
+
* // Use low level Node.js APIs with "server" backend
|
|
23
|
+
* const {res} = ctx.backend;
|
|
24
|
+
* res.writeHead(200);
|
|
25
|
+
* res.end('Hello World!');
|
|
26
|
+
*/
|
|
27
|
+
backend;
|
|
28
|
+
/**
|
|
29
|
+
* Partial content. Always returns at least an empty string, even for sections that have not been defined with
|
|
30
|
+
* `ctx.contentFor()`.
|
|
31
|
+
* @example
|
|
32
|
+
* // Get content for "head" section
|
|
33
|
+
* const head = ctx.content.head;
|
|
34
|
+
*/
|
|
35
|
+
content = new Proxy({}, {
|
|
36
|
+
get: function (target, name) {
|
|
37
|
+
return new SafeString(target[name] ?? '');
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
/**
|
|
41
|
+
* Format for HTTP exceptions ("html", "json", or "txt").
|
|
42
|
+
*/
|
|
43
|
+
exceptionFormat;
|
|
44
|
+
/**
|
|
45
|
+
* WebSocket JSON mode.
|
|
46
|
+
*/
|
|
47
|
+
jsonMode = false;
|
|
48
|
+
/**
|
|
49
|
+
* Logger with request id.
|
|
50
|
+
* @example
|
|
51
|
+
* // Pass logger with context to model
|
|
52
|
+
* const log = ctx.log;
|
|
53
|
+
* ctx.model.users.create({name: 'kraih'}, log);
|
|
54
|
+
*/
|
|
55
|
+
log;
|
|
56
|
+
/**
|
|
57
|
+
* Router dispatch plan.
|
|
58
|
+
*/
|
|
59
|
+
plan = null;
|
|
60
|
+
/**
|
|
61
|
+
* HTTP request information.
|
|
62
|
+
* @example
|
|
63
|
+
* // Extract request information
|
|
64
|
+
* const id = ctx.req.requestId;
|
|
65
|
+
* const method = ctx.req.method;
|
|
66
|
+
* const baseURL = ctx.req.baseURL;
|
|
67
|
+
* const url = ctx.req.url;
|
|
68
|
+
* const query = ctx.req.query;
|
|
69
|
+
* const info = ctx.req.userinfo;
|
|
70
|
+
* const agent = ctx.req.get('User-Agent');
|
|
71
|
+
* const buffer = await ctx.req.buffer();
|
|
72
|
+
* const text = await ctx.req.text();
|
|
73
|
+
* const form = await ctx.req.form();
|
|
74
|
+
* const value = await ctx.req.json();
|
|
75
|
+
* const html = await ctx.req.html();
|
|
76
|
+
* const xml = await ctx.req.xml();
|
|
77
|
+
*/
|
|
78
|
+
req;
|
|
79
|
+
/**
|
|
80
|
+
* HTTP response information.
|
|
81
|
+
* @example
|
|
82
|
+
* // Force file download by setting a response header
|
|
83
|
+
* ctx.res.set('Content-Disposition', 'attachment; filename=foo.png;');
|
|
84
|
+
*
|
|
85
|
+
* // Make sure response is cached correctly
|
|
86
|
+
* ctx.res.set('Cache-Control', 'public, max-age=300');
|
|
87
|
+
* ctx.res.headers.append('Vary', 'Accept-Encoding');
|
|
88
|
+
*/
|
|
89
|
+
res;
|
|
90
|
+
/**
|
|
91
|
+
* Non-persistent data storage and exchange for the current request.
|
|
92
|
+
*/
|
|
93
|
+
stash = {};
|
|
94
|
+
_flash = undefined;
|
|
95
|
+
_nestedHelpersCache = {};
|
|
96
|
+
_params = undefined;
|
|
97
|
+
_session = undefined;
|
|
98
|
+
_sse = undefined;
|
|
99
|
+
_ws = null;
|
|
10
100
|
constructor(app, req, res, backend) {
|
|
11
101
|
super({ captureRejections: true });
|
|
12
|
-
/**
|
|
13
|
-
* Partial content. Always returns at least an empty string, even for sections that have not been defined with
|
|
14
|
-
* `ctx.contentFor()`.
|
|
15
|
-
* @example
|
|
16
|
-
* // Get content for "head" section
|
|
17
|
-
* const head = ctx.content.head;
|
|
18
|
-
*/
|
|
19
|
-
this.content = new Proxy({}, {
|
|
20
|
-
get: function (target, name) {
|
|
21
|
-
return new SafeString(target[name] ?? '');
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
/**
|
|
25
|
-
* WebSocket JSON mode.
|
|
26
|
-
*/
|
|
27
|
-
this.jsonMode = false;
|
|
28
|
-
/**
|
|
29
|
-
* Router dispatch plan.
|
|
30
|
-
*/
|
|
31
|
-
this.plan = null;
|
|
32
|
-
/**
|
|
33
|
-
* Non-persistent data storage and exchange for the current request.
|
|
34
|
-
*/
|
|
35
|
-
this.stash = {};
|
|
36
|
-
this._flash = undefined;
|
|
37
|
-
this._nestedHelpersCache = {};
|
|
38
|
-
this._params = undefined;
|
|
39
|
-
this._session = undefined;
|
|
40
|
-
this._ws = null;
|
|
41
102
|
this.app = app;
|
|
42
103
|
this.exceptionFormat = app.exceptionFormat;
|
|
43
104
|
this.req = req;
|
|
105
|
+
// Mounted applications share the request, and the limit of the outer one
|
|
106
|
+
if (req.maxMessageSize === undefined)
|
|
107
|
+
req.maxMessageSize = app.maxRequestSize;
|
|
44
108
|
this.res = res;
|
|
45
109
|
this.res.bindContext(this);
|
|
46
110
|
this.backend = backend;
|
|
@@ -80,6 +144,12 @@ class Context extends EventEmitter {
|
|
|
80
144
|
async contentFor(name, content) {
|
|
81
145
|
this.content[name] += await (content instanceof Function ? content() : content);
|
|
82
146
|
}
|
|
147
|
+
/**
|
|
148
|
+
* Finish Server-Sent Events response. Note that this method is EXPERIMENTAL and might change without warning!
|
|
149
|
+
*/
|
|
150
|
+
finishSSE() {
|
|
151
|
+
this._sse?.end();
|
|
152
|
+
}
|
|
83
153
|
/**
|
|
84
154
|
* Data storage persistent only for the next request.
|
|
85
155
|
* @example
|
|
@@ -361,6 +431,42 @@ class Context extends EventEmitter {
|
|
|
361
431
|
get ws() {
|
|
362
432
|
return this._ws?.deref() ?? null;
|
|
363
433
|
}
|
|
434
|
+
/**
|
|
435
|
+
* Write Server-Sent Event, and wait for it to be written. Calling this method without an event will just send the
|
|
436
|
+
* response headers and allow for events to be written later. Note that this method is EXPERIMENTAL and might change
|
|
437
|
+
* without warning!
|
|
438
|
+
* @example
|
|
439
|
+
* // Send event with default "message" type
|
|
440
|
+
* await ctx.writeSSE({text: 'Hello World!'});
|
|
441
|
+
*
|
|
442
|
+
* // You can send comments in regular intervals to keep the connection alive
|
|
443
|
+
* await ctx.writeSSE({comment: 'Keep connection alive!'});
|
|
444
|
+
*
|
|
445
|
+
* // Send events with id and type
|
|
446
|
+
* await ctx.writeSSE({id: 23, type: 'greeting', text: 'Hello World!'});
|
|
447
|
+
* await ctx.writeSSE({id: 24, type: 'farewell', text: 'Goodbye World!'});
|
|
448
|
+
* ctx.finishSSE();
|
|
449
|
+
*/
|
|
450
|
+
async writeSSE(event) {
|
|
451
|
+
let stream = this._sse;
|
|
452
|
+
if (stream === undefined) {
|
|
453
|
+
stream = this._sse = new PassThrough();
|
|
454
|
+
this.on('finish', () => stream?.destroy());
|
|
455
|
+
await this.res.type('text/event-stream').send(stream);
|
|
456
|
+
}
|
|
457
|
+
if (event === undefined || stream.destroyed === true)
|
|
458
|
+
return;
|
|
459
|
+
if (stream.write(buildEvent(event)) === true)
|
|
460
|
+
return;
|
|
461
|
+
// Wait for drain, or the connection to be closed
|
|
462
|
+
await new Promise(resolve => {
|
|
463
|
+
const done = () => {
|
|
464
|
+
stream.off('drain', done).off('close', done);
|
|
465
|
+
resolve();
|
|
466
|
+
};
|
|
467
|
+
stream.on('drain', done).on('close', done);
|
|
468
|
+
});
|
|
469
|
+
}
|
|
364
470
|
_urlForPath(path, isWebSocket, options) {
|
|
365
471
|
path = this.req.basePath + (path === '' ? '/' : path);
|
|
366
472
|
let queryFragment = '';
|
|
@@ -376,4 +482,3 @@ class Context extends EventEmitter {
|
|
|
376
482
|
}
|
|
377
483
|
}
|
|
378
484
|
export { Context };
|
|
379
|
-
//# sourceMappingURL=context.js.map
|
package/lib/core.d.ts
CHANGED
|
@@ -3,21 +3,22 @@
|
|
|
3
3
|
* Copyright (C) 2021-2024 Sebastian Riedel
|
|
4
4
|
* MIT Licensed
|
|
5
5
|
*/
|
|
6
|
-
import type { AppOptions } from './types.
|
|
7
|
-
import { App } from './app.
|
|
6
|
+
import type { AppOptions } from './types.ts';
|
|
7
|
+
import { App } from './app.ts';
|
|
8
8
|
export declare const version: any;
|
|
9
9
|
/**
|
|
10
10
|
* Create a new mojo.js application.
|
|
11
11
|
*/
|
|
12
12
|
export default function mojo(options?: AppOptions): App;
|
|
13
|
-
export { default as jsonConfigPlugin } from './plugins/json-config.
|
|
14
|
-
export { default as yamlConfigPlugin } from './plugins/yaml-config.
|
|
15
|
-
export { default as mountPlugin } from './plugins/mount.
|
|
16
|
-
export { CGI } from './cgi.
|
|
17
|
-
export { Logger } from './logger.
|
|
18
|
-
export { Server } from './server.
|
|
19
|
-
export { Session } from './session.
|
|
20
|
-
export {
|
|
21
|
-
export {
|
|
22
|
-
export
|
|
23
|
-
export
|
|
13
|
+
export { default as jsonConfigPlugin } from './plugins/json-config.ts';
|
|
14
|
+
export { default as yamlConfigPlugin } from './plugins/yaml-config.ts';
|
|
15
|
+
export { default as mountPlugin } from './plugins/mount.ts';
|
|
16
|
+
export { CGI } from './cgi.ts';
|
|
17
|
+
export { Logger } from './logger.ts';
|
|
18
|
+
export { Server } from './server.ts';
|
|
19
|
+
export { Session } from './session.ts';
|
|
20
|
+
export { buildEvent, parseEvent } from './sse.ts';
|
|
21
|
+
export { TestUserAgent } from './user-agent/test.ts';
|
|
22
|
+
export { UserAgent } from './user-agent.ts';
|
|
23
|
+
export * as util from './util.ts';
|
|
24
|
+
export type { JSONValue, MojoAction, MojoApp, MojoContext, MojoModels, MojoRenderOptions, MojoRoute, MojoTags, MojoURLOptions, SSEEvent } from './types.ts';
|
package/lib/core.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { App } from
|
|
1
|
+
import { App } from "./app.js";
|
|
2
2
|
import Path from '@mojojs/path';
|
|
3
3
|
export const version = JSON.parse(Path.currentFile().dirname().sibling('package.json').readFileSync().toString()).version;
|
|
4
4
|
/**
|
|
@@ -22,14 +22,14 @@ export default function mojo(options) {
|
|
|
22
22
|
return app;
|
|
23
23
|
}
|
|
24
24
|
// "Professor: These old Doomsday devices are dangerously unstable. I'll rest easier not knowing where they are."
|
|
25
|
-
export { default as jsonConfigPlugin } from
|
|
26
|
-
export { default as yamlConfigPlugin } from
|
|
27
|
-
export { default as mountPlugin } from
|
|
28
|
-
export { CGI } from
|
|
29
|
-
export { Logger } from
|
|
30
|
-
export { Server } from
|
|
31
|
-
export { Session } from
|
|
32
|
-
export {
|
|
33
|
-
export {
|
|
34
|
-
export
|
|
35
|
-
|
|
25
|
+
export { default as jsonConfigPlugin } from "./plugins/json-config.js";
|
|
26
|
+
export { default as yamlConfigPlugin } from "./plugins/yaml-config.js";
|
|
27
|
+
export { default as mountPlugin } from "./plugins/mount.js";
|
|
28
|
+
export { CGI } from "./cgi.js";
|
|
29
|
+
export { Logger } from "./logger.js";
|
|
30
|
+
export { Server } from "./server.js";
|
|
31
|
+
export { Session } from "./session.js";
|
|
32
|
+
export { buildEvent, parseEvent } from "./sse.js";
|
|
33
|
+
export { TestUserAgent } from "./user-agent/test.js";
|
|
34
|
+
export { UserAgent } from "./user-agent.js";
|
|
35
|
+
export * as util from "./util.js";
|
package/lib/headers.js
CHANGED
|
@@ -13,6 +13,8 @@ const HOP_BY_HOP = [
|
|
|
13
13
|
* HTTP header class.
|
|
14
14
|
*/
|
|
15
15
|
export class Headers {
|
|
16
|
+
_headers;
|
|
17
|
+
_init;
|
|
16
18
|
constructor(init = []) {
|
|
17
19
|
this._init = init;
|
|
18
20
|
}
|
|
@@ -70,7 +72,6 @@ export class Headers {
|
|
|
70
72
|
* const {link, title} = headers.getLinks().next;
|
|
71
73
|
*/
|
|
72
74
|
getLinks() {
|
|
73
|
-
var _a;
|
|
74
75
|
const data = {};
|
|
75
76
|
let value = this.get('Link') ?? '';
|
|
76
77
|
while (value.length > 0) {
|
|
@@ -81,7 +82,7 @@ export class Headers {
|
|
|
81
82
|
const { params, remainder } = headerParams(linkMatch[2]);
|
|
82
83
|
value = remainder;
|
|
83
84
|
if (params.rel !== undefined)
|
|
84
|
-
data[
|
|
85
|
+
data[params.rel] ??= { ...params, link };
|
|
85
86
|
}
|
|
86
87
|
return data;
|
|
87
88
|
}
|
|
@@ -149,7 +150,8 @@ export class Headers {
|
|
|
149
150
|
}
|
|
150
151
|
_getHeaders() {
|
|
151
152
|
if (this._headers === undefined) {
|
|
152
|
-
|
|
153
|
+
// No prototype, so names like "constructor" are just regular headers
|
|
154
|
+
const headers = (this._headers = Object.create(null));
|
|
153
155
|
const init = this._init;
|
|
154
156
|
for (let i = 0; i < init.length; i += 2) {
|
|
155
157
|
const name = init[i];
|
|
@@ -166,4 +168,3 @@ export class Headers {
|
|
|
166
168
|
return this._headers;
|
|
167
169
|
}
|
|
168
170
|
}
|
|
169
|
-
//# sourceMappingURL=headers.js.map
|
package/lib/hooks.d.ts
CHANGED
package/lib/hooks.js
CHANGED
|
@@ -3,10 +3,7 @@ import { AsyncHooks } from '@mojojs/util';
|
|
|
3
3
|
* Hook class.
|
|
4
4
|
*/
|
|
5
5
|
export class Hooks extends AsyncHooks {
|
|
6
|
-
|
|
7
|
-
super(...arguments);
|
|
8
|
-
this._lifecycleHookScore = 0;
|
|
9
|
-
}
|
|
6
|
+
_lifecycleHookScore = 0;
|
|
10
7
|
/**
|
|
11
8
|
* Run `command:before` hook with dependencies. Note that this method is EXPERIMENTAL and might change without
|
|
12
9
|
* warning!
|
|
@@ -49,4 +46,3 @@ export class Hooks extends AsyncHooks {
|
|
|
49
46
|
await this.runHook('app:stop', app);
|
|
50
47
|
}
|
|
51
48
|
}
|
|
52
|
-
//# sourceMappingURL=hooks.js.map
|
package/lib/logger.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ interface LogEvent extends LogContext {
|
|
|
8
8
|
time: string;
|
|
9
9
|
msg: string;
|
|
10
10
|
}
|
|
11
|
-
type LogFormatter = (data: LogEvent) => string;
|
|
11
|
+
type LogFormatter = (data: LogEvent, destination: NodeJS.WritableStream) => string;
|
|
12
12
|
/**
|
|
13
13
|
* A simple logger class.
|
|
14
14
|
*/
|
|
@@ -45,7 +45,7 @@ export declare class Logger {
|
|
|
45
45
|
/**
|
|
46
46
|
* Log formatter with color highlighting.
|
|
47
47
|
*/
|
|
48
|
-
static colorFormatter(data: LogEvent): string;
|
|
48
|
+
static colorFormatter(data: LogEvent, destination?: NodeJS.WritableStream): string;
|
|
49
49
|
/**
|
|
50
50
|
* Log `debug` message.
|
|
51
51
|
*/
|
package/lib/logger.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import assert from 'node:assert';
|
|
2
|
-
import
|
|
2
|
+
import { styleText } from 'node:util';
|
|
3
3
|
const LEVEL = {
|
|
4
4
|
fatal: 2,
|
|
5
5
|
error: 3,
|
|
@@ -12,13 +12,22 @@ const LEVEL = {
|
|
|
12
12
|
* A simple logger class.
|
|
13
13
|
*/
|
|
14
14
|
export class Logger {
|
|
15
|
+
/**
|
|
16
|
+
* Log destination stream.
|
|
17
|
+
*/
|
|
18
|
+
destination;
|
|
19
|
+
/**
|
|
20
|
+
* Log formatter.
|
|
21
|
+
*/
|
|
22
|
+
formatter;
|
|
23
|
+
/**
|
|
24
|
+
* The last few logged messages.
|
|
25
|
+
*/
|
|
26
|
+
history = [];
|
|
27
|
+
_capture = undefined;
|
|
28
|
+
_historySize;
|
|
29
|
+
_level = 7;
|
|
15
30
|
constructor(options = {}) {
|
|
16
|
-
/**
|
|
17
|
-
* The last few logged messages.
|
|
18
|
-
*/
|
|
19
|
-
this.history = [];
|
|
20
|
-
this._capture = undefined;
|
|
21
|
-
this._level = 7;
|
|
22
31
|
this.destination = options.destination ?? process.stderr;
|
|
23
32
|
this.formatter = options.formatter ?? Logger.colorFormatter;
|
|
24
33
|
this.level = process.env.MOJO_LOG_LEVEL ?? options.level ?? 'trace';
|
|
@@ -47,13 +56,14 @@ export class Logger {
|
|
|
47
56
|
/**
|
|
48
57
|
* Log formatter with color highlighting.
|
|
49
58
|
*/
|
|
50
|
-
static colorFormatter(data) {
|
|
59
|
+
static colorFormatter(data, destination = process.stderr) {
|
|
51
60
|
const formatted = Logger.stringFormatter(data);
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (
|
|
55
|
-
return
|
|
56
|
-
|
|
61
|
+
const { level } = data;
|
|
62
|
+
const color = level === 'error' || level === 'fatal' ? 'red' : level === 'warn' ? 'yellow' : null;
|
|
63
|
+
if (color === null)
|
|
64
|
+
return formatted;
|
|
65
|
+
// Keep the trailing newline outside of the color codes, and only use colors if the destination supports them
|
|
66
|
+
return styleText(color, formatted.slice(0, -1), { stream: destination }) + '\n';
|
|
57
67
|
}
|
|
58
68
|
/**
|
|
59
69
|
* Log `debug` message.
|
|
@@ -139,7 +149,7 @@ export class Logger {
|
|
|
139
149
|
if (historySize > maxHistorySize)
|
|
140
150
|
history.splice(0, historySize - maxHistorySize);
|
|
141
151
|
}
|
|
142
|
-
const formatted = this.formatter(data);
|
|
152
|
+
const formatted = this.formatter(data, this.destination);
|
|
143
153
|
if (this._capture === undefined) {
|
|
144
154
|
this.destination.write(formatted);
|
|
145
155
|
}
|
|
@@ -152,9 +162,10 @@ export class Logger {
|
|
|
152
162
|
* Captured log message class.
|
|
153
163
|
*/
|
|
154
164
|
class CapturedLogs extends Array {
|
|
165
|
+
_cb;
|
|
166
|
+
_stopped = false;
|
|
155
167
|
constructor(cb) {
|
|
156
168
|
super();
|
|
157
|
-
this._stopped = false;
|
|
158
169
|
this._cb = cb;
|
|
159
170
|
}
|
|
160
171
|
/**
|
|
@@ -177,6 +188,8 @@ class CapturedLogs extends Array {
|
|
|
177
188
|
* Child logger class.
|
|
178
189
|
*/
|
|
179
190
|
export class ChildLogger {
|
|
191
|
+
parent;
|
|
192
|
+
context;
|
|
180
193
|
constructor(parent, context) {
|
|
181
194
|
this.parent = parent;
|
|
182
195
|
this.context = context;
|
|
@@ -218,4 +231,3 @@ export class ChildLogger {
|
|
|
218
231
|
this.parent.warn(msg, this.context);
|
|
219
232
|
}
|
|
220
233
|
}
|
|
221
|
-
//# sourceMappingURL=logger.js.map
|
package/lib/mime.js
CHANGED
|
@@ -3,18 +3,16 @@ import mime from 'mime-types';
|
|
|
3
3
|
* MIME class.
|
|
4
4
|
*/
|
|
5
5
|
export class Mime {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
this._reverseCustom = undefined;
|
|
17
|
-
}
|
|
6
|
+
/**
|
|
7
|
+
* Custom MIME types.
|
|
8
|
+
*/
|
|
9
|
+
custom = {
|
|
10
|
+
html: 'text/html; charset=utf-8',
|
|
11
|
+
json: 'application/json; charset=utf-8',
|
|
12
|
+
txt: 'text/plain; charset=utf-8',
|
|
13
|
+
yaml: 'text/yaml; charset=utf-8'
|
|
14
|
+
};
|
|
15
|
+
_reverseCustom = undefined;
|
|
18
16
|
/**
|
|
19
17
|
* Detect file extensions from `Accept` header value.
|
|
20
18
|
*/
|
|
@@ -50,4 +48,3 @@ export class Mime {
|
|
|
50
48
|
return this.custom[ext] ?? mime.types[ext] ?? null;
|
|
51
49
|
}
|
|
52
50
|
}
|
|
53
|
-
//# sourceMappingURL=mime.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { inspect } from 'node:util';
|
|
2
|
-
import { Logger } from
|
|
3
|
-
import { exceptionContext, SafeString } from
|
|
2
|
+
import { Logger } from "../logger.js";
|
|
3
|
+
import { exceptionContext, HTTPError, httpStatusMessages, SafeString } from "../util.js";
|
|
4
4
|
import DOM from '@mojojs/dom';
|
|
5
5
|
/**
|
|
6
6
|
* Default helper plugin.
|
|
@@ -140,12 +140,13 @@ async function hiddenFieldTag(ctx, name, value, attrs = {}) {
|
|
|
140
140
|
async function htmlException(ctx, error) {
|
|
141
141
|
ctx.stash.exception = error;
|
|
142
142
|
const mode = ctx.app.mode;
|
|
143
|
-
|
|
143
|
+
const status = errorStatus(error);
|
|
144
|
+
if (await ctx.render({ view: `exception.${mode}`, maybe: true, status }))
|
|
144
145
|
return true;
|
|
145
|
-
if (await ctx.render({ view: 'exception', maybe: true, status
|
|
146
|
+
if (await ctx.render({ view: 'exception', maybe: true, status }))
|
|
146
147
|
return true;
|
|
147
148
|
const view = mode === 'development' ? 'mojo/debug' : 'mojo/exception';
|
|
148
|
-
return await ctx.render({ view, status
|
|
149
|
+
return await ctx.render({ view, status, stringFormatter: Logger.stringFormatter, exceptionContext });
|
|
149
150
|
}
|
|
150
151
|
async function htmlNotFound(ctx) {
|
|
151
152
|
ctx.stash.exception = null;
|
|
@@ -159,7 +160,15 @@ async function htmlNotFound(ctx) {
|
|
|
159
160
|
}
|
|
160
161
|
async function httpException(ctx, error) {
|
|
161
162
|
error = ensureError(error);
|
|
162
|
-
|
|
163
|
+
// Client errors are not worth a stack trace
|
|
164
|
+
const status = errorStatus(error);
|
|
165
|
+
if (status < 500)
|
|
166
|
+
ctx.log.debug(error.message);
|
|
167
|
+
else
|
|
168
|
+
ctx.log.error(error.stack);
|
|
169
|
+
// Do not wait for the rest of a request body that is too large
|
|
170
|
+
if (status === 413)
|
|
171
|
+
ctx.res.set('Connection', 'close');
|
|
163
172
|
const exceptionFormat = ctx.exceptionFormat;
|
|
164
173
|
if (exceptionFormat === 'txt')
|
|
165
174
|
return ctx.txtException(error);
|
|
@@ -195,6 +204,7 @@ async function include(ctx, options, stash) {
|
|
|
195
204
|
return str === null ? null : new SafeString(str);
|
|
196
205
|
}
|
|
197
206
|
async function jsonException(ctx, error) {
|
|
207
|
+
const status = errorStatus(error);
|
|
198
208
|
if (ctx.app.mode === 'development') {
|
|
199
209
|
return await ctx.render({
|
|
200
210
|
json: {
|
|
@@ -205,10 +215,10 @@ async function jsonException(ctx, error) {
|
|
|
205
215
|
}
|
|
206
216
|
},
|
|
207
217
|
pretty: true,
|
|
208
|
-
status
|
|
218
|
+
status
|
|
209
219
|
});
|
|
210
220
|
}
|
|
211
|
-
return await ctx.render({ json: { error: { message:
|
|
221
|
+
return await ctx.render({ json: { error: { message: httpStatusMessages[status] } }, pretty: true, status });
|
|
212
222
|
}
|
|
213
223
|
async function jsonNotFound(ctx) {
|
|
214
224
|
return await ctx.render({ json: { error: { message: 'Not Found' } }, pretty: true, status: 404 });
|
|
@@ -276,10 +286,11 @@ async function textAreaTag(ctx, name, attrs = {}, content) {
|
|
|
276
286
|
return ctx.tag('textarea', { ...attrs, name }, content);
|
|
277
287
|
}
|
|
278
288
|
async function txtException(ctx, error) {
|
|
289
|
+
const status = errorStatus(error);
|
|
279
290
|
if (ctx.app.mode === 'development') {
|
|
280
|
-
return await ctx.render({ text: error.stack, status
|
|
291
|
+
return await ctx.render({ text: error.stack, status });
|
|
281
292
|
}
|
|
282
|
-
return await ctx.render({ text:
|
|
293
|
+
return await ctx.render({ text: httpStatusMessages[status], status });
|
|
283
294
|
}
|
|
284
295
|
async function txtNotFound(ctx) {
|
|
285
296
|
return await ctx.render({ text: 'Not Found', status: 404 });
|
|
@@ -299,4 +310,10 @@ function urlTarget(target) {
|
|
|
299
310
|
function ensureError(error) {
|
|
300
311
|
return error instanceof Error ? error : new Error(error);
|
|
301
312
|
}
|
|
302
|
-
|
|
313
|
+
// Only our own errors, a "status" property from other libraries could hide real server errors
|
|
314
|
+
function errorStatus(error) {
|
|
315
|
+
if (!(error instanceof HTTPError))
|
|
316
|
+
return 500;
|
|
317
|
+
const { status } = error;
|
|
318
|
+
return Number.isInteger(status) && status >= 400 && status <= 599 ? status : 500;
|
|
319
|
+
}
|