shokupan 0.4.5 → 0.6.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.
Files changed (57) hide show
  1. package/README.md +10 -9
  2. package/dist/analysis/openapi-analyzer.d.ts +0 -4
  3. package/dist/cli.cjs +1 -1
  4. package/dist/cli.js +1 -1
  5. package/dist/context.d.ts +30 -8
  6. package/dist/index.cjs +692 -461
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.js +635 -426
  9. package/dist/index.js.map +1 -1
  10. package/dist/json-parser-B3dnQmCC.js +35 -0
  11. package/dist/json-parser-B3dnQmCC.js.map +1 -0
  12. package/dist/json-parser-COdZ0fqY.cjs +35 -0
  13. package/dist/json-parser-COdZ0fqY.cjs.map +1 -0
  14. package/dist/{openapi-analyzer-D9YB3IkV.cjs → openapi-analyzer-Bei1sVWp.cjs} +63 -49
  15. package/dist/openapi-analyzer-Bei1sVWp.cjs.map +1 -0
  16. package/dist/{openapi-analyzer-BtIaHIfe.js → openapi-analyzer-Ce_7JxZh.js} +63 -49
  17. package/dist/openapi-analyzer-Ce_7JxZh.js.map +1 -0
  18. package/dist/plugins/scalar.d.ts +1 -1
  19. package/dist/router.d.ts +33 -22
  20. package/dist/{server-adapter-BWrEJbKL.js → server-adapter-0xH174zz.js} +4 -2
  21. package/dist/server-adapter-0xH174zz.js.map +1 -0
  22. package/dist/{server-adapter-fVKP60e0.cjs → server-adapter-DFhwlK8e.cjs} +4 -2
  23. package/dist/server-adapter-DFhwlK8e.cjs.map +1 -0
  24. package/dist/shokupan.d.ts +4 -8
  25. package/dist/types.d.ts +32 -3
  26. package/dist/util/datastore.d.ts +6 -0
  27. package/dist/util/json-parser.d.ts +12 -0
  28. package/dist/util/plugin-deps.d.ts +25 -0
  29. package/package.json +74 -14
  30. package/dist/benchmarking/advanced-cases/elysia.d.ts +0 -1
  31. package/dist/benchmarking/advanced-cases/express.d.ts +0 -1
  32. package/dist/benchmarking/advanced-cases/fastify.d.ts +0 -1
  33. package/dist/benchmarking/advanced-cases/hapi.d.ts +0 -1
  34. package/dist/benchmarking/advanced-cases/hono.d.ts +0 -1
  35. package/dist/benchmarking/advanced-cases/koa.d.ts +0 -1
  36. package/dist/benchmarking/advanced-cases/nest.d.ts +0 -1
  37. package/dist/benchmarking/advanced-cases/shokupan.d.ts +0 -1
  38. package/dist/benchmarking/advanced-data.d.ts +0 -33
  39. package/dist/benchmarking/advanced-runner.d.ts +0 -1
  40. package/dist/benchmarking/advanced-worker.d.ts +0 -0
  41. package/dist/benchmarking/cases/elysia.d.ts +0 -1
  42. package/dist/benchmarking/cases/express.d.ts +0 -1
  43. package/dist/benchmarking/cases/fastify.d.ts +0 -1
  44. package/dist/benchmarking/cases/hapi.d.ts +0 -1
  45. package/dist/benchmarking/cases/hono.d.ts +0 -1
  46. package/dist/benchmarking/cases/koa.d.ts +0 -1
  47. package/dist/benchmarking/cases/nest.d.ts +0 -1
  48. package/dist/benchmarking/cases/shokupan.d.ts +0 -1
  49. package/dist/benchmarking/data.d.ts +0 -15
  50. package/dist/benchmarking/quick_bench.d.ts +0 -1
  51. package/dist/benchmarking/runner.d.ts +0 -1
  52. package/dist/benchmarking/worker.d.ts +0 -0
  53. package/dist/buntest.d.ts +0 -1
  54. package/dist/openapi-analyzer-BtIaHIfe.js.map +0 -1
  55. package/dist/openapi-analyzer-D9YB3IkV.cjs.map +0 -1
  56. package/dist/server-adapter-BWrEJbKL.js.map +0 -1
  57. package/dist/server-adapter-fVKP60e0.cjs.map +0 -1
@@ -13,7 +13,9 @@ function createHttpServer() {
13
13
  req.on("end", () => controller.close());
14
14
  req.on("error", (err) => controller.error(err));
15
15
  }
16
- })
16
+ }),
17
+ // Required for Node.js undici when sending a body
18
+ duplex: "half"
17
19
  });
18
20
  const response = await options.fetch(request, fauxServer);
19
21
  res.statusCode = response.status;
