@bjoernboss/mws 1.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/LICENSE.txt ADDED
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2024-2026 Bjoern Boss Henrichsen
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ 3. Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package/README.md ADDED
@@ -0,0 +1,301 @@
1
+ # MWS - Modular Web Server
2
+ ![TypeScript](https://img.shields.io/badge/language-TypeScript-blue?style=flat-square)
3
+ [![License](https://img.shields.io/badge/license-BSD--3--Clause-brightgreen?style=flat-square)](LICENSE.txt)
4
+
5
+ A lightweight TypeScript framework for hosting isolated modules behind HTTP/HTTPS and WebSocket endpoints. Each module owns a subtree in the URL space and is isolated from its siblings. Modules compose into trees for path-based routing, hostname routing, and request interception.
6
+
7
+ The server integrates various automation features, such as error handling, validations, caching... Further, it contains integrated logging for proper connection tracing and logging.
8
+
9
+ ## Installation
10
+ Only depends on [`ws`](https://github.com/websockets/ws) at runtime.
11
+
12
+ $ npm install @bjoernboss/mws
13
+
14
+ Requires Node.js 22 or later.
15
+
16
+ Note: Look into the source `TypeScript` code on [`GitHub`](https://github.com/BjoernBoss/mws).
17
+
18
+ ## Quick Start
19
+
20
+ ```typescript
21
+ import { Server, ModuleHandler, ClientRequest, Media, addLogger, createConsoleLogger } from "@bjoernboss/mws";
22
+
23
+ addLogger(createConsoleLogger());
24
+
25
+ class HelloModule extends ModuleHandler {
26
+ constructor() {
27
+ super('hello');
28
+ }
29
+
30
+ protected override async handleRequest(client: ClientRequest): Promise<void> {
31
+ client.respond('Hello, World!', { media: Media.Text });
32
+ }
33
+ }
34
+
35
+ const server = new Server();
36
+ server.listen(new HelloModule(), { port: 8080 });
37
+ ```
38
+
39
+ For HTTPS, pass a TLS configuration:
40
+
41
+ ```typescript
42
+ server.listen(new HelloModule(), {
43
+ port: 443,
44
+ tls: { key: './privkey.pem', cert: './fullchain.pem' }
45
+ });
46
+ ```
47
+
48
+ ## Writing Modules
49
+
50
+ A module extends `ModuleHandler` and implements up to three lifecycle hooks:
51
+
52
+ ```typescript
53
+ import { ModuleHandler, ClientRequest, Server } from "@bjoernboss/mws";
54
+
55
+ export class MyModule extends ModuleHandler {
56
+ constructor() {
57
+ super('my-module');
58
+ }
59
+
60
+ /* Called once when first attached to a server (directly or through a parent module) */
61
+ protected override async handleInitialize(server: Server): Promise<void> {
62
+ /* allocate resources, start timers */
63
+ }
64
+
65
+ /* Called for every incoming request routed to this module */
66
+ protected override async handleRequest(client: ClientRequest, params?: object): Promise<void> {
67
+ /* respond to the client */
68
+ }
69
+
70
+ /* Called once after the module has stopped and all clients have left;
71
+ accepted WebSockets are still open and must be closed manually */
72
+ protected override async handleStop(): Promise<void> {
73
+ /* release resources, close WebSockets */
74
+ }
75
+ }
76
+ ```
77
+
78
+ Only `handleRequest` is required. Unhandled requests can be handled by parent modules or receive an automatic `404 Not Found`.
79
+
80
+ Modules form a tree via `linkModule()`. They only ever see requests relative to their `root`. Path translation happens automatically when dispatching to children — client paths are rebased relative to each child module's position in the tree. A module can be linked to multiple parents and will only be initialized once, on first attachment to a server.
81
+
82
+ ### Header and HTML Patching
83
+
84
+ Parent modules can register patches that modify outgoing headers or HTML pages before they are sent to the client:
85
+
86
+ ```typescript
87
+ /* called just before headers are sent (in reverse registration order) */
88
+ client.patchHeaders((status, headers) => {
89
+ headers['X-Custom'] = 'value';
90
+ });
91
+
92
+ /* called just before an HTML page is finalized (in reverse registration order) */
93
+ client.patchHtmlPage(async (page, status, headers) => {
94
+ page.head.push(build.LoadScript('/analytics.js'));
95
+ });
96
+ ```
97
+
98
+ Patches are scoped to the current handler context and automatically removed when the handler returns.
99
+
100
+ ### Shutdown
101
+
102
+ `server.stop()` waits for all active request handlers to complete before shutting down. Long-running handlers **must** check `client.claimed` or await `client.responded` to detect when the connection has been broken, and exit promptly.
103
+
104
+ ### Stopping Individual Modules
105
+
106
+ Calling `module.stop()` detaches the module and waits for its active clients to drain. By default, a module stops automatically when all its parents unlink it; this can be disabled with `module.stopOnDetach(false)`.
107
+
108
+ ## Helper Modules
109
+
110
+ Factory functions create common module patterns without subclassing:
111
+
112
+ ### dispatch — Path Routing
113
+
114
+ Routes requests to children by longest URL path match. Stops itself once all children have been unlinked.
115
+
116
+ ```typescript
117
+ import { Server, dispatch } from "@bjoernboss/mws";
118
+
119
+ const server = new Server();
120
+ server.listen(dispatch({
121
+ '/api': apiModule,
122
+ '/static': staticModule
123
+ }), { port: 8080 });
124
+ ```
125
+
126
+ ### host — Hostname Routing
127
+
128
+ Routes requests to children by longest hostname match (supports sub-domain matching).
129
+
130
+ ```typescript
131
+ import { Server, host } from "@bjoernboss/mws";
132
+
133
+ server.listen(host({
134
+ 'api.example.com': apiModule,
135
+ 'example.com': mainModule
136
+ }), { port: 8080 });
137
+ ```
138
+
139
+ ### bind — Parameter and Translation Binding
140
+
141
+ Forwards all requests to a single child handler, optionally injecting `params` and a path `translate` map.
142
+
143
+ ```typescript
144
+ import { bind } from "@bjoernboss/mws";
145
+
146
+ const bound = bind(myModule, {
147
+ params: { role: 'admin' },
148
+ translate: { '/v2': '/' }
149
+ });
150
+ ```
151
+
152
+ ### check — Host and Port Validation
153
+
154
+ Validates the request hostname and port before forwarding. Responds `404` and kills the connection on mismatch.
155
+
156
+ ```typescript
157
+ import { check } from "@bjoernboss/mws";
158
+
159
+ const checked = check(myModule, ['localhost', '127.0.0.1'], { port: 8080 });
160
+ ```
161
+
162
+ ### lambda — Callback-Based Handler
163
+
164
+ Handles requests via callbacks instead of subclassing, with optional attached child modules.
165
+
166
+ ```typescript
167
+ import { lambda, ClientRequest, Server, AttachedModule } from "@bjoernboss/mws";
168
+
169
+ const handler = lambda({
170
+ attach: { api: apiModule },
171
+ setup: async function (server: Server, links: Record<string, AttachedModule>) {
172
+ /* runs on first attachment */
173
+ },
174
+ handle: async function (client: ClientRequest, params, links) {
175
+ if (client.isSubPathOf('/api'))
176
+ await links.api.handle(client, { translate: { '/api': '/' } });
177
+ else
178
+ client.respondNotFound();
179
+ },
180
+ stop: async function (links) {
181
+ /* cleanup */
182
+ }
183
+ });
184
+ ```
185
+
186
+ ## Request Handling
187
+
188
+ `ClientRequest` automatically manages requests to handle errors, prevent double-responses, and ensure expected HTTP behavior.
189
+
190
+ ### Receiving Data
191
+
192
+ ```typescript
193
+ /* as a complete buffer */
194
+ const data = await client.receiveAllBuffer(1_000_000);
195
+
196
+ /* as a decoded string */
197
+ const text = await client.receiveAllText('utf-8', 1_000_000);
198
+
199
+ /* as a readable stream */
200
+ const stream = client.receiveData(1_000_000);
201
+
202
+ /* directly to a file (fails if the file already exists) */
203
+ await client.receiveToFile('/uploads/file.bin', 10_000_000);
204
+ ```
205
+
206
+ ### Responding
207
+
208
+ ```typescript
209
+ /* simple text or buffer */
210
+ client.respond('OK', { media: Media.Text, status: Status.Ok });
211
+
212
+ /* streaming response */
213
+ const writer = client.respondData({ media: Media.Json, dynamicEncode: true });
214
+ writer.end(JSON.stringify(data));
215
+
216
+ /* file with automatic range requests, etag, last-modified, encoding, and caching */
217
+ if (!await client.tryRespondFile('/var/www/index.html'))
218
+ client.respondNotFound();
219
+
220
+ /* HTML page */
221
+ await client.respondHtml(page, { status: Status.Ok });
222
+ ```
223
+
224
+ Convenience methods: `respondOk`, `respondNotFound`, `respondBadRequest`, `respondForbidden`, `respondInternalError`, `respondConflict`, `respondSeeOther`, `respondTemporaryRedirect`, `respondPermanentRedirect`, `respondCreated`, and others.
225
+
226
+ ## WebSocket
227
+
228
+ ```typescript
229
+ protected override async handleRequest(client: ClientRequest): Promise<void> {
230
+ const ws = await client.acceptWebSocket();
231
+ if (ws == null) return;
232
+
233
+ ws.on('data', (data: Buffer) => {
234
+ ws.send(data);
235
+ });
236
+ ws.on('close', () => {
237
+ /* cleanup */
238
+ });
239
+ }
240
+ ```
241
+
242
+ `ClientSocket` handles alive checks automatically via configurable ping/pong intervals (`ClientConfig.webSocketTimeout`, `ClientConfig.webSocketAliveTimeout`).
243
+
244
+ Non-upgrade requests that reach `acceptWebSocket()` receive an automatic `426 Upgrade Required` response.
245
+
246
+ ## Caching
247
+
248
+ `Server` provides integrated caching with a two-layer system:
249
+
250
+ ### In-Memory File Cache
251
+
252
+ An LRU cache for frequently served files. Encoded variants (gzip, brotli, ...) are cached alongside the original.
253
+
254
+ `client.tryRespondFile()` reads through this cache automatically.
255
+
256
+ ### Immutable Versioned Paths
257
+
258
+ File paths can be tagged with a unique version identifier so clients can cache them immutably:
259
+
260
+ ```typescript
261
+ /* style.css becomes style.<id>.css — the id changes when the file changes */
262
+ const versionedPath = server.cache.immutable('my-handler', '/static/style.css');
263
+ ```
264
+
265
+ When a request arrives for an immutable path, the cache strips the version tag, serves the underlying file, and redirects stale version tags to the current one. Set `CacheConfig.immutableStatePath` to persist version mappings across restarts.
266
+
267
+ ### Direct Cache Access
268
+
269
+ ```typescript
270
+ const buffer = await server.cache.read('/path/to/data.json');
271
+ await server.cache.write('/path/to/output.json', JSON.stringify(data));
272
+ server.cache.flush();
273
+ ```
274
+
275
+ ## HTML Building
276
+
277
+ The `build` namespace provides programmatic HTML construction with automatic escaping:
278
+
279
+ ```typescript
280
+ import { build } from "@bjoernboss/mws";
281
+
282
+ const page = new build.HtmlPage({
283
+ head: [
284
+ build.Title('My Page'),
285
+ build.Meta('viewport', 'width=device-width, initial-scale=1'),
286
+ build.LoadStyle('/style.css'),
287
+ build.LoadScript('/app.js', { defer: '' })
288
+ ],
289
+ body: [
290
+ build.Div({ id: 'root' }, [
291
+ build.Text('Hello, World!')
292
+ ])
293
+ ]
294
+ });
295
+ ```
296
+
297
+ Plain strings passed as `HtmlString` values are automatically HTML-escaped. Use `build.Safe(content)` to mark trusted content that should not be escaped.
298
+
299
+ ## Internal Methods
300
+
301
+ Methods prefixed with `_` (e.g. `_rootAttachToServer`, `_pushTranslation`, `_restoreSnapshot`) are framework-internal and **must not** be called by module implementations. They are `public` for cross-class access within the framework, but are not part of the public API and may change without notice.
package/dist/base.d.ts ADDED
@@ -0,0 +1,198 @@
1
+ import * as libZlib from "zlib";
2
+ import * as libStream from "stream";
3
+ export interface StatusType {
4
+ code: number;
5
+ msg: string;
6
+ }
7
+ export declare const Status: {
8
+ readonly Ok: {
9
+ readonly code: 200;
10
+ readonly msg: "OK";
11
+ };
12
+ readonly Created: {
13
+ readonly code: 201;
14
+ readonly msg: "Created";
15
+ };
16
+ readonly PartialContent: {
17
+ readonly code: 206;
18
+ readonly msg: "Partial Content";
19
+ };
20
+ readonly SeeOther: {
21
+ readonly code: 303;
22
+ readonly msg: "See Other";
23
+ };
24
+ readonly NotModified: {
25
+ readonly code: 304;
26
+ readonly msg: "Not Modified";
27
+ };
28
+ readonly TemporaryRedirect: {
29
+ readonly code: 307;
30
+ readonly msg: "Temporary Redirect";
31
+ };
32
+ readonly PermanentRedirect: {
33
+ readonly code: 308;
34
+ readonly msg: "Permanent Redirect";
35
+ };
36
+ readonly BadRequest: {
37
+ readonly code: 400;
38
+ readonly msg: "Bad Request";
39
+ };
40
+ readonly Forbidden: {
41
+ readonly code: 403;
42
+ readonly msg: "Forbidden";
43
+ };
44
+ readonly NotFound: {
45
+ readonly code: 404;
46
+ readonly msg: "Not Found";
47
+ };
48
+ readonly MethodNotAllowed: {
49
+ readonly code: 405;
50
+ readonly msg: "Method Not Allowed";
51
+ };
52
+ readonly RequestTimeout: {
53
+ readonly code: 408;
54
+ readonly msg: "Request Timeout";
55
+ };
56
+ readonly Conflict: {
57
+ readonly code: 409;
58
+ readonly msg: "Conflict";
59
+ };
60
+ readonly PreconditionFailed: {
61
+ readonly code: 412;
62
+ readonly msg: "Precondition Failed";
63
+ };
64
+ readonly ContentTooLarge: {
65
+ readonly code: 413;
66
+ readonly msg: "Content Too Large";
67
+ };
68
+ readonly UnsupportedMediaType: {
69
+ readonly code: 415;
70
+ readonly msg: "Unsupported Media Type";
71
+ };
72
+ readonly RangeIssue: {
73
+ readonly code: 416;
74
+ readonly msg: "Range Not Satisfiable";
75
+ };
76
+ readonly UpgradeRequired: {
77
+ readonly code: 426;
78
+ readonly msg: "Upgrade Required";
79
+ };
80
+ readonly InternalError: {
81
+ readonly code: 500;
82
+ readonly msg: "Internal Server Error";
83
+ };
84
+ };
85
+ export interface MediaType {
86
+ fileEnding: string[];
87
+ mediaType: string;
88
+ encoding: string;
89
+ compressible: boolean;
90
+ }
91
+ export declare const Media: {
92
+ readonly Html: {
93
+ readonly fileEnding: ["html"];
94
+ readonly mediaType: "text/html";
95
+ readonly encoding: "charset=utf-8";
96
+ readonly compressible: true;
97
+ };
98
+ readonly Css: {
99
+ readonly fileEnding: ["css"];
100
+ readonly mediaType: "text/css";
101
+ readonly encoding: "charset=utf-8";
102
+ readonly compressible: true;
103
+ };
104
+ readonly JavaScript: {
105
+ readonly fileEnding: ["js"];
106
+ readonly mediaType: "text/javascript";
107
+ readonly encoding: "charset=utf-8";
108
+ readonly compressible: true;
109
+ };
110
+ readonly Text: {
111
+ readonly fileEnding: ["txt", "text"];
112
+ readonly mediaType: "text/plain";
113
+ readonly encoding: "charset=utf-8";
114
+ readonly compressible: true;
115
+ };
116
+ readonly Json: {
117
+ readonly fileEnding: ["json"];
118
+ readonly mediaType: "application/json";
119
+ readonly encoding: "charset=utf-8";
120
+ readonly compressible: true;
121
+ };
122
+ readonly Mp4: {
123
+ readonly fileEnding: ["mp4"];
124
+ readonly mediaType: "video/mp4";
125
+ readonly encoding: "";
126
+ readonly compressible: false;
127
+ };
128
+ readonly Png: {
129
+ readonly fileEnding: ["png"];
130
+ readonly mediaType: "image/png";
131
+ readonly encoding: "";
132
+ readonly compressible: false;
133
+ };
134
+ readonly Gif: {
135
+ readonly fileEnding: ["gif"];
136
+ readonly mediaType: "image/gif";
137
+ readonly encoding: "";
138
+ readonly compressible: false;
139
+ };
140
+ readonly Jpg: {
141
+ readonly fileEnding: ["jpg", "jpeg"];
142
+ readonly mediaType: "image/jpeg";
143
+ readonly encoding: "";
144
+ readonly compressible: false;
145
+ };
146
+ readonly Svg: {
147
+ readonly fileEnding: ["svg"];
148
+ readonly mediaType: "image/svg+xml";
149
+ readonly encoding: "";
150
+ readonly compressible: true;
151
+ };
152
+ readonly Unknown: {
153
+ readonly fileEnding: [];
154
+ readonly mediaType: "application/octet-stream";
155
+ readonly encoding: "";
156
+ readonly compressible: false;
157
+ };
158
+ };
159
+ export interface EncodingType {
160
+ name: string;
161
+ makeDecode(): libStream.Transform;
162
+ makeEncode(): libStream.Transform;
163
+ encodeBuffer(buffer: Buffer): Buffer;
164
+ }
165
+ export declare const Encoding: {
166
+ readonly Br: {
167
+ readonly name: "br";
168
+ readonly makeDecode: () => libZlib.BrotliDecompress;
169
+ readonly makeEncode: () => libZlib.BrotliCompress;
170
+ readonly encodeBuffer: (buffer: Buffer) => NonSharedBuffer;
171
+ };
172
+ readonly Zstd: {
173
+ readonly name: "zstd";
174
+ readonly makeDecode: () => libZlib.ZstdDecompress;
175
+ readonly makeEncode: () => libZlib.ZstdCompress;
176
+ readonly encodeBuffer: (buffer: Buffer) => NonSharedBuffer;
177
+ };
178
+ readonly Gzip: {
179
+ readonly name: "gzip";
180
+ readonly makeDecode: () => libZlib.Gunzip;
181
+ readonly makeEncode: () => libZlib.Gzip;
182
+ readonly encodeBuffer: (buffer: Buffer) => NonSharedBuffer;
183
+ };
184
+ readonly Deflate: {
185
+ readonly name: "deflate";
186
+ readonly makeDecode: () => libZlib.Inflate;
187
+ readonly makeEncode: () => libZlib.Deflate;
188
+ readonly encodeBuffer: (buffer: Buffer) => NonSharedBuffer;
189
+ };
190
+ readonly Identity: {
191
+ readonly name: "identity";
192
+ readonly makeDecode: () => libStream.PassThrough;
193
+ readonly makeEncode: () => libStream.PassThrough;
194
+ readonly encodeBuffer: (buffer: Buffer) => Buffer<ArrayBufferLike>;
195
+ };
196
+ };
197
+ export declare const MIN_ENCODING_SIZE: number;
198
+ //# sourceMappingURL=base.d.ts.map
package/dist/base.js ADDED
@@ -0,0 +1,73 @@
1
+ /* SPDX-License-Identifier: BSD-3-Clause */
2
+ /* Copyright (c) 2026 Bjoern Boss Henrichsen */
3
+ import * as libZlib from "zlib";
4
+ import * as libStream from "stream";
5
+ export const Status = {
6
+ Ok: { code: 200, msg: 'OK' },
7
+ Created: { code: 201, msg: 'Created' },
8
+ PartialContent: { code: 206, msg: 'Partial Content' },
9
+ SeeOther: { code: 303, msg: 'See Other' },
10
+ NotModified: { code: 304, msg: 'Not Modified' },
11
+ TemporaryRedirect: { code: 307, msg: 'Temporary Redirect' },
12
+ PermanentRedirect: { code: 308, msg: 'Permanent Redirect' },
13
+ BadRequest: { code: 400, msg: 'Bad Request' },
14
+ Forbidden: { code: 403, msg: 'Forbidden' },
15
+ NotFound: { code: 404, msg: 'Not Found' },
16
+ MethodNotAllowed: { code: 405, msg: 'Method Not Allowed' },
17
+ RequestTimeout: { code: 408, msg: 'Request Timeout' },
18
+ Conflict: { code: 409, msg: 'Conflict' },
19
+ PreconditionFailed: { code: 412, msg: 'Precondition Failed' },
20
+ ContentTooLarge: { code: 413, msg: 'Content Too Large' },
21
+ UnsupportedMediaType: { code: 415, msg: 'Unsupported Media Type' },
22
+ RangeIssue: { code: 416, msg: 'Range Not Satisfiable' },
23
+ UpgradeRequired: { code: 426, msg: 'Upgrade Required' },
24
+ InternalError: { code: 500, msg: 'Internal Server Error' }
25
+ };
26
+ export const Media = {
27
+ Html: { fileEnding: ['html'], mediaType: 'text/html', encoding: 'charset=utf-8', compressible: true },
28
+ Css: { fileEnding: ['css'], mediaType: 'text/css', encoding: 'charset=utf-8', compressible: true },
29
+ JavaScript: { fileEnding: ['js'], mediaType: 'text/javascript', encoding: 'charset=utf-8', compressible: true },
30
+ Text: { fileEnding: ['txt', 'text'], mediaType: 'text/plain', encoding: 'charset=utf-8', compressible: true },
31
+ Json: { fileEnding: ['json'], mediaType: 'application/json', encoding: 'charset=utf-8', compressible: true },
32
+ Mp4: { fileEnding: ['mp4'], mediaType: 'video/mp4', encoding: '', compressible: false },
33
+ Png: { fileEnding: ['png'], mediaType: 'image/png', encoding: '', compressible: false },
34
+ Gif: { fileEnding: ['gif'], mediaType: 'image/gif', encoding: '', compressible: false },
35
+ Jpg: { fileEnding: ['jpg', 'jpeg'], mediaType: 'image/jpeg', encoding: '', compressible: false },
36
+ Svg: { fileEnding: ['svg'], mediaType: 'image/svg+xml', encoding: '', compressible: true },
37
+ Unknown: { fileEnding: [], mediaType: 'application/octet-stream', encoding: '', compressible: false }
38
+ };
39
+ export const Encoding = {
40
+ Br: {
41
+ name: 'br',
42
+ makeDecode: () => libZlib.createBrotliDecompress(),
43
+ makeEncode: () => libZlib.createBrotliCompress(),
44
+ encodeBuffer: (buffer) => libZlib.brotliCompressSync(buffer)
45
+ },
46
+ Zstd: {
47
+ name: 'zstd',
48
+ makeDecode: () => libZlib.createZstdDecompress(),
49
+ makeEncode: () => libZlib.createZstdCompress(),
50
+ encodeBuffer: (buffer) => libZlib.zstdCompressSync(buffer)
51
+ },
52
+ Gzip: {
53
+ name: 'gzip',
54
+ makeDecode: () => libZlib.createGunzip(),
55
+ makeEncode: () => libZlib.createGzip(),
56
+ encodeBuffer: (buffer) => libZlib.gzipSync(buffer)
57
+ },
58
+ Deflate: {
59
+ name: 'deflate',
60
+ makeDecode: () => libZlib.createInflate(),
61
+ makeEncode: () => libZlib.createDeflate(),
62
+ encodeBuffer: (buffer) => libZlib.deflateSync(buffer)
63
+ },
64
+ Identity: {
65
+ name: 'identity',
66
+ makeDecode: () => new libStream.PassThrough(),
67
+ makeEncode: () => new libStream.PassThrough(),
68
+ encodeBuffer: (buffer) => buffer
69
+ }
70
+ };
71
+ /* minimum size for content is considered encodable */
72
+ export const MIN_ENCODING_SIZE = 1_000;
73
+ //# sourceMappingURL=base.js.map
@@ -0,0 +1,58 @@
1
+ export type HtmlString = HtmlGuard | string;
2
+ export declare class HtmlGuard {
3
+ content: string;
4
+ private constructor();
5
+ static get(str: HtmlString): HtmlGuard;
6
+ static make(str: string, safe: boolean): HtmlGuard;
7
+ }
8
+ export declare function Safe(content: string, safe?: boolean): HtmlGuard;
9
+ export interface HtmlComponent {
10
+ finalize(indent: string): string;
11
+ simple(): boolean;
12
+ }
13
+ export declare class EmbeddedContent implements HtmlComponent {
14
+ private content;
15
+ constructor(content: string, safe: boolean);
16
+ simple(): boolean;
17
+ finalize(_: string): string;
18
+ }
19
+ export declare class SingleTag implements HtmlComponent {
20
+ private content;
21
+ constructor(name: string, properties: Record<string, HtmlString>);
22
+ simple(): boolean;
23
+ finalize(indent: string): string;
24
+ }
25
+ export declare class DualTag implements HtmlComponent {
26
+ private openTag;
27
+ private closeTag;
28
+ private children;
29
+ constructor(name: string, properties: Record<string, HtmlString>, children: HtmlComponent[] | HtmlComponent);
30
+ simple(): boolean;
31
+ finalize(indent: string): string;
32
+ }
33
+ export declare class HtmlPage {
34
+ private _head;
35
+ private _body;
36
+ language: HtmlString;
37
+ constructor(options?: {
38
+ language?: HtmlString;
39
+ head?: HtmlComponent[] | HtmlComponent;
40
+ body?: HtmlComponent[] | HtmlComponent;
41
+ });
42
+ get head(): HtmlComponent[];
43
+ set head(value: HtmlComponent | HtmlComponent[]);
44
+ get body(): HtmlComponent[];
45
+ set body(value: HtmlComponent | HtmlComponent[]);
46
+ finalize(): string;
47
+ }
48
+ export declare function Embed(content: string, safe: boolean): HtmlComponent;
49
+ export declare function Meta(name: HtmlString, content: HtmlString): HtmlComponent;
50
+ export declare function Title(name: HtmlString): HtmlComponent;
51
+ export declare function LoadStyle(path: HtmlString): HtmlComponent;
52
+ export declare function LoadScript(path: HtmlString, properties?: Record<string, HtmlString>): HtmlComponent;
53
+ export declare function AddStyle(content: string, properties?: Record<string, HtmlString>): HtmlComponent;
54
+ export declare function AddScript(content: string, properties?: Record<string, HtmlString>): HtmlComponent;
55
+ export declare function Text(text: HtmlString, properties?: Record<string, HtmlString>): HtmlComponent;
56
+ export declare function Div(properties?: Record<string, HtmlString>, children?: HtmlComponent[] | HtmlComponent): HtmlComponent;
57
+ export declare function LoadingError(): HtmlComponent;
58
+ //# sourceMappingURL=builder.d.ts.map