shokupan 0.1.0 → 0.3.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 +1 -0
- package/dist/benchmarking/advanced-cases/elysia.d.ts +1 -0
- package/dist/benchmarking/advanced-cases/express.d.ts +1 -0
- package/dist/benchmarking/advanced-cases/fastify.d.ts +1 -0
- package/dist/benchmarking/advanced-cases/hapi.d.ts +1 -0
- package/dist/benchmarking/advanced-cases/hono.d.ts +1 -0
- package/dist/benchmarking/advanced-cases/koa.d.ts +1 -0
- package/dist/benchmarking/advanced-cases/nest.d.ts +1 -0
- package/dist/benchmarking/advanced-cases/shokupan.d.ts +1 -0
- package/dist/benchmarking/advanced-data.d.ts +33 -0
- package/dist/benchmarking/advanced-runner.d.ts +1 -0
- package/dist/benchmarking/advanced-worker.d.ts +0 -0
- package/dist/benchmarking/cases/elysia.d.ts +1 -0
- package/dist/benchmarking/cases/express.d.ts +1 -0
- package/dist/benchmarking/cases/fastify.d.ts +1 -0
- package/dist/benchmarking/cases/hapi.d.ts +1 -0
- package/dist/benchmarking/cases/hono.d.ts +1 -0
- package/dist/benchmarking/cases/koa.d.ts +1 -0
- package/dist/benchmarking/cases/nest.d.ts +1 -0
- package/dist/benchmarking/cases/shokupan.d.ts +1 -0
- package/dist/benchmarking/data.d.ts +15 -0
- package/dist/benchmarking/quick_bench.d.ts +1 -0
- package/dist/benchmarking/runner.d.ts +1 -0
- package/dist/benchmarking/worker.d.ts +0 -0
- package/dist/buntest.d.ts +1 -0
- package/dist/cli.cjs +1 -1
- package/dist/cli.js +1 -1
- package/dist/context.d.ts +25 -8
- package/dist/decorators.d.ts +47 -0
- package/dist/index.cjs +1538 -655
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1532 -651
- package/dist/index.js.map +1 -1
- package/dist/middleware.d.ts +2 -0
- package/dist/{openapi-analyzer-cjdGeQ5a.js → openapi-analyzer-BtIaHIfe.js} +14 -6
- package/dist/openapi-analyzer-BtIaHIfe.js.map +1 -0
- package/dist/{openapi-analyzer-CFqgSLNK.cjs → openapi-analyzer-D9YB3IkV.cjs} +14 -6
- package/dist/openapi-analyzer-D9YB3IkV.cjs.map +1 -0
- package/dist/plugins/auth.d.ts +1 -1
- package/dist/plugins/debugview/plugin.d.ts +28 -0
- package/dist/plugins/failed-request-recorder.d.ts +14 -0
- package/dist/plugins/idempotency/plugin.d.ts +14 -0
- package/dist/plugins/openapi-validator.d.ts +30 -0
- package/dist/plugins/proxy.d.ts +9 -0
- package/dist/plugins/rate-limit.d.ts +3 -1
- package/dist/plugins/serve-static.d.ts +2 -3
- package/dist/response.d.ts +4 -0
- package/dist/router/trie.d.ts +14 -0
- package/dist/router.d.ts +50 -3
- package/dist/server-adapter-BWrEJbKL.js +64 -0
- package/dist/server-adapter-BWrEJbKL.js.map +1 -0
- package/dist/server-adapter-fVKP60e0.cjs +81 -0
- package/dist/server-adapter-fVKP60e0.cjs.map +1 -0
- package/dist/shokupan.d.ts +16 -3
- package/dist/types.d.ts +108 -4
- package/dist/util/cpu-monitor.d.ts +11 -0
- package/dist/util/stack.d.ts +8 -0
- package/package.json +8 -3
- package/dist/openapi-analyzer-CFqgSLNK.cjs.map +0 -1
- package/dist/openapi-analyzer-cjdGeQ5a.js.map +0 -1
package/dist/router.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ShokupanContext } from './context';
|
|
2
2
|
import { Shokupan } from './shokupan';
|
|
3
3
|
import { $appRoot, $childControllers, $childRouters, $isApplication, $isMounted, $isRouter, $mountPath, $parent, $routes } from './symbol';
|
|
4
|
-
import { GuardAPISpec,
|
|
4
|
+
import { GuardAPISpec, JSXRenderer, Method, MethodAPISpec, Middleware, OpenAPIOptions, ProcessResult, RequestOptions, RouteMetadata, ShokupanController, ShokupanHandler, ShokupanRoute, ShokupanRouteConfig, StaticServeOptions } from './types';
|
|
5
5
|
type HeadersInit = Headers | Record<string, string> | [string, string][];
|
|
6
6
|
export declare const RouterRegistry: Map<string, ShokupanRouter<any>>;
|
|
7
7
|
export declare const ShokupanApplicationTree: {};
|
|
@@ -15,6 +15,7 @@ export declare class ShokupanRouter<T extends Record<string, any> = Record<strin
|
|
|
15
15
|
private [$parent];
|
|
16
16
|
[$childRouters]: ShokupanRouter<T>[];
|
|
17
17
|
[$childControllers]: ShokupanController[];
|
|
18
|
+
middleware: Middleware[];
|
|
18
19
|
get rootConfig(): {
|
|
19
20
|
[x: string]: any;
|
|
20
21
|
port?: number;
|
|
@@ -24,6 +25,12 @@ export declare class ShokupanRouter<T extends Record<string, any> = Record<strin
|
|
|
24
25
|
enableOpenApiGen?: boolean;
|
|
25
26
|
reusePort?: boolean;
|
|
26
27
|
controllersOnly?: boolean;
|
|
28
|
+
enableTracing?: boolean;
|
|
29
|
+
autoBackpressureFeedback?: boolean;
|
|
30
|
+
autoBackpressureLevel?: number;
|
|
31
|
+
enableMiddlewareTracking?: boolean;
|
|
32
|
+
middlewareTrackingMaxCapacity?: number;
|
|
33
|
+
middlewareTrackingTTL?: number;
|
|
27
34
|
httpLogger?: (ctx: ShokupanContext<Record<string, any>>) => void;
|
|
28
35
|
logger?: {
|
|
29
36
|
verbose?: boolean;
|
|
@@ -49,11 +56,50 @@ export declare class ShokupanRouter<T extends Record<string, any> = Record<strin
|
|
|
49
56
|
onReadTimeout?: (ctx: ShokupanContext<Record<string, any>>) => void | Promise<void>;
|
|
50
57
|
onWriteTimeout?: (ctx: ShokupanContext<Record<string, any>>) => void | Promise<void>;
|
|
51
58
|
onRequestTimeout?: (ctx: ShokupanContext<Record<string, any>>) => void | Promise<void>;
|
|
52
|
-
}
|
|
59
|
+
} | {
|
|
60
|
+
onError?: (error: unknown, ctx: ShokupanContext<Record<string, any>>) => void | Promise<void>;
|
|
61
|
+
onRequestStart?: (ctx: ShokupanContext<Record<string, any>>) => void | Promise<void>;
|
|
62
|
+
onRequestEnd?: (ctx: ShokupanContext<Record<string, any>>) => void | Promise<void>;
|
|
63
|
+
onResponseStart?: (ctx: ShokupanContext<Record<string, any>>, response: Response) => void | Promise<void>;
|
|
64
|
+
onResponseEnd?: (ctx: ShokupanContext<Record<string, any>>, response: Response) => void | Promise<void>;
|
|
65
|
+
beforeValidate?: (ctx: ShokupanContext<Record<string, any>>, data: any) => void | Promise<void>;
|
|
66
|
+
afterValidate?: (ctx: ShokupanContext<Record<string, any>>, data: any) => void | Promise<void>;
|
|
67
|
+
onReadTimeout?: (ctx: ShokupanContext<Record<string, any>>) => void | Promise<void>;
|
|
68
|
+
onWriteTimeout?: (ctx: ShokupanContext<Record<string, any>>) => void | Promise<void>;
|
|
69
|
+
onRequestTimeout?: (ctx: ShokupanContext<Record<string, any>>) => void | Promise<void>;
|
|
70
|
+
}[];
|
|
53
71
|
};
|
|
54
72
|
get root(): Shokupan<any>;
|
|
55
73
|
[$routes]: ShokupanRoute[];
|
|
74
|
+
private trie;
|
|
75
|
+
metadata?: RouteMetadata;
|
|
56
76
|
private currentGuards;
|
|
77
|
+
getComponentRegistry(): {
|
|
78
|
+
metadata: RouteMetadata;
|
|
79
|
+
middleware: {
|
|
80
|
+
name: string;
|
|
81
|
+
metadata: RouteMetadata;
|
|
82
|
+
order: number;
|
|
83
|
+
_fn: Middleware;
|
|
84
|
+
}[];
|
|
85
|
+
routes: {
|
|
86
|
+
type: string;
|
|
87
|
+
path: string;
|
|
88
|
+
method: Method;
|
|
89
|
+
metadata: RouteMetadata;
|
|
90
|
+
handlerName: string;
|
|
91
|
+
tags: any;
|
|
92
|
+
order: number;
|
|
93
|
+
_fn: ShokupanHandler<Record<string, any>>;
|
|
94
|
+
}[];
|
|
95
|
+
routers: any;
|
|
96
|
+
controllers: {
|
|
97
|
+
type: string;
|
|
98
|
+
path: any;
|
|
99
|
+
name: string;
|
|
100
|
+
metadata: any;
|
|
101
|
+
}[];
|
|
102
|
+
};
|
|
57
103
|
constructor(config?: ShokupanRouteConfig);
|
|
58
104
|
private isRouterInstance;
|
|
59
105
|
/**
|
|
@@ -91,7 +137,8 @@ export declare class ShokupanRouter<T extends Record<string, any> = Record<strin
|
|
|
91
137
|
* Processes a request directly.
|
|
92
138
|
*/
|
|
93
139
|
processRequest(options: RequestOptions): Promise<ProcessResult>;
|
|
94
|
-
private
|
|
140
|
+
private applyRouterHooks;
|
|
141
|
+
private wrapWithHooks;
|
|
95
142
|
/**
|
|
96
143
|
* Find a route matching the given method and path.
|
|
97
144
|
* @param method HTTP method
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import * as http from "node:http";
|
|
2
|
+
import "node:https";
|
|
3
|
+
function createHttpServer() {
|
|
4
|
+
return async (options) => {
|
|
5
|
+
const server = http.createServer(async (req, res) => {
|
|
6
|
+
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
7
|
+
const request = new Request(url.toString(), {
|
|
8
|
+
method: req.method,
|
|
9
|
+
headers: req.headers,
|
|
10
|
+
body: ["GET", "HEAD"].includes(req.method) ? void 0 : new ReadableStream({
|
|
11
|
+
start(controller) {
|
|
12
|
+
req.on("data", (chunk) => controller.enqueue(chunk));
|
|
13
|
+
req.on("end", () => controller.close());
|
|
14
|
+
req.on("error", (err) => controller.error(err));
|
|
15
|
+
}
|
|
16
|
+
})
|
|
17
|
+
});
|
|
18
|
+
const response = await options.fetch(request, fauxServer);
|
|
19
|
+
res.statusCode = response.status;
|
|
20
|
+
response.headers.forEach((v, k) => res.setHeader(k, v));
|
|
21
|
+
if (response.body) {
|
|
22
|
+
const buffer = await response.arrayBuffer();
|
|
23
|
+
res.end(Buffer.from(buffer));
|
|
24
|
+
} else {
|
|
25
|
+
res.end();
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
const fauxServer = {
|
|
29
|
+
stop: () => {
|
|
30
|
+
server.close();
|
|
31
|
+
return Promise.resolve();
|
|
32
|
+
},
|
|
33
|
+
upgrade(req, options2) {
|
|
34
|
+
return false;
|
|
35
|
+
},
|
|
36
|
+
reload(options2) {
|
|
37
|
+
return fauxServer;
|
|
38
|
+
},
|
|
39
|
+
get port() {
|
|
40
|
+
const addr = server.address();
|
|
41
|
+
if (typeof addr === "object" && addr !== null) {
|
|
42
|
+
return addr.port;
|
|
43
|
+
}
|
|
44
|
+
return options.port;
|
|
45
|
+
},
|
|
46
|
+
hostname: options.hostname,
|
|
47
|
+
development: options.development,
|
|
48
|
+
pendingRequests: 0,
|
|
49
|
+
requestIP: (req) => null,
|
|
50
|
+
publish: () => 0,
|
|
51
|
+
subscriberCount: () => 0,
|
|
52
|
+
url: new URL(`http://${options.hostname}:${options.port}`)
|
|
53
|
+
};
|
|
54
|
+
return new Promise((resolve) => {
|
|
55
|
+
server.listen(options.port, options.hostname, () => {
|
|
56
|
+
resolve(fauxServer);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export {
|
|
62
|
+
createHttpServer
|
|
63
|
+
};
|
|
64
|
+
//# sourceMappingURL=server-adapter-BWrEJbKL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-adapter-BWrEJbKL.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<any>> => {\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 });\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<any> = {\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<any>;\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<any>> => {\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 });\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<any> = {\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<any>;\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,YAAuC;AACjD,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,MAAA,CACJ;AAED,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,aAA0B;AAAA,MAC5B,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;"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const http = require("node:http");
|
|
4
|
+
require("node:https");
|
|
5
|
+
function _interopNamespaceDefault(e) {
|
|
6
|
+
const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
|
|
7
|
+
if (e) {
|
|
8
|
+
for (const k in e) {
|
|
9
|
+
if (k !== "default") {
|
|
10
|
+
const d = Object.getOwnPropertyDescriptor(e, k);
|
|
11
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
12
|
+
enumerable: true,
|
|
13
|
+
get: () => e[k]
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
n.default = e;
|
|
19
|
+
return Object.freeze(n);
|
|
20
|
+
}
|
|
21
|
+
const http__namespace = /* @__PURE__ */ _interopNamespaceDefault(http);
|
|
22
|
+
function createHttpServer() {
|
|
23
|
+
return async (options) => {
|
|
24
|
+
const server = http__namespace.createServer(async (req, res) => {
|
|
25
|
+
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
26
|
+
const request = new Request(url.toString(), {
|
|
27
|
+
method: req.method,
|
|
28
|
+
headers: req.headers,
|
|
29
|
+
body: ["GET", "HEAD"].includes(req.method) ? void 0 : new ReadableStream({
|
|
30
|
+
start(controller) {
|
|
31
|
+
req.on("data", (chunk) => controller.enqueue(chunk));
|
|
32
|
+
req.on("end", () => controller.close());
|
|
33
|
+
req.on("error", (err) => controller.error(err));
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
});
|
|
37
|
+
const response = await options.fetch(request, fauxServer);
|
|
38
|
+
res.statusCode = response.status;
|
|
39
|
+
response.headers.forEach((v, k) => res.setHeader(k, v));
|
|
40
|
+
if (response.body) {
|
|
41
|
+
const buffer = await response.arrayBuffer();
|
|
42
|
+
res.end(Buffer.from(buffer));
|
|
43
|
+
} else {
|
|
44
|
+
res.end();
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
const fauxServer = {
|
|
48
|
+
stop: () => {
|
|
49
|
+
server.close();
|
|
50
|
+
return Promise.resolve();
|
|
51
|
+
},
|
|
52
|
+
upgrade(req, options2) {
|
|
53
|
+
return false;
|
|
54
|
+
},
|
|
55
|
+
reload(options2) {
|
|
56
|
+
return fauxServer;
|
|
57
|
+
},
|
|
58
|
+
get port() {
|
|
59
|
+
const addr = server.address();
|
|
60
|
+
if (typeof addr === "object" && addr !== null) {
|
|
61
|
+
return addr.port;
|
|
62
|
+
}
|
|
63
|
+
return options.port;
|
|
64
|
+
},
|
|
65
|
+
hostname: options.hostname,
|
|
66
|
+
development: options.development,
|
|
67
|
+
pendingRequests: 0,
|
|
68
|
+
requestIP: (req) => null,
|
|
69
|
+
publish: () => 0,
|
|
70
|
+
subscriberCount: () => 0,
|
|
71
|
+
url: new URL(`http://${options.hostname}:${options.port}`)
|
|
72
|
+
};
|
|
73
|
+
return new Promise((resolve) => {
|
|
74
|
+
server.listen(options.port, options.hostname, () => {
|
|
75
|
+
resolve(fauxServer);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
exports.createHttpServer = createHttpServer;
|
|
81
|
+
//# sourceMappingURL=server-adapter-fVKP60e0.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-adapter-fVKP60e0.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<any>> => {\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 });\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<any> = {\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<any>;\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<any>> => {\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 });\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<any> = {\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<any>;\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,YAAuC;AACjD,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,MAAA,CACJ;AAED,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,aAA0B;AAAA,MAC5B,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;;"}
|
package/dist/shokupan.d.ts
CHANGED
|
@@ -5,7 +5,10 @@ import { Middleware, ProcessResult, RequestOptions, ShokupanConfig } from './typ
|
|
|
5
5
|
export declare class Shokupan<T = any> extends ShokupanRouter<T> {
|
|
6
6
|
readonly applicationConfig: ShokupanConfig;
|
|
7
7
|
openApiSpec?: any;
|
|
8
|
-
private
|
|
8
|
+
private composedMiddleware?;
|
|
9
|
+
private cpuMonitor?;
|
|
10
|
+
private hookCache;
|
|
11
|
+
private hooksInitialized;
|
|
9
12
|
get logger(): {
|
|
10
13
|
verbose?: boolean;
|
|
11
14
|
info?: (msg: string, props: Record<string, any>) => void;
|
|
@@ -24,13 +27,19 @@ export declare class Shokupan<T = any> extends ShokupanRouter<T> {
|
|
|
24
27
|
* Registers a callback to be executed before the server starts listening.
|
|
25
28
|
*/
|
|
26
29
|
onStart(callback: () => Promise<void> | void): this;
|
|
30
|
+
private specAvailableHooks;
|
|
31
|
+
/**
|
|
32
|
+
* Registers a callback to be executed when the OpenAPI spec is available.
|
|
33
|
+
* This happens after generateOpenApi() but before the server starts listening (or at least before it finishes startup if async).
|
|
34
|
+
*/
|
|
35
|
+
onSpecAvailable(callback: (spec: any) => void | Promise<void>): this;
|
|
27
36
|
/**
|
|
28
37
|
* Starts the application server.
|
|
29
38
|
*
|
|
30
39
|
* @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.
|
|
31
40
|
* @returns The server instance.
|
|
32
41
|
*/
|
|
33
|
-
listen(port?: number): Promise<Bun.Server
|
|
42
|
+
listen(port?: number): Promise<Bun.Server<any>>;
|
|
34
43
|
[$dispatch](req: ShokupanRequest<T>): Promise<Response>;
|
|
35
44
|
/**
|
|
36
45
|
* Processes a request by wrapping the standard fetch method.
|
|
@@ -44,5 +53,9 @@ export declare class Shokupan<T = any> extends ShokupanRouter<T> {
|
|
|
44
53
|
* @param server - The server instance.
|
|
45
54
|
* @returns The response to send.
|
|
46
55
|
*/
|
|
47
|
-
fetch(req: Request, server?: import('bun').Server): Promise<Response>;
|
|
56
|
+
fetch(req: Request, server?: import('bun').Server<any>): Promise<Response>;
|
|
57
|
+
private handleRequest;
|
|
58
|
+
private ensureHooksInitialized;
|
|
59
|
+
private executeHook;
|
|
60
|
+
private hasHook;
|
|
48
61
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -5,6 +5,13 @@ import { $isRouter } from './symbol';
|
|
|
5
5
|
export type DeepPartial<T> = T extends Function ? T : T extends object ? {
|
|
6
6
|
[P in keyof T]?: DeepPartial<T[P]>;
|
|
7
7
|
} : T;
|
|
8
|
+
export interface RouteMetadata {
|
|
9
|
+
file: string;
|
|
10
|
+
line: number;
|
|
11
|
+
name?: string;
|
|
12
|
+
isBuiltin?: boolean;
|
|
13
|
+
pluginName?: string;
|
|
14
|
+
}
|
|
8
15
|
export type MethodAPISpec = OpenAPI.Operation;
|
|
9
16
|
export type GuardAPISpec = DeepPartial<OpenAPI.Operation>;
|
|
10
17
|
export type RouterAPISpec = OpenAPI.Operation & Pick<Required<OpenAPI.Operation>, 'tags'> & {
|
|
@@ -53,10 +60,15 @@ export declare enum RouteParamType {
|
|
|
53
60
|
CONTEXT = "CONTEXT"
|
|
54
61
|
}
|
|
55
62
|
export interface ServerFactory {
|
|
56
|
-
(options: any): Server | Promise<Server
|
|
63
|
+
(options: any): Server<any> | Promise<Server<any>>;
|
|
57
64
|
}
|
|
58
65
|
export type NextFn = () => Promise<any>;
|
|
59
|
-
export type Middleware = (ctx: ShokupanContext<unknown>, next: NextFn) => Promise<any> | any
|
|
66
|
+
export type Middleware = ((ctx: ShokupanContext<unknown>, next: NextFn) => Promise<any> | any) & {
|
|
67
|
+
isBuiltin?: boolean;
|
|
68
|
+
pluginName?: string;
|
|
69
|
+
metadata?: RouteMetadata;
|
|
70
|
+
order?: number;
|
|
71
|
+
};
|
|
60
72
|
export type JSXRenderer = (element: any, args?: unknown) => string | Promise<string>;
|
|
61
73
|
export type ShokupanRouteConfig = DeepPartial<{
|
|
62
74
|
name: string;
|
|
@@ -73,30 +85,87 @@ export type ShokupanRouteConfig = DeepPartial<{
|
|
|
73
85
|
/**
|
|
74
86
|
* Hooks for this route/router.
|
|
75
87
|
*/
|
|
76
|
-
hooks: ShokupanHooks;
|
|
88
|
+
hooks: ShokupanHooks | ShokupanHooks[];
|
|
77
89
|
/**
|
|
78
90
|
* Whether to enforce that only controller classes (constructors) are accepted by the router.
|
|
79
91
|
*/
|
|
80
92
|
controllersOnly: boolean;
|
|
93
|
+
/**
|
|
94
|
+
* Whether to enable automatic backpressure based on system CPU load.
|
|
95
|
+
*/
|
|
96
|
+
autoBackpressureFeedback: boolean;
|
|
97
|
+
/**
|
|
98
|
+
* The CPU usage percentage threshold (0-100) at which to start rejecting requests.
|
|
99
|
+
*/
|
|
100
|
+
autoBackpressureLevel: number;
|
|
81
101
|
}>;
|
|
82
102
|
export type ShokupanRoute = {
|
|
103
|
+
/**
|
|
104
|
+
* HTTP method
|
|
105
|
+
*/
|
|
83
106
|
method: Method;
|
|
107
|
+
/**
|
|
108
|
+
* Route path
|
|
109
|
+
*/
|
|
84
110
|
path: string;
|
|
111
|
+
/**
|
|
112
|
+
* Compiled regex for the route
|
|
113
|
+
*/
|
|
85
114
|
regex: RegExp;
|
|
115
|
+
/**
|
|
116
|
+
* Route parameters
|
|
117
|
+
*/
|
|
86
118
|
keys: string[];
|
|
119
|
+
/**
|
|
120
|
+
* Route handler
|
|
121
|
+
*/
|
|
87
122
|
handler: ShokupanHandler;
|
|
123
|
+
/**
|
|
124
|
+
* Optimization: Handler with hooks baked in.
|
|
125
|
+
* Used by runtime router, while `handler` is used by OpenAPI generator.
|
|
126
|
+
*/
|
|
127
|
+
bakedHandler?: ShokupanHandler;
|
|
128
|
+
/**
|
|
129
|
+
* OpenAPI spec for the route
|
|
130
|
+
*/
|
|
88
131
|
handlerSpec?: MethodAPISpec;
|
|
132
|
+
/**
|
|
133
|
+
* Group for the route
|
|
134
|
+
*/
|
|
89
135
|
group?: string;
|
|
136
|
+
/**
|
|
137
|
+
* Guards for the route
|
|
138
|
+
*/
|
|
90
139
|
guards?: {
|
|
140
|
+
/**
|
|
141
|
+
* Guard handler
|
|
142
|
+
*/
|
|
91
143
|
handler: ShokupanHandler;
|
|
144
|
+
/**
|
|
145
|
+
* Guard OpenAPI spec
|
|
146
|
+
*/
|
|
92
147
|
spec?: GuardAPISpec;
|
|
93
148
|
}[];
|
|
149
|
+
/**
|
|
150
|
+
* Timeout for this specific route (milliseconds).
|
|
151
|
+
*/
|
|
94
152
|
requestTimeout?: number;
|
|
153
|
+
/**
|
|
154
|
+
* Custom JSX renderer for this route.
|
|
155
|
+
*/
|
|
95
156
|
renderer?: JSXRenderer;
|
|
96
157
|
/**
|
|
97
158
|
* Hooks from the router/route definition
|
|
98
159
|
*/
|
|
99
160
|
hooks?: ShokupanHooks;
|
|
161
|
+
/**
|
|
162
|
+
* Source metadata
|
|
163
|
+
*/
|
|
164
|
+
metadata?: RouteMetadata;
|
|
165
|
+
/**
|
|
166
|
+
* Order of the middleware
|
|
167
|
+
*/
|
|
168
|
+
order?: number;
|
|
100
169
|
};
|
|
101
170
|
export type ShokupanConfig<T extends Record<string, any> = Record<string, any>> = DeepPartial<{
|
|
102
171
|
/**
|
|
@@ -135,6 +204,41 @@ export type ShokupanConfig<T extends Record<string, any> = Record<string, any>>
|
|
|
135
204
|
* @default false
|
|
136
205
|
*/
|
|
137
206
|
controllersOnly: boolean;
|
|
207
|
+
/**
|
|
208
|
+
* Whether to enable OpenTelemetry tracing.
|
|
209
|
+
* @default false
|
|
210
|
+
*/
|
|
211
|
+
enableTracing?: boolean;
|
|
212
|
+
/**
|
|
213
|
+
* Whether to enable automatic backpressure based on system CPU load.
|
|
214
|
+
* @default false
|
|
215
|
+
*/
|
|
216
|
+
autoBackpressureFeedback?: boolean;
|
|
217
|
+
/**
|
|
218
|
+
* The CPU usage percentage threshold (0-100) at which to start rejecting requests.
|
|
219
|
+
* @default 60
|
|
220
|
+
*/
|
|
221
|
+
autoBackpressureLevel?: number;
|
|
222
|
+
/**
|
|
223
|
+
* Whether to enable middleware and handler tracking.
|
|
224
|
+
* When enabled, `ctx.handlerStack` will be populated with the handlers the request has passed through.
|
|
225
|
+
* Also, `ctx.state` will be a Proxy that tracks changes made by each handler.
|
|
226
|
+
* @default false
|
|
227
|
+
*/
|
|
228
|
+
enableMiddlewareTracking: boolean;
|
|
229
|
+
/**
|
|
230
|
+
* Maximum number of middleware executions to store in the datastore.
|
|
231
|
+
* Only applies when enableMiddlewareTracking is true.
|
|
232
|
+
* @default 10000
|
|
233
|
+
*/
|
|
234
|
+
middlewareTrackingMaxCapacity?: number;
|
|
235
|
+
/**
|
|
236
|
+
* Time-to-live for middleware tracking entries in milliseconds.
|
|
237
|
+
* Entries older than this will be cleaned up.
|
|
238
|
+
* Only applies when enableMiddlewareTracking is true.
|
|
239
|
+
* @default 86400000 (1 day)
|
|
240
|
+
*/
|
|
241
|
+
middlewareTrackingTTL?: number;
|
|
138
242
|
/**
|
|
139
243
|
* HTTP logger function.
|
|
140
244
|
*/
|
|
@@ -182,7 +286,7 @@ export type ShokupanConfig<T extends Record<string, any> = Record<string, any>>
|
|
|
182
286
|
/**
|
|
183
287
|
* Lifecycle hooks.
|
|
184
288
|
*/
|
|
185
|
-
hooks: ShokupanHooks<T
|
|
289
|
+
hooks: ShokupanHooks<T> | ShokupanHooks<T>[];
|
|
186
290
|
[key: string]: any;
|
|
187
291
|
}>;
|
|
188
292
|
export interface RequestOptions {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shokupan",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Shokupan is a low-lift modern web framework for Bun.",
|
|
5
5
|
"author": "Andrew G. Knackstedt",
|
|
6
6
|
"repository": {
|
|
@@ -20,7 +20,9 @@
|
|
|
20
20
|
"scripts": {
|
|
21
21
|
"dev": "bun --watch --inspect src/example/main.ts",
|
|
22
22
|
"debug:otel": "sh scripts/debug-otel.sh",
|
|
23
|
-
"build": "vite build"
|
|
23
|
+
"build": "vite build",
|
|
24
|
+
"bench": "cd src/benchmarking && bun runner.ts",
|
|
25
|
+
"bench:advanced": "cd src/benchmarking && bun advanced-runner.ts"
|
|
24
26
|
},
|
|
25
27
|
"bin": {
|
|
26
28
|
"shokupan": "./dist/cli.js",
|
|
@@ -58,12 +60,16 @@
|
|
|
58
60
|
"@opentelemetry/semantic-conventions": "^1.38.0",
|
|
59
61
|
"@scalar/api-reference": "^1.40.9",
|
|
60
62
|
"@scalar/openapi-types": "^0.5.3",
|
|
63
|
+
"@surrealdb/node": "^2.4.0",
|
|
64
|
+
"ajv": "^8.17.1",
|
|
65
|
+
"ajv-formats": "^3.0.1",
|
|
61
66
|
"arctic": "^3.7.0",
|
|
62
67
|
"class-transformer": "^0.5.1",
|
|
63
68
|
"class-validator": "^0.14.3",
|
|
64
69
|
"eta": "^4.5.0",
|
|
65
70
|
"jose": "^6.1.3",
|
|
66
71
|
"reflect-metadata": "^0.2.2",
|
|
72
|
+
"surrealdb": "^2.0.0-alpha.14",
|
|
67
73
|
"tslib": "^2.8.1"
|
|
68
74
|
},
|
|
69
75
|
"devDependencies": {
|
|
@@ -72,7 +78,6 @@
|
|
|
72
78
|
"@types/axios": "^0.9.36",
|
|
73
79
|
"@types/bun": "^1.2.23",
|
|
74
80
|
"@types/supertest": "^6.0.3",
|
|
75
|
-
"ajv": "^8.17.1",
|
|
76
81
|
"axios": "^1.13.2",
|
|
77
82
|
"get-port": "^7.1.0",
|
|
78
83
|
"supertest": "^7.1.4",
|