@spirobel/mininext 0.3.2 → 0.3.3

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,213 @@
1
+ /// <reference types="bun-types" />
2
+ /// <reference types="bun-types" />
3
+ import type { Server, WebSocketHandler } from "bun";
4
+ import { html, json, dangerjson, HtmlString } from "./html";
5
+ import type { BasedHtml, DangerJsonInHtml, JsonString, JsonStringValues } from "./html";
6
+ export type Form = {
7
+ post: boolean;
8
+ urlencoded: boolean;
9
+ multipart: boolean;
10
+ formJson?: any;
11
+ formData?: FormData;
12
+ formName?: string;
13
+ hiddenField?: HtmlString;
14
+ actionlink<Y = unknown>(qs?: string[] | string, settings?: LinkSettings): (mini: Mini<Y>) => string;
15
+ onPostSubmit<F>(cb: () => F): F | undefined;
16
+ };
17
+ export type DataMaker<X, Z = unknown> = ((mini: Mini, rerun?: Z) => DataMakerReturnType<X>) | (() => DataMakerReturnType<X>);
18
+ export type DataMakerReturnType<X> = X | Promise<X>;
19
+ export type HandlerReturnType = JsonString | DangerJsonInHtml | HtmlString | string | void;
20
+ export type LazyHandlerReturnType = HandlerReturnType | Promise<HandlerReturnType>;
21
+ export type NamedForm<Z> = {
22
+ formResponse: LazyHandlerReturnType;
23
+ formInfo?: Z;
24
+ };
25
+ export type NamedFormHandlerReturnType<X> = HandlerReturnType | Promise<HandlerReturnType> | NamedForm<X> | Promise<NamedForm<X>>;
26
+ /**
27
+ * Mini - the data object can be filled with url.data
28
+ * @example
29
+ * ``` js
30
+ * const {html,json, css, data, req, form, link, svg, deliver, route, params, header, head } = mini //pull everything out of the mini handbag
31
+ * ```
32
+ */
33
+ export declare class Mini<X = unknown> {
34
+ html: typeof html<X>;
35
+ css: typeof html<X>;
36
+ json: typeof json<X>;
37
+ dangerjson: typeof dangerjson<X>;
38
+ data: X;
39
+ req: Request;
40
+ head: (head: HtmlHandler | HtmlString) => undefined;
41
+ headers: (headers: HeadersInit, overwrite?: boolean) => undefined;
42
+ options: (options: ResponseInit) => undefined;
43
+ deliver: typeof url.deliver;
44
+ route: string;
45
+ params: URLSearchParams;
46
+ form: Form;
47
+ requrl: Readonly<URL>;
48
+ constructor(mini: Mini<unknown>, data: X);
49
+ }
50
+ /**
51
+ * HtmlHandler
52
+ * @param mini - the mini object
53
+ * @returns - return a partially resolved html string with mini.html
54
+ * @example
55
+ * ``` js
56
+ * const {html,json, css, data, req, form, link, svg, deliver, route, params, header, head } = mini //pull everything out of the mini handbag
57
+ * ```
58
+ */
59
+ export type HtmlHandler<Y = unknown> = ((mini: Mini<Y>) => LazyHandlerReturnType) | (() => LazyHandlerReturnType);
60
+ export type NamedFormHandler<Y = unknown, Z = undefined> = ((mini: Mini<Y>) => NamedFormHandlerReturnType<Z>) | (() => NamedFormHandlerReturnType<Z>);
61
+ declare global {
62
+ var FrontendScripts: Array<string>;
63
+ var FrontendScriptUrls: Array<string>;
64
+ var bundledSVGs: Record<string, {
65
+ svgContent: string;
66
+ svgPath: string;
67
+ }>;
68
+ }
69
+ export type ScriptTag = (...params: any[]) => Promise<HtmlString>;
70
+ interface LinkSettings {
71
+ [key: string]: string | null | undefined;
72
+ }
73
+ export declare class url {
74
+ static websocket: WebSocketHandler | undefined;
75
+ static server: Server;
76
+ static direct_handlers_html: ReadonlyMap<string, HtmlHandler>;
77
+ private static frontends;
78
+ private static svgs;
79
+ static svg(path: string, options?: ResponseInit): string | undefined;
80
+ static frontend(path: string, snippet?: HtmlHandler): HtmlString;
81
+ /**
82
+ * This is used by the frontend bundler in order to find all frontends and their corresponding script files.
83
+ */
84
+ static getFrontends(): string[];
85
+ static getSvgPaths(): string[];
86
+ static serveFrontend(req: Request): Response | undefined;
87
+ static serveSvg(req: Request): Response | undefined;
88
+ /**
89
+ * tool to expose data to a frontend as a global variable.
90
+ * @param name this will be added as window.name to the window object in the frontend
91
+ * @param value this will be parsed as json in the frontend and asigned as follows: window.name = JSON.parsed(value)
92
+ * @returns the script tag to be embeded in the html response
93
+ *
94
+ * @example
95
+ * ``` js
96
+ * //backend
97
+ * url.deliver("user", userData); // window.user = JSON.parse(userData)
98
+ * //frontend
99
+ * const user = window["user"];
100
+ * ```
101
+ * if you want to use types, declare them like so in your frontend code:
102
+ * ``` ts
103
+ * declare global {
104
+ * var user: string;
105
+ *}
106
+ * ```
107
+ */
108
+ static deliver(name: string, value: JsonStringValues): HtmlString;
109
+ /**
110
+ * @param dataHandler the function that prepares the data for the handlers
111
+ * @example const {html,json, css, data, req, form, link, svg, deliver, route, params, header, head } = mini //pull everything out of the mini handbag
112
+ * @returns
113
+ */
114
+ static data<T, Z>(dataMaker: DataMaker<T, Z>): {
115
+ /**
116
+ * @param dataHandler the function that prepares the data for the handlers
117
+ * @example const {html,json, css, data, req, form, link, svg, deliver, route, params, header, head } = mini //pull everything out of the mini handbag
118
+ * @returns
119
+ */
120
+ handler: (dataHandler: HtmlHandler<T>) => (oldmini: Mini) => Promise<string | void | HtmlString>;
121
+ dataMaker: DataMaker<T, Z>;
122
+ /**
123
+ * use this to **specify the input type for the functions**,
124
+ *
125
+ * that you want to use in the HtmlHandlers that follow this **data blend!**
126
+ * @example type lol = typeof MaybeLoggedIn.$Mini
127
+ */
128
+ $Mini: Mini<T>;
129
+ /**
130
+ * use this to **specify the input type for the functions**,
131
+ *
132
+ * that you want to use in the Htmlhandlers that follow this **data blend!**
133
+ * @example type haha = Mini<typeof MaybeLoggedIn.$Data>
134
+ */
135
+ $Data: T;
136
+ };
137
+ /**
138
+ * use this to define your routes.
139
+ * @example
140
+ * ``` js
141
+ * url.set([
142
+ * ["/", (mini) => mini.html`<h1>Hello world</h1>`],
143
+ * ["/apple", (mini) => mini.html`<h1>Hello apple</h1>`],
144
+ * ["/banana", (mini) => mini.html`<h1>Hello banana</h1>`],
145
+ * ]);
146
+ * ```
147
+ */
148
+ static set<K extends string>(entries: [K, HtmlHandler][]): void;
149
+ /**
150
+ * wrap your handlers in this if you mutate something to prevent CSRF issues.
151
+ * @param handler - normal html handler with mini as the argument
152
+ * @returns a wrapped html handler that will only be called when the request is post
153
+ */
154
+ static post(handler: HtmlHandler): (mini: Mini) => LazyHandlerReturnType;
155
+ /**
156
+ * wrap your handlers in this if you mutate something to prevent CSRF issues.
157
+ * @param handler - normal html handler with mini as the argument
158
+ * @returns a wrapped html handler that will only be called when the request is post and contains a json body
159
+ */
160
+ static postJson(handler: HtmlHandler): (mini: Mini) => LazyHandlerReturnType;
161
+ /**
162
+ * wrap your handlers in this if you mutate something to prevent CSRF issues.
163
+ * @param handler - normal html handler with mini as the argument
164
+ * @returns a wrapped html handler that will only be called when the request is post and contains a FormData body
165
+ */
166
+ static postFormData(handler: HtmlHandler): (mini: Mini) => LazyHandlerReturnType;
167
+ /**
168
+ * This is useful to decouple forms from routes.
169
+ * @param name name of the form - mini.form.onPostSubmit() will only be called if a (possibly hidden) field called formName matches this
170
+ * @param handler just like a normal handler (aka you can return the form as a HtmlString), but you can optionally return additional data in formInfo
171
+ * @returns - { formResponse: result of the handler, formInfo?: some info about the form. Totally up to you}
172
+ */
173
+ static namedForm<X = unknown, Z = undefined>(name: string, handler: NamedFormHandler<X, Z>): (mini: Mini<X>) => Promise<NamedForm<Z>>;
174
+ /**
175
+ * pass in all the query string parameter names that you want to preserve in the link
176
+ * @param Url - the url that you want to link to (example: "/login")
177
+ * @param qs - the query string parameters that you want to preserve in the link
178
+ * @param settings - key and string values that you want to set in the link
179
+ * @returns - the link that you can use in your html template
180
+ */
181
+ static link<X>(Url: string, qs?: string[] | string, settings?: LinkSettings): (mini: Mini<X>) => string;
182
+ static currylink(Url: string, qs: string[] | string, req: Request, settings?: LinkSettings): string;
183
+ /**
184
+ * This method retrieves a url from the urls array. If the url does not exist in the urls array, an error will be thrown.
185
+ * @param {string} Url - The url to retrieve.
186
+ * @return {string} - The retrieved url.
187
+ * @throws Will throw an Error if the provided url is not found in the urls array.
188
+ */
189
+ static get(Url: string): string;
190
+ static match(req: Request, reqPath?: string): Promise<Response | undefined>;
191
+ /**
192
+ * user this to set the Websocket object. Check out [the bun docs](https://bun.sh/docs/api/websockets) for more details.
193
+ * @param wsObject the websocketsocket object {@link WebSocketHandler}
194
+ */
195
+ static setWebsocket<T = undefined>(wsObject: WebSocketHandler<T>): void;
196
+ /**
197
+ * Send a message to all connected {@link ServerWebSocket} subscribed to a topic
198
+ * @param topic The topic to publish to
199
+ * @param message The data to send
200
+ * @returns 0 if the message was dropped, -1 if backpressure was applied, or the number of bytes sent.
201
+ */
202
+ static publishHtml(topic: string, message: BasedHtml): number;
203
+ /**
204
+ * Fetch handler that is called by the server when a request is made to any of the urls.
205
+ * @param {Request} req - The Request object.
206
+ * @return {Promise<Response>} - The Response object.
207
+ */
208
+ static install(): {
209
+ fetch: (req: Request, server: Server) => Promise<Response>;
210
+ websocket: WebSocketHandler<undefined> | undefined;
211
+ };
212
+ }
213
+ export {};
@@ -0,0 +1,449 @@
1
+ import { htmlResponder, html, json, dangerjson, HtmlString } from "./html";
2
+ /**
3
+ * Mini - the data object can be filled with url.data
4
+ * @example
5
+ * ``` js
6
+ * const {html,json, css, data, req, form, link, svg, deliver, route, params, header, head } = mini //pull everything out of the mini handbag
7
+ * ```
8
+ */
9
+ export class Mini {
10
+ html;
11
+ css;
12
+ json;
13
+ dangerjson;
14
+ data;
15
+ req;
16
+ head;
17
+ headers;
18
+ options;
19
+ deliver;
20
+ route;
21
+ params;
22
+ form;
23
+ requrl;
24
+ constructor(mini, data) {
25
+ Object.assign(this, mini);
26
+ this.html = (html);
27
+ this.css = (html);
28
+ this.json = (json);
29
+ this.dangerjson = (dangerjson);
30
+ this.data = data;
31
+ this.deliver = url.deliver;
32
+ this.form.onPostSubmit = (cb) => {
33
+ if (this.form.formName) {
34
+ if (this.form.formData &&
35
+ this.form.formData.get("formName") === this.form.formName) {
36
+ return cb();
37
+ }
38
+ else if (this.form.formJson &&
39
+ this.form.formJson.formName === this.form.formName) {
40
+ return cb();
41
+ }
42
+ }
43
+ else if (this.form.post) {
44
+ return cb();
45
+ }
46
+ };
47
+ }
48
+ }
49
+ export class url {
50
+ static websocket = undefined;
51
+ static server;
52
+ // direct mapping of "url string" -> function leads to Html Response
53
+ static direct_handlers_html;
54
+ // An array of the uncompiled frontend files, example frontends[0] = "index.tsx" -> frontend/index.tsx (from the project root)
55
+ static frontends = [];
56
+ static svgs = new Map();
57
+ static svg(path, options = {
58
+ headers: {
59
+ "Content-Type": "image/svg+xml",
60
+ "Content-Disposition": "attachment",
61
+ },
62
+ }) {
63
+ url.svgs.set(path, options);
64
+ var foundEntry = Object.entries(bundledSVGs).find(([key, value]) => value.svgPath === path);
65
+ return foundEntry && foundEntry[0];
66
+ }
67
+ static frontend(path, snippet) {
68
+ const frontendIndex = url.frontends.push(path) - 1;
69
+ const scriptUrl = FrontendScriptUrls[frontendIndex];
70
+ return html ` ${snippet}
71
+ <script type="module" src="${scriptUrl}"></script>`; // return an html script tag with the index hash
72
+ }
73
+ /**
74
+ * This is used by the frontend bundler in order to find all frontends and their corresponding script files.
75
+ */
76
+ static getFrontends() {
77
+ return url.frontends;
78
+ }
79
+ static getSvgPaths() {
80
+ return [...url.svgs.keys()];
81
+ }
82
+ static serveFrontend(req) {
83
+ const reqPath = new URL(req.url).pathname;
84
+ const index = FrontendScriptUrls.indexOf(reqPath);
85
+ if (index !== -1) {
86
+ return new Response(FrontendScripts[index], {
87
+ headers: {
88
+ "Content-Type": "application/javascript; charset=utf-8",
89
+ },
90
+ });
91
+ }
92
+ }
93
+ static serveSvg(req) {
94
+ const reqPath = new URL(req.url).pathname;
95
+ const resolvedSvg = bundledSVGs[reqPath];
96
+ if (resolvedSvg) {
97
+ return new Response(resolvedSvg.svgContent, url.svgs.get(resolvedSvg.svgPath));
98
+ }
99
+ }
100
+ /**
101
+ * tool to expose data to a frontend as a global variable.
102
+ * @param name this will be added as window.name to the window object in the frontend
103
+ * @param value this will be parsed as json in the frontend and asigned as follows: window.name = JSON.parsed(value)
104
+ * @returns the script tag to be embeded in the html response
105
+ *
106
+ * @example
107
+ * ``` js
108
+ * //backend
109
+ * url.deliver("user", userData); // window.user = JSON.parse(userData)
110
+ * //frontend
111
+ * const user = window["user"];
112
+ * ```
113
+ * if you want to use types, declare them like so in your frontend code:
114
+ * ``` ts
115
+ * declare global {
116
+ * var user: string;
117
+ *}
118
+ * ```
119
+ */
120
+ static deliver(name, value) {
121
+ return html ` <script type="application/json" id="${name}">
122
+ ${dangerjson `${value}`}
123
+ </script>
124
+
125
+ <script>
126
+ window["${name}"] = JSON.parse(
127
+ document.getElementById("${name}").innerHTML
128
+ );
129
+ </script>`;
130
+ }
131
+ /**
132
+ * @param dataHandler the function that prepares the data for the handlers
133
+ * @example const {html,json, css, data, req, form, link, svg, deliver, route, params, header, head } = mini //pull everything out of the mini handbag
134
+ * @returns
135
+ */
136
+ static data(dataMaker) {
137
+ return {
138
+ /**
139
+ * @param dataHandler the function that prepares the data for the handlers
140
+ * @example const {html,json, css, data, req, form, link, svg, deliver, route, params, header, head } = mini //pull everything out of the mini handbag
141
+ * @returns
142
+ */
143
+ handler: (dataHandler) => {
144
+ return async (oldmini) => {
145
+ const data = await dataMaker(oldmini);
146
+ const mini = new Mini(oldmini, data);
147
+ const unresolvedDataHandler = await dataHandler(mini); // passing mini
148
+ if (unresolvedDataHandler instanceof HtmlString) {
149
+ return await unresolvedDataHandler.resolve(mini);
150
+ }
151
+ return unresolvedDataHandler;
152
+ };
153
+ },
154
+ dataMaker,
155
+ /**
156
+ * use this to **specify the input type for the functions**,
157
+ *
158
+ * that you want to use in the HtmlHandlers that follow this **data blend!**
159
+ * @example type lol = typeof MaybeLoggedIn.$Mini
160
+ */
161
+ $Mini: {
162
+ data: "DONT USE THIS DIRECTLY, ya goofball. This is just to infer the Mini type",
163
+ },
164
+ /**
165
+ * use this to **specify the input type for the functions**,
166
+ *
167
+ * that you want to use in the Htmlhandlers that follow this **data blend!**
168
+ * @example type haha = Mini<typeof MaybeLoggedIn.$Data>
169
+ */
170
+ $Data: {
171
+ data: "DONT USE THIS DIRECTLY, ya goofball. This is just to infer the Mini type",
172
+ },
173
+ };
174
+ }
175
+ /**
176
+ * use this to define your routes.
177
+ * @example
178
+ * ``` js
179
+ * url.set([
180
+ * ["/", (mini) => mini.html`<h1>Hello world</h1>`],
181
+ * ["/apple", (mini) => mini.html`<h1>Hello apple</h1>`],
182
+ * ["/banana", (mini) => mini.html`<h1>Hello banana</h1>`],
183
+ * ]);
184
+ * ```
185
+ */
186
+ static set(entries) {
187
+ url.direct_handlers_html = new Map(entries);
188
+ }
189
+ /**
190
+ * wrap your handlers in this if you mutate something to prevent CSRF issues.
191
+ * @param handler - normal html handler with mini as the argument
192
+ * @returns a wrapped html handler that will only be called when the request is post
193
+ */
194
+ static post(handler) {
195
+ return (mini) => {
196
+ if (mini.form.post) {
197
+ return handler(mini);
198
+ }
199
+ else {
200
+ return no_post_warning;
201
+ }
202
+ };
203
+ }
204
+ /**
205
+ * wrap your handlers in this if you mutate something to prevent CSRF issues.
206
+ * @param handler - normal html handler with mini as the argument
207
+ * @returns a wrapped html handler that will only be called when the request is post and contains a json body
208
+ */
209
+ static postJson(handler) {
210
+ return (mini) => {
211
+ if (mini.form.formJson) {
212
+ return handler(mini);
213
+ }
214
+ else {
215
+ return no_post_warning;
216
+ }
217
+ };
218
+ }
219
+ /**
220
+ * wrap your handlers in this if you mutate something to prevent CSRF issues.
221
+ * @param handler - normal html handler with mini as the argument
222
+ * @returns a wrapped html handler that will only be called when the request is post and contains a FormData body
223
+ */
224
+ static postFormData(handler) {
225
+ return (mini) => {
226
+ if (mini.form.formData) {
227
+ return handler(mini);
228
+ }
229
+ else {
230
+ return no_post_warning;
231
+ }
232
+ };
233
+ }
234
+ /**
235
+ * This is useful to decouple forms from routes.
236
+ * @param name name of the form - mini.form.onPostSubmit() will only be called if a (possibly hidden) field called formName matches this
237
+ * @param handler just like a normal handler (aka you can return the form as a HtmlString), but you can optionally return additional data in formInfo
238
+ * @returns - { formResponse: result of the handler, formInfo?: some info about the form. Totally up to you}
239
+ */
240
+ static namedForm(name, handler) {
241
+ return async (mini) => {
242
+ mini.form.formName = name;
243
+ mini.form.hiddenField = html `<input
244
+ type="hidden"
245
+ name="formName"
246
+ value="${name}"
247
+ />`;
248
+ const namedFormResponse = await handler(mini);
249
+ let handlerResult = {};
250
+ if (typeof namedFormResponse !== "string" &&
251
+ namedFormResponse &&
252
+ "formResponse" in namedFormResponse) {
253
+ handlerResult.formResponse = await namedFormResponse.formResponse;
254
+ handlerResult.formInfo = namedFormResponse.formInfo;
255
+ }
256
+ else {
257
+ handlerResult.formResponse = namedFormResponse;
258
+ }
259
+ delete mini.form.formName;
260
+ delete mini.form.hiddenField;
261
+ return handlerResult;
262
+ };
263
+ }
264
+ /**
265
+ * pass in all the query string parameter names that you want to preserve in the link
266
+ * @param Url - the url that you want to link to (example: "/login")
267
+ * @param qs - the query string parameters that you want to preserve in the link
268
+ * @param settings - key and string values that you want to set in the link
269
+ * @returns - the link that you can use in your html template
270
+ */
271
+ static link(Url, qs = "", settings) {
272
+ return (mini) => {
273
+ return url.currylink(Url, qs, mini.req, settings);
274
+ };
275
+ }
276
+ static currylink(Url, qs, req, settings) {
277
+ if (!Array.isArray(qs)) {
278
+ qs = [qs];
279
+ }
280
+ // Create a new URL object from the current location
281
+ // https://github.com/whatwg/url/issues/531#issuecomment-1337050285
282
+ const GOOFY_HACK = "http://goofyhack.com";
283
+ const updatedUrl = new URL(url.get(Url), GOOFY_HACK);
284
+ for (const q of qs) {
285
+ // Use URLSearchParams to set the name query parameter
286
+ const reqParam = new URL(req.url).searchParams.get(q);
287
+ if (reqParam) {
288
+ updatedUrl.searchParams.set(q, reqParam);
289
+ }
290
+ }
291
+ for (const key in settings) {
292
+ const value = settings[key];
293
+ if (value !== undefined && value !== null) {
294
+ updatedUrl.searchParams.set(key, value);
295
+ }
296
+ }
297
+ // Return the updated URL as a string
298
+ return updatedUrl.toString().slice(GOOFY_HACK.length);
299
+ }
300
+ /**
301
+ * This method retrieves a url from the urls array. If the url does not exist in the urls array, an error will be thrown.
302
+ * @param {string} Url - The url to retrieve.
303
+ * @return {string} - The retrieved url.
304
+ * @throws Will throw an Error if the provided url is not found in the urls array.
305
+ */
306
+ static get(Url) {
307
+ const foundUrl = url.direct_handlers_html.get(Url);
308
+ if (!foundUrl) {
309
+ throw new Error(`URL "${html `${Url}`}" was not set.`);
310
+ }
311
+ return Url;
312
+ }
313
+ static async match(req, reqPath) {
314
+ const miniurl = Object.freeze(new URL(req.url));
315
+ if (typeof reqPath === "undefined") {
316
+ reqPath = miniurl.pathname;
317
+ }
318
+ const handler = url.direct_handlers_html.get(reqPath);
319
+ if (handler) {
320
+ //this is the source of mini
321
+ let handlerHead = undefined;
322
+ let handlerOptions = {
323
+ headers: {
324
+ "Content-Type": "text/html; charset=utf-8",
325
+ },
326
+ };
327
+ const post = req.method === "POST";
328
+ let formJson;
329
+ let formData;
330
+ const urlencoded = (req.headers.get("Content-Type") + "").includes("application/x-www-form-urlencoded");
331
+ const multipart = (req.headers.get("Content-Type") + "").includes("multipart/form-data");
332
+ if (post && !urlencoded && !multipart) {
333
+ const length = Number(req.headers.get("content-length"));
334
+ const bodyNotEmpty = length > 0;
335
+ if (bodyNotEmpty) {
336
+ formJson = await req.json();
337
+ }
338
+ else {
339
+ formJson = {};
340
+ }
341
+ }
342
+ if (post && (urlencoded || multipart)) {
343
+ formData = await req.formData();
344
+ }
345
+ const mini = new Mini({
346
+ requrl: miniurl,
347
+ data: undefined,
348
+ req,
349
+ html,
350
+ css: html,
351
+ deliver: url.deliver,
352
+ route: reqPath,
353
+ params: new URL(req.url).searchParams,
354
+ json,
355
+ form: {
356
+ post,
357
+ urlencoded,
358
+ multipart,
359
+ formJson,
360
+ formData,
361
+ onPostSubmit(cb) {
362
+ if (post) {
363
+ return cb();
364
+ }
365
+ },
366
+ actionlink: (qs = "", settings) => url.link(reqPath, qs, settings),
367
+ },
368
+ dangerjson,
369
+ head: (head) => {
370
+ handlerHead = head;
371
+ },
372
+ headers: (headers, overwrite = false) => {
373
+ if (overwrite) {
374
+ handlerOptions.headers = headers;
375
+ }
376
+ else {
377
+ handlerOptions.headers = {
378
+ ...handlerOptions.headers,
379
+ ...headers,
380
+ };
381
+ }
382
+ },
383
+ options: (options) => {
384
+ handlerOptions = options;
385
+ },
386
+ }, undefined);
387
+ const unresolved = await handler(mini); //passing mini
388
+ return htmlResponder(mini, unresolved, handlerHead, handlerOptions);
389
+ }
390
+ }
391
+ /**
392
+ * user this to set the Websocket object. Check out [the bun docs](https://bun.sh/docs/api/websockets) for more details.
393
+ * @param wsObject the websocketsocket object {@link WebSocketHandler}
394
+ */
395
+ static setWebsocket(wsObject) {
396
+ url.websocket = wsObject;
397
+ }
398
+ /**
399
+ * Send a message to all connected {@link ServerWebSocket} subscribed to a topic
400
+ * @param topic The topic to publish to
401
+ * @param message The data to send
402
+ * @returns 0 if the message was dropped, -1 if backpressure was applied, or the number of bytes sent.
403
+ */
404
+ static publishHtml(topic, message) {
405
+ return url.server.publish(topic, message);
406
+ }
407
+ /**
408
+ * Fetch handler that is called by the server when a request is made to any of the urls.
409
+ * @param {Request} req - The Request object.
410
+ * @return {Promise<Response>} - The Response object.
411
+ */
412
+ static install() {
413
+ async function fetchFunction(req, server) {
414
+ if (!url.server)
415
+ url.server = server;
416
+ //go through all the Htmlhandlers and see if there is a match
417
+ let res = await url.match(req);
418
+ if (res)
419
+ return res;
420
+ //handle frontend js file serving
421
+ res = url.serveFrontend(req);
422
+ if (res)
423
+ return res;
424
+ //handle svg file serving
425
+ res = url.serveSvg(req);
426
+ if (res)
427
+ return res;
428
+ // go through all the Htmlhandlers again with added slash at the end.
429
+ res = await url.match(req, new URL(req.url).pathname + "/");
430
+ if (res)
431
+ return res;
432
+ return new Response("No matching url found", { status: 404 });
433
+ }
434
+ return { fetch: fetchFunction, websocket: url.websocket };
435
+ }
436
+ }
437
+ const no_post_warning = html `<div style="color:red;">
438
+ This method is only accessible through the POST method. Remember to make all
439
+ mutations (insert / update data in the database) only accessible via POST and
440
+ implement your session cookies like this:
441
+ <div
442
+ style="color:#0FFF50; width:800px; overflow:wrap; margin-left:30px; margin-top:20px; margin-bottom:20px;"
443
+ >
444
+ "Set-Cookie": sessionId=="some random string made with crypto.randomUUID()"
445
+ expires=Thu, 01 Jan 1970 00:00:00 GMT Secure; HttpOnly; SameSite=Strict;
446
+ path=/,
447
+ </div>
448
+ This is necessary to prevent CSRF issues.
449
+ </div>`;