@flightdev/http 0.0.4
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/LICENSE +21 -0
- package/README.md +580 -0
- package/dist/adapters/bun.d.ts +39 -0
- package/dist/adapters/bun.js +29 -0
- package/dist/adapters/bun.js.map +1 -0
- package/dist/adapters/deno.d.ts +36 -0
- package/dist/adapters/deno.js +28 -0
- package/dist/adapters/deno.js.map +1 -0
- package/dist/adapters/node.d.ts +34 -0
- package/dist/adapters/node.js +89 -0
- package/dist/adapters/node.js.map +1 -0
- package/dist/index.d.ts +116 -0
- package/dist/index.js +377 -0
- package/dist/index.js.map +1 -0
- package/dist/types-CtqOEBIR.d.ts +88 -0
- package/package.json +57 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// src/adapters/deno.ts
|
|
2
|
+
function serve(app, options = {}) {
|
|
3
|
+
const {
|
|
4
|
+
port = 3e3,
|
|
5
|
+
hostname = "0.0.0.0",
|
|
6
|
+
onListen
|
|
7
|
+
} = options;
|
|
8
|
+
const server = Deno.serve(
|
|
9
|
+
(request) => app.fetch(request),
|
|
10
|
+
{
|
|
11
|
+
port,
|
|
12
|
+
hostname,
|
|
13
|
+
onListen: onListen ? (params) => onListen({ port: params.port, hostname: params.hostname }) : (params) => {
|
|
14
|
+
console.log(`
|
|
15
|
+
\u2708\uFE0F Flight HTTP Server (Deno) running!
|
|
16
|
+
|
|
17
|
+
\u279C Local: http://localhost:${params.port}/
|
|
18
|
+
\u279C Network: http://${params.hostname}:${params.port}/
|
|
19
|
+
`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
);
|
|
23
|
+
return server;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export { serve };
|
|
27
|
+
//# sourceMappingURL=deno.js.map
|
|
28
|
+
//# sourceMappingURL=deno.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/deno.ts"],"names":[],"mappings":";AAmDO,SAAS,KAAA,CACZ,GAAA,EACA,OAAA,GAAwB,EAAC,EACQ;AACjC,EAAA,MAAM;AAAA,IACF,IAAA,GAAO,GAAA;AAAA,IACP,QAAA,GAAW,SAAA;AAAA,IACX;AAAA,GACJ,GAAI,OAAA;AAEJ,EAAA,MAAM,SAAS,IAAA,CAAK,KAAA;AAAA,IAChB,CAAC,OAAA,KAAY,GAAA,CAAI,KAAA,CAAM,OAAO,CAAA;AAAA,IAC9B;AAAA,MACI,IAAA;AAAA,MACA,QAAA;AAAA,MACA,QAAA,EAAU,QAAA,GACJ,CAAC,MAAA,KAAW,SAAS,EAAE,IAAA,EAAM,MAAA,CAAO,IAAA,EAAM,UAAU,MAAA,CAAO,QAAA,EAAU,CAAA,GACrE,CAAC,MAAA,KAAW;AACV,QAAA,OAAA,CAAQ,GAAA,CAAI;AAAA;AAAA;AAAA,oCAAA,EAGC,OAAO,IAAI,CAAA;AAAA,0BAAA,EACrB,MAAA,CAAO,QAAQ,CAAA,CAAA,EAAI,MAAA,CAAO,IAAI,CAAA;AAAA,CACpD,CAAA;AAAA,MACe;AAAA;AACR,GACJ;AAEA,EAAA,OAAO,MAAA;AACX","file":"deno.js","sourcesContent":["/**\r\n * @flightdev/http - Deno Adapter\r\n * \r\n * Allows running Flight HTTP server on Deno runtime.\r\n * Deno natively supports Web Standard Request/Response.\r\n */\r\n\r\nimport type { FlightHttpServer, Env } from '../types.js';\r\n\r\n// ============================================================================\r\n// Deno Server Types\r\n// ============================================================================\r\n\r\ninterface DenoServeOptions {\r\n port?: number;\r\n hostname?: string;\r\n onListen?: (params: { hostname: string; port: number }) => void;\r\n}\r\n\r\n// We don't import Deno types directly to avoid build issues\r\ndeclare const Deno: {\r\n serve: (\r\n handler: (request: Request) => Response | Promise<Response>,\r\n options?: DenoServeOptions\r\n ) => { shutdown: () => Promise<void> };\r\n};\r\n\r\n// ============================================================================\r\n// serve() Function\r\n// ============================================================================\r\n\r\nexport interface ServeOptions {\r\n port?: number;\r\n hostname?: string;\r\n onListen?: (info: { port: number; hostname: string }) => void;\r\n}\r\n\r\n/**\r\n * Start a Deno server with the Flight app\r\n * \r\n * @example\r\n * ```typescript\r\n * import { createServer } from '@flightdev/http';\r\n * import { serve } from '@flightdev/http/deno';\r\n * \r\n * const app = createServer();\r\n * app.get('/', (c) => c.json({ hello: 'world' }));\r\n * \r\n * serve(app, { port: 3000 });\r\n * ```\r\n */\r\nexport function serve<E extends Env = Env>(\r\n app: FlightHttpServer<E>,\r\n options: ServeOptions = {}\r\n): { shutdown: () => Promise<void> } {\r\n const {\r\n port = 3000,\r\n hostname = '0.0.0.0',\r\n onListen,\r\n } = options;\r\n\r\n const server = Deno.serve(\r\n (request) => app.fetch(request),\r\n {\r\n port,\r\n hostname,\r\n onListen: onListen\r\n ? (params) => onListen({ port: params.port, hostname: params.hostname })\r\n : (params) => {\r\n console.log(`\r\n ✈️ Flight HTTP Server (Deno) running!\r\n \r\n ➜ Local: http://localhost:${params.port}/\r\n ➜ Network: http://${params.hostname}:${params.port}/\r\n`);\r\n },\r\n }\r\n );\r\n\r\n return server;\r\n}\r\n\r\n// Re-export useful types\r\nexport type { FlightHttpServer } from '../types.js';\r\n"]}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
import { E as Env, F as FlightHttpServer } from '../types-CtqOEBIR.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @flightdev/http - Node.js Adapter
|
|
6
|
+
*
|
|
7
|
+
* Allows running Flight HTTP server on Node.js.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
interface ServeOptions {
|
|
11
|
+
port?: number;
|
|
12
|
+
hostname?: string;
|
|
13
|
+
onListen?: (info: {
|
|
14
|
+
port: number;
|
|
15
|
+
hostname: string;
|
|
16
|
+
}) => void;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Start a Node.js HTTP server with the Flight app
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* import { createServer } from '@flightdev/http';
|
|
24
|
+
* import { serve } from '@flightdev/http/node';
|
|
25
|
+
*
|
|
26
|
+
* const app = createServer();
|
|
27
|
+
* app.get('/', (c) => c.json({ hello: 'world' }));
|
|
28
|
+
*
|
|
29
|
+
* serve(app, { port: 3000 });
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
declare function serve<E extends Env = Env>(app: FlightHttpServer<E>, options?: ServeOptions): ReturnType<typeof createServer>;
|
|
33
|
+
|
|
34
|
+
export { FlightHttpServer, type ServeOptions, serve };
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { createServer } from 'http';
|
|
2
|
+
|
|
3
|
+
// src/adapters/node.ts
|
|
4
|
+
async function toWebRequest(req) {
|
|
5
|
+
const host = req.headers.host || "localhost";
|
|
6
|
+
const protocol = "http";
|
|
7
|
+
const url = new URL(req.url || "/", `${protocol}://${host}`);
|
|
8
|
+
let body = null;
|
|
9
|
+
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
10
|
+
const chunks = [];
|
|
11
|
+
for await (const chunk of req) {
|
|
12
|
+
chunks.push(chunk);
|
|
13
|
+
}
|
|
14
|
+
body = Buffer.concat(chunks);
|
|
15
|
+
}
|
|
16
|
+
const headers = new Headers();
|
|
17
|
+
for (const [key, value] of Object.entries(req.headers)) {
|
|
18
|
+
if (value) {
|
|
19
|
+
if (Array.isArray(value)) {
|
|
20
|
+
for (const v of value) {
|
|
21
|
+
headers.append(key, v);
|
|
22
|
+
}
|
|
23
|
+
} else {
|
|
24
|
+
headers.set(key, value);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return new Request(url.toString(), {
|
|
29
|
+
method: req.method || "GET",
|
|
30
|
+
headers,
|
|
31
|
+
body
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
async function writeWebResponse(webResponse, res) {
|
|
35
|
+
res.statusCode = webResponse.status;
|
|
36
|
+
res.statusMessage = webResponse.statusText;
|
|
37
|
+
webResponse.headers.forEach((value, key) => {
|
|
38
|
+
res.setHeader(key, value);
|
|
39
|
+
});
|
|
40
|
+
if (webResponse.body) {
|
|
41
|
+
const reader = webResponse.body.getReader();
|
|
42
|
+
try {
|
|
43
|
+
while (true) {
|
|
44
|
+
const { done, value } = await reader.read();
|
|
45
|
+
if (done) break;
|
|
46
|
+
res.write(value);
|
|
47
|
+
}
|
|
48
|
+
} finally {
|
|
49
|
+
reader.releaseLock();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
res.end();
|
|
53
|
+
}
|
|
54
|
+
function serve(app, options = {}) {
|
|
55
|
+
const {
|
|
56
|
+
port = 3e3,
|
|
57
|
+
hostname = "0.0.0.0",
|
|
58
|
+
onListen
|
|
59
|
+
} = options;
|
|
60
|
+
const server = createServer(async (req, res) => {
|
|
61
|
+
try {
|
|
62
|
+
const webRequest = await toWebRequest(req);
|
|
63
|
+
const webResponse = await app.fetch(webRequest);
|
|
64
|
+
await writeWebResponse(webResponse, res);
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error("[Flight HTTP Node] Error:", error);
|
|
67
|
+
res.statusCode = 500;
|
|
68
|
+
res.end("Internal Server Error");
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
server.listen(port, hostname, () => {
|
|
72
|
+
const info = { port, hostname };
|
|
73
|
+
if (onListen) {
|
|
74
|
+
onListen(info);
|
|
75
|
+
} else {
|
|
76
|
+
console.log(`
|
|
77
|
+
\u2708\uFE0F Flight HTTP Server running!
|
|
78
|
+
|
|
79
|
+
\u279C Local: http://${hostname === "0.0.0.0" ? "localhost" : hostname}:${port}/
|
|
80
|
+
\u279C Network: http://${hostname}:${port}/
|
|
81
|
+
`);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
return server;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export { serve };
|
|
88
|
+
//# sourceMappingURL=node.js.map
|
|
89
|
+
//# sourceMappingURL=node.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/node.ts"],"names":["createNodeServer"],"mappings":"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","file":"node.js","sourcesContent":["/**\r\n * @flightdev/http - Node.js Adapter\r\n * \r\n * Allows running Flight HTTP server on Node.js.\r\n */\r\n\r\nimport { createServer as createNodeServer, type IncomingMessage, type ServerResponse } from 'node:http';\r\nimport type { FlightHttpServer, Env } from '../types.js';\r\n\r\n// ============================================================================\r\n// Node.js Request Converter\r\n// ============================================================================\r\n\r\nasync function toWebRequest(req: IncomingMessage): Promise<Request> {\r\n const host = req.headers.host || 'localhost';\r\n const protocol = 'http'; // Simplified, use https detection in production\r\n const url = new URL(req.url || '/', `${protocol}://${host}`);\r\n\r\n // Collect body for non-GET/HEAD requests\r\n let body: BodyInit | null = null;\r\n\r\n if (req.method !== 'GET' && req.method !== 'HEAD') {\r\n const chunks: Buffer[] = [];\r\n for await (const chunk of req) {\r\n chunks.push(chunk as Buffer);\r\n }\r\n body = Buffer.concat(chunks);\r\n }\r\n\r\n // Convert headers\r\n const headers = new Headers();\r\n for (const [key, value] of Object.entries(req.headers)) {\r\n if (value) {\r\n if (Array.isArray(value)) {\r\n for (const v of value) {\r\n headers.append(key, v);\r\n }\r\n } else {\r\n headers.set(key, value);\r\n }\r\n }\r\n }\r\n\r\n return new Request(url.toString(), {\r\n method: req.method || 'GET',\r\n headers,\r\n body,\r\n });\r\n}\r\n\r\n// ============================================================================\r\n// Node.js Response Writer\r\n// ============================================================================\r\n\r\nasync function writeWebResponse(webResponse: Response, res: ServerResponse): Promise<void> {\r\n // Set status\r\n res.statusCode = webResponse.status;\r\n res.statusMessage = webResponse.statusText;\r\n\r\n // Set headers\r\n webResponse.headers.forEach((value, key) => {\r\n res.setHeader(key, value);\r\n });\r\n\r\n // Send body\r\n if (webResponse.body) {\r\n const reader = webResponse.body.getReader();\r\n\r\n try {\r\n while (true) {\r\n const { done, value } = await reader.read();\r\n if (done) break;\r\n res.write(value);\r\n }\r\n } finally {\r\n reader.releaseLock();\r\n }\r\n }\r\n\r\n res.end();\r\n}\r\n\r\n// ============================================================================\r\n// serve() Function\r\n// ============================================================================\r\n\r\nexport interface ServeOptions {\r\n port?: number;\r\n hostname?: string;\r\n onListen?: (info: { port: number; hostname: string }) => void;\r\n}\r\n\r\n/**\r\n * Start a Node.js HTTP server with the Flight app\r\n * \r\n * @example\r\n * ```typescript\r\n * import { createServer } from '@flightdev/http';\r\n * import { serve } from '@flightdev/http/node';\r\n * \r\n * const app = createServer();\r\n * app.get('/', (c) => c.json({ hello: 'world' }));\r\n * \r\n * serve(app, { port: 3000 });\r\n * ```\r\n */\r\nexport function serve<E extends Env = Env>(\r\n app: FlightHttpServer<E>,\r\n options: ServeOptions = {}\r\n): ReturnType<typeof createNodeServer> {\r\n const {\r\n port = 3000,\r\n hostname = '0.0.0.0',\r\n onListen,\r\n } = options;\r\n\r\n const server = createNodeServer(async (req, res) => {\r\n try {\r\n const webRequest = await toWebRequest(req);\r\n const webResponse = await app.fetch(webRequest);\r\n await writeWebResponse(webResponse, res);\r\n } catch (error) {\r\n console.error('[Flight HTTP Node] Error:', error);\r\n res.statusCode = 500;\r\n res.end('Internal Server Error');\r\n }\r\n });\r\n\r\n server.listen(port, hostname, () => {\r\n const info = { port, hostname };\r\n\r\n if (onListen) {\r\n onListen(info);\r\n } else {\r\n console.log(`\r\n ✈️ Flight HTTP Server running!\r\n \r\n ➜ Local: http://${hostname === '0.0.0.0' ? 'localhost' : hostname}:${port}/\r\n ➜ Network: http://${hostname}:${port}/\r\n`);\r\n }\r\n });\r\n\r\n return server;\r\n}\r\n\r\n// Re-export useful types\r\nexport type { FlightHttpServer } from '../types.js';\r\n"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { E as Env, S as ServerOptions, F as FlightHttpServer, H as HandlerOrMiddleware, M as Middleware, a as Handler, C as Context, b as FetchOptions, L as ListenOptions, c as HttpMethod, R as Route } from './types-CtqOEBIR.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @flightdev/http - Server
|
|
5
|
+
*
|
|
6
|
+
* Main server class that implements the FlightHttpServer interface.
|
|
7
|
+
*
|
|
8
|
+
* PERFORMANCE OPTIMIZATIONS (2026):
|
|
9
|
+
* - Middleware chains pre-compiled at route registration (not per-request)
|
|
10
|
+
* - Minimal allocations in fetch() hot path
|
|
11
|
+
* - Uses optimized class-based Context
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
declare class FlightHttp<E extends Env = Env> implements FlightHttpServer<E> {
|
|
15
|
+
private router;
|
|
16
|
+
private globalMiddleware;
|
|
17
|
+
private notFoundHandler;
|
|
18
|
+
private errorHandler;
|
|
19
|
+
private basePath;
|
|
20
|
+
private compiledNotFoundHandler?;
|
|
21
|
+
private middlewareVersion;
|
|
22
|
+
private lastCompiledVersion;
|
|
23
|
+
constructor(options?: ServerOptions);
|
|
24
|
+
get(path: string, ...handlers: HandlerOrMiddleware<E>[]): FlightHttpServer<E>;
|
|
25
|
+
post(path: string, ...handlers: HandlerOrMiddleware<E>[]): FlightHttpServer<E>;
|
|
26
|
+
put(path: string, ...handlers: HandlerOrMiddleware<E>[]): FlightHttpServer<E>;
|
|
27
|
+
delete(path: string, ...handlers: HandlerOrMiddleware<E>[]): FlightHttpServer<E>;
|
|
28
|
+
patch(path: string, ...handlers: HandlerOrMiddleware<E>[]): FlightHttpServer<E>;
|
|
29
|
+
options(path: string, ...handlers: HandlerOrMiddleware<E>[]): FlightHttpServer<E>;
|
|
30
|
+
head(path: string, ...handlers: HandlerOrMiddleware<E>[]): FlightHttpServer<E>;
|
|
31
|
+
all(path: string, ...handlers: HandlerOrMiddleware<E>[]): FlightHttpServer<E>;
|
|
32
|
+
use(...handlers: Middleware<E>[]): FlightHttpServer<E>;
|
|
33
|
+
route(path: string, router: FlightHttpServer<E>): FlightHttpServer<E>;
|
|
34
|
+
notFound(handler: Handler<E>): FlightHttpServer<E>;
|
|
35
|
+
onError(handler: (error: Error, c: Context<E>) => Response | Promise<Response>): FlightHttpServer<E>;
|
|
36
|
+
fetch(request: Request, options?: FetchOptions): Promise<Response>;
|
|
37
|
+
listen(options?: ListenOptions | number): void;
|
|
38
|
+
private addRoute;
|
|
39
|
+
/**
|
|
40
|
+
* Get pre-compiled not found handler (with global middleware)
|
|
41
|
+
*/
|
|
42
|
+
private getCompiledNotFoundHandler;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Create a new Flight HTTP server
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* import { createServer } from '@flightdev/http';
|
|
50
|
+
*
|
|
51
|
+
* const app = createServer();
|
|
52
|
+
*
|
|
53
|
+
* app.get('/', (c) => c.json({ message: 'Hello Flight!' }));
|
|
54
|
+
*
|
|
55
|
+
* export default app;
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
declare function createServer<E extends Env = Env>(options?: ServerOptions): FlightHttpServer<E>;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @flightdev/http - Context
|
|
62
|
+
*
|
|
63
|
+
* Request/Response context object passed to handlers.
|
|
64
|
+
*
|
|
65
|
+
* PERFORMANCE OPTIMIZATIONS (2026):
|
|
66
|
+
* - Class-based implementation (methods on prototype, not recreated per request)
|
|
67
|
+
* - Lazy URL parsing (parsed once, cached)
|
|
68
|
+
* - Lazy cookie parsing (parsed once, cached)
|
|
69
|
+
* - Minimal allocations in hot path
|
|
70
|
+
*/
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Create a new Context instance.
|
|
74
|
+
* Uses class-based implementation for optimal performance.
|
|
75
|
+
*/
|
|
76
|
+
declare function createContext<E extends Env = Env>(request: Request, params?: Record<string, string>, env?: E['Bindings']): Context<E>;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @flightdev/http - Router
|
|
80
|
+
*
|
|
81
|
+
* Radix-tree based router for ultra-fast path matching.
|
|
82
|
+
* Uses radix3 under the hood.
|
|
83
|
+
*/
|
|
84
|
+
|
|
85
|
+
interface MatchResult<E extends Env = Env> {
|
|
86
|
+
handler: Handler<E>;
|
|
87
|
+
params: Record<string, string>;
|
|
88
|
+
}
|
|
89
|
+
declare class Router<E extends Env = Env> {
|
|
90
|
+
private radix;
|
|
91
|
+
private routes;
|
|
92
|
+
constructor();
|
|
93
|
+
/**
|
|
94
|
+
* Add a route
|
|
95
|
+
*/
|
|
96
|
+
add(method: HttpMethod | '*', path: string, handler: Handler<E>): void;
|
|
97
|
+
/**
|
|
98
|
+
* Match a request to a route
|
|
99
|
+
*/
|
|
100
|
+
match(method: HttpMethod, path: string): MatchResult<E> | null;
|
|
101
|
+
/**
|
|
102
|
+
* Get all registered routes
|
|
103
|
+
*/
|
|
104
|
+
getRoutes(): Route<E>[];
|
|
105
|
+
/**
|
|
106
|
+
* Normalize path
|
|
107
|
+
*/
|
|
108
|
+
private normalizePath;
|
|
109
|
+
/**
|
|
110
|
+
* Extract params from matched path
|
|
111
|
+
* @deprecated radix3 handles param extraction internally
|
|
112
|
+
*/
|
|
113
|
+
private _extractParams;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export { Context, Env, FetchOptions, FlightHttp, FlightHttpServer, Handler, HandlerOrMiddleware, HttpMethod, ListenOptions, Middleware, Route, Router, ServerOptions, createContext, createServer };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
import { createRouter } from 'radix3';
|
|
2
|
+
|
|
3
|
+
// src/router.ts
|
|
4
|
+
var Router = class {
|
|
5
|
+
radix;
|
|
6
|
+
routes = [];
|
|
7
|
+
constructor() {
|
|
8
|
+
this.radix = createRouter();
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Add a route
|
|
12
|
+
*/
|
|
13
|
+
add(method, path, handler) {
|
|
14
|
+
const normalizedPath = this.normalizePath(path);
|
|
15
|
+
let node = this.radix.lookup(normalizedPath);
|
|
16
|
+
if (!node) {
|
|
17
|
+
node = { handlers: /* @__PURE__ */ new Map() };
|
|
18
|
+
this.radix.insert(normalizedPath, node);
|
|
19
|
+
}
|
|
20
|
+
node.handlers.set(method, handler);
|
|
21
|
+
this.routes.push({
|
|
22
|
+
method,
|
|
23
|
+
path: normalizedPath,
|
|
24
|
+
handler,
|
|
25
|
+
middleware: []
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Match a request to a route
|
|
30
|
+
*/
|
|
31
|
+
match(method, path) {
|
|
32
|
+
const normalizedPath = this.normalizePath(path);
|
|
33
|
+
const result = this.radix.lookup(normalizedPath);
|
|
34
|
+
if (!result) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
let handler = result.handlers.get(method);
|
|
38
|
+
if (!handler) {
|
|
39
|
+
handler = result.handlers.get("*");
|
|
40
|
+
}
|
|
41
|
+
if (!handler) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
const params = result.params || {};
|
|
45
|
+
return { handler, params };
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Get all registered routes
|
|
49
|
+
*/
|
|
50
|
+
getRoutes() {
|
|
51
|
+
return [...this.routes];
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Normalize path
|
|
55
|
+
*/
|
|
56
|
+
normalizePath(path) {
|
|
57
|
+
if (!path.startsWith("/")) {
|
|
58
|
+
path = "/" + path;
|
|
59
|
+
}
|
|
60
|
+
if (path !== "/" && path.endsWith("/")) {
|
|
61
|
+
path = path.slice(0, -1);
|
|
62
|
+
}
|
|
63
|
+
path = path.replace(/\[([^\]]+)\]/g, ":$1");
|
|
64
|
+
path = path.replace(/\[\.\.\.([\w]+)\]/g, "**");
|
|
65
|
+
return path;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Extract params from matched path
|
|
69
|
+
* @deprecated radix3 handles param extraction internally
|
|
70
|
+
*/
|
|
71
|
+
_extractParams(pattern, actualPath) {
|
|
72
|
+
const params = {};
|
|
73
|
+
const patternParts = pattern.split("/");
|
|
74
|
+
const actualParts = actualPath.split("/");
|
|
75
|
+
for (let i = 0; i < patternParts.length; i++) {
|
|
76
|
+
const patternPart = patternParts[i];
|
|
77
|
+
if (!patternPart) continue;
|
|
78
|
+
if (patternPart.startsWith(":")) {
|
|
79
|
+
const paramName = patternPart.slice(1);
|
|
80
|
+
params[paramName] = actualParts[i] || "";
|
|
81
|
+
} else if (patternPart === "**") {
|
|
82
|
+
const paramName = "slug";
|
|
83
|
+
params[paramName] = actualParts.slice(i).join("/");
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return params;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// src/context.ts
|
|
92
|
+
var ContextImpl = class {
|
|
93
|
+
// Core properties
|
|
94
|
+
req;
|
|
95
|
+
env;
|
|
96
|
+
params;
|
|
97
|
+
variables;
|
|
98
|
+
// Cached values (lazy initialization)
|
|
99
|
+
_url;
|
|
100
|
+
_cookies;
|
|
101
|
+
_variables;
|
|
102
|
+
constructor(request, params, env) {
|
|
103
|
+
this.req = request;
|
|
104
|
+
this.params = params;
|
|
105
|
+
this.env = env;
|
|
106
|
+
this._variables = {};
|
|
107
|
+
this.variables = this._variables;
|
|
108
|
+
}
|
|
109
|
+
// ========================================================================
|
|
110
|
+
// Cached Getters (Lazy initialization)
|
|
111
|
+
// ========================================================================
|
|
112
|
+
/**
|
|
113
|
+
* Get parsed URL (cached after first access)
|
|
114
|
+
*/
|
|
115
|
+
get url() {
|
|
116
|
+
return this._url ??= new URL(this.req.url);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Get pathname from cached URL
|
|
120
|
+
*/
|
|
121
|
+
get pathname() {
|
|
122
|
+
return this.url.pathname;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Get query string from cached URL
|
|
126
|
+
*/
|
|
127
|
+
get query() {
|
|
128
|
+
return this.url.searchParams;
|
|
129
|
+
}
|
|
130
|
+
// ========================================================================
|
|
131
|
+
// Response Helpers (on prototype, not recreated)
|
|
132
|
+
// ========================================================================
|
|
133
|
+
json(data, status = 200) {
|
|
134
|
+
return new Response(JSON.stringify(data), {
|
|
135
|
+
status,
|
|
136
|
+
headers: { "Content-Type": "application/json; charset=utf-8" }
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
text(text, status = 200) {
|
|
140
|
+
return new Response(text, {
|
|
141
|
+
status,
|
|
142
|
+
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
html(html, status = 200) {
|
|
146
|
+
return new Response(html, {
|
|
147
|
+
status,
|
|
148
|
+
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
redirect(url, status = 302) {
|
|
152
|
+
return new Response(null, {
|
|
153
|
+
status,
|
|
154
|
+
headers: { Location: url }
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
// ========================================================================
|
|
158
|
+
// Variable Getter/Setter
|
|
159
|
+
// ========================================================================
|
|
160
|
+
get(key) {
|
|
161
|
+
return this._variables[key];
|
|
162
|
+
}
|
|
163
|
+
set(key, value) {
|
|
164
|
+
this._variables[key] = value;
|
|
165
|
+
}
|
|
166
|
+
// ========================================================================
|
|
167
|
+
// Request Helpers
|
|
168
|
+
// ========================================================================
|
|
169
|
+
header(name) {
|
|
170
|
+
return this.req.headers.get(name);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Get cookie value (cookies parsed once, cached)
|
|
174
|
+
*/
|
|
175
|
+
cookie(name) {
|
|
176
|
+
if (!this._cookies) {
|
|
177
|
+
const cookieHeader = this.req.headers.get("cookie");
|
|
178
|
+
this._cookies = cookieHeader ? parseCookies(cookieHeader) : {};
|
|
179
|
+
}
|
|
180
|
+
return this._cookies[name];
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
function parseCookies(cookieHeader) {
|
|
184
|
+
const cookies = {};
|
|
185
|
+
const pairs = cookieHeader.split(";");
|
|
186
|
+
for (let i = 0; i < pairs.length; i++) {
|
|
187
|
+
const pair = pairs[i].trim();
|
|
188
|
+
const eqIdx = pair.indexOf("=");
|
|
189
|
+
if (eqIdx > 0) {
|
|
190
|
+
const name = pair.slice(0, eqIdx);
|
|
191
|
+
const value = pair.slice(eqIdx + 1);
|
|
192
|
+
cookies[name] = value;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return cookies;
|
|
196
|
+
}
|
|
197
|
+
function createContext(request, params = {}, env = {}) {
|
|
198
|
+
return new ContextImpl(request, params, env);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// src/server.ts
|
|
202
|
+
function compileMiddlewareChain(middlewares, handler) {
|
|
203
|
+
if (middlewares.length === 0) {
|
|
204
|
+
return handler;
|
|
205
|
+
}
|
|
206
|
+
if (middlewares.length === 1) {
|
|
207
|
+
const mw = middlewares[0];
|
|
208
|
+
return (c) => mw(c, () => Promise.resolve(handler(c)));
|
|
209
|
+
}
|
|
210
|
+
return (c) => {
|
|
211
|
+
let index = 0;
|
|
212
|
+
const dispatch = () => {
|
|
213
|
+
if (index < middlewares.length) {
|
|
214
|
+
const middleware = middlewares[index++];
|
|
215
|
+
return Promise.resolve(middleware(c, dispatch));
|
|
216
|
+
}
|
|
217
|
+
return Promise.resolve(handler(c));
|
|
218
|
+
};
|
|
219
|
+
return dispatch();
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
var FlightHttp = class _FlightHttp {
|
|
223
|
+
router;
|
|
224
|
+
globalMiddleware = [];
|
|
225
|
+
notFoundHandler;
|
|
226
|
+
errorHandler;
|
|
227
|
+
basePath;
|
|
228
|
+
// Pre-compiled handlers cache (updated when middleware changes)
|
|
229
|
+
compiledNotFoundHandler;
|
|
230
|
+
middlewareVersion = 0;
|
|
231
|
+
lastCompiledVersion = -1;
|
|
232
|
+
constructor(options = {}) {
|
|
233
|
+
this.router = new Router();
|
|
234
|
+
this.basePath = options.basePath || "";
|
|
235
|
+
this.notFoundHandler = (c) => c.json(
|
|
236
|
+
{ error: "Not Found", path: c.pathname },
|
|
237
|
+
404
|
|
238
|
+
);
|
|
239
|
+
this.errorHandler = (error, c) => c.json(
|
|
240
|
+
{ error: error.message || "Internal Server Error" },
|
|
241
|
+
500
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
// ========================================================================
|
|
245
|
+
// Route Registration
|
|
246
|
+
// ========================================================================
|
|
247
|
+
get(path, ...handlers) {
|
|
248
|
+
return this.addRoute("GET", path, handlers);
|
|
249
|
+
}
|
|
250
|
+
post(path, ...handlers) {
|
|
251
|
+
return this.addRoute("POST", path, handlers);
|
|
252
|
+
}
|
|
253
|
+
put(path, ...handlers) {
|
|
254
|
+
return this.addRoute("PUT", path, handlers);
|
|
255
|
+
}
|
|
256
|
+
delete(path, ...handlers) {
|
|
257
|
+
return this.addRoute("DELETE", path, handlers);
|
|
258
|
+
}
|
|
259
|
+
patch(path, ...handlers) {
|
|
260
|
+
return this.addRoute("PATCH", path, handlers);
|
|
261
|
+
}
|
|
262
|
+
options(path, ...handlers) {
|
|
263
|
+
return this.addRoute("OPTIONS", path, handlers);
|
|
264
|
+
}
|
|
265
|
+
head(path, ...handlers) {
|
|
266
|
+
return this.addRoute("HEAD", path, handlers);
|
|
267
|
+
}
|
|
268
|
+
all(path, ...handlers) {
|
|
269
|
+
return this.addRoute("*", path, handlers);
|
|
270
|
+
}
|
|
271
|
+
// ========================================================================
|
|
272
|
+
// Middleware
|
|
273
|
+
// ========================================================================
|
|
274
|
+
use(...handlers) {
|
|
275
|
+
this.globalMiddleware.push(...handlers);
|
|
276
|
+
this.middlewareVersion++;
|
|
277
|
+
return this;
|
|
278
|
+
}
|
|
279
|
+
// ========================================================================
|
|
280
|
+
// Sub-routing
|
|
281
|
+
// ========================================================================
|
|
282
|
+
route(path, router) {
|
|
283
|
+
if (router instanceof _FlightHttp) {
|
|
284
|
+
const routes = router.router.getRoutes();
|
|
285
|
+
for (const route of routes) {
|
|
286
|
+
const fullPath = this.basePath + path + route.path;
|
|
287
|
+
this.router.add(route.method, fullPath, route.handler);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return this;
|
|
291
|
+
}
|
|
292
|
+
// ========================================================================
|
|
293
|
+
// Error Handling
|
|
294
|
+
// ========================================================================
|
|
295
|
+
notFound(handler) {
|
|
296
|
+
this.notFoundHandler = handler;
|
|
297
|
+
this.compiledNotFoundHandler = void 0;
|
|
298
|
+
return this;
|
|
299
|
+
}
|
|
300
|
+
onError(handler) {
|
|
301
|
+
this.errorHandler = handler;
|
|
302
|
+
return this;
|
|
303
|
+
}
|
|
304
|
+
// ========================================================================
|
|
305
|
+
// Core Fetch Handler (Optimized Hot Path)
|
|
306
|
+
// ========================================================================
|
|
307
|
+
async fetch(request, options = {}) {
|
|
308
|
+
const url = request.url;
|
|
309
|
+
const pathStart = url.indexOf("/", url.indexOf("//") + 2);
|
|
310
|
+
const queryStart = url.indexOf("?", pathStart);
|
|
311
|
+
const path = queryStart === -1 ? url.slice(pathStart) : url.slice(pathStart, queryStart);
|
|
312
|
+
const method = request.method;
|
|
313
|
+
const match = this.router.match(method, path);
|
|
314
|
+
const context = createContext(
|
|
315
|
+
request,
|
|
316
|
+
match?.params || {},
|
|
317
|
+
options.env || {}
|
|
318
|
+
);
|
|
319
|
+
try {
|
|
320
|
+
if (match) {
|
|
321
|
+
if (this.globalMiddleware.length === 0) {
|
|
322
|
+
return await match.handler(context);
|
|
323
|
+
} else {
|
|
324
|
+
const composed = compileMiddlewareChain(
|
|
325
|
+
this.globalMiddleware,
|
|
326
|
+
match.handler
|
|
327
|
+
);
|
|
328
|
+
return await composed(context);
|
|
329
|
+
}
|
|
330
|
+
} else {
|
|
331
|
+
return await this.getCompiledNotFoundHandler()(context);
|
|
332
|
+
}
|
|
333
|
+
} catch (error) {
|
|
334
|
+
console.error("[Flight HTTP] Error:", error);
|
|
335
|
+
return await this.errorHandler(
|
|
336
|
+
error instanceof Error ? error : new Error(String(error)),
|
|
337
|
+
context
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
// ========================================================================
|
|
342
|
+
// Listen (Node.js adapter will override this)
|
|
343
|
+
// ========================================================================
|
|
344
|
+
listen(options) {
|
|
345
|
+
const port = typeof options === "number" ? options : options?.port || 3e3;
|
|
346
|
+
console.log(`[Flight HTTP] Call adapter-specific listen() to start server on port ${port}`);
|
|
347
|
+
console.log('[Flight HTTP] Import from "@flightdev/http/node" or "@flightdev/http/bun"');
|
|
348
|
+
}
|
|
349
|
+
// ========================================================================
|
|
350
|
+
// Private Methods
|
|
351
|
+
// ========================================================================
|
|
352
|
+
addRoute(method, path, handlers) {
|
|
353
|
+
const mainHandler = handlers[handlers.length - 1];
|
|
354
|
+
const routeMiddleware = handlers.slice(0, -1);
|
|
355
|
+
const fullPath = this.basePath + path;
|
|
356
|
+
const compiledHandler = routeMiddleware.length > 0 ? compileMiddlewareChain(routeMiddleware, mainHandler) : mainHandler;
|
|
357
|
+
this.router.add(method, fullPath, compiledHandler);
|
|
358
|
+
return this;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Get pre-compiled not found handler (with global middleware)
|
|
362
|
+
*/
|
|
363
|
+
getCompiledNotFoundHandler() {
|
|
364
|
+
if (this.lastCompiledVersion !== this.middlewareVersion || !this.compiledNotFoundHandler) {
|
|
365
|
+
this.compiledNotFoundHandler = this.globalMiddleware.length > 0 ? compileMiddlewareChain(this.globalMiddleware, this.notFoundHandler) : this.notFoundHandler;
|
|
366
|
+
this.lastCompiledVersion = this.middlewareVersion;
|
|
367
|
+
}
|
|
368
|
+
return this.compiledNotFoundHandler;
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
function createServer(options = {}) {
|
|
372
|
+
return new FlightHttp(options);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
export { FlightHttp, Router, createContext, createServer };
|
|
376
|
+
//# sourceMappingURL=index.js.map
|
|
377
|
+
//# sourceMappingURL=index.js.map
|