@spirobel/mininext 0.7.6 → 0.8.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/dist/html.js DELETED
@@ -1,288 +0,0 @@
1
- export class HtmlString extends Array {
2
- /**
3
- * a HtmlString is by default resolved.
4
- * if we we pass a function as a value to the html`` template string, it will be unresolved.
5
- * it can also become unresolved if an unresolved HtmlString is passed into it as a value
6
- */
7
- resolved = true;
8
- async resolve(mini) {
9
- if (this.resolved)
10
- return this;
11
- for (const [index, htmlPiece] of this.entries()) {
12
- if (htmlPiece instanceof HtmlString) {
13
- let resolvedHtmlPiece = await htmlPiece.resolve(mini);
14
- if (this instanceof JsonString || this instanceof DangerJsonInHtml) {
15
- this[index] = JSON.stringify(resolvedHtmlPiece);
16
- }
17
- else {
18
- this[index] = resolvedHtmlPiece;
19
- }
20
- }
21
- else if (typeof htmlPiece === "function") {
22
- let resolvedHtmlPiece = await htmlPiece(mini); //passing mini
23
- //same cases as outer if statement
24
- if (resolvedHtmlPiece instanceof HtmlString) {
25
- resolvedHtmlPiece = await resolvedHtmlPiece.resolve(mini);
26
- }
27
- else if (htmlPiece instanceof BasedHtml) {
28
- this[index] = htmlPiece;
29
- }
30
- else {
31
- if (this instanceof JsonString || this instanceof DangerJsonInHtml) {
32
- resolvedHtmlPiece = JSON.stringify(resolvedHtmlPiece);
33
- }
34
- else {
35
- const notEmpty = resolvedHtmlPiece || "";
36
- // values will be escaped by default
37
- resolvedHtmlPiece = Bun.escapeHTML(notEmpty + "");
38
- }
39
- }
40
- // Replace the function with the resolved HTML piece in place
41
- this[index] = resolvedHtmlPiece;
42
- }
43
- else if (htmlPiece instanceof BasedHtml) {
44
- this[index] = htmlPiece;
45
- }
46
- }
47
- this.resolved = true;
48
- return this;
49
- }
50
- flat(depth = 1) {
51
- const flattened = super.flat(depth);
52
- const newHtmlString = new this.constructor(...flattened);
53
- newHtmlString.resolved = this.resolved;
54
- return newHtmlString;
55
- }
56
- }
57
- export function html(strings, ...values) {
58
- const htmlStringArray = new HtmlString();
59
- htmlStringArray.resolved = true;
60
- // Iterate over strings and values, alternating between them
61
- for (const [index, string] of strings.entries()) {
62
- htmlStringArray.push(string);
63
- if (index < values.length) {
64
- const value = values[index];
65
- // we can pass arrays of HtmlString and they will get flattened in the HtmlResponder
66
- if (Array.isArray(value) &&
67
- value.every((val) => val instanceof HtmlString || val instanceof BasedHtml)) {
68
- // If the value is an array of HtmlString objects, add the whole array as a single value
69
- const notResolved = new HtmlString(...value);
70
- notResolved.resolved = false;
71
- values[index] = notResolved;
72
- htmlStringArray.resolved = false; // we could bother with .find here
73
- }
74
- else if (typeof value === "function") {
75
- htmlStringArray.resolved = false;
76
- values[index] = value;
77
- }
78
- else if (value instanceof JsonString) {
79
- values[index] = html `<div style="color:red;">
80
- Please use dangerjson to include json in html. Untrusted input needs
81
- to pass through a html template function to get escaped. You can do
82
- html -> dangerjson -> html if you want!
83
- </div>`;
84
- }
85
- else if (!(value instanceof HtmlString || value instanceof BasedHtml)) {
86
- const notEmpty = value || "";
87
- // values will be escaped by default
88
- values[index] = Bun.escapeHTML(notEmpty + "");
89
- }
90
- else if (value instanceof HtmlString) {
91
- if (!value.resolved) {
92
- htmlStringArray.resolved = false;
93
- }
94
- }
95
- htmlStringArray.push(values[index]);
96
- }
97
- }
98
- return htmlStringArray;
99
- }
100
- export class JsonString extends HtmlString {
101
- }
102
- export class DangerJsonInHtml extends HtmlString {
103
- }
104
- function JsonTemplateProcessor(danger = false) {
105
- const constructorr = danger
106
- ? () => new DangerJsonInHtml()
107
- : () => new JsonString();
108
- return function (strings, ...values) {
109
- const jsonStringArray = constructorr();
110
- jsonStringArray.resolved = true;
111
- // Iterate over strings and values, alternating between them
112
- for (const [index, string] of strings.entries()) {
113
- jsonStringArray.push(string);
114
- if (index < values.length) {
115
- const value = values[index];
116
- // we can pass arrays of HtmlString and they will get flattened in the HtmlResponder
117
- if (Array.isArray(value) &&
118
- value.every((val) => val instanceof HtmlString || val instanceof BasedHtml)) {
119
- // If the value is an array of HtmlString objects, add the whole array as a single value
120
- const notResolved = new HtmlString(...value);
121
- notResolved.resolved = false;
122
- values[index] = notResolved;
123
- jsonStringArray.resolved = false; // we could bother with .find here
124
- }
125
- else if (typeof value === "function") {
126
- jsonStringArray.resolved = false;
127
- values[index] = value;
128
- }
129
- else if (value instanceof HtmlString || value instanceof BasedHtml) {
130
- if (value instanceof HtmlString && !value.resolved) {
131
- jsonStringArray.resolved = false;
132
- }
133
- values[index] = value;
134
- }
135
- else if (!(value instanceof JsonString)) {
136
- // values will be turned into a JSON string
137
- if (value) {
138
- values[index] = JSON.stringify(value);
139
- }
140
- }
141
- jsonStringArray.push(values[index]);
142
- }
143
- }
144
- return jsonStringArray;
145
- };
146
- }
147
- export const json = JsonTemplateProcessor();
148
- export const dangerjson = JsonTemplateProcessor(true);
149
- export const commonHead = html ` <meta
150
- name="viewport"
151
- content="width=device-width, initial-scale=1.0"
152
- />
153
- <script>
154
- /* prevent form resubmission */
155
- if (window.history.replaceState) {
156
- window.history.replaceState(null, null, window.location.href);
157
- }
158
- </script>`;
159
- export const cssReset = html ` <style>
160
- /* CSS Reset */
161
- * {
162
- margin: 0;
163
- padding: 0;
164
- box-sizing: border-box;
165
- }
166
-
167
- /* Set the background color to black */
168
- html,
169
- body {
170
- background-color: #000;
171
- color: #fff; /* Set the default text color to white for better contrast */
172
- }
173
- </style>`;
174
- let default_head = (mini) => mini.html `
175
- <title>mini-next</title>
176
- ${commonHead} ${cssReset}
177
- `;
178
- /**
179
- * Set the default head for all pages. Can still be overwritten on a per page basis
180
- * @param defaultHead - HtmlString
181
- *
182
- * @example Here is what a default head might look like:
183
- * ```ts
184
- *head((mini)=>mini.html` <title>hello hello</title> `);
185
- * url.set([
186
- * ["/", (mini) => mini.html`<h1>Hello world</h1>`],
187
- * [
188
- * "/bye",
189
- * (mini) =>
190
- * mini.html`<h1>Goodbye world</h1>${mini.head(
191
- * mini.html` <title>bye bye</title>`
192
- * )}`,
193
- * ],
194
- * ]);
195
- * ```
196
- */
197
- export function head(defaultHead) {
198
- default_head = defaultHead;
199
- }
200
- export async function htmlResponder(mini, maybeUnresolved, head = default_head, options = {
201
- headers: {
202
- "Content-Type": "text/html; charset=utf-8",
203
- },
204
- }) {
205
- if (!(maybeUnresolved instanceof HtmlString)) {
206
- maybeUnresolved = html `${maybeUnresolved + ""}`;
207
- }
208
- if (maybeUnresolved instanceof DangerJsonInHtml) {
209
- maybeUnresolved = html `<div style="color:red;">
210
- Use json and not dangerjson. The purpose of dangerjson is to be explicit
211
- when you embed unescaped json elements in an html document.
212
- </div>`;
213
- }
214
- if (!(maybeUnresolved instanceof JsonString)) {
215
- const reloader = new HtmlString();
216
- reloader.push(global.Reloader || "");
217
- maybeUnresolved = html `<!DOCTYPE html>
218
- <html>
219
- <head>
220
- ${reloader} ${head}
221
- </head>
222
- <body>
223
- ${maybeUnresolved}
224
- </body>
225
- </html> `;
226
- }
227
- else {
228
- const headers = {
229
- ...options.headers,
230
- ...{ "Content-Type": "application/json; charset=utf-8" },
231
- };
232
- options.headers = headers;
233
- }
234
- const definitelyResolved = await maybeUnresolved.resolve(mini);
235
- const flattend = definitelyResolved.flat(Infinity);
236
- async function* stepGen() {
237
- let index = 0;
238
- while (index < flattend.length) {
239
- const step = flattend[index++];
240
- if (step)
241
- yield String(step);
242
- }
243
- }
244
- function Stream(a) {
245
- return a;
246
- }
247
- return new Response(Stream(stepGen), options);
248
- }
249
- /**
250
- * Generic html error type guard
251
- * @param submissionResult output of some function
252
- * @returns boolean - true if the given object has a property called "error" and its value is an instance of HtmlString
253
- */
254
- export function isError(submissionResult) {
255
- return ("error" in submissionResult && submissionResult.error instanceof HtmlString);
256
- }
257
- /**
258
- * The difference between this and HtmlString is that it is fully resolved and only accepts primitive types.
259
- * In plain english this means:
260
- * It does not accept functions (that will be resolved at request time with (mini)=>mini.html) like mini.html does.
261
- */
262
- export class BasedHtml extends String {
263
- }
264
- //TODO make it so we can embed BasedHtml into mini.html partially resolved html strings
265
- /**
266
- * The difference between this and HtmlString is that it is fully resolved and only accepts primitive types.
267
- * @param strings - html literals
268
- * @param values - values will get escaped to prevent xss
269
- * @returns
270
- */
271
- export const basedHtml = (strings, ...values) => {
272
- // Apply escapeHtml to each value before using them in the template string
273
- // In case it didn't already get escaped
274
- for (const [index, value] of values.entries()) {
275
- // we can pass arrays of BasedHtml and they will get flattened automatically
276
- if (Array.isArray(value) &&
277
- value.every((val) => val instanceof BasedHtml)) {
278
- // If the value is an array of BasedHtml objects, flatten it and add to ...values
279
- values[index] = value.join("");
280
- }
281
- else if (!(value instanceof BasedHtml)) {
282
- const notEmpty = value || "";
283
- // values will be escaped by default
284
- values[index] = Bun.escapeHTML(notEmpty + "");
285
- }
286
- }
287
- return new BasedHtml(String.raw({ raw: strings }, ...values));
288
- };
package/dist/url.d.ts DELETED
@@ -1,282 +0,0 @@
1
- /// <reference types="bun-types" />
2
- /// <reference types="bun-types" />
3
- /// <reference types="bun-types" />
4
- /// <reference types="bun-types" />
5
- /// <reference types="bun-types" />
6
- /// <reference types="bun-types" />
7
- /// <reference types="bun-types" />
8
- /// <reference types="bun-types" />
9
- import type { Server, WebSocketHandler, RouterTypes, BunRequest } from "bun";
10
- import { html, json, dangerjson, HtmlString } from "./html";
11
- import { BasedHtml, type DangerJsonInHtml, type JsonString, type JsonStringValues } from "./html";
12
- export type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "HEAD" | "OPTIONS";
13
- export type MiniNextRouteHandlerObject<T extends string> = {
14
- [K in HTTPMethod]?: HtmlHandler<unknown, T>;
15
- };
16
- export type MiniNextRouteValue<T extends string> = HtmlHandler<unknown, T> | MiniNextRouteHandlerObject<T>;
17
- export type BunRoutes<R extends {
18
- [K in keyof R]: RouterTypes.RouteValue<Extract<K, string>>;
19
- }> = R;
20
- export type MiniNextRoutes = Record<string, MiniNextRouteValue<"">>;
21
- /**
22
- * A helper function that helps narrow unknown objects
23
- * @param object - the object of type unknown that is to be narrowed
24
- * @param key - the key that may or may not exist in object
25
- * @returns true if the key is present and false if not
26
- * @example
27
- * ``` js
28
- * has(this.form.formJson, "formName") &&
29
- * this.form.formJson.formName === this.form.formName
30
- * ```
31
- * https://stackoverflow.com/questions/70028907/narrowing-an-object-of-type-unknown
32
- */
33
- export declare function has<T, K extends string>(object: T, key: K): object is T & object & Record<K, unknown>;
34
- export type Form = {
35
- post: boolean;
36
- urlencoded: boolean;
37
- multipart: boolean;
38
- formJson?: unknown;
39
- formData?: FormData;
40
- formName?: string;
41
- hiddenField?: HtmlString;
42
- actionlink<Y = unknown>(qs?: string[] | string, settings?: LinkSettings): (mini: Mini<Y>) => string;
43
- onPostSubmit<F>(cb: () => F): F | undefined;
44
- };
45
- export type DataMaker<X, Z = unknown> = ((mini: Mini, rerun?: Z) => DataMakerReturnType<X>) | (() => DataMakerReturnType<X>);
46
- export type DataMakerReturnType<X> = X | Promise<X>;
47
- export type HandlerReturnType = JsonString | DangerJsonInHtml | HtmlString | string | void;
48
- export type LazyHandlerReturnType = HandlerReturnType | Promise<HandlerReturnType>;
49
- export type NamedForm<Z> = {
50
- formResponse: LazyHandlerReturnType;
51
- formInfo?: Z;
52
- };
53
- export type NamedFormHandlerReturnType<X> = HandlerReturnType | Promise<HandlerReturnType> | NamedForm<X> | Promise<NamedForm<X>>;
54
- /**
55
- * Mini - the data object can be filled with url.data
56
- * @example
57
- * ``` js
58
- * const {html,json, css, data, req, form, link, svg, deliver, route, params, header, head } = mini //pull everything out of the mini handbag
59
- * ```
60
- */
61
- export declare class Mini<X = unknown, ROUTE extends string = ""> {
62
- html: typeof html<X>;
63
- css: typeof html<X>;
64
- json: typeof json<X>;
65
- dangerjson: typeof dangerjson<X>;
66
- data: X;
67
- req: BunRequest<ROUTE>;
68
- head: (head: HtmlHandler | HtmlString) => undefined;
69
- headers: (headers: HeadersInit, overwrite?: boolean) => undefined;
70
- options: (options: ResponseInit) => undefined;
71
- deliver: typeof url.deliver;
72
- route: string;
73
- params: URLSearchParams;
74
- form: Form;
75
- requrl: Readonly<URL>;
76
- /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/redirect_static) */
77
- redirect: (url: string | URL, status?: number) => void;
78
- constructor(mini: Mini<unknown>, data: X);
79
- }
80
- /**
81
- * HtmlHandler
82
- * @param mini - the mini object
83
- * @returns - return a partially resolved html string with mini.html
84
- * @example
85
- * ``` js
86
- * const {html,json, css, data, req, form, link, svg, deliver, route, params, header, head } = mini //pull everything out of the mini handbag
87
- * ```
88
- */
89
- export type HtmlHandler<Y = unknown, ROUTE extends string = ""> = ((mini: Mini<Y, ROUTE>) => LazyHandlerReturnType) | (() => LazyHandlerReturnType);
90
- export type NamedFormHandler<Y = unknown, Z = undefined> = ((mini: Mini<Y>) => NamedFormHandlerReturnType<Z>) | (() => NamedFormHandlerReturnType<Z>);
91
- declare global {
92
- var bundledFrontends: Record<string, {
93
- frontendFilePath: string;
94
- frontendContent: string;
95
- position: number;
96
- }>;
97
- var bundledSVGs: Record<string, {
98
- svgContent: string;
99
- svgFilePath: string;
100
- options: ResponseInit;
101
- position: number;
102
- }>;
103
- }
104
- export type ScriptTag = (...params: any[]) => Promise<HtmlString>;
105
- interface LinkSettings {
106
- [key: string]: string | null | undefined;
107
- }
108
- export declare class url {
109
- static websocket: WebSocketHandler | undefined;
110
- static server: Server;
111
- static routes: MiniNextRoutes;
112
- static direct_handlers_html: Map<string, HtmlHandler>;
113
- private static frontends;
114
- private static svgs;
115
- /**
116
- * This function takes care of bundling your svg (icons?) into the webapp
117
- * they will have a hash in the name to break the cache when needed
118
- * @param svgFilePath first place to look: svgs folder in the same path the calling file, after that path from project root.
119
- * @param options ResponseInit, default headers for an svg
120
- * @returns url to the svg
121
- */
122
- static svg(svgFilePath: string, options?: ResponseInit): string | undefined;
123
- /**
124
- * this function helps you build frontends with any kind of framework (no framework at all) and get the bundle
125
- * @param path first place to look: frontend folder in the same path the calling file, after that /frontend path from project root.
126
- * @param snippet this is handy to pass in a piece of html that often goes along with a certain frontend
127
- * @returns a html script element with the bundled frontend as the src
128
- */
129
- static frontend<X>(frontendFilePath: string, snippet?: BasedHtml): HtmlString;
130
- static frontend<X>(frontendFilePath: string, snippet?: HtmlHandler<X>): (mini: Mini<X>) => HtmlString;
131
- /**
132
- * This is used by the frontend bundler in order to find all frontends and their corresponding script files.
133
- */
134
- static getFrontends(): {
135
- frontendFilePath: string;
136
- callerPath: string;
137
- position: number;
138
- }[];
139
- static getSvgs(): {
140
- svgFilePath: string;
141
- callerPath: string;
142
- position: number;
143
- options: ResponseInit;
144
- }[];
145
- /**
146
- * tool to expose data to a frontend as a global variable.
147
- * @param name this will be added as window.name to the window object in the frontend
148
- * @param value this will be parsed as json in the frontend and asigned as follows: window.name = JSON.parsed(value)
149
- * @returns the script tag to be embeded in the html response
150
- *
151
- * @example
152
- * ``` js
153
- * //backend
154
- * url.deliver("user", userData); // window.user = JSON.parse(userData)
155
- * //frontend
156
- * const user = window["user"];
157
- * ```
158
- * if you want to use types, declare them like so in your frontend code:
159
- * ``` ts
160
- * declare global {
161
- * var user: string;
162
- *}
163
- * ```
164
- */
165
- static deliver(name: string, value: JsonStringValues): HtmlString;
166
- /**
167
- * @param dataHandler the function that prepares the data for the handlers
168
- * @example const {html,json, css, data, req, form, link, svg, deliver, route, params, header, head } = mini //pull everything out of the mini handbag
169
- * @returns
170
- */
171
- static data<T, Z>(dataMaker: DataMaker<T, Z>): {
172
- /**
173
- * @param dataHandler the function that prepares the data for the handlers
174
- * @example const {html,json, css, data, req, form, link, svg, deliver, route, params, header, head } = mini //pull everything out of the mini handbag
175
- * @returns
176
- */
177
- handler: (dataHandler: HtmlHandler<T>) => (oldmini: Mini) => Promise<string | void | JsonString>;
178
- dataMaker: DataMaker<T, Z>;
179
- /**
180
- * use this to **specify the input type for the functions**,
181
- *
182
- * that you want to use in the HtmlHandlers that follow this **data blend!**
183
- * @example type lol = typeof MaybeLoggedIn.$Mini
184
- */
185
- $Mini: Mini<T, "">;
186
- /**
187
- * use this to **specify the input type for the functions**,
188
- *
189
- * that you want to use in the Htmlhandlers that follow this **data blend!**
190
- * @example type haha = Mini<typeof MaybeLoggedIn.$Data>
191
- */
192
- $Data: T;
193
- };
194
- /**
195
- * use this to define your routes.
196
- * @example
197
- * ``` js
198
- * //define all routes at once
199
- * url.set([
200
- * ["/", (mini) => mini.html`<h1>Hello world</h1>`],
201
- * ["/apple", (mini) => mini.html`<h1>Hello apple</h1>`],
202
- * ["/banana", (mini) => mini.html`<h1>Hello banana</h1>`],
203
- * ]);
204
- * //define or overwrite just one route
205
- * url.set("/apple", (mini)=>mini.html`<h1> Hello pineapple </h1>`)
206
- * ```
207
- */
208
- static set<K extends string>(entries: [K, HtmlHandler][]): void;
209
- static set<R extends {
210
- [X in keyof R]: MiniNextRouteValue<Extract<X, string>>;
211
- }>({ routes }: {
212
- routes: R;
213
- }): void;
214
- static set(urlPath: string, handler: HtmlHandler): void;
215
- /**
216
- * wrap your handlers in this if you mutate something to prevent CSRF issues.
217
- * @param handler - normal html handler with mini as the argument
218
- * @returns a wrapped html handler that will only be called when the request is post
219
- */
220
- static post(handler: HtmlHandler): (mini: Mini) => LazyHandlerReturnType;
221
- /**
222
- * wrap your handlers in this if you mutate something to prevent CSRF issues.
223
- * @param handler - normal html handler with mini as the argument
224
- * @returns a wrapped html handler that will only be called when the request is post and contains a json body
225
- */
226
- static postJson(handler: HtmlHandler): (mini: Mini) => LazyHandlerReturnType;
227
- /**
228
- * wrap your handlers in this if you mutate something to prevent CSRF issues.
229
- * @param handler - normal html handler with mini as the argument
230
- * @returns a wrapped html handler that will only be called when the request is post and contains a FormData body
231
- */
232
- static postFormData(handler: HtmlHandler): (mini: Mini) => LazyHandlerReturnType;
233
- /**
234
- * This is useful to decouple forms from routes.
235
- * @param name name of the form - mini.form.onPostSubmit() will only be called if a (possibly hidden) field called formName matches this
236
- * @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
237
- * @returns - { formResponse: result of the handler, formInfo?: some info about the form. Totally up to you}
238
- */
239
- static namedForm<X = unknown, Z = undefined>(name: string, handler: NamedFormHandler<X, Z>): (mini: Mini<X>) => Promise<NamedForm<Z>>;
240
- /**
241
- * pass in all the query string parameter names that you want to preserve in the link
242
- * @param Url - the url that you want to link to (example: "/login")
243
- * @param qs - the query string parameters that you want to preserve in the link
244
- * @param settings - key and string values that you want to set in the link
245
- * @returns - the link that you can use in your html template
246
- */
247
- static link<X>(Url: string, qs?: string[] | string, settings?: LinkSettings): (mini: Mini<X>) => string;
248
- static currylink(Url: string, qs: string[] | string, req: Request, settings?: LinkSettings): string;
249
- /**
250
- * users expect links to work with or without a trailing slash.
251
- * Developers expect that that links work with or without a preceding slash.
252
- * We make sure that these expectations are met when using url.set and url.get.
253
- * (by adding all the variations to the url.direct_handlers Map)
254
- * @param {string} inputString - the url
255
- * @returns {string[]} - returns array of variations (added slash in the beginning, added, removed slash at the end)
256
- */
257
- static generateVariations(inputString: string): string[];
258
- static handleWithMini(req: BunRequest<string>, server: Server, handler: HtmlHandler): Promise<Response>;
259
- /**
260
- * use this to set the Websocket object. Check out [the bun docs](https://bun.sh/docs/api/websockets) for more details.
261
- * @param wsObject the websocketsocket object {@link WebSocketHandler}
262
- */
263
- static setWebsocket<T = undefined>(wsObject: WebSocketHandler<T>): void;
264
- /**
265
- * Send a message to all connected {@link ServerWebSocket} subscribed to a topic
266
- * @param topic The topic to publish to
267
- * @param message The data to send
268
- * @returns 0 if the message was dropped, -1 if backpressure was applied, or the number of bytes sent.
269
- */
270
- static publishHtml(topic: string, message: BasedHtml): number;
271
- /**
272
- * Fetch handler that is called by the server when a request is made to any of the urls.
273
- * @param {Request} req - The Request object.
274
- * @return {Promise<Response>} - The Response object.
275
- */
276
- static install(): {
277
- fetch: (req: Request, server: Server) => Response;
278
- websocket: WebSocketHandler<undefined> | undefined;
279
- routes: Record<string, RouterTypes.RouteValue<string>>;
280
- };
281
- }
282
- export {};