shokupan 0.5.0 → 0.6.1

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 (35) hide show
  1. package/README.md +11 -8
  2. package/dist/cli.cjs +1 -1
  3. package/dist/cli.js +1 -1
  4. package/dist/context.d.ts +90 -7
  5. package/dist/index.cjs +746 -453
  6. package/dist/index.cjs.map +1 -1
  7. package/dist/index.js +690 -419
  8. package/dist/index.js.map +1 -1
  9. package/dist/json-parser-B3dnQmCC.js +35 -0
  10. package/dist/json-parser-B3dnQmCC.js.map +1 -0
  11. package/dist/json-parser-COdZ0fqY.cjs +35 -0
  12. package/dist/json-parser-COdZ0fqY.cjs.map +1 -0
  13. package/dist/{openapi-analyzer-z-7AoFRC.cjs → openapi-analyzer-Bei1sVWp.cjs} +33 -16
  14. package/dist/openapi-analyzer-Bei1sVWp.cjs.map +1 -0
  15. package/dist/{openapi-analyzer-D7y6Qa38.js → openapi-analyzer-Ce_7JxZh.js} +33 -16
  16. package/dist/openapi-analyzer-Ce_7JxZh.js.map +1 -0
  17. package/dist/plugins/proxy.d.ts +2 -0
  18. package/dist/plugins/rate-limit.d.ts +1 -0
  19. package/dist/plugins/scalar.d.ts +1 -1
  20. package/dist/router.d.ts +125 -55
  21. package/dist/{server-adapter-BWrEJbKL.js → server-adapter-0xH174zz.js} +4 -2
  22. package/dist/server-adapter-0xH174zz.js.map +1 -0
  23. package/dist/{server-adapter-fVKP60e0.cjs → server-adapter-DFhwlK8e.cjs} +4 -2
  24. package/dist/server-adapter-DFhwlK8e.cjs.map +1 -0
  25. package/dist/shokupan.d.ts +66 -7
  26. package/dist/types.d.ts +63 -3
  27. package/dist/util/datastore.d.ts +6 -0
  28. package/dist/util/json-parser.d.ts +12 -0
  29. package/dist/util/plugin-deps.d.ts +25 -0
  30. package/package.json +73 -13
  31. package/dist/buntest.d.ts +0 -1
  32. package/dist/openapi-analyzer-D7y6Qa38.js.map +0 -1
  33. package/dist/openapi-analyzer-z-7AoFRC.cjs.map +0 -1
  34. package/dist/server-adapter-BWrEJbKL.js.map +0 -1
  35. package/dist/server-adapter-fVKP60e0.cjs.map +0 -1
package/README.md CHANGED
@@ -7,6 +7,8 @@ Shokupan is designed to make building APIs delightful again. With zero-config de
7
7
 
8
8
  ### Note: Shokupan is still in alpha and is not guaranteed to be stable. Please use with caution. We will be adding more features and APIs in the future. Please file an issue if you find any bugs or have suggestions for improvement.
9
9
 
