@bepalo/spine 1.5.16 → 2.6.18

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.
Files changed (104) hide show
  1. package/README.md +533 -81
  2. package/dist/auth-middlewares.d.ts.map +1 -1
  3. package/dist/auth-middlewares.js +1 -0
  4. package/dist/auth-middlewares.js.map +1 -1
  5. package/dist/cache.d.ts +72 -0
  6. package/dist/cache.d.ts.map +1 -0
  7. package/dist/cache.js +387 -0
  8. package/dist/cache.js.map +1 -0
  9. package/dist/cjs/auth-middlewares.d.ts.map +1 -1
  10. package/dist/cjs/auth-middlewares.js +1 -0
  11. package/dist/cjs/auth-middlewares.js.map +1 -1
  12. package/dist/cjs/cache.d.ts +72 -0
  13. package/dist/cjs/cache.d.ts.map +1 -0
  14. package/dist/cjs/cache.js +387 -0
  15. package/dist/cjs/cache.js.map +1 -0
  16. package/dist/cjs/exp-cache.d.ts +101 -0
  17. package/dist/cjs/exp-cache.d.ts.map +1 -0
  18. package/dist/cjs/exp-cache.js +551 -0
  19. package/dist/cjs/exp-cache.js.map +1 -0
  20. package/dist/cjs/generator.d.ts +142 -19
  21. package/dist/cjs/generator.d.ts.map +1 -1
  22. package/dist/cjs/generator.js +415 -35
  23. package/dist/cjs/generator.js.map +1 -1
  24. package/dist/cjs/helpers.d.ts +49 -2
  25. package/dist/cjs/helpers.d.ts.map +1 -1
  26. package/dist/cjs/helpers.js +169 -14
  27. package/dist/cjs/helpers.js.map +1 -1
  28. package/dist/cjs/index.d.ts +2 -0
  29. package/dist/cjs/index.d.ts.map +1 -1
  30. package/dist/cjs/index.js +3 -0
  31. package/dist/cjs/index.js.map +1 -1
  32. package/dist/cjs/linked-list.d.ts +35 -0
  33. package/dist/cjs/linked-list.d.ts.map +1 -0
  34. package/dist/cjs/linked-list.js +300 -0
  35. package/dist/cjs/linked-list.js.map +1 -0
  36. package/dist/cjs/middlewares.d.ts +102 -12
  37. package/dist/cjs/middlewares.d.ts.map +1 -1
  38. package/dist/cjs/middlewares.js +119 -22
  39. package/dist/cjs/middlewares.js.map +1 -1
  40. package/dist/cjs/parsers.d.ts.map +1 -1
  41. package/dist/cjs/parsers.js +1 -0
  42. package/dist/cjs/parsers.js.map +1 -1
  43. package/dist/cjs/router.d.ts +5 -6
  44. package/dist/cjs/router.d.ts.map +1 -1
  45. package/dist/cjs/router.js +271 -148
  46. package/dist/cjs/router.js.map +1 -1
  47. package/dist/cjs/status.d.ts.map +1 -1
  48. package/dist/cjs/status.js +80 -79
  49. package/dist/cjs/status.js.map +1 -1
  50. package/dist/cjs/types.d.ts +74 -2
  51. package/dist/cjs/types.d.ts.map +1 -1
  52. package/dist/cjs/types.js +15 -2
  53. package/dist/cjs/types.js.map +1 -1
  54. package/dist/cjs/utils.d.ts.map +1 -1
  55. package/dist/cjs/utils.js +1 -0
  56. package/dist/cjs/utils.js.map +1 -1
  57. package/dist/cjs/utils.node.d.ts.map +1 -1
  58. package/dist/cjs/utils.node.js +1 -0
  59. package/dist/cjs/utils.node.js.map +1 -1
  60. package/dist/exp-cache.d.ts +101 -0
  61. package/dist/exp-cache.d.ts.map +1 -0
  62. package/dist/exp-cache.js +551 -0
  63. package/dist/exp-cache.js.map +1 -0
  64. package/dist/generator.d.ts +142 -19
  65. package/dist/generator.d.ts.map +1 -1
  66. package/dist/generator.js +415 -35
  67. package/dist/generator.js.map +1 -1
  68. package/dist/helpers.d.ts +49 -2
  69. package/dist/helpers.d.ts.map +1 -1
  70. package/dist/helpers.js +169 -14
  71. package/dist/helpers.js.map +1 -1
  72. package/dist/index.d.ts +2 -0
  73. package/dist/index.d.ts.map +1 -1
  74. package/dist/index.js +3 -0
  75. package/dist/index.js.map +1 -1
  76. package/dist/linked-list.d.ts +35 -0
  77. package/dist/linked-list.d.ts.map +1 -0
  78. package/dist/linked-list.js +300 -0
  79. package/dist/linked-list.js.map +1 -0
  80. package/dist/middlewares.d.ts +102 -12
  81. package/dist/middlewares.d.ts.map +1 -1
  82. package/dist/middlewares.js +119 -22
  83. package/dist/middlewares.js.map +1 -1
  84. package/dist/parsers.d.ts.map +1 -1
  85. package/dist/parsers.js +1 -0
  86. package/dist/parsers.js.map +1 -1
  87. package/dist/router.d.ts +5 -6
  88. package/dist/router.d.ts.map +1 -1
  89. package/dist/router.js +271 -148
  90. package/dist/router.js.map +1 -1
  91. package/dist/status.d.ts.map +1 -1
  92. package/dist/status.js +80 -79
  93. package/dist/status.js.map +1 -1
  94. package/dist/types.d.ts +74 -2
  95. package/dist/types.d.ts.map +1 -1
  96. package/dist/types.js +15 -2
  97. package/dist/types.js.map +1 -1
  98. package/dist/utils.d.ts.map +1 -1
  99. package/dist/utils.js +1 -0
  100. package/dist/utils.js.map +1 -1
  101. package/dist/utils.node.d.ts.map +1 -1
  102. package/dist/utils.node.js +1 -0
  103. package/dist/utils.node.js.map +1 -1
  104. package/package.json +1 -1
