@mojojs/core 1.27.0 → 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.
Files changed (157) hide show
  1. package/README.md +1 -1
  2. package/lib/app.d.ts +24 -16
  3. package/lib/app.js +172 -126
  4. package/lib/body/params.js +0 -1
  5. package/lib/body.d.ts +13 -5
  6. package/lib/body.js +47 -17
  7. package/lib/cache.d.ts +1 -2
  8. package/lib/cache.js +12 -21
  9. package/lib/cgi.d.ts +3 -3
  10. package/lib/cgi.js +6 -5
  11. package/lib/cli/cgi.d.ts +1 -1
  12. package/lib/cli/cgi.js +1 -2
  13. package/lib/cli/create-full-app.d.ts +1 -1
  14. package/lib/cli/create-full-app.js +27 -27
  15. package/lib/cli/create-lite-app.d.ts +1 -1
  16. package/lib/cli/create-lite-app.js +2 -3
  17. package/lib/cli/create-plugin.d.ts +1 -1
  18. package/lib/cli/create-plugin.js +25 -23
  19. package/lib/cli/eval.d.ts +1 -1
  20. package/lib/cli/eval.js +12 -5
  21. package/lib/cli/get.d.ts +1 -1
  22. package/lib/cli/get.js +30 -26
  23. package/lib/cli/repl.d.ts +1 -1
  24. package/lib/cli/repl.js +0 -1
  25. package/lib/cli/routes.d.ts +1 -1
  26. package/lib/cli/routes.js +10 -7
  27. package/lib/cli/server.d.ts +1 -1
  28. package/lib/cli/server.js +41 -24
  29. package/lib/cli/version.js +1 -2
  30. package/lib/cli.d.ts +1 -1
  31. package/lib/cli.js +25 -19
  32. package/lib/context.d.ts +10 -10
  33. package/lib/context.js +94 -34
  34. package/lib/core.d.ts +14 -14
  35. package/lib/core.js +12 -13
  36. package/lib/headers.js +5 -4
  37. package/lib/hooks.d.ts +1 -1
  38. package/lib/hooks.js +1 -5
  39. package/lib/logger.d.ts +2 -2
  40. package/lib/logger.js +28 -16
  41. package/lib/mime.js +10 -13
  42. package/lib/plugins/default-conditions.d.ts +1 -1
  43. package/lib/plugins/default-conditions.js +0 -1
  44. package/lib/plugins/default-helpers.d.ts +1 -1
  45. package/lib/plugins/default-helpers.js +28 -11
  46. package/lib/plugins/json-config.d.ts +1 -1
  47. package/lib/plugins/json-config.js +0 -1
  48. package/lib/plugins/mount.d.ts +1 -1
  49. package/lib/plugins/mount.js +0 -1
  50. package/lib/plugins/tmpl-engine.d.ts +1 -1
  51. package/lib/plugins/tmpl-engine.js +2 -5
  52. package/lib/plugins/yaml-config.d.ts +1 -1
  53. package/lib/plugins/yaml-config.js +1 -2
  54. package/lib/renderer.d.ts +1 -1
  55. package/lib/renderer.js +27 -30
  56. package/lib/router/pattern.d.ts +1 -1
  57. package/lib/router/pattern.js +25 -14
  58. package/lib/router/plan.d.ts +1 -1
  59. package/lib/router/plan.js +12 -15
  60. package/lib/router/route.d.ts +3 -3
  61. package/lib/router/route.js +35 -38
  62. package/lib/router.d.ts +4 -4
  63. package/lib/router.js +25 -26
  64. package/lib/server/cookie.d.ts +1 -1
  65. package/lib/server/cookie.js +1 -2
  66. package/lib/server/request.d.ts +3 -3
  67. package/lib/server/request.js +44 -17
  68. package/lib/server/response.d.ts +3 -3
  69. package/lib/server/response.js +20 -20
  70. package/lib/server.d.ts +7 -2
  71. package/lib/server.js +69 -19
  72. package/lib/session.d.ts +3 -3
  73. package/lib/session.js +46 -34
  74. package/lib/sse.d.ts +1 -1
  75. package/lib/sse.js +0 -1
  76. package/lib/static.d.ts +1 -1
  77. package/lib/static.js +27 -22
  78. package/lib/types.d.ts +6 -5
  79. package/lib/types.js +0 -1
  80. package/lib/user-agent/cookie-jar.d.ts +2 -2
  81. package/lib/user-agent/cookie-jar.js +6 -9
  82. package/lib/user-agent/mock.d.ts +4 -4
  83. package/lib/user-agent/mock.js +6 -7
  84. package/lib/user-agent/response.d.ts +2 -2
  85. package/lib/user-agent/response.js +14 -3
  86. package/lib/user-agent/test.d.ts +5 -5
  87. package/lib/user-agent/test.js +14 -15
  88. package/lib/user-agent/transport/http.d.ts +2 -2
  89. package/lib/user-agent/transport/http.js +7 -10
  90. package/lib/user-agent/transport/https.d.ts +2 -2
  91. package/lib/user-agent/transport/https.js +2 -6
  92. package/lib/user-agent/transport/ws.d.ts +2 -2
  93. package/lib/user-agent/transport/ws.js +2 -3
  94. package/lib/user-agent.d.ts +18 -19
  95. package/lib/user-agent.js +64 -49
  96. package/lib/util.d.ts +13 -0
  97. package/lib/util.js +35 -11
  98. package/lib/validator/result.d.ts +1 -1
  99. package/lib/validator/result.js +8 -1
  100. package/lib/validator.d.ts +2 -2
  101. package/lib/validator.js +3 -6
  102. package/lib/websocket.d.ts +2 -2
  103. package/lib/websocket.js +9 -1
  104. package/package.json +22 -29
  105. package/lib/app.js.map +0 -1
  106. package/lib/body/params.js.map +0 -1
  107. package/lib/body.js.map +0 -1
  108. package/lib/cache.js.map +0 -1
  109. package/lib/cgi.js.map +0 -1
  110. package/lib/cli/cgi.js.map +0 -1
  111. package/lib/cli/create-full-app.js.map +0 -1
  112. package/lib/cli/create-lite-app.js.map +0 -1
  113. package/lib/cli/create-plugin.js.map +0 -1
  114. package/lib/cli/eval.js.map +0 -1
  115. package/lib/cli/get.js.map +0 -1
  116. package/lib/cli/repl.js.map +0 -1
  117. package/lib/cli/routes.js.map +0 -1
  118. package/lib/cli/server.js.map +0 -1
  119. package/lib/cli/version.js.map +0 -1
  120. package/lib/cli.js.map +0 -1
  121. package/lib/context.js.map +0 -1
  122. package/lib/core.js.map +0 -1
  123. package/lib/headers.js.map +0 -1
  124. package/lib/hooks.js.map +0 -1
  125. package/lib/logger.js.map +0 -1
  126. package/lib/mime.js.map +0 -1
  127. package/lib/plugins/default-conditions.js.map +0 -1
  128. package/lib/plugins/default-helpers.js.map +0 -1
  129. package/lib/plugins/json-config.js.map +0 -1
  130. package/lib/plugins/mount.js.map +0 -1
  131. package/lib/plugins/tmpl-engine.js.map +0 -1
  132. package/lib/plugins/yaml-config.js.map +0 -1
  133. package/lib/renderer.js.map +0 -1
  134. package/lib/router/pattern.js.map +0 -1
  135. package/lib/router/plan.js.map +0 -1
  136. package/lib/router/route.js.map +0 -1
  137. package/lib/router.js.map +0 -1
  138. package/lib/server/cookie.js.map +0 -1
  139. package/lib/server/request.js.map +0 -1
  140. package/lib/server/response.js.map +0 -1
  141. package/lib/server.js.map +0 -1
  142. package/lib/session.js.map +0 -1
  143. package/lib/sse.js.map +0 -1
  144. package/lib/static.js.map +0 -1
  145. package/lib/types.js.map +0 -1
  146. package/lib/user-agent/cookie-jar.js.map +0 -1
  147. package/lib/user-agent/mock.js.map +0 -1
  148. package/lib/user-agent/response.js.map +0 -1
  149. package/lib/user-agent/test.js.map +0 -1
  150. package/lib/user-agent/transport/http.js.map +0 -1
  151. package/lib/user-agent/transport/https.js.map +0 -1
  152. package/lib/user-agent/transport/ws.js.map +0 -1
  153. package/lib/user-agent.js.map +0 -1
  154. package/lib/util.js.map +0 -1
  155. package/lib/validator/result.js.map +0 -1
  156. package/lib/validator.js.map +0 -1
  157. package/lib/websocket.js.map +0 -1