10
+ 📚 **[Full documentation available at https://shokupan.dev](https://shokupan.dev)**
11
+
10
12
  ## ✨ Features
11
13
 
12
14
  - 🎯 **TypeScript First** - End-to-end type safety with decorators and generics. No manual types needed.
@@ -75,6 +77,7 @@ That's it! Your server is running at `http://localhost:3000` 🎉
75
77
  - [Using Express Middleware](#using-express-middleware)
76
78
  - [Testing](#testing)
77
79
  - [Deployment](#deployment)
80
+ - [Production Best Practices](https://knackstedt.github.io/shokupan/guides/production/) 📚
78
81
  - [CLI Tools](#cli-tools)
79
82
  - [API Reference](#api-reference)
80
83
  - [Roadmap](#-roadmap)
@@ -929,7 +932,7 @@ This works great when combined with the Debug Dashboard.
929
932
  A visual dashboard to inspect your application, view metrics, analyze the middleware graph, and replay failed requests.
930
933
 
931
934
  ```typescript
932
- import { DebugDashboard } from 'shokupan/plugins/debugview';
935
+ import { DebugDashboard } from 'shokupan';
933
936
 
934
937
  // Mount the dashboard
935
938
  app.mount('/debug', new DebugDashboard({
@@ -1057,8 +1060,8 @@ router.get('/wines/white', async (ctx) => {
1057
1060
  router.get('/wines/all', async (ctx) => {
1058
1061
  // Make parallel sub-requests
1059
1062
  const [redResponse, whiteResponse] = await Promise.all([
1060
- router.subRequest('/wines/red'),
1061
- router.subRequest('/wines/white')
1063
+ router.internalRequest('/wines/red'),
1064
+ router.internalRequest('/wines/white')
1062
1065
  ]);
1063
1066
 
1064
1067
  const red = await redResponse.json();
@@ -1559,7 +1562,7 @@ describe('My App', () => {
1559
1562
  app.get('/', () => ({ message: 'Hello' }));
1560
1563
 
1561
1564
  // Process a request without starting the server
1562
- const res = await app.processRequest({
1565
+ const res = await app.testRequest({
1563
1566
  method: 'GET',
1564
1567
  path: '/'
1565
1568
  });
@@ -1703,8 +1706,8 @@ const app = new Shokupan(config?: ShokupanConfig);
1703
1706
  - `mount(path, controller)` - Mount controller or router
1704
1707
  - `static(path, options)` - Serve static files
1705
1708
  - `listen(port?)` - Start server
1706
- - `processRequest(options)` - Process request (testing)
1707
- - `subRequest(options)` - Make sub-request
1709
+ - `testRequest(options)` - Process request (for testing purposes)
1710
+ - `internalRequest(options)` - Make sub-request
1708
1711
  - `computeOpenAPISpec(base)` - Generate OpenAPI spec
1709
1712
 
1710
1713
  ### ShokupanRouter Class
@@ -1737,8 +1740,8 @@ const router = new ShokupanRouter(config?: ShokupanRouteConfig);
1737
1740
  - `head(path, spec?, ...handlers)` - Add HEAD route
1738
1741
  - `mount(path, controller)` - Mount controller or router
1739
1742
  - `static(path, options)` - Serve static files
1740
- - `processRequest(options)` - Process request (testing)
1741
- - `subRequest(options)` - Make sub-request
1743
+ - `testRequest(options)` - Process request (for testing purposes)
1744
+ - `internalRequest(options)` - Make sub-request
1742
1745
 
1743
1746
 
1744
1747
  ### ShokupanContext
package/dist/cli.cjs CHANGED
@@ -4,7 +4,7 @@ const p = require("@clack/prompts");
4
4
  const fs = require("node:fs");
5
5
  const path = require("node:path");
6
6
  const promises = require("node:timers/promises");
7
- const openapiAnalyzer = require("./openapi-analyzer-z-7AoFRC.cjs");
7
+ const openapiAnalyzer = require("./openapi-analyzer-Bei1sVWp.cjs");
8
8
  function _interopNamespaceDefault(e) {
9
9
  const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
10
10
  if (e) {
package/dist/cli.js CHANGED
@@ -3,7 +3,7 @@ import * as p from "@clack/prompts";
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
5
  import { setTimeout } from "node:timers/promises";
6
- import { analyzeDirectory } from "./openapi-analyzer-D7y6Qa38.js";
6
+ import { analyzeDirectory } from "./openapi-analyzer-Ce_7JxZh.js";
7
7
  const templates = {
8
8
  controller: (name) => `import { Controller, Get, Ctx } from 'shokupan';
9
9
  import { ShokupanContext } from 'shokupan';
package/dist/context.d.ts CHANGED
@@ -18,19 +18,89 @@ export interface DebugCollector {
18
18
  setNode(id: string): void;
19
19
  getCurrentNode(): string | undefined;
20
20
  }
21
- export declare class ShokupanContext<State extends Record<string, any> = Record<string, any>> {
21
+ /**
22
+ * Shokupan Request Context
23
+ *
24
+ * The context object passed to all middleware and route handlers.
25
+ * Provides access to request data, response helpers, and typed state management.
26
+ *
27
+ * @template State - The shape of `ctx.state` for type-safe state access across middleware.
28
+ * @template Params - The shape of `ctx.params` based on the route path pattern.
29
+ *
30
+ * @example Basic Usage
31
+ * ```typescript
32
+ * app.get('/hello', (ctx) => {
33
+ * return ctx.json({ message: 'Hello' });
34
+ * });
35
+ * ```
36
+ *
37
+ * @example Typed State
38
+ * ```typescript
39
+ * interface AppState {
40
+ * userId: string;
41
+ * requestId: string;
42
+ * }
43
+ *
44
+ * const app = new Shokupan<AppState>();
45
+ *
46
+ * app.use((ctx, next) => {
47
+ * ctx.state.requestId = crypto.randomUUID(); // ✓ Type-safe
48
+ * return next();
49
+ * });
50
+ * ```
51
+ *
52
+ * @example Typed Path Parameters
53
+ * ```typescript
54
+ * app.get('/users/:userId/posts/:postId', (ctx) => {
55
+ * // ctx.params is automatically typed as { userId: string; postId: string }
56
+ * const { userId, postId } = ctx.params;
57
+ * return ctx.json({ userId, postId });
58
+ * });
59
+ * ```
60
+ *
61
+ * @example Full Type Safety (State + Params)
62
+ * ```typescript
63
+ * interface RequestState {
64
+ * userId: string;
65
+ * permissions: string[];
66
+ * }
67
+ *
68
+ * const app = new Shokupan<RequestState>();
69
+ *
70
+ * app.get('/admin/users/:userId', (ctx) => {
71
+ * // Both typed!
72
+ * const { userId } = ctx.params; // ✓ From path
73
+ * const { permissions } = ctx.state; // ✓ From state
74
+ *
75
+ * if (!permissions.includes('admin')) {
76
+ * return ctx.json({ error: 'Forbidden' }, 403);
77
+ * }
78
+ * return ctx.json({ userId });
79
+ * });
80
+ * ```
81
+ */
82
+ export declare class ShokupanContext<State extends Record<string, any> = Record<string, any>, Params extends Record<string, string> = Record<string, string>> {
22
83
  readonly request: ShokupanRequest<any>;
23
84
  readonly server?: Server;
24
85
  readonly app?: Shokupan;
25
86
  readonly signal?: AbortSignal;
26
- private _url;
27
- params: Record<string, string>;
87
+ params: Params;
28
88
  state: State;
29
89
  handlerStack: HandlerStackItem[];
30
90
  readonly response: ShokupanResponse;
31
91
  _debug?: DebugCollector;
32
92
  _finalResponse?: Response;
33
93
  _rawBody?: string | ArrayBuffer | Uint8Array;
94
+ private _url?;
95
+ private _cachedBody?;
96
+ private _bodyType?;
97
+ private _bodyParsed;
98
+ _bodyParseError?: Error;
99
+ private _cachedHostname?;
100
+ private _cachedProtocol?;
101
+ private _cachedHost?;
102
+ private _cachedOrigin?;
103
+ private _cachedQuery?;
34
104
  constructor(request: ShokupanRequest<any>, server?: Server, state?: State, app?: Shokupan, signal?: AbortSignal, // Optional as it might not be provided in tests or simple creates
35
105
  enableMiddlewareTracking?: boolean);
36
106
  get url(): URL;
@@ -101,6 +171,23 @@ export declare class ShokupanContext<State extends Record<string, any> = Record<
101
171
  */
102
172
  setCookie(name: string, value: string, options?: CookieOptions): this;
103
173
  private mergeHeaders;
174
+ /**
175
+ * Read request body with caching to avoid double parsing.
176
+ * The body is only parsed once and cached for subsequent reads.
177
+ */
178
+ body<T = any>(): Promise<T>;
179
+ /**
180
+ * Pre-parse the request body before handler execution.
181
+ * This improves performance and enables Node.js compatibility for large payloads.
182
+ * Errors are deferred until the body is actually accessed in the handler.
183
+ */
184
+ parseBody(): Promise<void>;
185
+ /**
186
+ * Read raw body from ReadableStream efficiently.
187
+ * This is much faster than request.text() for large payloads.
188
+ * Also handles the case where body is already a string (e.g., in tests).
189
+ */
190
+ private readRawBody;
104
191
  /**
105
192
  * Send a response
106
193
  * @param body Response body
@@ -108,10 +195,6 @@ export declare class ShokupanContext<State extends Record<string, any> = Record<
108
195
  * @returns Response
109
196
  */
110
197
  send(body?: BodyInit, options?: ResponseInit): Response;
111
- /**
112
- * Read request body
113
- */
114
- body<T = any>(): Promise<T>;
115
198
  /**
116
199
  * Respond with a JSON object
117
200
  */