@@ -61,4 +63,4 @@ function createHttpServer() {
61
63
  export {
62
64
  createHttpServer
63
65
  };
64
- //# sourceMappingURL=server-adapter-BWrEJbKL.js.map
66
+ //# sourceMappingURL=server-adapter-0xH174zz.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-adapter-0xH174zz.js","sources":["../src/plugins/server-adapter.ts"],"sourcesContent":["import type { Server } from \"bun\";\nimport * as http from \"node:http\";\nimport * as https from \"node:https\";\nimport type { ServerFactory } from \"../types\";\n\n/**\n * Creates a server factory that uses the standard Node.js `http` module.\n * @returns A ServerFactory compatible with Shokupan.\n */\nexport function createHttpServer(): ServerFactory {\n return async (options: any): Promise<Server> => {\n const server = http.createServer(async (req, res) => {\n const url = new URL(req.url!, `http://${req.headers.host}`);\n const request = new Request(url.toString(), {\n method: req.method,\n headers: req.headers as any,\n body: ['GET', 'HEAD'].includes(req.method!) ? undefined : new ReadableStream({\n start(controller) {\n req.on('data', chunk => controller.enqueue(chunk));\n req.on('end', () => controller.close());\n req.on('error', err => controller.error(err));\n }\n }) as any,\n // Required for Node.js undici when sending a body\n duplex: 'half'\n } as any);\n\n const response = await options.fetch(request, fauxServer);\n\n res.statusCode = response.status;\n response.headers.forEach((v, k) => res.setHeader(k, v));\n\n if (response.body) {\n // Optimize: Use arrayBuffer for direct conversion instead of async iteration\n const buffer = await response.arrayBuffer();\n res.end(Buffer.from(buffer));\n } else {\n res.end();\n }\n });\n\n const fauxServer: Server = {\n stop: () => {\n server.close();\n return Promise.resolve(); // Bun.Server stop usually returns void but in type definition it might vary.\n },\n upgrade(req, options) {\n return false;\n },\n reload(options) {\n return fauxServer as any;\n },\n get port() {\n const addr = server.address();\n if (typeof addr === 'object' && addr !== null) {\n return addr.port;\n }\n return options.port;\n },\n hostname: options.hostname,\n development: options.development,\n pendingRequests: 0,\n requestIP: (req) => null,\n publish: () => 0,\n subscriberCount: () => 0,\n url: new URL(`http://${options.hostname}:${options.port}`)\n } as unknown as Server;\n\n return new Promise((resolve) => {\n server.listen(options.port, options.hostname, () => {\n resolve(fauxServer);\n });\n });\n };\n}\n\n/**\n * Creates a server factory that uses the standard Node.js `https` module.\n * @param sslOptions - Node.js HTTPS options (key, cert, etc.)\n * @returns A ServerFactory compatible with Shokupan.\n */\nexport function createHttpsServer(sslOptions: https.ServerOptions): ServerFactory {\n return async (options: any): Promise<Server> => {\n const server = https.createServer(sslOptions, async (req, res) => {\n const url = new URL(req.url!, `https://${req.headers.host}`);\n const request = new Request(url.toString(), {\n method: req.method,\n headers: req.headers as any,\n body: ['GET', 'HEAD'].includes(req.method!) ? undefined : new ReadableStream({\n start(controller) {\n req.on('data', chunk => controller.enqueue(chunk));\n req.on('end', () => controller.close());\n req.on('error', err => controller.error(err));\n }\n }) as any,\n // Required for Node.js undici when sending a body\n duplex: 'half'\n } as any);\n\n const response = await options.fetch(request, fauxServer);\n\n res.statusCode = response.status;\n response.headers.forEach((v, k) => res.setHeader(k, v));\n\n if (response.body) {\n // Optimize: Use arrayBuffer for direct conversion instead of async iteration\n const buffer = await response.arrayBuffer();\n res.end(Buffer.from(buffer));\n } else {\n res.end();\n }\n });\n\n const fauxServer: Server = {\n stop: () => {\n server.close();\n },\n upgrade(req, options) {\n return false;\n },\n reload(options) {\n return fauxServer as any;\n },\n get port() {\n const addr = server.address();\n if (typeof addr === 'object' && addr !== null) {\n return addr.port;\n }\n return options.port;\n },\n hostname: options.hostname,\n development: options.development,\n pendingRequests: 0,\n requestIP: (req) => null,\n publish: () => 0,\n subscriberCount: () => 0,\n url: new URL(`https://${options.hostname}:${options.port}`)\n } as unknown as Server;\n\n return new Promise((resolve) => {\n server.listen(options.port, options.hostname, () => {\n resolve(fauxServer);\n });\n });\n };\n}\n"],"names":["options"],"mappings":";;AASO,SAAS,mBAAkC;AAC9C,SAAO,OAAO,YAAkC;AAC5C,UAAM,SAAS,KAAK,aAAa,OAAO,KAAK,QAAQ;AACjD,YAAM,MAAM,IAAI,IAAI,IAAI,KAAM,UAAU,IAAI,QAAQ,IAAI,EAAE;AAC1D,YAAM,UAAU,IAAI,QAAQ,IAAI,YAAY;AAAA,QACxC,QAAQ,IAAI;AAAA,QACZ,SAAS,IAAI;AAAA,QACb,MAAM,CAAC,OAAO,MAAM,EAAE,SAAS,IAAI,MAAO,IAAI,SAAY,IAAI,eAAe;AAAA,UACzE,MAAM,YAAY;AACd,gBAAI,GAAG,QAAQ,CAAA,UAAS,WAAW,QAAQ,KAAK,CAAC;AACjD,gBAAI,GAAG,OAAO,MAAM,WAAW,OAAO;AACtC,gBAAI,GAAG,SAAS,CAAA,QAAO,WAAW,MAAM,GAAG,CAAC;AAAA,UAChD;AAAA,QAAA,CACH;AAAA;AAAA,QAED,QAAQ;AAAA,MAAA,CACJ;AAER,YAAM,WAAW,MAAM,QAAQ,MAAM,SAAS,UAAU;AAExD,UAAI,aAAa,SAAS;AAC1B,eAAS,QAAQ,QAAQ,CAAC,GAAG,MAAM,IAAI,UAAU,GAAG,CAAC,CAAC;AAEtD,UAAI,SAAS,MAAM;AAEf,cAAM,SAAS,MAAM,SAAS,YAAA;AAC9B,YAAI,IAAI,OAAO,KAAK,MAAM,CAAC;AAAA,MAC/B,OAAO;AACH,YAAI,IAAA;AAAA,MACR;AAAA,IACJ,CAAC;AAED,UAAM,aAAqB;AAAA,MACvB,MAAM,MAAM;AACR,eAAO,MAAA;AACP,eAAO,QAAQ,QAAA;AAAA,MACnB;AAAA,MACA,QAAQ,KAAKA,UAAS;AAClB,eAAO;AAAA,MACX;AAAA,MACA,OAAOA,UAAS;AACZ,eAAO;AAAA,MACX;AAAA,MACA,IAAI,OAAO;AACP,cAAM,OAAO,OAAO,QAAA;AACpB,YAAI,OAAO,SAAS,YAAY,SAAS,MAAM;AAC3C,iBAAO,KAAK;AAAA,QAChB;AACA,eAAO,QAAQ;AAAA,MACnB;AAAA,MACA,UAAU,QAAQ;AAAA,MAClB,aAAa,QAAQ;AAAA,MACrB,iBAAiB;AAAA,MACjB,WAAW,CAAC,QAAQ;AAAA,MACpB,SAAS,MAAM;AAAA,MACf,iBAAiB,MAAM;AAAA,MACvB,KAAK,IAAI,IAAI,UAAU,QAAQ,QAAQ,IAAI,QAAQ,IAAI,EAAE;AAAA,IAAA;AAG7D,WAAO,IAAI,QAAQ,CAAC,YAAY;AAC5B,aAAO,OAAO,QAAQ,MAAM,QAAQ,UAAU,MAAM;AAChD,gBAAQ,UAAU;AAAA,MACtB,CAAC;AAAA,IACL,CAAC;AAAA,EACL;AACJ;"}
@@ -32,7 +32,9 @@ function createHttpServer() {
32
32
  req.on("end", () => controller.close());
33
33
  req.on("error", (err) => controller.error(err));
34
34
  }
35
- })
35
+ }),
36
+ // Required for Node.js undici when sending a body
37
+ duplex: "half"
36
38
  });