package/README.md CHANGED
@@ -73,14 +73,14 @@ Average 10.05k 8.13k 7.65k ┌────────
73
73
  - 🧠 **TypeScript-first** — extend the request context with your own application data
74
74
  - 🌐 **Runtime agnostic** — works with Bun, Deno, Node.js, and other Web API-compatible runtimes
75
75
  - 📁 **File-based routing** — optionally load routes directly from a directory structure
76
- - 📖 **OpenAPI generation**attach metadata to routes and generate an OpenAPI 3.0 document
76
+ - 📖 OpenAPI 3.0 generation — route metadata, tags, parameters, schemas, security, components, and configurable sorting
77
77
  - 🛠️ **Built-in utilities** — request parsing, responses, CORS, rate limiting, and authentication
78
78
  - 🪶 **No server lock-in** — Spine only deals with `Request` in and `Response` out
79
79
 
80
80
  ## 📑 Table of Contents
81
81
 
82
- - [What is new?](#what-is-new)
83
82
  - [Quick Start](#quick-start)
83
+ - [Real World Usecase Example](#real-world-usecase-example)
84
84
  - [Routing](#routing)
85
85
  - [Parameters](#parameters)
86
86
  - [Alternatives](#alternatives)
@@ -106,85 +106,6 @@ Average 10.05k 8.13k 7.65k ┌────────
106
106
  - [Thanks and Enjoy](#️-thanks-and-enjoy)
107
107
  - [Be a Sponsor](#-be-a-sponsor)
108
108
 
109
- ## What is new v1.2.10?
110
-
111
- - Implemented static route generator function `generateStaticRoutesImporter` to use with nextjs, cloudflare workers and more which do not do accept dynamic imports.
112
-
113
- ```ts
114
- // src/server/utils/gen-routes.ts
115
- import Router from "@bepalo/spine";
116
- import generateStaticRoutesImporter from "@bepalo/spine";
117
-
118
- await generateStaticRoutesImporter(
119
- new Router(),
120
- "./src/server/routes",
121
- "./routes/",
122
- ).then((content) =>
123
- Bun.write("./src/server/index.ts", "// src/server/index.ts\n" + content),
124
- );
125
- ```
126
-
127
- ```ts
128
- // src/server/index.ts
129
- // This is auto-generated for static importing of @bepalo/spine file-based routes
130
- import {
131
- Router,
132
- Handler,
133
- HandlerRegisterPiplineOptions,
134
- Pipeline,
135
- } from "@bepalo/spine";
136
- import * as route__index from "./routes/_index";
137
- import * as route_api_$$$path from "./routes/api/[## path]";
138
- import * as route_api_$ from "./routes/api/[[#]]";
139
- import * as route_api_fruits from "./routes/api/fruits";
140
- import * as route_api_users_$$$usersPath from "./routes/api/users/[## usersPath]";
141
- import * as route_api_users_$$$id from "./routes/api/users/[id]";
142
- import * as route_pages_$$ from "./routes/pages/[[##]]";
143
-
144
- const getOptions = <ExtendContext extends Record<string, unknown>>(
145
- def: {
146
- pipe: Handler<ExtendContext> | Pipeline<ExtendContext>;
147
- } & HandlerRegisterPiplineOptions,
148
- ) => {
149
- const { pipe, ...options } = def;
150
- return options;
151
- };
152
-
153
- export const setRoutes = async <ExtendContext extends Record<string, unknown>>(
154
- router: Router<ExtendContext>,
155
- ) => {
156
- // /
157
- router.handleGet(
158
- "/_index",
159
- route__index.Get.pipe,
160
- getOptions(route__index.Get),
161
- );
162
- router.filterGet("/_index", route__index.Get_Filter);
163
- // /api
164
- router.filterGet("/api/::path", route_api_$$$path.Get_Filter);
165
- router.filterGet("/api/*!", route_api_$.Get_Filter);
166
- router.handleDelete("/api/fruits", route_api_fruits.Delete);
167
- router.handleGet("/api/fruits", route_api_fruits.Get);
168
- router.handlePatch("/api/fruits", route_api_fruits.Patch);
169
- router.handlePost("/api/fruits", route_api_fruits.Post);
170
- // /api/users
171
- router.filterGet(
172
- "/api/users/::usersPath",
173
- route_api_users_$$$usersPath.Get_Filter,
174
- );
175
- router.handleGet(
176
- "/api/users/:id",
177
- route_api_users_$$$id.Get.pipe,
178
- getOptions(route_api_users_$$$id.Get),
179
- );
180
- // /pages
181
- router.handleGet("/pages/**!", route_pages_$$.Get);
182
- };
183
-
184
- // Use this to set the routes in your router
185
- export default setRoutes;
186
- ```
187
-
188
109
  ## Quick Start
189
110
 
190
111
  Install
@@ -262,6 +183,537 @@ const response = await spine.respond(request);
262
183
 
263
184
  This makes the spine easy to embed into servers, frameworks, workers, and custom runtimes.
264
185
 
186
+ ## Sneek peek of what is possible
187
+
188
+ ### `src/utils/generate.ts`
189
+
190
+ <details open>
191
+
192
+ <summary> Generator utilities to watch for changes and generate static-routes-imports and static-assets-manifest.</summary>
193
+
194
+ ```ts
195
+ // src/utils/generate.ts
196
+ import {
197
+ generateStaticAssetsManifestWatcher,
198
+ generateStaticRoutesWatcher,
199
+ } from "@bepalo/spine";
200
+ import { writeFile } from "node:fs/promises";
201
+ import { readFile } from "node:fs/promises";
202
+
203
+ const abortController = new AbortController();
204
+
205
+ // setTimeout(() => abortController.abort(), 10000);
206
+
207
+ generateStaticRoutesWatcher({
208
+ routesPath: "./routes",
209
+ importRoot: "./routes/",
210
+ output: "./routes.ts",
211
+ read: (filepath) => readFile(filepath, { encoding: "utf-8" }),
212
+ write: (filepath, content) =>
213
+ writeFile(filepath, content, { encoding: "utf-8" }),
214
+ abortSignal: abortController.signal,
215
+ // generateDelay: 1000,
216
+ });
217
+
218
+ generateStaticAssetsManifestWatcher({
219
+ assetsPath: "./public",
220
+ output: "./static-assets.json",
221
+ abortSignal: abortController.signal,
222
+ sortOrder: 1,
223
+ // exclude: ({ name, ext }) => !name || ext === ".env",
224
+ read: (filepath) => readFile(filepath, { encoding: "utf-8" }),
225
+ write: (filepath, content) =>
226
+ writeFile(filepath, content, { encoding: "utf-8" }),
227
+ // generateDelay: 1000,
228
+ });
229
+ ```
230
+
231
+ </details>
232
+
233
+ ### Static assets
234
+
235
+ #### `404.html`
236
+
237
+ <details>
238
+ <summary>404.html</summary>
239
+
240
+ ```html
241
+ <!DOCTYPE html>
242
+ <html lang="en">
243
+ <head>
244
+ <meta charset="UTF-8" />
245
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
246
+ <title>Document</title>
247
+ </head>
248
+
249
+ <body>
250
+ <h1>404 Page not found!</h1>
251
+ <p>We couldn't locate the page you were looking for</p>
252
+ </body>
253
+ </html>
254
+ ```
255
+
256
+ </details>
257
+
258
+ #### `500.html`
259
+
260
+ </details>
261
+
262
+ <details>
263
+ <summary>500.html</summary>
264
+
265
+ ```html
266
+ <!DOCTYPE html>
267
+ <html lang="en">
268
+ <head>
269
+ <meta charset="UTF-8" />
270
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
271
+ <title>Document</title>
272
+ </head>
273
+
274
+ <body>
275
+ <h1>{{STATUS}} {{STATUS_TEXT}}</h1>
276
+ <p><strong>{{ERROR}}!</strong></p>
277
+ </body>
278
+ </html>
279
+ ```
280
+
281
+ </details>
282
+
283
+ #### `Swagger`
284
+
285
+ <details>
286
+ <summary> public/openapi/index.html</summary>
287
+
288
+ ```html
289
+ <!DOCTYPE html>
290
+ <html lang="en">
291
+ <head>
292
+ <meta charset="utf-8" />
293
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
294
+ <meta name="description" content="SwaggerUI" />
295
+ <title>SwaggerUI</title>
296
+ <link
297
+ rel="stylesheet"
298
+ href="https://unpkg.com/swagger-ui-dist@5.11.0/swagger-ui.css"
299
+ />
300
+ </head>
301
+
302
+ <body>
303
+ <div id="swagger-ui"></div>
304
+ <script
305
+ src="https://unpkg.com/swagger-ui-dist@5.11.0/swagger-ui-bundle.js"
306
+ crossorigin
307
+ ></script>
308
+ <script>
309
+ window.onload = () => {
310
+ window.ui = SwaggerUIBundle({
311
+ url: "/openapi/doc.json",
312
+ dom_id: "#swagger-ui",
313
+ });
314
+ };
315
+ </script>
316
+ </body>
317
+ </html>
318
+ ```
319
+
320
+ </details>
321
+
322
+ ### Source Codes
323
+
324
+ ### `/user/:id Route`
325
+
326
+ <details>
327
+ <summary>src/routes/users/[id].ts</summary>
328
+
329
+ ```ts
330
+ // src/routes/user/[id].ts
331
+
332
+ import {
333
+ json,
334
+ parseBody,
335
+ type CTBody,
336
+ type CTParams,
337
+ type HandlerDef,
338
+ type PipeDef,
339
+ } from "@bepalo/spine";
340
+ import { ArkErrors, type } from "arktype";
341
+
342
+ export const get_filter: HandlerDef = [
343
+ ({ params }) => {
344
+ // valdate params
345
+ const r = type({
346
+ id: "3 <= string.numeric <= 5",
347
+ }).assert(params);
348
+ if (r instanceof ArkErrors) {
349
+ return json({ error: r.toJSON() }, { status: 400 });
350
+ }
351
+ },
352
+ ];
353
+
354
+ export const get: PipeDef<CTParams<"id">> = {
355
+ pipe: ({ params: { id } }) => {
356
+ return parseInt(id) < 0
357
+ ? json({ error: "User not found" })
358
+ : json({ user: { name: `user-${id}` } });
359
+ },
360
+
361
+ openApi: {
362
+ summary: "Get user by ID",
363
+ responses: {
364
+ "200": {
365
+ description: "Successfull response",
366
+ content: {
367
+ "application/json": {
368
+ schema: {
369
+ type: "object",
370
+ properties: {
371
+ user: {
372
+ type: "object",
373
+ properties: {
374
+ name: {
375
+ type: "string",
376
+ },
377
+ },
378
+ },
379
+ },
380
+ },
381
+ },
382
+ },
383
+ },
384
+ "404": {
385
+ description: "User not found",
386
+ content: {
387
+ "application/json": {
388
+ schema: {
389
+ type: "object",
390
+ properties: {
391
+ error: {
392
+ type: "string",
393
+ },
394
+ },
395
+ },
396
+ },
397
+ },
398
+ },
399
+ "400": {
400
+ description: "Bad request",
401
+ content: {
402
+ "application/json": {
403
+ schema: {
404
+ type: "object",
405
+ properties: {
406
+ error: {
407
+ type: "string",
408
+ },
409
+ },
410
+ },
411
+ },
412
+ },
413
+ },
414
+ },
415
+ },
416
+ };
417
+
418
+ export const post_filter: HandlerDef<CTBody> = [parseBody({ maxSize: 1024 })];
419
+
420
+ export const post: HandlerDef<CTBody> = ({
421
+ url: { pathname },
422
+ params,
423
+ body,
424
+ }) => {
425
+ return json({ pathname, params, body });
426
+ };
427
+ ```
428
+
429
+ </details>
430
+
431
+ #### `Main`
432
+
433
+ <details>
434
+ <summary>src/index.ts</summary>
435
+
436
+ ```ts
437
+ import type { Path, StaticAssetsManifestFile } from "@bepalo/spine";
438
+ import {
439
+ Router,
440
+ ExpCache,
441
+ HttpError,
442
+ Status,
443
+ json,
444
+ cors,
445
+ limitRate,
446
+ getHttpStatusText,
447
+ status,
448
+ securityHeaders,
449
+ Break_Pipeline,
450
+ } from "@bepalo/spine";
451
+ import { readFileSync } from "node:fs";
452
+ import { writeFile } from "node:fs/promises";
453
+ // Import generated static routes imports
454
+ import setRoutes from "./routes";
455
+ // Import generated static assets manifest
456
+ import staticAssetsManifest from "./static-assets.json";
457
+
458
+ const {
459
+ "/404": notFoundAsset,
460
+ "/500": serverErrorAsset,
461
+ ...staticAssets
462
+ } = staticAssetsManifest.files;
463
+
464
+ // lru-exp cache for static assets
465
+ const assetsCache: ExpCache<string, Buffer<ArrayBuffer>> = new ExpCache({
466
+ maxMemory: 32 * 1024 * 1024, // 32Mb
467
+ onMiss(key, entry, reason, cache) {
468
+ if (!(key in staticAssetsManifest.files)) return;
469
+ const asset = (staticAssetsManifest.files as any)[key];
470
+ // Read from file into cache because it is missing.
471
+ // You only have to call `assetsCache.get` elsewhere
472
+ // as this will automatically load it on cache-miss.
473
+ cache.set(
474
+ key,
475
+ readFileSync(asset.path, { encoding: undefined }),
476
+ asset.size,
477
+ {
478
+ ttl: 3_600_000, // 1 hour
479
+ },
480
+ );
481
+ return true;
482
+ },
483
+ });
484
+
485
+ // Static assets cache cleanup timer.
486
+ // You could also setup a cron api.
487
+ setInterval(() => {
488
+ console.log("Cleared ", assetsCache.evictExpired());
489
+ }, 3_600_000);
490
+
491
+ export type CTMain = { clientIP: string };
492
+
493
+ // Create router instance
494
+ export const spine = new Router<CTMain>({
495
+ maxPath: 10,
496
+ });
497
+
498
+ // Serve
499
+ const server = Bun.serve({
500
+ port: 3000,
501
+ fetch: (request, server) =>
502
+ spine.respond(request, {
503
+ clientIP: server.requestIP(request)?.address || "anonymous",
504
+ }),
505
+ });
506
+ console.log(`Listening on ${server.url}`);
507
+
508
+ //////////////////////////////////////////////
509
+
510
+ // Set generated dynamic-routes' static-imports
511
+ setRoutes(spine);
512
+
513
+ // Security Headers, CORS, Rate Limiting, ... for /**
514
+ spine.filterAll("/**", [
515
+ // forceHttps({ toPort: server.port }),
516
+ limitRate<CTMain>({
517
+ key: ({ clientIP }) => clientIP,
518
+ maxTokens: 100,
519
+ refillInterval: 60 * 1000, // every minute
520
+ // refillRate:
521
+ setXRateLimitHeaders: process.env.NODE_ENV !== "production",
522
+ }),
523
+ securityHeaders({
524
+ headers: {
525
+ "Reporting-Endpoints": `coep-endpoint="${server.url.origin + "/coep"}"`,
526
+ },
527
+ crossOriginEmbedderPolicy: 'credentialless; report-to="coep-endpoint"',
528
+ crossOriginResourcePolicy: "same-site",
529
+ crossOriginOpenerPolicy: "same-origin-allow-popups",
530
+ referrerPolicy: "strict-origin-when-cross-origin",
531
+ xFrameOptions: "DENY",
532
+ contentSecurityPolicy: [
533
+ ["default-src", "'self'"],
534
+ ["object-src", "'none'"],
535
+ ["frame-ancestors", "'none'"],
536
+ [
537
+ "script-src style-src font-src",
538
+ "'self'",
539
+ "https://unpkg.com",
540
+ "'unsafe-inline'",
541
+ ],
542
+ ["script-src", "'self'", "'strict-dynamic'", "'unsafe-inline'"],
543
+ ["img-src", "'self'", "data:", "'unsafe-inline'"],
544
+ ["upgrade-insecure-requests"],
545
+ ],
546
+ }),
547
+ cors({
548
+ origins: ["https://example.com", server.url.origin],
549
+ methods: ["Get", "Head", "Options"],
550
+ allowedHeaders: ["Authorization", "X-API-Key"],
551
+ credentials: true,
552
+ maxAge: 60 * 60,
553
+ }),
554
+ ]);
555
+
556
+ // Security Headers, CORS, Rate Limiting, ... for /api/**
557
+ spine.filterAll("/api/**", [
558
+ // forceHttps({ toPort: server.port }),
559
+ limitRate<CTMain>({
560
+ key: ({ clientIP }) => clientIP,
561
+ maxTokens: 300,
562
+ refillInterval: 60 * 1000, // every minute
563
+ refillRate: 100, // 100 tokens every minute
564
+ setXRateLimitHeaders: process.env.NODE_ENV !== "production",
565
+ }),
566
+ securityHeaders({
567
+ // crossOriginResourcePolicy: "same-site",
568
+ // referrerPolicy: "strict-origin-when-cross-origin",
569
+ xFrameOptions: "DENY",
570
+ contentSecurityPolicy: [["upgrade-insecure-requests"]],
571
+ }),
572
+ cors({
573
+ origins: ["https://example.com", server.url.origin],
574
+ methods: ["Get", "Post", "Put", "Patch", "Delete", "Head", "Options"],
575
+ allowedHeaders: ["Content-Type", "Authorization", "X-API-Key"],
576
+ credentials: true,
577
+ maxAge: 60 * 60,
578
+ }),
579
+ // do not bubble to other matching filters such as /**
580
+ () => Break_Pipeline,
581
+ ]);
582
+
583
+ // Handle Options for all /** to return no content
584
+ spine.handleOptions("/**", () => status(204, null));
585
+
586
+ // Handler get and head of static assets using generated manifest
587
+ spine.handle(
588
+ [["Head", "Get"], ...(Object.keys(staticAssets) as Path[])],
589
+ ({ url, request, headers }) => {
590
+ const asset: StaticAssetsManifestFile = (staticAssets as any)[url.pathname];
591
+ headers.set("Content-Type", asset.contentType);
592
+ headers.set("Content-Length", asset.size.toFixed());
593
+ const fileContent = assetsCache.get(asset.pathname);
594
+ if (request.method === "HEAD") {
595
+ return status(200, null);
596
+ }
597
+ return new Response(fileContent);
598
+ },
599
+ );
600
+
601
+ // Handle fallbacks of get /** using 404.html static page
602
+ spine.fallback([["Head", "Get"], "/**"], ({ url, request, headers }) => {
603
+ const asset: StaticAssetsManifestFile = notFoundAsset;
604
+ headers.set("Content-Type", asset.contentType);
605
+ headers.set("Content-Length", asset.size.toFixed());
606
+ if (request.method === "HEAD") {
607
+ return status(200, null);
608
+ }
609
+ const fileContent = assetsCache.get(notFoundAsset.pathname);
610
+ return new Response(fileContent, { status: 404 });
611
+ });
612
+
613
+ // Set error handler of All /**
614
+ // Note: The string replacement is just for demonstration.
615
+ // You are probably going to use a framework like
616
+ // pug, react, Nextjs or others.
617
+ spine.catchAll("/**", ({ error, request, headers }) => {
618
+ // process.env.NODE_ENV !== "production" && console.error(error);
619
+ const statusCode = (error as HttpError).status || 500;
620
+ const asset: StaticAssetsManifestFile = serverErrorAsset;
621
+ headers.set("Content-Type", asset.contentType);
622
+ headers.set("Content-Length", asset.size.toFixed());
623
+ if (request.method === "HEAD") {
624
+ return status(200, null);
625
+ }
626
+ const fileContent = assetsCache.get(asset.pathname);
627
+ const vars = {
628
+ STATUS: String(statusCode),
629
+ STATUS_TEXT: getHttpStatusText(statusCode),
630
+ ERROR: error.message,
631
+ };
632
+ return new Response(
633
+ fileContent!
634
+ .toString()
635
+ .replace(/(\\)?\{\{(.+?)\}\}/g, (match, escape, id) =>
636
+ escape ? match : ((vars as any)[id] ?? match),
637
+ ),
638
+ { status: statusCode },
639
+ );
640
+ });
641
+
642
+ // Set error handler of All /api/**
643
+ // Takes precedence over /**
644
+ spine.catchAll("/api/**", ({ error }) => {
645
+ process.env.NODE_ENV !== "production" && console.error(error);
646
+ const status = (error as HttpError).status || 500;
647
+ return json({ error: error.message }, { status });
648
+ });
649
+
650
+ // Set fallback handler of (Get,Post,Put,Patch,Delete) /api/**
651
+ spine.fallbackCrud("/api/**", () =>
652
+ json({ error: "Not found" }, { status: 404 }),
653
+ );
654
+
655
+ // Error test
656
+ spine.get(["/error", "/api/error"], () => {
657
+ throw new HttpError(Status._503_ServiceUnavailable, "Come back tomorrow");
658
+ });
659
+
660
+ // Stats
661
+ spine.get("/api/stats", () => json({ staticAssetsCache: assetsCache.stats }));
662
+
663
+ spine.afterAll(
664
+ "/**",
665
+ ({
666
+ request: { method },
667
+ response: { status, statusText, headers, body },
668
+ url,
669
+ timestamps,
670
+ }) => {
671
+ const { request, start, end } = timestamps;
672
+ const size = ["OPTIONS", "HEAD"].includes(method)
673
+ ? 0
674
+ : Number(headers.get("Content-Length") || "0");
675
+ const kbSize = ((size ?? 0) / 1024).toFixed(2).padStart(5);
676
+ const time = (end - start).toFixed(3).padStart(6);
677
+ let logstr = `[${new Date(request).toISOString()}]`;
678
+ logstr += `[${status}]`;
679
+ logstr += ` ${time}ms ${kbSize}KB`;
680
+ logstr += ` -- ${method} ${url.pathname} ${url.search}`;
681
+ logstr += ` -- ${statusText}`;
682
+ console.log(logstr);
683
+ },
684
+ );
685
+
686
+ //////////////////////////////////////////
687
+
688
+ // generate public/openapi/doc.json
689
+ spine
690
+ .generateOpenAPI(
691
+ {
692
+ title: "@bepalo/spine Demo",
693
+ version: "1.0.0",
694
+ // ...
695
+ },
696
+ {
697
+ pick: ({ path }) => path.startsWith("/api"),
698
+ // autoTag: false,
699
+ // autoSummary: false,
700
+ includeOperationId: true,
701
+ sortTagsOrder: 1,
702
+ sortPathnameOrder: 1,
703
+ sortMethodOrder: 1,
704
+ },
705
+ )
706
+ .then(async (openapi) => {
707
+ const output = "./public/openapi/doc.json";
708
+ const content = JSON.stringify(openapi, null, 2);
709
+ await writeFile(output, content, { encoding: "utf-8" });
710
+ });
711
+ ```
712
+
713
+ </details>
714
+
715
+ ---
716
+
265
717
  ## Routing
266
718
 
267
719
  Use the convenient HTTP method helpers:
@@ -1 +1 @@
1
- {"version":3,"file":"auth-middlewares.d.ts","sourceRoot":"","sources":["../src/auth-middlewares.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auth-middlewares.d.ts","sourceRoot":"","sources":["../src/auth-middlewares.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  "use strict";
2
+ // src/auth-middlewares.ts
2
3
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
4
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
5
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -1 +1 @@
1
- {"version":3,"file":"auth-middlewares.js","sourceRoot":"","sources":["../src/auth-middlewares.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auth-middlewares.js","sourceRoot":"","sources":["../src/auth-middlewares.ts"],"names":[],"mappings":"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"}