@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.
@@ -0,0 +1,146 @@
1
+ /* SPDX-License-Identifier: BSD-3-Clause */
2
+ /* Copyright (c) 2026 Bjoern Boss Henrichsen */
3
+ import * as libHelper from "./helper.js";
4
+ export class HtmlGuard {
5
+ content;
6
+ constructor(content) {
7
+ this.content = content;
8
+ }
9
+ /* ensure the value is safe for html insertion; escapes plain strings, passes through HtmlGuard as-is */
10
+ static get(str) {
11
+ return (str instanceof HtmlGuard ? str : new HtmlGuard(libHelper.escapeHtml(str)));
12
+ }
13
+ /* wrap a string for html insertion; if safe is false, it will be html-escaped first */
14
+ static make(str, safe) {
15
+ return new HtmlGuard(safe ? str : libHelper.escapeHtml(str));
16
+ }
17
+ }
18
+ /* create a secure string [if safe is true, content is taken as-is; otherwise it is html escaped] */
19
+ export function Safe(content, safe = true) {
20
+ return HtmlGuard.make(content, safe);
21
+ }
22
+ /* raw text content to be embedded into an html tree */
23
+ export class EmbeddedContent {
24
+ content;
25
+ constructor(content, safe) {
26
+ this.content = (safe ? content : libHelper.escapeHtml(content));
27
+ }
28
+ simple() {
29
+ return (this.content.indexOf('\n') < 0);
30
+ }
31
+ finalize(_) {
32
+ return this.content;
33
+ }
34
+ }
35
+ /* self-closing html tag (e.g. <meta/>, <link/>, <br/>) */
36
+ export class SingleTag {
37
+ content;
38
+ constructor(name, properties) {
39
+ let details = '';
40
+ for (const name in properties)
41
+ details += ` ${name}="${HtmlGuard.get(properties[name]).content}"`;
42
+ this.content = `<${name}${details}/>`;
43
+ }
44
+ simple() { return false; }
45
+ finalize(indent) {
46
+ return `${indent}${this.content}`;
47
+ }
48
+ }
49
+ /* html tag with children (e.g. <div>...</div>, <p>text</p>) */
50
+ export class DualTag {
51
+ openTag;
52
+ closeTag;
53
+ children;
54
+ constructor(name, properties, children) {
55
+ let details = '';
56
+ for (const name in properties)
57
+ details += ` ${name}="${HtmlGuard.get(properties[name]).content}"`;
58
+ this.openTag = `<${name}${details}>`;
59
+ this.closeTag = `</${name}>`;
60
+ this.children = (Array.isArray(children) ? children : [children]);
61
+ }
62
+ simple() { return false; }
63
+ finalize(indent) {
64
+ if (this.children.length == 0)
65
+ return `${indent}${this.openTag}${this.closeTag}`;
66
+ /* inline the child if its the only one and it fits on a single line */
67
+ if (this.children.length == 1 && this.children[0].simple())
68
+ return `${indent}${this.openTag}${this.children[0].finalize('')}${this.closeTag}`;
69
+ /* construct the element with indented children */
70
+ let body = '', childIndent = `\t${indent}`;
71
+ for (const child of this.children)
72
+ body += `\n${child.finalize(childIndent)}`;
73
+ return `${indent}${this.openTag}${body}\n${indent}${this.closeTag}`;
74
+ }
75
+ }
76
+ /* full html page; automatically adds utf-8 charset and defaults language to 'en' */
77
+ export class HtmlPage {
78
+ _head;
79
+ _body;
80
+ language;
81
+ constructor(options) {
82
+ this._head = (Array.isArray(options?.head) ? options?.head : (options?.head == undefined ? [] : [options?.head]));
83
+ this._body = (Array.isArray(options?.body) ? options?.body : (options?.body == undefined ? [] : [options?.body]));
84
+ this.language = options?.language ?? Safe('en', true);
85
+ }
86
+ get head() {
87
+ return this._head;
88
+ }
89
+ set head(value) {
90
+ this._head = (Array.isArray(value) ? value : [value]);
91
+ }
92
+ get body() {
93
+ return this._body;
94
+ }
95
+ set body(value) {
96
+ this._body = (Array.isArray(value) ? value : [value]);
97
+ }
98
+ finalize() {
99
+ const properties = { style: 'margin:0;height:100%;' };
100
+ if ((typeof this.language == 'string' ? this.language : this.language.content) != '')
101
+ properties['lang'] = this.language;
102
+ const page = new DualTag('html', properties, [
103
+ new DualTag('head', {}, [
104
+ new SingleTag('meta', { charset: 'utf-8' }),
105
+ ...this._head
106
+ ]),
107
+ new DualTag('body', { style: 'margin:0;height:100%;display:flex;' }, this._body)
108
+ ]);
109
+ return `<!DOCTYPE html>\n${page.finalize('')}`;
110
+ }
111
+ }
112
+ /* embed raw content; if safe is true, the content is trusted and
113
+ * inserted verbatim; otherwise it is html-escaped before insertion */
114
+ export function Embed(content, safe) {
115
+ return new EmbeddedContent(content, safe);
116
+ }
117
+ export function Meta(name, content) {
118
+ return new SingleTag('meta', { name, content });
119
+ }
120
+ export function Title(name) {
121
+ return new DualTag('title', {}, new EmbeddedContent(HtmlGuard.get(name).content, true));
122
+ }
123
+ export function LoadStyle(path) {
124
+ return new SingleTag('link', { rel: 'stylesheet', type: 'text/css', href: path });
125
+ }
126
+ export function LoadScript(path, properties = {}) {
127
+ return new DualTag('script', { ...properties, src: path }, []);
128
+ }
129
+ /* inline css; content is inserted verbatim as <style> is a raw text element */
130
+ export function AddStyle(content, properties = {}) {
131
+ return new DualTag('style', properties, [new EmbeddedContent(content, true)]);
132
+ }
133
+ /* inline javascript; content is inserted verbatim as <script> is a raw text element */
134
+ export function AddScript(content, properties = {}) {
135
+ return new DualTag('script', properties, new EmbeddedContent(content, true));
136
+ }
137
+ export function Text(text, properties = {}) {
138
+ return new DualTag('p', properties, [new EmbeddedContent(HtmlGuard.get(text).content, true)]);
139
+ }
140
+ export function Div(properties = {}, children = []) {
141
+ return new DualTag('div', properties, children);
142
+ }
143
+ export function LoadingError() {
144
+ return Text('Failed to load Page Content', { style: 'font-family: monospace; color: red; font-weight: bold; text-align: center;' });
145
+ }
146
+ //# sourceMappingURL=builder.js.map
@@ -0,0 +1,66 @@
1
+ import * as libLog from "./log.js";
2
+ import * as libBase from "./base.js";
3
+ import * as libStream from "stream";
4
+ export interface Cached {
5
+ isImmutable(): boolean;
6
+ filePath(): string;
7
+ fileSize(): number;
8
+ lastModified(): string;
9
+ uniqueId(): string;
10
+ stream(options?: {
11
+ start?: number;
12
+ end?: number;
13
+ }): libStream.Readable;
14
+ read(): Promise<Buffer>;
15
+ readSync(): Buffer;
16
+ encoded(encoding?: libBase.EncodingType): EncodedCache;
17
+ }
18
+ export interface EncodedCache {
19
+ contentSize(): number | null;
20
+ stream(): libStream.Readable;
21
+ read(): Promise<Buffer>;
22
+ readSync(): Buffer;
23
+ }
24
+ export declare class CacheHost extends libLog.Logger {
25
+ private _cacheManager;
26
+ private _immutableManager;
27
+ private _config;
28
+ constructor(config?: CacheConfig | BurntCacheConfig);
29
+ private resolveCache;
30
+ get config(): BurntCacheConfig;
31
+ fetchImmutable(path: string, options?: {
32
+ checkFreshness?: boolean;
33
+ }): Cached | string | null;
34
+ fetchDirect(path: string, options?: {
35
+ checkFreshness?: boolean;
36
+ }): Cached | null;
37
+ immutable(handler: string, path: string, options?: {
38
+ checkFreshness?: boolean;
39
+ }): string;
40
+ flush(): void;
41
+ read(path: string, options?: {
42
+ checkFreshness?: boolean;
43
+ }): Promise<Buffer | null>;
44
+ write(path: string, data: Buffer | string, options?: {
45
+ what?: string;
46
+ temporary?: string;
47
+ }): Promise<void>;
48
+ }
49
+ export declare function createCache(config?: CacheConfig | BurntCacheConfig): CacheHost;
50
+ export interface CacheConfig {
51
+ immutableStatePath?: string;
52
+ cacheSize?: number;
53
+ fileSizeLimit?: number;
54
+ alwaysValidate?: boolean;
55
+ immutableTagging?: boolean;
56
+ }
57
+ export declare class BurntCacheConfig {
58
+ readonly immutableStatePath: string;
59
+ readonly cacheSize: number;
60
+ readonly fileSizeLimit: number;
61
+ readonly alwaysValidate: boolean;
62
+ readonly immutableTagging: boolean;
63
+ constructor(config?: CacheConfig);
64
+ static from(config?: CacheConfig | BurntCacheConfig): BurntCacheConfig;
65
+ }
66
+ //# sourceMappingURL=cache.d.ts.map