37
39
  const response = await options.fetch(request, fauxServer);
38
40
  res.statusCode = response.status;
@@ -78,4 +80,4 @@ function createHttpServer() {
78
80
  };
79
81
  }
80
82
  exports.createHttpServer = createHttpServer;
81
- //# sourceMappingURL=server-adapter-fVKP60e0.cjs.map
83
+ //# sourceMappingURL=server-adapter-DFhwlK8e.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-adapter-DFhwlK8e.cjs","sources":["../src/plugins/server-adapter.ts"],"sourcesContent":["import type { Server } from \"bun\";\nimport * as http from \"node:http\";\nimport * as https from \"node:https\";\nimport type { ServerFactory } from \"../types\";\n\n/**\n * Creates a server factory that uses the standard Node.js `http` module.\n * @returns A ServerFactory compatible with Shokupan.\n */\nexport function createHttpServer(): ServerFactory {\n return async (options: any): Promise<Server> => {\n const server = http.createServer(async (req, res) => {\n const url = new URL(req.url!, `http://${req.headers.host}`);\n const request = new Request(url.toString(), {\n method: req.method,\n headers: req.headers as any,\n body: ['GET', 'HEAD'].includes(req.method!) ? undefined : new ReadableStream({\n start(controller) {\n req.on('data', chunk => controller.enqueue(chunk));\n req.on('end', () => controller.close());\n req.on('error', err => controller.error(err));\n }\n }) as any,\n // Required for Node.js undici when sending a body\n duplex: 'half'\n } as any);\n\n const response = await options.fetch(request, fauxServer);\n\n res.statusCode = response.status;\n response.headers.forEach((v, k) => res.setHeader(k, v));\n\n if (response.body) {\n // Optimize: Use arrayBuffer for direct conversion instead of async iteration\n const buffer = await response.arrayBuffer();\n res.end(Buffer.from(buffer));\n } else {\n res.end();\n }\n });\n\n const fauxServer: Server = {\n stop: () => {\n server.close();\n return Promise.resolve(); // Bun.Server stop usually returns void but in type definition it might vary.\n },\n upgrade(req, options) {\n return false;\n },\n reload(options) {\n return fauxServer as any;\n },\n get port() {\n const addr = server.address();\n if (typeof addr === 'object' && addr !== null) {\n return addr.port;\n }\n return options.port;\n },\n hostname: options.hostname,\n development: options.development,\n pendingRequests: 0,\n requestIP: (req) => null,\n publish: () => 0,\n subscriberCount: () => 0,\n url: new URL(`http://${options.hostname}:${options.port}`)\n } as unknown as Server;\n\n return new Promise((resolve) => {\n server.listen(options.port, options.hostname, () => {\n resolve(fauxServer);\n });\n });\n };\n}\n\n/**\n * Creates a server factory that uses the standard Node.js `https` module.\n * @param sslOptions - Node.js HTTPS options (key, cert, etc.)\n * @returns A ServerFactory compatible with Shokupan.\n */\nexport function createHttpsServer(sslOptions: https.ServerOptions): ServerFactory {\n return async (options: any): Promise<Server> => {\n const server = https.createServer(sslOptions, async (req, res) => {\n const url = new URL(req.url!, `https://${req.headers.host}`);\n const request = new Request(url.toString(), {\n method: req.method,\n headers: req.headers as any,\n body: ['GET', 'HEAD'].includes(req.method!) ? undefined : new ReadableStream({\n start(controller) {\n req.on('data', chunk => controller.enqueue(chunk));\n req.on('end', () => controller.close());\n req.on('error', err => controller.error(err));\n }\n }) as any,\n // Required for Node.js undici when sending a body\n duplex: 'half'\n } as any);\n\n const response = await options.fetch(request, fauxServer);\n\n res.statusCode = response.status;\n response.headers.forEach((v, k) => res.setHeader(k, v));\n\n if (response.body) {\n // Optimize: Use arrayBuffer for direct conversion instead of async iteration\n const buffer = await response.arrayBuffer();\n res.end(Buffer.from(buffer));\n } else {\n res.end();\n }\n });\n\n const fauxServer: Server = {\n stop: () => {\n server.close();\n },\n upgrade(req, options) {\n return false;\n },\n reload(options) {\n return fauxServer as any;\n },\n get port() {\n const addr = server.address();\n if (typeof addr === 'object' && addr !== null) {\n return addr.port;\n }\n return options.port;\n },\n hostname: options.hostname,\n development: options.development,\n pendingRequests: 0,\n requestIP: (req) => null,\n publish: () => 0,\n subscriberCount: () => 0,\n url: new URL(`https://${options.hostname}:${options.port}`)\n } as unknown as Server;\n\n return new Promise((resolve) => {\n server.listen(options.port, options.hostname, () => {\n resolve(fauxServer);\n });\n });\n };\n}\n"],"names":["http","options"],"mappings":";;;;;;;;;;;;;;;;;;;;;AASO,SAAS,mBAAkC;AAC9C,SAAO,OAAO,YAAkC;AAC5C,UAAM,SAASA,gBAAK,aAAa,OAAO,KAAK,QAAQ;AACjD,YAAM,MAAM,IAAI,IAAI,IAAI,KAAM,UAAU,IAAI,QAAQ,IAAI,EAAE;AAC1D,YAAM,UAAU,IAAI,QAAQ,IAAI,YAAY;AAAA,QACxC,QAAQ,IAAI;AAAA,QACZ,SAAS,IAAI;AAAA,QACb,MAAM,CAAC,OAAO,MAAM,EAAE,SAAS,IAAI,MAAO,IAAI,SAAY,IAAI,eAAe;AAAA,UACzE,MAAM,YAAY;AACd,gBAAI,GAAG,QAAQ,CAAA,UAAS,WAAW,QAAQ,KAAK,CAAC;AACjD,gBAAI,GAAG,OAAO,MAAM,WAAW,OAAO;AACtC,gBAAI,GAAG,SAAS,CAAA,QAAO,WAAW,MAAM,GAAG,CAAC;AAAA,UAChD;AAAA,QAAA,CACH;AAAA;AAAA,QAED,QAAQ;AAAA,MAAA,CACJ;AAER,YAAM,WAAW,MAAM,QAAQ,MAAM,SAAS,UAAU;AAExD,UAAI,aAAa,SAAS;AAC1B,eAAS,QAAQ,QAAQ,CAAC,GAAG,MAAM,IAAI,UAAU,GAAG,CAAC,CAAC;AAEtD,UAAI,SAAS,MAAM;AAEf,cAAM,SAAS,MAAM,SAAS,YAAA;AAC9B,YAAI,IAAI,OAAO,KAAK,MAAM,CAAC;AAAA,MAC/B,OAAO;AACH,YAAI,IAAA;AAAA,MACR;AAAA,IACJ,CAAC;AAED,UAAM,aAAqB;AAAA,MACvB,MAAM,MAAM;AACR,eAAO,MAAA;AACP,eAAO,QAAQ,QAAA;AAAA,MACnB;AAAA,MACA,QAAQ,KAAKC,UAAS;AAClB,eAAO;AAAA,MACX;AAAA,MACA,OAAOA,UAAS;AACZ,eAAO;AAAA,MACX;AAAA,MACA,IAAI,OAAO;AACP,cAAM,OAAO,OAAO,QAAA;AACpB,YAAI,OAAO,SAAS,YAAY,SAAS,MAAM;AAC3C,iBAAO,KAAK;AAAA,QAChB;AACA,eAAO,QAAQ;AAAA,MACnB;AAAA,MACA,UAAU,QAAQ;AAAA,MAClB,aAAa,QAAQ;AAAA,MACrB,iBAAiB;AAAA,MACjB,WAAW,CAAC,QAAQ;AAAA,MACpB,SAAS,MAAM;AAAA,MACf,iBAAiB,MAAM;AAAA,MACvB,KAAK,IAAI,IAAI,UAAU,QAAQ,QAAQ,IAAI,QAAQ,IAAI,EAAE;AAAA,IAAA;AAG7D,WAAO,IAAI,QAAQ,CAAC,YAAY;AAC5B,aAAO,OAAO,QAAQ,MAAM,QAAQ,UAAU,MAAM;AAChD,gBAAQ,UAAU;AAAA,MACtB,CAAC;AAAA,IACL,CAAC;AAAA,EACL;AACJ;;"}
@@ -2,13 +2,12 @@ import { ShokupanRequest } from './request';
2
2
  import { ShokupanRouter } from './router';
