spooder 3.0.8 → 3.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 CHANGED
@@ -2,13 +2,25 @@
2
2
 
3
3
  # Spooder · ![typescript](https://img.shields.io/badge/language-typescript-blue) [![license badge](https://img.shields.io/github/license/Kruithne/spooder?color=yellow)](LICENSE) ![npm version](https://img.shields.io/npm/v/spooder?color=c53635) ![bun](https://img.shields.io/badge/runtime-bun-f9f1e1)
4
4
 
5
- `spooder` is a purpose-built web server solution written in [TypeScript](https://www.typescriptlang.org/) for [Bun](https://bun.sh/). It is designed to be highly opinionated with minimal configuration.
5
+ `spooder` is a purpose-built server solution written using the [Bun](https://bun.sh/) runtime.
6
6
 
7
- > **Warning** - This project is built with specific use-cases in mind and is not intended to be a general-purpose web server. The authors of this project are not responsible for any damage caused by using this software.
7
+ ### What does it do?
8
8
 
9
- > **Warning** - This project is developed for [Bun](https://bun.sh/), which at the time of writing is still experimental. It is not recommended to use this project in production environments unless you understand the risks.
9
+ `spooder` consists of a command-line tool which provides automatic updating/restarting and canary functionality, and a building-block API for creating servers.
10
10
 
11
- ## Installation
11
+ ### Should I use it?
12
+
13
+ Probably not. You are free to use `spooder` if you fully understand the risks and limitations of doing so, however here is a list of things you should consider before using it:
14
+
15
+ ⚠️ This is not a Node.js package. It is built using the [Bun](https://bun.sh/) runtime, which is still experimental as of writing.
16
+
17
+ ⚠️ It is designed to be highly opinionated and is not intended to be a general-purpose server, so configuration is limited.
18
+
19
+ ⚠️ It is not a full-featured web server and only provides the functionality as required for the projects it has been built for.
20
+
21
+ ⚠️ It has not been battle-tested and may contain bugs or security issues. The authors of this project are not responsible for any problems caused by using this software.
22
+
23
+ # Installation
12
24
 
13
25
  ```bash
14
26
  # Installing globally for CLI runner usage.
@@ -18,7 +30,28 @@ bun add spooder --global
18
30
  bun add spooder
19
31
  ```
20
32
 
21
- ## Runner
33
+ # Configuration
34
+
35
+ Both the runner and the API are configured in the same way by providing a `spooder` object in your `package.json` file.
36
+
37
+ ```json
38
+ {
39
+ "spooder": {
40
+ "autoRestart": 5000,
41
+ "run": "bun run index.ts",
42
+ "update": [
43
+ "git pull",
44
+ "bun install"
45
+ ]
46
+ }
47
+ }
48
+ ```
49
+
50
+ If there are any issues with the provided configuration, a warning will be printed to the console but will not halt execution. `spooder` will always fall back to default values where invalid configuration is provided.
51
+
52
+ Configuration warnings **do not** raise `caution` events with the `spooder` canary functionality.
53
+
54
+ # Runner
22
55
 
23
56
  `spooder` includes a global command-line tool for running servers. It is recommended that you run this in a `screen` session.
24
57
 
@@ -28,9 +61,9 @@ cd /var/www/my_server/
28
61
  spooder
29
62
  ```
30
63
 
31
- While the intended use of this runner is for web servers, it can be used to run anything. It provides two primary features: automatic updating and restarting.
64
+ While the intended use of this runner is for web servers, it can be used to run anything. It provides two primary features: automatic updating and automatic restarting.
32
65
 
33
- ### Entry Point
66
+ ## Entry Point
34
67
 
35
68
  `spooder` will attempt to launch the server from the current working directory using the command `bun run index.ts` as a default.
36
69
 
@@ -46,13 +79,9 @@ To customize this, provide an alternative command via the `run` configuration.
46
79
 
47
80
  While `spooder` uses a `bun run` command by default, it is possible to use any command string.
48
81
 
49
- It is possible to chain commands, such as updating your source with `git pull && bun run index.ts`, however it is recommended that `run` is only used to launch the service. Instead, use the `update` property for updating which will fail gracefully and will not block the server from starting.
50
-
51
- ### Auto Restart
52
-
53
- In the event that the server exits (regardless of exit code), `spooder` will automatically restart it after a short delay.
82
+ ## Auto Restart
54
83
 
55
- This feature is enabled by default with a delay of `5000` milliseconds. The delay can be changed by providing a value for `autoRestart` in the configuration.
84
+ In the event that the server exits (regardless of exit code), `spooder` can automatically restart it after a short delay. To enable this feature specify the restart delay in milliseconds as `autoRestart` in the configuration.
56
85
 
57
86
  ```json
58
87
  {
@@ -64,18 +93,9 @@ This feature is enabled by default with a delay of `5000` milliseconds. The dela
64
93
 
65
94
  If set to `0`, the server will be restarted immediately without delay. If set to `-1`, the server will not be restarted at all.
66
95
 
67
- ### Auto Update
96
+ ## Auto Update
68
97
 
69
- When starting your server, `spooder` can automatically update the source code in the working directory. To enable this feature, provide an update command as `update` in the configuration.
70
-
71
- ```json
72
- {
73
- "spooder": {
74
- "update": "git pull"
75
- }
76
- }
77
- ```
78
- To execute multiple commands in sequence, provide an array rather than using the `&&` operator.
98
+ When starting your server, `spooder` can automatically update the source code in the working directory. To enable this feature, the necessary update commands can be provided in the configuration as an array of strings.
79
99
 
80
100
  ```json
81
101
  {
@@ -88,7 +108,11 @@ To execute multiple commands in sequence, provide an array rather than using the
88
108
  }
89
109
  ```
90
110
 
91
- If a command in the sequence fails, the remaining commands will not be executed. However, if the update fails, the server will still be started. This is preferred over entering a restart loop or failing to start the server at all.
111
+ Commands will be executed in sequence, and the server will not be started until after the commands have resolved.
112
+
113
+ Each command should be a separate item in the array. Chaining commands in a single string using the `&&` or `||` operators will not work.
114
+
115
+ If a command in the sequence fails, the remaining commands will not be executed, however the server will still be started. This is preferred over entering a restart loop or failing to start the server at all.
92
116
 
93
117
  As well as being executed when the server is first started, the `update` commands are also run when `spooder` automatically restarts the server after it exits.
94
118
 
@@ -101,13 +125,568 @@ events.on('receive-webhook', () => {
101
125
  });
102
126
  ```
103
127
 
104
- ## API
128
+ ## Canary
129
+
130
+ `canary` is a feature in `spooder` which allows server problems to be raised as issues in your repository on GitHub.
131
+
132
+ To enable this feature, there are a couple of steps you need to take.
133
+
134
+ ### 1. Create a GitHub App
135
+
136
+ Create a new GitHub App either on your personal account or on an organization. The app will need the following permissions:
137
+
138
+ - **Issues** - Read & Write
139
+ - **Metadata** - Read-only
140
+
141
+ Once created, install the GitHub App to your account. The app will need to be given access to the repositories you want to use the canary feature with.
142
+
143
+ In addition to the **App ID** that is assigned automatically, you will also need to generate a **Private Key** for the app. This can be done by clicking the **Generate a private key** button on the app page.
144
+
145
+ > Note: The private keys provided by GitHub are in PKCS#1 format, but only PKCS#8 is supported. You can convert the key file with the following command.
146
+
147
+ ```bash
148
+ openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in private-key.pem -out private-key-pkcs8.key
149
+ ```
150
+
151
+ Each server that intends to use the canary feature will need to have the private key installed somewhere the server process can access it.
152
+
153
+ ### 2. Add package.json configuration
154
+
155
+ ```json
156
+ "spooder": {
157
+ "canary": {
158
+ "account": "<GITHUB_ACCOUNT_NAME>",
159
+ "repository": "<GITHUB_REPOSITORY>",
160
+ "labels": ["some-label"]
161
+ }
162
+ }
163
+ ```
164
+
165
+ Replace `<GITHUB_ACCOUNT_NAME>` with the account name you have installed the GitHub App to, and `<GITHUB_REPOSITORY>` with the repository name you want to use for issues.
166
+
167
+ The repository name must in the format `owner/repo` (e.g. `facebook/react`).
168
+
169
+ The `labels` property can be used to provide a list of labels to automatically add to the issue. This property is optional and can be omitted.
170
+
171
+ ### 3. Setup environment variables
172
+
173
+ The following two environment variables must be defined on the server.
174
+
175
+ ```
176
+ SPOODER_CANARY_APP_ID=1234
177
+ SPOODER_CANARY_KEY=/home/bond/.ssh/id_007_pcks8.key
178
+ ```
179
+
180
+ `SPOODER_CANARY_APP_ID` is the **App ID** as shown on the GitHub App page.
181
+ `SPOODER_CANARY_KEY` is the path to the private key file in PKCS#8 format.
182
+
183
+ ### 4. Use canary
184
+
185
+ Once configured, `spooder` will automatically raise an issue when the server exits with a non-zero exit code.
186
+
187
+ In addition, you can manually raise issues using the `spooder` API by calling `caution()` or `panic()`. More information about these functions can be found in the `API` section.
188
+
189
+ ## Crash
190
+
191
+ It is recommended that you harden your server code against unexpected exceptions and use `panic()` and `caution()` to raise issues with selected diagnostic information.
192
+
193
+ In the event that the server does encounter an unexpected exception which causes it to exit with a non-zero exit code, `spooder` will automatically raise an issue on GitHub using the canary feature, if configured.
194
+
195
+ Since this issue has been caught externally, `spooder` has no context of the exception which was raised. Instead, the canary report will contain the output from `stderr`.
196
+
197
+ ```json
198
+ {
199
+ "exitCode": 1,
200
+ "stderr": [
201
+ "[2.48ms] \".env.local\"",
202
+ "Test output",
203
+ "Test output",
204
+ "4 | console.warn('Test output');",
205
+ "5 | ",
206
+ "6 | // Create custom error class.",
207
+ "7 | class TestError extends Error {",
208
+ "8 | constructor(message: string) {",
209
+ "9 | super(message);",
210
+ " ^",
211
+ "TestError: Home is [IPv4 address]",
212
+ " at new TestError (/mnt/i/spooder/test.ts:9:2)",
213
+ " at /mnt/i/spooder/test.ts:13:6",
214
+ ""
215
+ ]
216
+ }
217
+ ```
218
+
219
+ This information is subject to sanitization, as described in the `Sanitization` section, however you should be aware that stack traces may contain sensitive information.
220
+
221
+ Additionally, Bun includes a relevant code snippet from the source file where the exception was raised. This is intended to help you identify the source of the problem.
222
+
223
+ ## Sanitization
224
+
225
+ All reports sent via the canary feature are sanitized to prevent sensitive information from being leaked. This includes:
226
+
227
+ - Environment variables from `.env.local`
228
+ - IPv4 / IPv6 addresses.
229
+ - E-mail addresses.
230
+
231
+ ```bash
232
+ # .env.local
233
+ DB_PASSWORD=secret
234
+ ```
235
+
236
+ ```ts
237
+ await panic({
238
+ a: 'foo',
239
+ b: process.env.DB_PASSWORD,
240
+ c: 'Hello person@place.net',
241
+ d: 'Client: 192.168.1.1'
242
+ });
243
+ ```
244
+
245
+ ```json
246
+ [
247
+ {
248
+ "a": "foo",
249
+ "b": "[redacted]",
250
+ "c": "Hello [e-mail address]",
251
+ "d": "Client: [IPv4 address]"
252
+ }
253
+ ]
254
+ ```
255
+
256
+ The sanitization behavior can be disabled by setting `spooder.canary.sanitize` to `false` in the configuration. This is not recommended as it may leak sensitive information.
257
+
258
+ ```json
259
+ {
260
+ "spooder": {
261
+ "canary": {
262
+ "sanitize": false
263
+ }
264
+ }
265
+ }
266
+ ```
267
+
268
+ While this sanitization adds a layer of protection against information leaking, it does not catch everything. You should pay special attention to messages and objects provided to the canary to not unintentionally leak sensitive information.
269
+
270
+ ## System Information
271
+
272
+ In addition to the information provided by the developer, `spooder` also includes some system information in the canary reports.
273
+
274
+ ```json
275
+ {
276
+ "loadavg": [
277
+ 0,
278
+ 0,
279
+ 0
280
+ ],
281
+ "memory": {
282
+ "free": 7620907008,
283
+ "total": 8261840896
284
+ },
285
+ "platform": "linux",
286
+ "uptime": 7123,
287
+ "versions": {
288
+ "node": "18.15.0",
289
+ "bun": "0.6.5",
290
+ "webkit": "60d11703a533fd694cd1d6ddda04813eecb5d69f",
291
+ "boringssl": "b275c5ce1c88bc06f5a967026d3c0ce1df2be815",
292
+ "libarchive": "dc321febde83dd0f31158e1be61a7aedda65e7a2",
293
+ "mimalloc": "3c7079967a269027e438a2aac83197076d9fe09d",
294
+ "picohttpparser": "066d2b1e9ab820703db0837a7255d92d30f0c9f5",
295
+ "uwebsockets": "70b1b9fc1341e8b791b42c5447f90505c2abe156",
296
+ "zig": "0.11.0-dev.2571+31738de28",
297
+ "zlib": "885674026394870b7e7a05b7bf1ec5eb7bd8a9c0",
298
+ "tinycc": "2d3ad9e0d32194ad7fd867b66ebe218dcc8cb5cd",
299
+ "lolhtml": "2eed349dcdfa4ff5c19fe7c6e501cfd687601033",
300
+ "ares": "0e7a5dee0fbb04080750cf6eabbe89d8bae87faa",
301
+ "usockets": "fafc241e8664243fc0c51d69684d5d02b9805134",
302
+ "v8": "10.8.168.20-node.8",
303
+ "uv": "1.44.2",
304
+ "napi": "8",
305
+ "modules": "108"
306
+ }
307
+ }
308
+ ```
309
+
310
+ # API
311
+
312
+ `spooder` exposes a build-block style API for developing servers. The API is designed to be minimal to leave control in the hands of the developer and not add overhead for features you may not need.
313
+
314
+ ```ts
315
+ import { ... } from 'spooder';
316
+ ```
317
+
318
+ #### `serve(port: number): Server`
319
+
320
+ The `serve` function simplifies the process of boostrapping a server. Setting up a functioning server is as simple as calling the function and passing a port number to listen on.
321
+
322
+ ```ts
323
+ const server = serve(8080);
324
+ ```
325
+
326
+ Without any additional configuration, this will create a server which listens on the specified port and responds to all requests with the following response.
327
+
328
+ ```http
329
+ HTTP/1.1 404 Not Found
330
+ Content-Length: 9
331
+ Content-Type: text/plain;charset=utf-8
332
+
333
+ Not Found
334
+ ```
335
+
336
+ To build functionality on top of this, there are a number of functions that can be called from the `Server` object.
337
+
338
+ #### `server.route(path: string, handler: RequestHandler)`
339
+
340
+ The `route` function allows you to register a handler for a specific path. The handler will be called for all requests that exactly match the given path.
341
+
342
+ ```ts
343
+ server.route('/test/route', (req, url) => {
344
+ return new Response('Hello, world!', { status: 200 });
345
+ });
346
+ ```
347
+
348
+ Additionally, routes also support named parameters. These are defined by prefixing a path segment with a colon. These are added directly to the `searchParams` property of the `URL` object.
349
+
350
+ ```ts
351
+ server.route('/test/:param', (req, url) => {
352
+ return new Response(url.searchParams.get('param'), { status: 200 });
353
+ });
354
+ ```
355
+ > Note: Named parameters will overwrite existing search parameters with the same name.
356
+
357
+ By default routes are matched exactly, but you can also use a wildcard to match any path that starts with a given path.
358
+
359
+ ```ts
360
+ server.route('/test/*', (req, url) => {
361
+ return new Response('Hello, world!', { status: 200 });
362
+ });
363
+ ```
364
+
365
+ The above will match any path the starts with `/test`, such as:
366
+ - `/test`
367
+ - `/test/`
368
+ - `/test/route`
369
+ - `/test/route/foo.txt`
370
+
371
+ If you intend to use this for directory serving, you may be better suited looking at the `server.dir()` function.
372
+
373
+ Wildcards can also be placed anywhere in the path, allowing anything to be placed in a given single segment - it does not span multiple segments.
374
+
375
+ ```ts
376
+ server.route('/test/*/route', (req, url) => {
377
+ return new Response('Hello, world!', { status: 200 });
378
+ });
379
+ ```
380
+
381
+ The above would allow anything to be placed in the middle segment. This behavior is documented for clarity as it is a byproduct of wildcard implementation for directories, but it is recommended you use the named parameters feature instead.
105
382
 
106
- `spooder` exposes a simple API which can be imported into your project for bootstrapping a server in Bun. The API is designed to be minimal to leave control in the hands of the developer and not add overhead for features you may not need.
383
+ Using the standard Web API, the route handler above receives a [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object and returns a [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) object, which is then sent to the client.
384
+
385
+ All handle registration functions in `spooder` support registering async functions which will be awaited before sending the response.
386
+
387
+ ```ts
388
+ server.route('/test/route', async (req, url) => {
389
+ await new Promise((resolve) => setTimeout(resolve, 1000));
390
+ return new Response('Hello, world!', { status: 200 });
391
+ });
392
+ ```
393
+
394
+ To streamline this process, `spooder` allows a number of other return types to be used as shortcuts.
395
+
396
+ Returning a `number` type treats the number as a status code and sends a relevant response.
397
+
398
+ By default, this will be a plain text response with the applicable status message as the body. This can be overridden with `server.handle()` or `server.default()`, which will be covered later.
399
+
400
+ ```ts
401
+ server.route('/test/route', (req) => {
402
+ return 500;
403
+ });
404
+ ```
405
+ ```http
406
+ HTTP/1.1 500 Internal Server Error
407
+ Content-Length: 21
408
+ Content-Type: text/plain;charset=utf-8
409
+
410
+ Internal Server Error
411
+ ```
412
+
413
+ Returning a `Blob` type, such as the `FileBlob` returned from the `Bun.file()` API, will send the blob as the response body with the appropriate content type and length headers.
414
+
415
+ ```ts
416
+ server.route('test/route', (req) => {
417
+ // Note that calling Bun.file() does not immediately read
418
+ // the file from disk, it will be streamed with the response.
419
+ return Bun.file('test.png');
420
+ });
421
+ ```
422
+ ```http
423
+ HTTP/1.1 200 OK
424
+ Content-Length: 12345
425
+ Content-Type: image/png
426
+
427
+ <binary data>
428
+ ```
429
+
430
+ Return an `object` type, such as an array or a plain object, will send the object as JSON with the appropriate content type and length headers.
431
+
432
+ ```ts
433
+ server.route('test/route', (req) => {
434
+ return { message: 'Hello, world!' };
435
+ });
436
+ ```
437
+ ```http
438
+ HTTP/1.1 200 OK
439
+ Content-Length: 25
440
+ Content-Type: application/json;charset=utf-8
441
+
442
+ {"message":"Hello, world!"}
443
+ ```
444
+
445
+ Since custom classes are also objects, you can also return a custom class instance and it will be serialized to JSON. To control the serialization process, you can implement the `toJSON()` method on your class.
446
+
447
+ ```ts
448
+ class User {
449
+ constructor(public name: string, public age: number) {}
450
+
451
+ toJSON() {
452
+ return {
453
+ name: this.name,
454
+ age: this.age,
455
+ };
456
+ }
457
+ }
458
+
459
+ server.route('test/route', (req) => {
460
+ return new User('Bob', 42);
461
+ });
462
+ ```
463
+ ```http
464
+ HTTP/1.1 200 OK
465
+ Content-Length: 25
466
+ Content-Type: application/json;charset=utf-8
467
+
468
+ {"name":"Bob","age":42}
469
+ ```
470
+
471
+ Any other type that is returned from a route handler will be converted to a string and sent as the response body with the appropriate length header and the content type `text/plain`.
472
+
473
+ ```ts
474
+ server.route('test/route', (req) => {
475
+ return Symbol('foo');
476
+ });
477
+ ```
478
+ ```http
479
+ HTTP/1.1 200 OK
480
+ Content-Length: 7
481
+ Content-Type: text/plain;charset=utf-8
482
+
483
+ Symbol(foo)
484
+ ```
485
+
486
+ #### `server.default(handler: DefaultHandler)`
487
+
488
+ The server uses a default handler which responds to requests for which there was no handler registered, or the registered handler returned a numeric status code.
489
+
490
+ This default handler sends a simple response to the client with the status code and a body containing the status message.
491
+
492
+ ```http
493
+ HTTP/1.1 404 Not Found
494
+ Content-Length: 9
495
+ Content-Type: text/plain;charset=utf-8
496
+
497
+ Not Found
498
+ ```
499
+
500
+ To customize the behavior of this handler, you can register a custom default handler using the `default` function.
501
+
502
+ ```ts
503
+ server.default((req, status_code) => {
504
+ return new Response(`Custom error: ${status_code}`, { status: status_code });
505
+ });
506
+ ```
507
+
508
+ Using your own default handler allows you to provide a custom response for unhandled requests based on the status code.
509
+
510
+ The return type from this handler can be any of the expected return types from a normal route handler with the exception that returning a `number` type will not be treated as a status code and will instead be treated as a plain text response.
511
+
512
+ If is worth noting that if you return a `Response` object from this handler, you must implicitly set the status code. If you do not, the status code will be set to `200` by default.
513
+
514
+ ```ts
515
+ server.default((req, status_code) => {
516
+ return new Response(`Custom error: ${status_code}`);
517
+ });
518
+ ```
519
+ ```http
520
+ HTTP/1.1 200 OK
521
+ Content-Length: 18
522
+ Content-Type: text/plain;charset=utf-8
523
+
524
+ Custom error: 404
525
+ ```
526
+
527
+ Returning anything else, such as a `Blob`, `object` or `string`, the status code will automatically be set to `status_code`. To override this behavior you must provide a `Response` object.
528
+
529
+ #### `server.handle(status_code: number, handler: RequestHandler)`
530
+
531
+ The `handle` function allows you to register a handler for a specific status code. This handler will take priority over the default handler.
107
532
 
108
533
  ```ts
109
- import serve from 'spooder'; // WIP
534
+ server.handle(500, (req) => {
535
+ return new Response('Custom Internal Server Error Message', { status: 500 });
536
+ });
537
+ ```
538
+ ```http
539
+ HTTP/1.1 500 Internal Server Error
540
+ Content-Length: 36
541
+ Content-Type: text/plain;charset=utf-8
542
+
543
+ Custom Internal Server Error Message
110
544
  ```
111
545
 
546
+ The return type from this handler can be any of the expected return types from a normal route handler with the exception that returning a `number` type will not be treated as a status code and will instead be treated as a plain text response.
547
+
548
+ If is worth noting that if you return a `Response` object from this handler, you must implicitly set the status code. If you do not, the status code will be set to `200` by default.
549
+
550
+ ```ts
551
+ server.handle(500, (req) => {
552
+ return new Response('Custom Internal Server Error Message');
553
+ });
554
+ ```
555
+ ```http
556
+ HTTP/1.1 200 OK
557
+ Content-Length: 36
558
+ Content-Type: text/plain;charset=utf-8
559
+
560
+ Custom Internal Server Error Message
561
+ ```
562
+
563
+ Returning anything else, such as a `Blob`, `object` or `string`, the status code will automatically be set. To override this behavior you must provide a `Response` object.
564
+
565
+ #### `server.error(handler: ErrorHandler)`
566
+
567
+ The `error` function allows you to register a handler for any uncaught errors that occur during the request handling process.
568
+
569
+ Unlike other handlers, it does not accept asynchronous functions and it must return a `Response` object.
570
+
571
+ ```ts
572
+ server.error((req, err) => {
573
+ return new Response('Custom Internal Server Error Message', { status: 500 });
574
+ });
575
+ ```
576
+ ```http
577
+ HTTP/1.1 500 Internal Server Error
578
+ Content-Length: 36
579
+ Content-Type: text/plain;charset=utf-8
580
+
581
+ Custom Internal Server Error Message
582
+ ```
583
+ This should be used as a last resort to catch unintended errors and should not be part of your normal request handling process. Generally speaking, this handler should only be called if you have a bug in your code.
584
+
585
+ #### `server.dir(path: string, dir: string)`
586
+
587
+ The `dir` function allows you to serve static files from a directory on your file system.
588
+
589
+ ```ts
590
+ server.dir('/content', './public/content');
591
+ ```
592
+
593
+ The above example will serve all files from `./public/content` to any requests made to `/content`. For example `/content/test.txt` will serve the file `./public/content/test.txt`.
594
+
595
+ - This function is recursive and will serve all files from the specified directory and any subdirectories.
596
+ - Requesting a directory will return a 401 response (subject to your configured handlers).
597
+ - Requesting a file that does not exist will return a 404 response (subject to your configured handlers).
598
+ - Requesting a file that is not readable will return a 500 response (subject to your configured handlers).
599
+
600
+ By default, hidden files (files prefixed with `.`) will not be served. To serve hidden files, you must set `ignoreHidden` to `false` in the `options` parameter.
601
+
602
+ ```ts
603
+ server.dir('/content', './public/content', { ignoreHidden: false });
604
+ ```
605
+
606
+ If `ignoreHidden` is set to `true` (default) then requesting a hidden file will return a 404 response (subject to your configured handlers).
607
+
608
+ Additionally, the `index` property can be set to a filename such as `index.html` to serve a default file when a directory is requested.
609
+
610
+ ```ts
611
+ server.dir('/content', './public/content', { index: 'index.html' });
612
+ ```
613
+
614
+ The above will serve `./public/content/index.html` when `/content` is requested.
615
+
616
+ ---
617
+
618
+ #### `route_location(redirect_url: string)`
619
+
620
+ The `route_location` is a built-in request handler that redirects the client to a specified URL with the status code `301 Moved Permanently`.
621
+
622
+ ```ts
623
+ server.route('test/route', route_location('https://example.com');
624
+ ```
625
+
626
+ The above is a much shorter equivalent to the following:
627
+
628
+ ```ts
629
+ server.route('test/route', (req, url) => {
630
+ return new Response(null, {
631
+ status: 301,
632
+ headers: {
633
+ Location: 'https://example.com',
634
+ },
635
+ });
636
+ });
637
+ ```
638
+ ---
639
+
640
+ #### `caution(err_message_or_obj: string | object, ...err: object[]): Promise<void>`
641
+ Raise a warning issue on GitHub. This is useful for non-fatal errors which you want to be notified about.
642
+
643
+ ```ts
644
+ try {
645
+ // connect to database
646
+ } catch (e) {
647
+ await caution('Failed to connect to database', e);
648
+ }
649
+ ```
650
+
651
+ Providing a custom error message is optional and can be omitted. Additionally you can also provide additional error objects which will be serialized to JSON and included in the report.
652
+
653
+ ```ts
654
+ caution(e); // provide just the error
655
+ caution(e, { foo: 42 }); // additional data
656
+ caution('Custom error', e, { foo: 42 }); // all
657
+ ```
658
+
659
+ To prevent spam, issues raised with `caution()` are rate-limited based on a configurable threshold in seconds. By default, the threshold is set to 24 hours per unique issue.
660
+
661
+ ```json
662
+ {
663
+ "spooder": {
664
+ "canary": {
665
+ "throttle": 86400
666
+ }
667
+ }
668
+ }
669
+ ```
670
+
671
+ Issues are considered unique by the `err_message` parameter, so it is recommended that you do not include any dynamic information in this parameter that would prevent the issue from being unique.
672
+
673
+ If you need to provide unique information, you can use the `err` parameter to provide an object which will be serialized to JSON and included in the issue body.
674
+
675
+ ```ts
676
+ const some_important_value = Math.random();
677
+
678
+ // Bad: Do not use dynamic information in err_message.
679
+ await caution('Error with number ' + some_important_value);
680
+
681
+ // Good: Use err parameter to provide dynamic information.
682
+ await caution('Error with number', { some_important_value });
683
+ ```
684
+ It is not required that you `await` the `caution()`, and in situations where parallel processing is required, it is recommended that you do not.
685
+
686
+ #### `panic(err_message_or_obj: string | object, ...err: object[]): Promise<void>`
687
+ This behaves the same as `caution()` with the difference that once `panic()` has raised the issue, it will exit the process with a non-zero exit code.
688
+
689
+ This should only be called in worst-case scenarios where the server cannot continue to run. Since the process will exit, it is recommended that you `await` the `panic()` call.
690
+
112
691
  ## License
113
692
  The code in this repository is licensed under the ISC license. See the [LICENSE](LICENSE) file for more information.