spooder 4.1.0 → 4.2.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/README.md +35 -24
- package/package.json +1 -1
- package/src/api.d.ts +2 -3
- package/src/api.ts +33 -13
package/README.md
CHANGED
|
@@ -431,8 +431,7 @@ In addition to the information provided by the developer, `spooder` also include
|
|
|
431
431
|
- [`caution(err_message_or_obj: string | object, ...err: object[]): Promise<void>`](#api-error-handling-caution)
|
|
432
432
|
- [`panic(err_message_or_obj: string | object, ...err: object[]): Promise<void>`](#api-error-handling-panic)
|
|
433
433
|
- [API > Content](#api-content)
|
|
434
|
-
- [`
|
|
435
|
-
- [`template_sub_file(template_file: string, replacements: Record<string, string>): Promise<string>`](#api-content-template-sub-file)
|
|
434
|
+
- [`parse_template(template: string, replacements: Record<string, string>): string`](#api-content-parse-template)
|
|
436
435
|
- [`generate_hash_subs(length: number, prefix: string): Promise<Record<string, string>>`](#api-content-generate-hash-subs)
|
|
437
436
|
- [`apply_range(file: BunFile, request: Request): HandlerReturnType`](#api-content-apply-range)
|
|
438
437
|
- [API > State Management](#api-state-management)
|
|
@@ -607,7 +606,7 @@ server.default((req, status_code) => {
|
|
|
607
606
|
Register a handler for uncaught errors.
|
|
608
607
|
|
|
609
608
|
> [!NOTE]
|
|
610
|
-
>
|
|
609
|
+
> Unlike other handlers, this should only return `Response` or `Promise<Response>`.
|
|
611
610
|
```ts
|
|
612
611
|
server.error((err, req, url) => {
|
|
613
612
|
return new Response('Custom Internal Server Error Message', { status: 500 });
|
|
@@ -876,8 +875,8 @@ try {
|
|
|
876
875
|
<a id="api-content"></a>
|
|
877
876
|
## API > Content
|
|
878
877
|
|
|
879
|
-
<a id="api-content-template
|
|
880
|
-
### 🔧 `
|
|
878
|
+
<a id="api-content-parse-template"></a>
|
|
879
|
+
### 🔧 `parse_template(template: string, replacements: Record<string, string>): string`
|
|
881
880
|
|
|
882
881
|
Replace placeholders in a template string with values from a replacement object.
|
|
883
882
|
|
|
@@ -888,12 +887,12 @@ Replace placeholders in a template string with values from a replacement object.
|
|
|
888
887
|
const template = `
|
|
889
888
|
<html>
|
|
890
889
|
<head>
|
|
891
|
-
<title>{title}</title>
|
|
890
|
+
<title>{$title}</title>
|
|
892
891
|
</head>
|
|
893
892
|
<body>
|
|
894
|
-
<h1>{title}</h1>
|
|
895
|
-
<p>{content}</p>
|
|
896
|
-
<p>{ignored}</p>
|
|
893
|
+
<h1>{$title}</h1>
|
|
894
|
+
<p>{$content}</p>
|
|
895
|
+
<p>{$ignored}</p>
|
|
897
896
|
</body>
|
|
898
897
|
</html>
|
|
899
898
|
`;
|
|
@@ -903,7 +902,7 @@ const replacements = {
|
|
|
903
902
|
content: 'This is a test.'
|
|
904
903
|
};
|
|
905
904
|
|
|
906
|
-
const html =
|
|
905
|
+
const html = parse_template(template, replacements);
|
|
907
906
|
```
|
|
908
907
|
|
|
909
908
|
```html
|
|
@@ -914,28 +913,40 @@ const html = template_sub(template, replacements);
|
|
|
914
913
|
<body>
|
|
915
914
|
<h1>Hello, world!</h1>
|
|
916
915
|
<p>This is a test.</p>
|
|
917
|
-
<p>{ignored}</p>
|
|
916
|
+
<p>{$ignored}</p>
|
|
918
917
|
</body>
|
|
919
918
|
</html>
|
|
920
919
|
```
|
|
921
920
|
|
|
922
|
-
|
|
923
|
-
### 🔧 `template_sub_file(template_file: string, replacements: Record<string, string>): Promise<string>`
|
|
921
|
+
`parse_template` supports looping arrays with the following syntax.
|
|
924
922
|
|
|
925
|
-
|
|
923
|
+
```html
|
|
924
|
+
{$for:foo}My colour is %s{/for}
|
|
925
|
+
```
|
|
926
|
+
```ts
|
|
927
|
+
const template = `
|
|
928
|
+
<ul>
|
|
929
|
+
{$for:foo}<li>%s</li>{/for}
|
|
930
|
+
</ul>
|
|
931
|
+
`;
|
|
926
932
|
|
|
927
|
-
|
|
928
|
-
|
|
933
|
+
const replacements = {
|
|
934
|
+
foo: ['red', 'green', 'blue']
|
|
935
|
+
};
|
|
929
936
|
|
|
930
|
-
|
|
931
|
-
|
|
937
|
+
const html = parse_template(template, replacements);
|
|
938
|
+
```
|
|
932
939
|
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
940
|
+
```html
|
|
941
|
+
<ul>
|
|
942
|
+
<li>red</li>
|
|
943
|
+
<li>green</li>
|
|
944
|
+
<li>blue</li>
|
|
945
|
+
</ul>
|
|
937
946
|
```
|
|
938
947
|
|
|
948
|
+
> [!WARNING]
|
|
949
|
+
> Nested loops are not supported.
|
|
939
950
|
|
|
940
951
|
<a id="api-content-generate-hash-subs"></a>
|
|
941
952
|
### 🔧 `generate_hash_subs(prefix: string): Promise<Record<string, string>>`
|
|
@@ -951,7 +962,7 @@ let hash_sub_table = {};
|
|
|
951
962
|
generate_hash_subs().then(subs => hash_sub_table = subs).catch(caution);
|
|
952
963
|
|
|
953
964
|
server.route('/test', (req, url) => {
|
|
954
|
-
return
|
|
965
|
+
return parse_template('Hello world {hash=docs/project-logo.png}', hash_sub_table);
|
|
955
966
|
});
|
|
956
967
|
```
|
|
957
968
|
|
|
@@ -978,7 +989,7 @@ Use a different prefix other than `hash=` by passing it as the first parameter.
|
|
|
978
989
|
generate_hash_subs(7, '#').then(subs => hash_sub_table = subs).catch(caution);
|
|
979
990
|
|
|
980
991
|
server.route('/test', (req, url) => {
|
|
981
|
-
return
|
|
992
|
+
return parse_template('Hello world {#docs/project-logo.png}', hash_sub_table);
|
|
982
993
|
});
|
|
983
994
|
```
|
|
984
995
|
|
package/package.json
CHANGED
package/src/api.d.ts
CHANGED
|
@@ -15,8 +15,7 @@ export declare class ErrorWithMetadata extends Error {
|
|
|
15
15
|
}
|
|
16
16
|
export declare function panic(err_message_or_obj: string | object, ...err: object[]): Promise<void>;
|
|
17
17
|
export declare function caution(err_message_or_obj: string | object, ...err: object[]): Promise<void>;
|
|
18
|
-
export declare function
|
|
19
|
-
export declare function template_sub(template: string, replacements: Record<string, string>): string;
|
|
18
|
+
export declare function parse_template(template: string, replacements: Record<string, string | Array<string>>): string;
|
|
20
19
|
export declare function generate_hash_subs(length?: number, prefix?: string): Promise<Record<string, string>>;
|
|
21
20
|
type CookieOptions = {
|
|
22
21
|
same_site?: 'Strict' | 'Lax' | 'None';
|
|
@@ -43,7 +42,7 @@ type JsonSerializable = JsonPrimitive | JsonObject | JsonArray | ToJson;
|
|
|
43
42
|
type HandlerReturnType = Resolvable<string | number | BunFile | Response | JsonSerializable | Blob>;
|
|
44
43
|
type RequestHandler = (req: Request, url: URL) => HandlerReturnType;
|
|
45
44
|
type WebhookHandler = (payload: JsonSerializable) => HandlerReturnType;
|
|
46
|
-
type ErrorHandler = (err: Error, req: Request, url: URL) => Response
|
|
45
|
+
type ErrorHandler = (err: Error, req: Request, url: URL) => Resolvable<Response>;
|
|
47
46
|
type DefaultHandler = (req: Request, status_code: number) => HandlerReturnType;
|
|
48
47
|
type StatusCodeHandler = (req: Request) => HandlerReturnType;
|
|
49
48
|
type ServerSentEventClient = {
|
package/src/api.ts
CHANGED
|
@@ -89,14 +89,7 @@ export async function caution(err_message_or_obj: string | object, ...err: objec
|
|
|
89
89
|
await handle_error('caution: ', err_message_or_obj, ...err);
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
export
|
|
93
|
-
const file = Bun.file(template_file);
|
|
94
|
-
const file_contents = await file.text();
|
|
95
|
-
|
|
96
|
-
return template_sub(file_contents, replacements);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export function template_sub(template: string, replacements: Record<string, string>): string {
|
|
92
|
+
export function parse_template(template: string, replacements: Record<string, string | Array<string>>): string {
|
|
100
93
|
let result = '';
|
|
101
94
|
let buffer = '';
|
|
102
95
|
let buffer_active = false;
|
|
@@ -105,14 +98,41 @@ export function template_sub(template: string, replacements: Record<string, stri
|
|
|
105
98
|
for (let i = 0; i < template_length; i++) {
|
|
106
99
|
const char = template[i];
|
|
107
100
|
|
|
108
|
-
if (char === '{') {
|
|
101
|
+
if (char === '{' && template[i + 1] === '$') {
|
|
102
|
+
i++;
|
|
109
103
|
buffer_active = true;
|
|
110
104
|
buffer = '';
|
|
111
|
-
} else if (char === '}') {
|
|
105
|
+
} else if (char === '}' && buffer_active) {
|
|
112
106
|
buffer_active = false;
|
|
113
107
|
|
|
114
|
-
|
|
108
|
+
if (buffer.startsWith('for:')) {
|
|
109
|
+
const loop_key = buffer.substring(4);
|
|
110
|
+
|
|
111
|
+
const loop_entries = replacements[loop_key];
|
|
112
|
+
if (loop_entries !== undefined) {
|
|
113
|
+
const loop_content_start_index = i + 1;
|
|
114
|
+
const loop_close_index = template.indexOf('{/for}', loop_content_start_index);
|
|
115
|
+
const loop_content = template.substring(loop_content_start_index, loop_close_index);
|
|
116
|
+
|
|
117
|
+
// More performat than replaceAll on larger arrays (and equal on tiny arrays).
|
|
118
|
+
const content_parts = loop_content.split('%s');
|
|
119
|
+
const indicies = [] as Array<number>;
|
|
115
120
|
|
|
121
|
+
for (let j = 0; j < content_parts.length; j++)
|
|
122
|
+
if (content_parts[j] === '%s')
|
|
123
|
+
indicies.push(j);
|
|
124
|
+
|
|
125
|
+
for (const loop_entry of loop_entries)
|
|
126
|
+
for (const index of indicies)
|
|
127
|
+
content_parts[index] = loop_entry;
|
|
128
|
+
|
|
129
|
+
i += loop_content.length + 6;
|
|
130
|
+
} else {
|
|
131
|
+
result += '{$' + buffer + '}';
|
|
132
|
+
}
|
|
133
|
+
} else {
|
|
134
|
+
result += replacements[buffer] ?? '{$' + buffer + '}';
|
|
135
|
+
}
|
|
116
136
|
buffer = '';
|
|
117
137
|
} else if (buffer_active) {
|
|
118
138
|
buffer += char;
|
|
@@ -249,7 +269,7 @@ type JsonSerializable = JsonPrimitive | JsonObject | JsonArray | ToJson;
|
|
|
249
269
|
type HandlerReturnType = Resolvable<string | number | BunFile | Response | JsonSerializable | Blob>;
|
|
250
270
|
type RequestHandler = (req: Request, url: URL) => HandlerReturnType;
|
|
251
271
|
type WebhookHandler = (payload: JsonSerializable) => HandlerReturnType;
|
|
252
|
-
type ErrorHandler = (err: Error, req: Request, url: URL) => Response
|
|
272
|
+
type ErrorHandler = (err: Error, req: Request, url: URL) => Resolvable<Response>;
|
|
253
273
|
type DefaultHandler = (req: Request, status_code: number) => HandlerReturnType;
|
|
254
274
|
type StatusCodeHandler = (req: Request) => HandlerReturnType;
|
|
255
275
|
|
|
@@ -413,7 +433,7 @@ export function serve(port: number) {
|
|
|
413
433
|
return new Response(http.STATUS_CODES[status_code], { status: status_code });
|
|
414
434
|
} catch (e) {
|
|
415
435
|
if (error_handler !== undefined)
|
|
416
|
-
return error_handler(e as Error, req, url);
|
|
436
|
+
return await error_handler(e as Error, req, url);
|
|
417
437
|
|
|
418
438
|
return new Response(http.STATUS_CODES[500], { status: 500 });
|
|
419
439
|
}
|