3
3
  import { $dispatch } from './symbol';
4
4
  import { Middleware, ProcessResult, RequestOptions, ShokupanConfig } from './types';
5
+ import { Server } from 'bun';
5
6
  export declare class Shokupan<T = any> extends ShokupanRouter<T> {
6
7
  readonly applicationConfig: ShokupanConfig;
7
8
  openApiSpec?: any;
8
9
  private composedMiddleware?;
9
10
  private cpuMonitor?;
10
- private hookCache;
11
- private hooksInitialized;
12
11
  get logger(): {
13
12
  verbose?: boolean;
14
13
  info?: (msg: string, props: Record<string, any>) => void;
@@ -39,12 +38,12 @@ export declare class Shokupan<T = any> extends ShokupanRouter<T> {
39
38
  * @param port - The port to listen on. If not specified, the port from the configuration is used. If that is not specified, port 3000 is used.
40
39
  * @returns The server instance.
41
40
  */
42
- listen(port?: number): Promise<any>;
41
+ listen(port?: number): Promise<Server | import('http').Server<typeof import('http').IncomingMessage, typeof import('http').ServerResponse>>;
43
42
  [$dispatch](req: ShokupanRequest<T>): Promise<Response>;
44
43
  /**
45
44
  * Processes a request by wrapping the standard fetch method.
46
45
  */
47
- processRequest(options: RequestOptions): Promise<ProcessResult>;
46
+ testRequest(options: RequestOptions): Promise<ProcessResult>;
48
47
  /**
49
48
  * Handles an incoming request (Bun.serve interface).
50
49
  * This logic contains the middleware chain and router dispatch.
@@ -53,9 +52,6 @@ export declare class Shokupan<T = any> extends ShokupanRouter<T> {
53
52
  * @param server - The server instance.
54
53
  * @returns The response to send.
55
54
  */
56
- fetch(req: Request, server?: import('bun').Server<any>): Promise<Response>;
55
+ fetch(req: Request, server?: Server): Promise<Response>;
57
56
  private handleRequest;
58
- private ensureHooksInitialized;
59
- private executeHook;
60
- private hasHook;
61
57
  }
package/dist/types.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { OpenAPI } from '@scalar/openapi-types';
2
2
  import { Server } from 'bun';
3
+ import { Server as NodeServer } from 'node:http';
3
4
  import { ShokupanContext } from './context';
4
5
  import { $isRouter } from './symbol';
5
6
  export type DeepPartial<T> = T extends Function ? T : T extends object ? {
@@ -27,7 +28,7 @@ export interface OpenAPIOptions {
27
28
  defaultTag?: string;
28
29
  }
29
30
  export interface ShokupanHooks<T = any> {
30
- onError?: (error: unknown, ctx: ShokupanContext<T>) => void | Promise<void>;
31
+ onError?: (ctx: ShokupanContext<T>, error: unknown) => void | Promise<void>;
31
32
  onRequestStart?: (ctx: ShokupanContext<T>) => void | Promise<void>;
32
33
  onRequestEnd?: (ctx: ShokupanContext<T>) => void | Promise<void>;
33
34
  onResponseStart?: (ctx: ShokupanContext<T>, response: Response) => void | Promise<void>;
@@ -60,7 +61,7 @@ export declare enum RouteParamType {
60
61
  CONTEXT = "CONTEXT"
61
62
  }
62
63
  export interface ServerFactory {
63
- (options: any): Server<any> | Promise<Server<any>>;
64
+ (options: any): Server | Promise<Server> | NodeServer | Promise<NodeServer>;
64
65
  }
65
66
  export type NextFn = () => Promise<any>;
66
67
  export type Middleware = ((ctx: ShokupanContext<unknown>, next: NextFn) => Promise<any> | any) & {
@@ -88,10 +89,12 @@ export type ShokupanRouteConfig = DeepPartial<{
88
89
  hooks: ShokupanHooks | ShokupanHooks[];
89
90
  /**
90
91
  * Whether to enforce that only controller classes (constructors) are accepted by the router.
92
+ * @default false
91
93
  */
92
94
  controllersOnly: boolean;
93
95
  /**
94
96
  * Whether to enable automatic backpressure based on system CPU load.
97
+ * @default false
95
98
  */
96
99
  autoBackpressureFeedback: boolean;
97
100
  /**
@@ -213,13 +216,29 @@ export type ShokupanConfig<T extends Record<string, any> = Record<string, any>>
213
216
  * @default false
214
217
  */
215
218
  enableTracing?: boolean;
219
+ /**
220
+ * JSON parser to use for parsing request bodies.
221
+ *
222
+ * Options:
223
+ * - `'native'`: Use the built-in JSON.parse (fastest, default)
224
+ * - `'parse-json'`: Use the parse-json library for better error messages with minimal performance overhead (~5% slower than native)
225
+ * - `'secure-json-parse'`: Use secure-json-parse for protection against prototype pollution (20-30% slower than native)
226
+ *
227
+ * Performance implications based on benchmarks:
228
+ * - `native`: Fastest option, excellent for production
229
+ * - `parse-json`: Nearly identical performance to native with better error messages, good for development
230
+ * - `secure-json-parse`: Provides security at the cost of performance, use only for untrusted input
231
+ *
232
+ * @default 'native'
233
+ */
234
+ jsonParser?: 'native' | 'parse-json' | 'secure-json-parse';
216
235
  /**
217
236
  * Whether to enable automatic backpressure based on system CPU load.
218
237
  * @default false
219
238
  */
220
239
  autoBackpressureFeedback?: boolean;
221
240
  /**
222
- * The CPU usage percentage threshold (0-100) at which to start rejecting requests.
241
+ * The CPU usage percentage threshold (0-100) at which to start rejecting requests (429).
223
242
  * @default 60
224
243
  */
225
244
  autoBackpressureLevel?: number;
@@ -276,6 +295,7 @@ export type ShokupanConfig<T extends Record<string, any> = Record<string, any>>
276
295
  /**
277
296
  * Timeout for writing the response (milliseconds).
278
297
  * Not currently supported by Bun.serve natively.
298
+ * @experimental
279
299
  */
280
300
  writeTimeout: number;
281
301
  /**
@@ -291,6 +311,15 @@ export type ShokupanConfig<T extends Record<string, any> = Record<string, any>>
291
311
  * Lifecycle hooks.
292
312
  */
293
313
  hooks: ShokupanHooks<T> | ShokupanHooks<T>[];
314
+ /**
315
+ * Whether to validate response status codes.
316
+ * @default true
317
+ */
318
+ validateStatusCodes: boolean;
319
+ /**
320
+ * Any other config options are allowed, but will be ignored.
321
+ * @deprecated
322
+ */
294
323
  [key: string]: any;
295
324
  }>;
296
325
  export interface RequestOptions {
@@ -0,0 +1,6 @@
1
+ export declare const datastore: {
2
+ get<T extends Record<string, any>>(store: string, key: string): Promise<T>;
3
+ set(store: string, key: string, value: any): Promise<any>;
4
+ query(query: string, vars?: Record<string, unknown>): Promise<any>;
5
+ readonly ready: Promise<any>;
6
+ };
@@ -0,0 +1,12 @@
1
+ /**
2
+ * JSON parser utilities for Shokupan
3
+ * Supports multiple JSON parsing libraries with different performance characteristics
4
+ */
5
+ type JSONParser = (text: string) => any;
6
+ /**
7
+ * Get the appropriate JSON parser based on the configuration
8
+ * @param parserType - The type of parser to use
9
+ * @returns A JSON parsing function
10
+ */
11
+ export declare function getJSONParser(parserType?: 'native' | 'parse-json' | 'secure-json-parse'): JSONParser;
12
+ export {};
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Plugin dependency loader with helpful error messages
3
+ * Provides lazy loading for optional plugin dependencies
4
+ */
5
+ /**
6
+ * Loads a plugin dependency with helpful error message if not installed
7
+ * @param packageName The npm package to load
8
+ * @param pluginName The shokupan plugin that requires it
9
+ * @param installCommand Optional custom install command
10
+ */
11
+ export declare function loadPluginDependency(packageName: string, pluginName: string, installCommand?: string): Promise<any>;
12
+ /**
13
+ * Loads multiple plugin dependencies
14
+ * @param dependencies Array of dependency info
15
+ */
16
+ export declare function loadPluginDependencies(dependencies: Array<{
17
+ package: string;
18
+ plugin: string;
19
+ installCommand?: string;
20
+ }>): Promise<Record<string, any>>;
21
+ /**
22
+ * Checks if a package is available without throwing
23
+ * @param packageName The npm package to check
24
+ */
25
+ export declare function isPackageAvailable(packageName: string): Promise<boolean>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shokupan",
3
- "version": "0.4.5",
3
+ "version": "0.6.0",
4
4
  "description": "Shokupan is a low-lift modern web framework for Bun.",
5
5
  "author": "Andrew G. Knackstedt",
6
6
  "publishConfig": {
@@ -27,7 +27,8 @@
27
27
  "docs": "cd docs && bun run dev",
28
28
  "build": "vite build",
29
29
  "bench": "cd src/benchmarking && bun runner.ts",
30
- "bench:advanced": "cd src/benchmarking && bun advanced-runner.ts"
30
+ "bench:advanced": "cd src/benchmarking && bun advanced-runner.ts",
31
+ "retag": "git push origin :refs/tags/v$(node -p \"require('./package.json').version\") 2>/dev/null || true && git tag -d v$(node -p \"require('./package.json').version\") 2>/dev/null || true && git tag v$(node -p \"require('./package.json').version\") && git push origin v$(node -p \"require('./package.json').version\") --force"
31
32
  },
32
33
  "bin": {
33
34
  "shokupan": "dist/cli.js",
@@ -63,33 +64,92 @@
63
64
  "@opentelemetry/sdk-trace-base": "^2.2.0",
64
65
  "@opentelemetry/sdk-trace-node": "^2.2.0",
65
66
  "@opentelemetry/semantic-conventions": "^1.38.0",
66
- "@scalar/api-reference": "^1.40.9",
67
67
  "@scalar/openapi-types": "^0.5.3",
68
- "@surrealdb/node": "^2.4.0",
69
- "ajv": "^8.17.1",
70
- "ajv-formats": "^3.0.1",
71
- "arctic": "^3.7.0",
72
- "class-transformer": "^0.5.1",
73
- "class-validator": "^0.14.3",
74
- "eta": "^4.5.0",
75
- "jose": "^6.1.3",
76
- "reflect-metadata": "^0.2.2",
77
- "surrealdb": "^2.0.0-alpha.14",
78
68
  "tslib": "^2.8.1"
79
69
  },
70
+ "peerDependencies": {
71
+ "ajv": "^8.0.0",
72
+ "ajv-formats": "^3.0.0",
73
+ "arctic": "^3",
74
+ "class-transformer": "^0.5.0",
75
+ "class-validator": "^0.14.0",
76
+ "eta": "^4.0.0",
77
+ "jose": "^6.0.0",
78
+ "parse-json": "^8.0.0",
79
+ "reflect-metadata": "^0.2.0",
80
+ "secure-json-parse": "^4.0.0",
81
+ "@scalar/api-reference": "^1.0.0",
82
+ "@surrealdb/node": "^2.4.0",
83
+ "surrealdb": "^2.0.0-alpha.14"
84
+ },
85
+ "peerDependenciesMeta": {
86
+ "ajv": {
87
+ "optional": true
88
+ },
89
+ "ajv-formats": {
90
+ "optional": true
91
+ },
92
+ "arctic": {
93
+ "optional": true
94
+ },
95
+ "class-transformer": {
96
+ "optional": true
97
+ },
98
+ "class-validator": {
99
+ "optional": true
100
+ },
101
+ "eta": {
102
+ "optional": true
103
+ },
104
+ "jose": {
105
+ "optional": true
106
+ },
107
+ "parse-json": {
108
+ "optional": true
109
+ },
110
+ "reflect-metadata": {
111
+ "optional": true
112
+ },
113
+ "secure-json-parse": {
114
+ "optional": true
115
+ },
116
+ "@scalar/api-reference": {
117
+ "optional": true
118
+ },
119
+ "@surrealdb/node": {
120
+ "optional": true
121
+ },
122
+ "surrealdb": {
123
+ "optional": true
124
+ }
125
+ },
80
126
  "devDependencies": {
127
+ "@scalar/api-reference": "^1.0.0",
81
128
  "@sinclair/typebox": "^0.34.45",
129
+ "@surrealdb/node": "^2.4.0",
82
130
  "@tsconfig/bun": "^1.0.8",
83
131
  "@types/axios": "^0.9.36",
84
132
  "@types/bun": "^1.2.23",
85
133
  "@types/supertest": "^6.0.3",
134
+ "ajv": "^8.0.0",
135
+ "ajv-formats": "^3.0.0",
136
+ "arctic": "^3",
86
137
  "axios": "^1.13.2",
138
+ "class-transformer": "^0.5.0",
139
+ "class-validator": "^0.14.0",
140
+ "eta": "^4.0.0",
141
+ "fast-json-stringify": "^6.1.1",
87
142
  "get-port": "^7.1.0",
143
+ "jose": "^6.0.0",
144
+ "parse-json": "^8.0.0",
145
+ "reflect-metadata": "^0.2.0",
146
+ "secure-json-parse": "^4.0.0",
88
147
  "supertest": "^7.1.4",
148
+ "surrealdb": "^2.0.0-alpha.14",
89
149
  "typescript": "~5.9.3",
90
150
  "valibot": "^1.2.0",
91
151
  "vite": "^7.3.0",
92
152
  "vite-plugin-dts": "^4.5.4",
93
153
  "zod": "^4.2.1"
94
154
  }
95
- }
155
+ }
@@ -1 +0,0 @@
1
- export declare function startAdvanced(port: number, scenario: string): Promise<() => Promise<void>>;
@@ -1 +0,0 @@
1
- export declare function startAdvanced(port: number, scenario: string): Promise<() => Promise<void>>;
@@ -1 +0,0 @@
1
- export declare function startAdvanced(port: number, scenario: string): Promise<() => Promise<void>>;
@@ -1 +0,0 @@
1
- export declare function startAdvanced(port: number, scenario: string): Promise<() => Promise<void>>;
@@ -1 +0,0 @@
1
- export declare function startAdvanced(port: number, scenario: string): Promise<() => Promise<void>>;
@@ -1 +0,0 @@
1
- export declare function startAdvanced(port: number, scenario: string): Promise<() => Promise<void>>;
@@ -1 +0,0 @@
1
- export declare function startAdvanced(port: number, scenario: string): Promise<() => Promise<void>>;
@@ -1 +0,0 @@
1
- export declare function startAdvanced(port: number, scenario: string): Promise<() => Promise<void>>;
@@ -1,33 +0,0 @@
1
- export declare const LARGE_JSON: {
2
- total: number;
3
- items: any[];
4
- metadata: {
5
- generated: string;
6
- size: string;
7
- purpose: string;
8
- };
9
- };
10
- export declare const LARGE_REQUEST_BODY: string;
11
- export declare const LARGE_HEADERS: Record<string, string>;
12
- /**
13
- * Calculate MD5 hash of a string
14
- */
15
- export declare function md5(input: string): string;
16
- /**
17
- * Serialize request data for hashing
18
- */
19
- export declare function serializeRequest(url: string, headers: Record<string, string>, body: string): string;
20
- /**
21
- * Get the actual byte size of the LARGE_JSON
22
- */
23
- export declare function getLargeJSONSize(): number;
24
- /**
25
- * Smaller compressible response (100KB) for compression testing
26
- */
27
- export declare const COMPRESSIBLE_JSON: {
28
- data: {
29
- id: number;
30
- text: string;
31
- timestamp: string;
32
- }[];
33
- };
@@ -1 +0,0 @@
1
- export {};
File without changes
@@ -1 +0,0 @@
1
- export declare function start(port: number): Promise<() => Promise<void>>;
@@ -1 +0,0 @@
1
- export declare function start(port: number): Promise<() => Promise<void>>;
@@ -1 +0,0 @@
1
- export declare function start(port: number): Promise<() => Promise<void>>;
@@ -1 +0,0 @@
1
- export declare function start(port: number): Promise<() => Promise<void>>;
@@ -1 +0,0 @@
1
- export declare function start(port: number): Promise<() => Promise<void>>;
@@ -1 +0,0 @@
1
- export declare function start(port: number): Promise<() => Promise<void>>;
@@ -1 +0,0 @@
1
- export declare function start(port: number): Promise<() => Promise<void>>;
@@ -1 +0,0 @@
1
- export declare function start(port: number): Promise<() => Promise<void>>;
@@ -1,15 +0,0 @@
1
- export declare const MEDIUM_JSON: {
2
- id: number;
3
- name: string;
4
- timestamp: string;
5
- tags: string[];
6
- nested: {
7
- layer1: {
8
- layer2: {
9
- value: string;
10
- numbers: number[];
11
- };
12
- };
13
- };
14
- description: string;
15
- };
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
File without changes
package/dist/buntest.d.ts DELETED
@@ -1 +0,0 @@
1
- export {};