spooder 4.2.8 → 4.2.10

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 CHANGED
@@ -158,7 +158,7 @@ The following differences will be observed when running in development mode:
158
158
  It is possible to detect in userland if a server is running in development mode by checking the `SPOODER_ENV` environment variable.
159
159
 
160
160
  ```ts
161
- if (process.env.SPOODER_DEV === 'dev') {
161
+ if (process.env.SPOODER_ENV === 'dev') {
162
162
  // Server is running in development mode.
163
163
  }
164
164
  ```
@@ -728,6 +728,16 @@ server.dir('/static', '/static', (file_path, file, stat, request, url) => {
728
728
  > [!NOTE]
729
729
  > The directory handler function is only called for files that exist on disk - including directories.
730
730
 
731
+ Asynchronous directory handlers are supported and will be awaited.
732
+
733
+ ```js
734
+ server.dir('/static', '/static', async (file_path, file) => {
735
+ let file_contents = await file.text();
736
+ // do something with file_contents
737
+ return file_contents;
738
+ });
739
+ ```
740
+
731
741
  <a id="api-routing-server-sse"></a>
732
742
  ## API > Routing > Server-Sent Events
733
743
 
@@ -937,7 +947,7 @@ await safe(() => {
937
947
  ## API > Content
938
948
 
939
949
  <a id="api-content-parse-template"></a>
940
- ### 🔧 `parse_template(template: string, replacements: Record<string, string>, drop_missing: boolean): string`
950
+ ### 🔧 `parse_template(template: string, replacements: Replacements, drop_missing: boolean): string`
941
951
 
942
952
  Replace placeholders in a template string with values from a replacement object.
943
953
 
@@ -995,6 +1005,29 @@ parse_template(template, replacements, true);
995
1005
  </html>
996
1006
  ```
997
1007
 
1008
+ `parse_template` supports passing a function instead of a replacement object. This function will be called for each placeholder and the return value will be used as the replacement.
1009
+
1010
+ ```ts
1011
+ const replacer = (placeholder: string) => {
1012
+ return placeholder.toUpperCase();
1013
+ };
1014
+
1015
+ parse_template('Hello {$world}', replacer);
1016
+ ```
1017
+
1018
+ ```html
1019
+ <html>
1020
+ <head>
1021
+ <title>TITLE</title>
1022
+ </head>
1023
+ <body>
1024
+ <h1>TITLE</h1>
1025
+ <p>CONTENT</p>
1026
+ <p>IGNORED</p>
1027
+ </body>
1028
+ </html>
1029
+ ```
1030
+
998
1031
  `parse_template` supports looping arrays with the following syntax.
999
1032
 
1000
1033
  ```html
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "spooder",
3
3
  "type": "module",
4
- "version": "4.2.8",
4
+ "version": "4.2.10",
5
5
  "exports": {
6
6
  ".": {
7
7
  "bun": "./src/api.ts",
@@ -10,8 +10,7 @@
10
10
  },
11
11
  "module": "./src/api.ts",
12
12
  "devDependencies": {
13
- "@types/node": "^20.0.0",
14
- "bun-types": "^0.5.0"
13
+ "@types/bun": "^1.0.5"
15
14
  },
16
15
  "bin": {
17
16
  "spooder": "./src/cli.ts"
package/src/api.d.ts CHANGED
@@ -1,6 +1,4 @@
1
1
  /// <reference types="node" />
2
- /// <reference types="bun-types" />
3
- /// <reference types="node" />
4
2
  /// <reference types="node" />
5
3
  import fs from 'node:fs/promises';
6
4
  import { Blob } from 'node:buffer';
@@ -20,7 +18,9 @@ export declare function caution(err_message_or_obj: string | object, ...err: obj
20
18
  type CallableFunction = (...args: any[]) => any;
21
19
  type Callable = Promise<any> | CallableFunction;
22
20
  export declare function safe(target_fn: Callable): Promise<void>;
23
- export declare function parse_template(template: string, replacements: Record<string, string | Array<string>>, drop_missing?: boolean): string;
21
+ type ReplacerFn = (key: string) => string | Array<string>;
22
+ type Replacements = Record<string, string | Array<string>> | ReplacerFn;
23
+ export declare function parse_template(template: string, replacements: Replacements, drop_missing?: boolean): string;
24
24
  export declare function generate_hash_subs(length?: number, prefix?: string): Promise<Record<string, string>>;
25
25
  type CookieOptions = {
26
26
  same_site?: 'Strict' | 'Lax' | 'None';
package/src/api.ts CHANGED
@@ -107,11 +107,16 @@ export async function safe(target_fn: Callable) {
107
107
  }
108
108
  }
109
109
 
110
- export function parse_template(template: string, replacements: Record<string, string | Array<string>>, drop_missing = false): string {
110
+ type ReplacerFn = (key: string) => string | Array<string>;
111
+ type Replacements = Record<string, string | Array<string>> | ReplacerFn;
112
+
113
+ export function parse_template(template: string, replacements: Replacements, drop_missing = false): string {
111
114
  let result = '';
112
115
  let buffer = '';
113
116
  let buffer_active = false;
114
117
 
118
+ const is_replacer_fn = typeof replacements === 'function';
119
+
115
120
  const template_length = template.length;
116
121
  for (let i = 0; i < template_length; i++) {
117
122
  const char = template[i];
@@ -126,7 +131,7 @@ export function parse_template(template: string, replacements: Record<string, st
126
131
  if (buffer.startsWith('for:')) {
127
132
  const loop_key = buffer.substring(4);
128
133
 
129
- const loop_entries = replacements[loop_key];
134
+ const loop_entries = is_replacer_fn ? replacements(loop_key) : replacements[loop_key];
130
135
  const loop_content_start_index = i + 1;
131
136
  const loop_close_index = template.indexOf('{/for}', loop_content_start_index);
132
137
 
@@ -147,7 +152,7 @@ export function parse_template(template: string, replacements: Record<string, st
147
152
  i += loop_content.length + 6;
148
153
  }
149
154
  } else {
150
- const replacement = replacements[buffer];
155
+ const replacement = is_replacer_fn ? replacements(buffer) : replacements[buffer];
151
156
  if (replacement !== undefined)
152
157
  result += replacement;
153
158
  else if (!drop_missing)
@@ -326,7 +331,7 @@ function route_directory(route_path: string, dir: string, handler: DirHandler):
326
331
  const file_stat = await fs.stat(file_path);
327
332
  const bun_file = Bun.file(file_path);
328
333
 
329
- return handler(file_path, bun_file, file_stat, req, url);
334
+ return await handler(file_path, bun_file, file_stat, req, url);
330
335
  } catch (e) {
331
336
  const err = e as NodeJS.ErrnoException;
332
337
  if (err?.code === 'ENOENT')
@@ -380,7 +385,8 @@ export function serve(port: number) {
380
385
 
381
386
  // Content-type/content-length are automatically set for blobs.
382
387
  if (response instanceof Blob)
383
- return new Response(response as Blob, { status: status_code });
388
+ // @ts-ignore Response does accept Blob in Bun, typing disagrees.
389
+ return new Response(response, { status: status_code });
384
390
 
385
391
  // Status codes can be returned from some handlers.
386
392
  if (return_status_code && typeof response === 'number')
@@ -554,14 +560,16 @@ export function serve(port: number) {
554
560
 
555
561
  const queue = Array<string>();
556
562
  const stream = new ReadableStream({
563
+ // @ts-ignore Bun implements a "direct" mode which does not exist in the spec.
557
564
  type: 'direct',
558
565
 
559
- async pull(controller: ReadableStreamDirectController) {
560
- stream_controller = controller;
566
+ async pull(controller) {
567
+ // @ts-ignore `controller` in "direct" mode is ReadableStreamDirectController.
568
+ stream_controller = controller as ReadableStreamDirectController;
561
569
  while (!req.signal.aborted) {
562
570
  if (queue.length > 0) {
563
- controller.write(queue.shift()!);
564
- controller.flush();
571
+ stream_controller.write(queue.shift()!);
572
+ stream_controller.flush();
565
573
  } else {
566
574
  await Bun.sleep(50);
567
575
  }
package/src/cli.ts CHANGED
@@ -64,7 +64,7 @@ async function start_server() {
64
64
  function capture_stream(stream: ReadableStream, output: NodeJS.WritableStream) {
65
65
  const reader = stream.getReader();
66
66
 
67
- reader.read().then(function read_chunk(chunk: ReadableStreamDefaultReadResult<Uint8Array>) {
67
+ reader.read().then(function read_chunk(chunk) {
68
68
  if (chunk.done)
69
69
  return;
70
70