package/README.md CHANGED
@@ -41,7 +41,7 @@ If you want to stay up to date on the
41
41
 
42
42
  ## Installation
43
43
 
44
- All you need is Node.js 20.0.0 (or newer).
44
+ All you need is Node.js 22.18.0 (or newer).
45
45
 
46
46
  ```
47
47
  $ npm install @mojojs/core
package/lib/app.d.ts CHANGED
@@ -1,19 +1,19 @@
1
- import type { Route } from './router/route.js';
2
- import type { AnyArguments, AppOptions, BackendInfo, UserAgentOptions, MojoAction, MojoContext, MojoModels, NestedHelpers, RouteArguments, ServerOptions, TestUserAgentOptions } from './types.js';
3
- import { CLI } from './cli.js';
4
- import { Hooks } from './hooks.js';
5
- import { Logger } from './logger.js';
6
- import { Mime } from './mime.js';
7
- import { Renderer } from './renderer.js';
8
- import { Router } from './router.js';
9
- import { ServerRequest } from './server/request.js';
10
- import { ServerResponse } from './server/response.js';
11
- import { Session } from './session.js';
12
- import { Static } from './static.js';
13
- import { MockUserAgent } from './user-agent/mock.js';
14
- import { TestUserAgent } from './user-agent/test.js';
15
- import { UserAgent } from './user-agent.js';
16
- import { Validator } from './validator.js';
1
+ import type { Route } from './router/route.ts';
2
+ import type { AnyArguments, AppOptions, BackendInfo, UserAgentOptions, MojoAction, MojoContext, MojoModels, NestedHelpers, RouteArguments, ServerOptions, TestUserAgentOptions } from './types.ts';
3
+ import { CLI } from './cli.ts';
4
+ import { Hooks } from './hooks.ts';
5
+ import { Logger } from './logger.ts';
6
+ import { Mime } from './mime.ts';
7
+ import { Renderer } from './renderer.ts';
8
+ import { Router } from './router.ts';
9
+ import { ServerRequest } from './server/request.ts';
10
+ import { ServerResponse } from './server/response.ts';
11
+ import { Session } from './session.ts';
12
+ import { Static } from './static.ts';
13
+ import { MockUserAgent } from './user-agent/mock.ts';
14
+ import { TestUserAgent } from './user-agent/test.ts';
15
+ import { UserAgent } from './user-agent.ts';
16
+ import { Validator } from './validator.ts';
17
17
  import Path from '@mojojs/path';
18
18
  type AppHook = (app: App, ...args: any[]) => any;
19
19
  type ContextHook = (app: MojoContext, ...args: any[]) => any;
@@ -85,6 +85,14 @@ export declare class App {
85
85
  * app.log.debug('It works');
86
86
  */
87
87
  log: Logger;
88
+ /**
89
+ * Maximum request body size in bytes, defaults to `16777216` (16 MiB). Requests exceeding the limit are answered
90
+ * with `413` when the body is read. Can also be changed for individual requests with `ctx.req.maxMessageSize`.
91
+ * @example
92
+ * // Allow larger uploads for the whole application
93
+ * app.maxRequestSize = 1024 * 1024 * 1024;
94
+ */
95
+ maxRequestSize: number;
88
96
  /**
89
97
  * MIME types.
90
98
  * @example
package/lib/app.js CHANGED
@@ -1,22 +1,22 @@
1
1
  import { Readable } from 'node:stream';
2
- import { CLI } from './cli.js';
3
- import { Context } from './context.js';
4
- import { Hooks } from './hooks.js';
5
- import { Logger } from './logger.js';
6
- import { Mime } from './mime.js';
7
- import defaultConditionsPlugin from './plugins/default-conditions.js';
8
- import defaultHelpersPlugin from './plugins/default-helpers.js';
9
- import tmplEnginePlugin from './plugins/tmpl-engine.js';
10
- import { Renderer } from './renderer.js';
11
- import { Router } from './router.js';
12
- import { ServerRequest } from './server/request.js';
13
- import { ServerResponse } from './server/response.js';
14
- import { Session } from './session.js';
15
- import { Static } from './static.js';
16
- import { MockUserAgent } from './user-agent/mock.js';
17
- import { TestUserAgent } from './user-agent/test.js';
18
- import { UserAgent } from './user-agent.js';
19
- import { Validator } from './validator.js';
2
+ import { CLI } from "./cli.js";
3
+ import { Context } from "./context.js";
4
+ import { Hooks } from "./hooks.js";
5
+ import { Logger } from "./logger.js";
6
+ import { Mime } from "./mime.js";
7
+ import defaultConditionsPlugin from "./plugins/default-conditions.js";
8
+ import defaultHelpersPlugin from "./plugins/default-helpers.js";
9
+ import tmplEnginePlugin from "./plugins/tmpl-engine.js";
10
+ import { Renderer } from "./renderer.js";
11
+ import { Router } from "./router.js";
12
+ import { ServerRequest } from "./server/request.js";
13
+ import { ServerResponse } from "./server/response.js";
14
+ import { Session } from "./session.js";
15
+ import { Static } from "./static.js";
16
+ import { MockUserAgent } from "./user-agent/mock.js";
17
+ import { TestUserAgent } from "./user-agent/test.js";
18
+ import { UserAgent } from "./user-agent.js";
19
+ import { Validator } from "./validator.js";
20
20
  import Path from '@mojojs/path';
21
21
  const ContextWrapper = class extends Context {
22
22
  };
@@ -24,111 +24,159 @@ const ContextWrapper = class extends Context {
24
24
  * Application class.
25
25
  */
26
26
  export class App {
27
+ /**
28
+ * Command line interface.
29
+ * @example
30
+ * // Add another path to load commands from
31
+ * app.cli.commandPaths.push(app.home.child('cli').toString());
32
+ */
33
+ cli = new CLI(this);
34
+ /**
35
+ * Application config.
36
+ * @example
37
+ * // Remove value
38
+ * delete app.config.foo;
39
+ *
40
+ * // Assign multiple values at once
41
+ * Object.assign(app.config, {foo: 'test', bar: 23});
42
+ */
43
+ config;
44
+ /**
45
+ * Default stash values.
46
+ * @example
47
+ * // Remove value
48
+ * delete app.defaults.foo;
49
+ *
50
+ * // Assign multiple values at once
51
+ * Object.assign(app.defaults, {foo: 'test', bar: 23});
52
+ */
53
+ defaults = {};
54
+ /**
55
+ * Detect if the application has been imported and disable the command line interface if it has.
56
+ */
57
+ detectImport;
58
+ /**
59
+ * Format for HTTP exceptions ("html", "json", or "txt").
60
+ * @example
61
+ * // Change default exception format for whole application
62
+ * app.exceptionFormat = 'json';
63
+ */
64
+ exceptionFormat;
65
+ /**
66
+ * Application hooks.
67
+ * @example
68
+ * // Run a custom hook
69
+ * await app.hooks.runHook('my:hook', foo, bar);
70
+ */
71
+ hooks = new Hooks();
72
+ /**
73
+ * Application home directory.
74
+ * @example
75
+ * // Portably generate path relative to home directory
76
+ * const path = app.home.child('data', 'important.txt');
77
+ */
78
+ home = new Path();
79
+ /**
80
+ * Application logger.
81
+ * @example
82
+ * // Log debug message
83
+ * app.log.debug('It works');
84
+ */
85
+ log;
86
+ /**
87
+ * Maximum request body size in bytes, defaults to `16777216` (16 MiB). Requests exceeding the limit are answered
88
+ * with `413` when the body is read. Can also be changed for individual requests with `ctx.req.maxMessageSize`.
89
+ * @example
90
+ * // Allow larger uploads for the whole application
91
+ * app.maxRequestSize = 1024 * 1024 * 1024;
92
+ */
93
+ maxRequestSize = 16 * 1024 * 1024;
94
+ /**
95
+ * MIME types.
96
+ * @example
97
+ * // Get MIME type for extension
98
+ * const type = app.mime.extType('txt');
99
+ */
100
+ mime = new Mime();
101
+ /**
102
+ * Operating mode for application. Defaults to the value of the `NODE_ENV` environment variable or `development`.
103
+ */
104
+ mode;
105
+ // Apps augment MojoModels with required keys they fill in later
106
+ /**
107
+ * Storage for user defined models.
108
+ * @example
109
+ * // Store database connection
110
+ * app.models.pg = new Pg('postgres://127.0.0.1:5432/db');
111
+ */
112
+ models = {};
113
+ /**
114
+ * Application renderer.
115
+ * @example
116
+ * // Disable compression
117
+ * app.renderer.autoCompress = false;
118
+ *
119
+ * // Add another "views" directory
120
+ * app.renderer.viewPaths.push(app.home.child('views').toString());
121
+ */
122
+ renderer = new Renderer();
123
+ /**
124
+ * Application router.
125
+ * @example
126
+ * // Add routes
127
+ * const r = app.router;
128
+ * r.get('/foo/bar').to('test#foo', {title: 'Hello Mojo!'});
129
+ * r.post('/baz').to('test#baz');
130
+ *
131
+ * // Add another path to load controllers from
132
+ * app.router.controllerPaths.push(app.home.child('more-controllers').toString());
133
+ */
134
+ router = new Router();
135
+ /**
136
+ * Rotating secret passphrases used for signed cookies and the like.
137
+ * @example
138
+ * // Rotate passphrases
139
+ * app.secrets = ['new_passw0rd', 'old_passw0rd', 'very_old_passw0rd'];
140
+ */
141
+ secrets;
142
+ /**
143
+ * Encrypted cookie based session manager.
144
+ * @example
145
+ * // Change name of cookie used for all sessions
146
+ * app.sessions.cookieName = 'mysession';
147
+ *
148
+ * // Disable SameSite feature
149
+ * app.sessions.sameSite = 'none';
150
+ */
151
+ session = new Session(this);
152
+ /**
153
+ * Static file server.
154
+ * @example
155
+ * // Add another "public" directory
156
+ * app.static.publicPaths.push('/home/sri/public');
157
+ *
158
+ * // Add another "public" directory with higher precedence
159
+ * app.static.publicPaths.unshift('/home/sri/themes/blue/public');
160
+ */
161
+ static = new Static();
162
+ /**
163
+ * HTTP/WebSocket user-agent.
164
+ * @example
165
+ * # Perform HTTP request
166
+ * const res = await app.ua.get('http://example.com');
167
+ */
168
+ ua = new UserAgent();
169
+ /**
170
+ * JSON schema validator.
171
+ * @example
172
+ * // Add a named schema for later use
173
+ * app.validator.addSchema({type: 'object', properties: {test: {type: 'number'}}}, 'testForm');
174
+ */
175
+ validator = new Validator();
176
+ _contextClass = class extends ContextWrapper {
177
+ };
178
+ _nestedHelpers = {};
27
179
  constructor(options = {}) {
28
- /**
29
- * Command line interface.
30
- * @example
31
- * // Add another path to load commands from
32
- * app.cli.commandPaths.push(app.home.child('cli').toString());
33
- */
34
- this.cli = new CLI(this);
35
- /**
36
- * Default stash values.
37
- * @example
38
- * // Remove value
39
- * delete app.defaults.foo;
40
- *
41
- * // Assign multiple values at once
42
- * Object.assign(app.defaults, {foo: 'test', bar: 23});
43
- */
44
- this.defaults = {};
45
- /**
46
- * Application hooks.
47
- * @example
48
- * // Run a custom hook
49
- * await app.hooks.runHook('my:hook', foo, bar);
50
- */
51
- this.hooks = new Hooks();
52
- /**
53
- * Application home directory.
54
- * @example
55
- * // Portably generate path relative to home directory
56
- * const path = app.home.child('data', 'important.txt');
57
- */
58
- this.home = new Path();
59
- /**
60
- * MIME types.
61
- * @example
62
- * // Get MIME type for extension
63
- * const type = app.mime.extType('txt');
64
- */
65
- this.mime = new Mime();
66
- /**
67
- * Storage for user defined models.
68
- * @example
69
- * // Store database connection
70
- * app.models.pg = new Pg('postgres://127.0.0.1:5432/db');
71
- */
72
- this.models = {};
73
- /**
74
- * Application renderer.
75
- * @example
76
- * // Disable compression
77
- * app.renderer.autoCompress = false;
78
- *
79
- * // Add another "views" directory
80
- * app.renderer.viewPaths.push(app.home.child('views').toString());
81
- */
82
- this.renderer = new Renderer();
83
- /**
84
- * Application router.
85
- * @example
86
- * // Add routes
87
- * const r = app.router;
88
- * r.get('/foo/bar').to('test#foo', {title: 'Hello Mojo!'});
89
- * r.post('/baz').to('test#baz');
90
- *
91
- * // Add another path to load controllers from
92
- * app.router.controllerPaths.push(app.home.child('more-controllers').toString());
93
- */
94
- this.router = new Router();
95
- /**
96
- * Encrypted cookie based session manager.
97
- * @example
98
- * // Change name of cookie used for all sessions
99
- * app.sessions.cookieName = 'mysession';
100
- *
101
- * // Disable SameSite feature
102
- * app.sessions.sameSite = 'none';
103
- */
104
- this.session = new Session(this);
105
- /**
106
- * Static file server.
107
- * @example
108
- * // Add another "public" directory
109
- * app.static.publicPaths.push('/home/sri/public');
110
- *
111
- * // Add another "public" directory with higher precedence
112
- * app.static.publicPaths.unshift('/home/sri/themes/blue/public');
113
- */
114
- this.static = new Static();
115
- /**
116
- * HTTP/WebSocket user-agent.
117
- * @example
118
- * # Perform HTTP request
119
- * const res = await app.ua.get('http://example.com');
120
- */
121
- this.ua = new UserAgent();
122
- /**
123
- * JSON schema validator.
124
- * @example
125
- * // Add a named schema for later use
126
- * app.validator.addSchema({type: 'object', properties: {test: {type: 'number'}}}, 'testForm');
127
- */
128
- this.validator = new Validator();
129
- this._contextClass = class extends ContextWrapper {
130
- };
131
- this._nestedHelpers = {};
132
180
  this.config = options.config ?? {};
133
181
  this.detectImport = options.detectImport ?? true;
134
182
  this.exceptionFormat = options.exceptionFormat ?? 'html';
@@ -194,10 +242,9 @@ export class App {
194
242
  this._nestedHelpers[getterName] = {};
195
243
  this.decorateContext(getterName, {
196
244
  get: function () {
197
- var _a;
198
- return ((_a = this._nestedHelpersCache)[getterName] ?? (_a[getterName] = Object.create(this.app._nestedHelpers[getterName], {
245
+ return (this._nestedHelpersCache[getterName] ??= Object.create(this.app._nestedHelpers[getterName], {
199
246
  _ctx: { value: this }
200
- })));
247
+ }));
201
248
  }
202
249
  });
203
250
  }
@@ -454,4 +501,3 @@ export class App {
454
501
  return this.router.websocket(...args);
455
502
  }
456
503
  }
457
- //# sourceMappingURL=app.js.map
@@ -27,4 +27,3 @@ export class Params extends URLSearchParams {
27
27
  return Object.fromEntries(this);
28
28
  }
29
29
  }
30
- //# sourceMappingURL=params.js.map
package/lib/body.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import type { JSONValue, UploadOptions } from './types.js';
2
- import type { Readable, Writable } from 'node:stream';
3
- import { Params } from './body/params.js';
4
- import { Headers } from './headers.js';
1
+ import type { JSONValue, UploadOptions } from './types.ts';
2
+ import { type Readable, type Writable } from 'node:stream';
3
+ import { Params } from './body/params.ts';
4
+ import { Headers } from './headers.ts';
5
5
  import DOM from '@mojojs/dom';
6
6
  interface FileUpload {
7
7
  fieldname: string;
@@ -10,7 +10,11 @@ interface FileUpload {
10
10
  encoding: string;
11
11
  mimetype: string;
12
12
  }
13
- type FormIterator = AsyncIterableIterator<[string, Readable, string, string, string]>;
13
+ type FormIterator = AsyncIterableIterator<[string, Readable, {
14
+ filename: string;
15
+ encoding: string;
16
+ mimeType: string;
17
+ }]>;
14
18
  /**
15
19
  * HTTP message body base class.
16
20
  */
@@ -23,6 +27,10 @@ export declare class Body {
23
27
  * HTTP headers.
24
28
  */
25
29
  headers: Headers;
30
+ /**
31
+ * Maximum message body size in bytes, after decompression, unlimited by default.
32
+ */
33
+ maxMessageSize: number | undefined;
26
34
  _form: Params | undefined;
27
35
  _stream: Readable;
28
36
  constructor(headers: string[], stream: Readable);
package/lib/body.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import { on } from 'node:events';
2
+ import { Transform } from 'node:stream';
2
3
  import zlib from 'node:zlib';
3
- import { Params } from './body/params.js';
4
- import { Headers } from './headers.js';
4
+ import { Params } from "./body/params.js";
5
+ import { Headers } from "./headers.js";
6
+ import { HTTPError } from "./util.js";
5
7
  import DOM from '@mojojs/dom';
6
8
  import busboy from 'busboy';
7
9
  import * as yaml from 'js-yaml';
@@ -9,12 +11,21 @@ import * as yaml from 'js-yaml';
9
11
  * HTTP message body base class.
10
12
  */
11
13
  export class Body {
14
+ /**
15
+ * Automatically decompress message body if necessary.
16
+ */
17
+ autoDecompress = true;
18
+ /**
19
+ * HTTP headers.
20
+ */
21
+ headers;
22
+ /**
23
+ * Maximum message body size in bytes, after decompression, unlimited by default.
24
+ */
25
+ maxMessageSize = undefined;
26
+ _form = undefined;
27
+ _stream;
12
28
  constructor(headers, stream) {
13
- /**
14
- * Automatically decompress message body if necessary.
15
- */
16
- this.autoDecompress = true;
17
- this._form = undefined;
18
29
  this._stream = stream;
19
30
  this.headers = new Headers(headers);
20
31
  }
@@ -34,11 +45,27 @@ export class Body {
34
45
  const stream = this._stream;
35
46
  if (stream.readableEnded === true)
36
47
  throw new Error('Request body has already been consumed');
37
- if (this.autoDecompress !== true || this.get('content-encoding') !== 'gzip')
38
- return stream;
39
- const gunzip = zlib.createGunzip();
40
- stream.pipe(gunzip);
41
- return gunzip;
48
+ const max = this.maxMessageSize;
49
+ if (max !== undefined && parseInt(this.get('Content-Length') ?? '0') > max)
50
+ throw messageTooLarge();
51
+ let body = stream;
52
+ if (this.autoDecompress === true && this.get('content-encoding') === 'gzip') {
53
+ const gunzip = zlib.createGunzip();
54
+ stream.on('error', error => gunzip.destroy(error));
55
+ body = stream.pipe(gunzip);
56
+ }
57
+ if (max === undefined)
58
+ return body;
59
+ // Count after decompression, so small gzip bodies cannot unpack to huge ones
60
+ let size = 0;
61
+ const limit = new Transform({
62
+ transform(chunk, encoding, cb) {
63
+ size += chunk.length;
64
+ cb(size > max ? messageTooLarge() : null, chunk);
65
+ }
66
+ });
67
+ body.on('error', error => limit.destroy(error));
68
+ return body.pipe(limit);
42
69
  }
43
70
  /**
44
71
  * Get async iterator for uploaded files from message body.
@@ -57,9 +84,8 @@ export class Body {
57
84
  if (this._isForm() === false)
58
85
  return;
59
86
  try {
60
- for await (const [fieldname, file, name, encoding, mimetype] of this._formIterator(options)) {
61
- const filename = name.filename;
62
- yield { fieldname, file, filename, encoding, mimetype };
87
+ for await (const [fieldname, file, { filename, encoding, mimeType }] of this._formIterator(options)) {
88
+ yield { fieldname, file, filename, encoding, mimetype: mimeType };
63
89
  }
64
90
  }
65
91
  catch (error) {
@@ -155,7 +181,9 @@ export class Body {
155
181
  bb.on('field', (fieldname, val) => params.append(fieldname, val));
156
182
  bb.on('end', () => ac.abort()).on('close', () => ac.abort());
157
183
  const files = on(bb, 'file', { signal: ac.signal });
158
- this._stream.pipe(bb);
184
+ this.createReadStream()
185
+ .on('error', error => bb.destroy(error))
186
+ .pipe(bb);
159
187
  return files;
160
188
  }
161
189
  _isForm() {
@@ -170,4 +198,6 @@ export class Body {
170
198
  return this._form;
171
199
  }
172
200
  }
173
- //# sourceMappingURL=body.js.map
201
+ function messageTooLarge() {
202
+ return new HTTPError(413, 'Maximum message size exceeded');
203
+ }
package/lib/cache.d.ts CHANGED
@@ -6,8 +6,7 @@ export declare class Cache<T> {
6
6
  * Maximum number of values in cache, defaults to `100`.
7
7
  */
8
8
  max: number;
9
- _cache: Record<string, T>;
10
- _queue: string[];
9
+ _cache: Map<string, T>;
11
10
  /**
12
11
  * Get cached value.
13
12
  */
package/lib/cache.js CHANGED
@@ -2,19 +2,17 @@
2
2
  * Cache class.
3
3
  */
4
4
  export class Cache {
5
- constructor() {
6
- /**
7
- * Maximum number of values in cache, defaults to `100`.
8
- */
9
- this.max = 100;
10
- this._cache = {};
11
- this._queue = [];
12
- }
5
+ /**
6
+ * Maximum number of values in cache, defaults to `100`.
7
+ */
8
+ max = 100;
9
+ // Maps keep insertion order, so the first key is always the oldest
10
+ _cache = new Map();
13
11
  /**
14
12
  * Get cached value.
15
13
  */
16
14
  get(key) {
17
- return this._cache[key];
15
+ return this._cache.get(key);
18
16
  }
19
17
  /**
20
18
  * Add value to the cache.
@@ -24,22 +22,15 @@ export class Cache {
24
22
  if (max <= 0)
25
23
  return;
26
24
  const cache = this._cache;
27
- const queue = this._queue;
28
- while (queue.length >= max) {
29
- const key = queue.shift();
30
- if (key !== undefined)
31
- delete cache[key];
32
- }
33
- if (cache[key] === undefined) {
34
- queue.push(key);
35
- cache[key] = value;
36
- }
25
+ while (cache.size >= max)
26
+ cache.delete(cache.keys().next().value);
27
+ if (cache.has(key) === false)
28
+ cache.set(key, value);
37
29
  }
38
30
  /**
39
31
  * Current size of the cache.
40
32
  */
41
33
  get size() {
42
- return this._queue.length;
34
+ return this._cache.size;
43
35
  }
44
36
  }
45
- //# sourceMappingURL=cache.js.map
package/lib/cgi.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import type { MojoApp, ServerResponseBody } from './types.js';
1
+ import type { MojoApp, ServerResponseBody } from './types.ts';
2
2
  import { type Readable } from 'node:stream';
3
- import { ServerRequest } from './server/request.js';
4
- import { ServerResponse } from './server/response.js';
3
+ import { ServerRequest } from './server/request.ts';
4
+ import { ServerResponse } from './server/response.ts';
5
5
  /**
6
6
  * CGI class. Is this a joke? Yes. Could you use it in production anyway? Probably.
7
7
  */
package/lib/cgi.js CHANGED
@@ -1,12 +1,13 @@
1
1
  import { Stream } from 'node:stream';
2
- import { ServerRequest } from './server/request.js';
3
- import { ServerResponse } from './server/response.js';
4
- import { httpStatusMessages } from './util.js';
2
+ import { ServerRequest } from "./server/request.js";
3
+ import { ServerResponse } from "./server/response.js";
4
+ import { httpStatusMessages } from "./util.js";
5
5
  import { decodeURIComponentSafe } from '@mojojs/util';
6
6
  /**
7
7
  * CGI class. Is this a joke? Yes. Could you use it in production anyway? Probably.
8
8
  */
9
9
  export class CGI {
10
+ app;
10
11
  constructor(app) {
11
12
  this.app = app;
12
13
  }
@@ -79,8 +80,8 @@ export class CGI {
79
80
  }
80
81
  else if (body instanceof Stream) {
81
82
  stdout.write(res.headers.toString());
82
- body.pipe(stdout);
83
+ // Not pipeline, because stdout must stay open, and the response is just cut short on errors
84
+ body.on('error', () => { }).pipe(stdout);
83
85
  }
84
86
  }
85
87
  }
86
- //# sourceMappingURL=cgi.js.map
package/lib/cli/cgi.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { MojoApp } from '../types.js';
1
+ import type { MojoApp } from '../types.ts';
2
2
  /**
3
3
  * CGI command.
4
4
  */
package/lib/cli/cgi.js CHANGED
@@ -1,4 +1,4 @@
1
- import { CGI } from '../cgi.js';
1
+ import { CGI } from "../cgi.js";
2
2
  /**
3
3
  * CGI command.
4
4
  */
@@ -13,4 +13,3 @@ cgiCommand.usage = `Usage: APPLICATION cgi [OPTIONS]
13
13
  Options:
14
14
  -h, --help Show this summary of available options
15
15
  `;
16
- //# sourceMappingURL=cgi.js.map
@@ -1,4 +1,4 @@
1
- import type { App } from '../app.js';
1
+ import type { App } from '../app.ts';
2
2
  /**
3
3
  * Minimal application generator command.
4
4
  */