Init: RoggioApp Architecture, Prisma Schema, API MVP
This commit is contained in:
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 - present, Yusuke Wada and Hono contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+396
@@ -0,0 +1,396 @@
|
||||
# Node.js Adapter for Hono
|
||||
|
||||
This adapter `@hono/node-server` allows you to run your Hono application on Node.js.
|
||||
Initially, Hono wasn't designed for Node.js, but with this adapter, you can now use Hono on Node.js. It utilizes web standard APIs implemented in Node.js.
|
||||
|
||||
## Benchmarks
|
||||
|
||||
Hono is 4.1 times faster than Express.
|
||||
|
||||
Express:
|
||||
|
||||
```txt
|
||||
$ bombardier -d 10s --fasthttp http://localhost:3000/
|
||||
|
||||
Statistics Avg Stdev Max
|
||||
Reqs/sec 20803.37 1713.06 24910.85
|
||||
Latency 6.01ms 5.21ms 451.37ms
|
||||
HTTP codes:
|
||||
1xx - 0, 2xx - 208131, 3xx - 0, 4xx - 0, 5xx - 0
|
||||
others - 0
|
||||
Throughput: 5.75MB/s
|
||||
```
|
||||
|
||||
Hono + `@hono/node-server`:
|
||||
|
||||
```txt
|
||||
$ bombardier -d 10s --fasthttp http://localhost:3000/
|
||||
|
||||
Statistics Avg Stdev Max
|
||||
Reqs/sec 85405.51 7250.65 102658.51
|
||||
Latency 1.46ms 1.00ms 149.95ms
|
||||
HTTP codes:
|
||||
1xx - 0, 2xx - 854120, 3xx - 0, 4xx - 0, 5xx - 0
|
||||
others - 0
|
||||
Throughput: 18.49MB/s
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
It works on Node.js versions greater than 20.x.
|
||||
|
||||
## Installation
|
||||
|
||||
You can install it from the npm registry with `npm` command:
|
||||
|
||||
```sh
|
||||
npm install @hono/node-server
|
||||
```
|
||||
|
||||
Or use `yarn`:
|
||||
|
||||
```sh
|
||||
yarn add @hono/node-server
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Just import `@hono/node-server` at the top and write the code as usual.
|
||||
The same code that runs on Cloudflare Workers, Deno, and Bun will work.
|
||||
|
||||
```ts
|
||||
import { serve } from '@hono/node-server'
|
||||
import { Hono } from 'hono'
|
||||
|
||||
const app = new Hono()
|
||||
app.get('/', (c) => c.text('Hono meets Node.js'))
|
||||
|
||||
serve(app, (info) => {
|
||||
console.log(`Listening on http://localhost:${info.port}`) // Listening on http://localhost:3000
|
||||
})
|
||||
```
|
||||
|
||||
## WebSocket
|
||||
|
||||
You can upgrade WebSocket connections with `upgradeWebSocket` from `@hono/node-server`.
|
||||
To enable this, install `ws` (and `@types/ws`) in your project, then create and provide a `WebSocketServer` as shown in the example below.
|
||||
|
||||
```ts
|
||||
import { serve, upgradeWebSocket } from '@hono/node-server'
|
||||
import { WebSocketServer } from 'ws'
|
||||
import { Hono } from 'hono'
|
||||
|
||||
const app = new Hono()
|
||||
|
||||
app.get(
|
||||
'/ws',
|
||||
upgradeWebSocket(() => ({
|
||||
onMessage(event, ws) {
|
||||
ws.send(event.data)
|
||||
},
|
||||
}))
|
||||
)
|
||||
|
||||
const wss = new WebSocketServer({ noServer: true }) // important to create with `noServer: true`
|
||||
serve({
|
||||
fetch: app.fetch,
|
||||
websocket: { server: wss },
|
||||
})
|
||||
```
|
||||
|
||||
For example, run it using `ts-node`. Then an HTTP server will be launched. The default port is `3000`.
|
||||
|
||||
```sh
|
||||
ts-node ./index.ts
|
||||
```
|
||||
|
||||
Open `http://localhost:3000` with your browser.
|
||||
|
||||
## Options
|
||||
|
||||
### `port`
|
||||
|
||||
```ts
|
||||
serve({
|
||||
fetch: app.fetch,
|
||||
port: 8787, // Port number, default is 3000
|
||||
})
|
||||
```
|
||||
|
||||
### `createServer`
|
||||
|
||||
```ts
|
||||
import { createServer } from 'node:https'
|
||||
import fs from 'node:fs'
|
||||
|
||||
//...
|
||||
|
||||
serve({
|
||||
fetch: app.fetch,
|
||||
createServer: createServer,
|
||||
serverOptions: {
|
||||
key: fs.readFileSync('test/fixtures/keys/agent1-key.pem'),
|
||||
cert: fs.readFileSync('test/fixtures/keys/agent1-cert.pem'),
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### `overrideGlobalObjects`
|
||||
|
||||
The default value is `true`. The Node.js Adapter rewrites the global Request/Response and uses a lightweight Request/Response to improve performance. If you don't want to do that, set `false`.
|
||||
|
||||
```ts
|
||||
serve({
|
||||
fetch: app.fetch,
|
||||
overrideGlobalObjects: false,
|
||||
})
|
||||
```
|
||||
|
||||
### `autoCleanupIncoming`
|
||||
|
||||
The default value is `true`. The Node.js Adapter automatically cleans up (explicitly call `destroy()` method) if application is not finished to consume the incoming request. If you don't want to do that, set `false`.
|
||||
|
||||
If the application accepts connections from arbitrary clients, this cleanup must be done otherwise incomplete requests from clients may cause the application to stop responding. If your application only accepts connections from trusted clients, such as in a reverse proxy environment and there is no process that returns a response without reading the body of the POST request all the way through, you can improve performance by setting it to `false`.
|
||||
|
||||
```ts
|
||||
serve({
|
||||
fetch: app.fetch,
|
||||
autoCleanupIncoming: false,
|
||||
})
|
||||
```
|
||||
|
||||
### `websocket`
|
||||
|
||||
provide a websocket server to enable websocket support.
|
||||
|
||||
```ts
|
||||
import { serve, upgradeWebSocket } from '@hono/node-server'
|
||||
import { WebSocketServer } from 'ws'
|
||||
|
||||
// ...
|
||||
const wss = new WebSocketServer({ noServer: true })
|
||||
|
||||
serve({
|
||||
fetch: app.fetch,
|
||||
websocket: { server: wss },
|
||||
})
|
||||
```
|
||||
|
||||
## Middleware
|
||||
|
||||
Most built-in middleware also works with Node.js.
|
||||
Read [the documentation](https://hono.dev/middleware/builtin/basic-auth) and use the Middleware of your liking.
|
||||
|
||||
```ts
|
||||
import { serve } from '@hono/node-server'
|
||||
import { Hono } from 'hono'
|
||||
import { prettyJSON } from 'hono/pretty-json'
|
||||
|
||||
const app = new Hono()
|
||||
|
||||
app.get('*', prettyJSON())
|
||||
app.get('/', (c) => c.json({ 'Hono meets': 'Node.js' }))
|
||||
|
||||
serve(app)
|
||||
```
|
||||
|
||||
## Serve Static Middleware
|
||||
|
||||
Use Serve Static Middleware that has been created for Node.js.
|
||||
|
||||
```ts
|
||||
import { serveStatic } from '@hono/node-server/serve-static'
|
||||
|
||||
//...
|
||||
|
||||
app.use('/static/*', serveStatic({ root: './' }))
|
||||
```
|
||||
|
||||
If using a relative path, `root` will be relative to the current working directory from which the app was started.
|
||||
|
||||
This can cause confusion when running your application locally.
|
||||
|
||||
Imagine your project structure is:
|
||||
|
||||
```
|
||||
my-hono-project/
|
||||
src/
|
||||
index.ts
|
||||
static/
|
||||
index.html
|
||||
```
|
||||
|
||||
Typically, you would run your app from the project's root directory (`my-hono-project`),
|
||||
so you would need the following code to serve the `static` folder:
|
||||
|
||||
```ts
|
||||
app.use('/static/*', serveStatic({ root: './static' }))
|
||||
```
|
||||
|
||||
Notice that `root` here is not relative to `src/index.ts`, rather to `my-hono-project`.
|
||||
|
||||
### Options
|
||||
|
||||
#### `rewriteRequestPath`
|
||||
|
||||
If you want to serve files in `./.foojs` with the request path `/__foo/*`, you can write like the following.
|
||||
|
||||
```ts
|
||||
app.use(
|
||||
'/__foo/*',
|
||||
serveStatic({
|
||||
root: './.foojs/',
|
||||
rewriteRequestPath: (path: string) => path.replace(/^\/__foo/, ''),
|
||||
})
|
||||
)
|
||||
```
|
||||
|
||||
#### `onFound`
|
||||
|
||||
You can specify handling when the requested file is found with `onFound`.
|
||||
|
||||
```ts
|
||||
app.use(
|
||||
'/static/*',
|
||||
serveStatic({
|
||||
// ...
|
||||
onFound: (_path, c) => {
|
||||
c.header('Cache-Control', `public, immutable, max-age=31536000`)
|
||||
},
|
||||
})
|
||||
)
|
||||
```
|
||||
|
||||
#### `onNotFound`
|
||||
|
||||
The `onNotFound` is useful for debugging. You can write a handle for when a file is not found.
|
||||
|
||||
```ts
|
||||
app.use(
|
||||
'/static/*',
|
||||
serveStatic({
|
||||
root: './non-existent-dir',
|
||||
onNotFound: (path, c) => {
|
||||
console.log(`${path} is not found, request to ${c.req.path}`)
|
||||
},
|
||||
})
|
||||
)
|
||||
```
|
||||
|
||||
#### `precompressed`
|
||||
|
||||
The `precompressed` option checks if files with extensions like `.br` or `.gz` are available and serves them based on the `Accept-Encoding` header. It prioritizes Brotli, then Zstd, and Gzip. If none are available, it serves the original file.
|
||||
|
||||
```ts
|
||||
app.use(
|
||||
'/static/*',
|
||||
serveStatic({
|
||||
precompressed: true,
|
||||
})
|
||||
)
|
||||
```
|
||||
|
||||
## ConnInfo Helper
|
||||
|
||||
You can use the [ConnInfo Helper](https://hono.dev/docs/helpers/conninfo) by importing `getConnInfo` from `@hono/node-server/conninfo`.
|
||||
|
||||
```ts
|
||||
import { getConnInfo } from '@hono/node-server/conninfo'
|
||||
|
||||
app.get('/', (c) => {
|
||||
const info = getConnInfo(c) // info is `ConnInfo`
|
||||
return c.text(`Your remote address is ${info.remote.address}`)
|
||||
})
|
||||
```
|
||||
|
||||
## Accessing Node.js API
|
||||
|
||||
You can access the Node.js API from `c.env` in Node.js. For example, if you want to specify a type, you can write the following.
|
||||
|
||||
```ts
|
||||
import { serve } from '@hono/node-server'
|
||||
import type { HttpBindings } from '@hono/node-server'
|
||||
import { Hono } from 'hono'
|
||||
|
||||
const app = new Hono<{ Bindings: HttpBindings }>()
|
||||
|
||||
app.get('/', (c) => {
|
||||
return c.json({
|
||||
remoteAddress: c.env.incoming.socket.remoteAddress,
|
||||
})
|
||||
})
|
||||
|
||||
serve(app)
|
||||
```
|
||||
|
||||
The APIs that you can get from `c.env` are as follows.
|
||||
|
||||
```ts
|
||||
type HttpBindings = {
|
||||
incoming: IncomingMessage
|
||||
outgoing: ServerResponse
|
||||
}
|
||||
|
||||
type Http2Bindings = {
|
||||
incoming: Http2ServerRequest
|
||||
outgoing: Http2ServerResponse
|
||||
}
|
||||
```
|
||||
|
||||
## Direct response from Node.js API
|
||||
|
||||
You can directly respond to the client from the Node.js API.
|
||||
In that case, the response from Hono should be ignored, so return `RESPONSE_ALREADY_SENT`.
|
||||
|
||||
> [!NOTE]
|
||||
> This feature can be used when migrating existing Node.js applications to Hono, but we recommend using Hono's API for new applications.
|
||||
|
||||
```ts
|
||||
import { serve } from '@hono/node-server'
|
||||
import type { HttpBindings } from '@hono/node-server'
|
||||
import { RESPONSE_ALREADY_SENT } from '@hono/node-server/utils/response'
|
||||
import { Hono } from 'hono'
|
||||
|
||||
const app = new Hono<{ Bindings: HttpBindings }>()
|
||||
|
||||
app.get('/', (c) => {
|
||||
const { outgoing } = c.env
|
||||
outgoing.writeHead(200, { 'Content-Type': 'text/plain' })
|
||||
outgoing.end('Hello World\n')
|
||||
|
||||
return RESPONSE_ALREADY_SENT
|
||||
})
|
||||
|
||||
serve(app)
|
||||
```
|
||||
|
||||
## Listen to a UNIX domain socket
|
||||
|
||||
You can configure the HTTP server to listen to a UNIX domain socket instead of a TCP port.
|
||||
|
||||
```ts
|
||||
import { createAdaptorServer } from '@hono/node-server'
|
||||
|
||||
// ...
|
||||
|
||||
const socketPath = '/tmp/example.sock'
|
||||
|
||||
const server = createAdaptorServer(app)
|
||||
server.listen(socketPath, () => {
|
||||
console.log(`Listening on ${socketPath}`)
|
||||
})
|
||||
```
|
||||
|
||||
## Related projects
|
||||
|
||||
- Hono - <https://hono.dev>
|
||||
- Hono GitHub repository - <https://github.com/honojs/hono>
|
||||
|
||||
## Authors
|
||||
|
||||
- Yusuke Wada <https://github.com/yusukebe>
|
||||
- Taku Amano <https://github.com/usualoma>
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
||||
|
||||
//#region src/conninfo.ts
|
||||
/**
|
||||
* ConnInfo Helper for Node.js
|
||||
* @param c Context
|
||||
* @returns ConnInfo
|
||||
*/
|
||||
const getConnInfo = (c) => {
|
||||
const bindings = c.env.server ? c.env.server : c.env;
|
||||
const address = bindings.incoming.socket.remoteAddress;
|
||||
const port = bindings.incoming.socket.remotePort;
|
||||
const family = bindings.incoming.socket.remoteFamily;
|
||||
return { remote: {
|
||||
address,
|
||||
port,
|
||||
addressType: family === "IPv4" ? "IPv4" : family === "IPv6" ? "IPv6" : void 0
|
||||
} };
|
||||
};
|
||||
|
||||
//#endregion
|
||||
exports.getConnInfo = getConnInfo;
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import { GetConnInfo } from "hono/conninfo";
|
||||
|
||||
//#region src/conninfo.d.ts
|
||||
/**
|
||||
* ConnInfo Helper for Node.js
|
||||
* @param c Context
|
||||
* @returns ConnInfo
|
||||
*/
|
||||
declare const getConnInfo: GetConnInfo;
|
||||
//#endregion
|
||||
export { getConnInfo };
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import { GetConnInfo } from "hono/conninfo";
|
||||
|
||||
//#region src/conninfo.d.ts
|
||||
/**
|
||||
* ConnInfo Helper for Node.js
|
||||
* @param c Context
|
||||
* @returns ConnInfo
|
||||
*/
|
||||
declare const getConnInfo: GetConnInfo;
|
||||
//#endregion
|
||||
export { getConnInfo };
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
//#region src/conninfo.ts
|
||||
/**
|
||||
* ConnInfo Helper for Node.js
|
||||
* @param c Context
|
||||
* @returns ConnInfo
|
||||
*/
|
||||
const getConnInfo = (c) => {
|
||||
const bindings = c.env.server ? c.env.server : c.env;
|
||||
const address = bindings.incoming.socket.remoteAddress;
|
||||
const port = bindings.incoming.socket.remotePort;
|
||||
const family = bindings.incoming.socket.remoteFamily;
|
||||
return { remote: {
|
||||
address,
|
||||
port,
|
||||
addressType: family === "IPv4" ? "IPv4" : family === "IPv6" ? "IPv6" : void 0
|
||||
} };
|
||||
};
|
||||
|
||||
//#endregion
|
||||
export { getConnInfo };
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
//#region src/utils/response/constants.ts
|
||||
const X_ALREADY_SENT = "x-hono-already-sent";
|
||||
|
||||
//#endregion
|
||||
export { X_ALREADY_SENT as t };
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
|
||||
//#region src/utils/response/constants.ts
|
||||
const X_ALREADY_SENT = "x-hono-already-sent";
|
||||
|
||||
//#endregion
|
||||
Object.defineProperty(exports, 'X_ALREADY_SENT', {
|
||||
enumerable: true,
|
||||
get: function () {
|
||||
return X_ALREADY_SENT;
|
||||
}
|
||||
});
|
||||
+1006
File diff suppressed because it is too large
Load Diff
+73
@@ -0,0 +1,73 @@
|
||||
import { AddressInfo } from "node:net";
|
||||
import { WebSocket, WebSocketServer } from "ws";
|
||||
import { IncomingMessage, Server, ServerOptions, ServerResponse, createServer } from "node:http";
|
||||
import { Http2SecureServer, Http2Server, Http2ServerRequest, Http2ServerResponse, SecureServerOptions, ServerOptions as ServerOptions$1, createSecureServer, createServer as createServer$1 } from "node:http2";
|
||||
import { ServerOptions as ServerOptions$2, createServer as createServer$2 } from "node:https";
|
||||
import { UpgradeWebSocket } from "hono/ws";
|
||||
|
||||
//#region src/types.d.ts
|
||||
type HttpBindings = {
|
||||
incoming: IncomingMessage;
|
||||
outgoing: ServerResponse;
|
||||
};
|
||||
type Http2Bindings = {
|
||||
incoming: Http2ServerRequest;
|
||||
outgoing: Http2ServerResponse;
|
||||
};
|
||||
type FetchCallback = (request: Request, env: HttpBindings | Http2Bindings) => Promise<unknown> | unknown;
|
||||
type ServerType = Server | Http2Server | Http2SecureServer;
|
||||
type createHttpOptions = {
|
||||
serverOptions?: ServerOptions;
|
||||
createServer?: typeof createServer;
|
||||
};
|
||||
type createHttpsOptions = {
|
||||
serverOptions?: ServerOptions$2;
|
||||
createServer?: typeof createServer$2;
|
||||
};
|
||||
type createHttp2Options = {
|
||||
serverOptions?: ServerOptions$1;
|
||||
createServer?: typeof createServer$1;
|
||||
};
|
||||
type createSecureHttp2Options = {
|
||||
serverOptions?: SecureServerOptions;
|
||||
createServer?: typeof createSecureServer;
|
||||
};
|
||||
type ServerOptions$3 = createHttpOptions | createHttpsOptions | createHttp2Options | createSecureHttp2Options;
|
||||
type Options = {
|
||||
fetch: FetchCallback;
|
||||
overrideGlobalObjects?: boolean;
|
||||
autoCleanupIncoming?: boolean;
|
||||
port?: number;
|
||||
hostname?: string;
|
||||
websocket?: {
|
||||
server: WebSocketServer;
|
||||
};
|
||||
} & ServerOptions$3;
|
||||
type CustomErrorHandler = (err: unknown) => void | Response | Promise<void | Response>;
|
||||
//#endregion
|
||||
//#region src/server.d.ts
|
||||
declare const createAdaptorServer: (options: Options) => ServerType;
|
||||
declare const serve: (options: Options, listeningListener?: (info: AddressInfo) => void) => ServerType;
|
||||
//#endregion
|
||||
//#region src/websocket.d.ts
|
||||
type UpgradeWebSocketOptions = {
|
||||
onError: (err: unknown) => void;
|
||||
};
|
||||
declare const upgradeWebSocket: UpgradeWebSocket<WebSocket, UpgradeWebSocketOptions>;
|
||||
//#endregion
|
||||
//#region src/listener.d.ts
|
||||
declare const getRequestListener: (fetchCallback: FetchCallback, options?: {
|
||||
hostname?: string;
|
||||
errorHandler?: CustomErrorHandler;
|
||||
overrideGlobalObjects?: boolean;
|
||||
autoCleanupIncoming?: boolean;
|
||||
}) => (incoming: IncomingMessage | Http2ServerRequest, outgoing: ServerResponse | Http2ServerResponse) => Promise<void>;
|
||||
//#endregion
|
||||
//#region src/error.d.ts
|
||||
declare class RequestError extends Error {
|
||||
constructor(message: string, options?: {
|
||||
cause?: unknown;
|
||||
});
|
||||
}
|
||||
//#endregion
|
||||
export { type Http2Bindings, type HttpBindings, RequestError, type ServerType, createAdaptorServer, getRequestListener, serve, upgradeWebSocket };
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
import { IncomingMessage, Server, ServerOptions, ServerResponse, createServer } from "node:http";
|
||||
import { Http2SecureServer, Http2Server, Http2ServerRequest, Http2ServerResponse, SecureServerOptions, ServerOptions as ServerOptions$1, createSecureServer, createServer as createServer$1 } from "node:http2";
|
||||
import { UpgradeWebSocket } from "hono/ws";
|
||||
import { AddressInfo } from "node:net";
|
||||
import { WebSocket, WebSocketServer } from "ws";
|
||||
import { ServerOptions as ServerOptions$2, createServer as createServer$2 } from "node:https";
|
||||
|
||||
//#region src/types.d.ts
|
||||
type HttpBindings = {
|
||||
incoming: IncomingMessage;
|
||||
outgoing: ServerResponse;
|
||||
};
|
||||
type Http2Bindings = {
|
||||
incoming: Http2ServerRequest;
|
||||
outgoing: Http2ServerResponse;
|
||||
};
|
||||
type FetchCallback = (request: Request, env: HttpBindings | Http2Bindings) => Promise<unknown> | unknown;
|
||||
type ServerType = Server | Http2Server | Http2SecureServer;
|
||||
type createHttpOptions = {
|
||||
serverOptions?: ServerOptions;
|
||||
createServer?: typeof createServer;
|
||||
};
|
||||
type createHttpsOptions = {
|
||||
serverOptions?: ServerOptions$2;
|
||||
createServer?: typeof createServer$2;
|
||||
};
|
||||
type createHttp2Options = {
|
||||
serverOptions?: ServerOptions$1;
|
||||
createServer?: typeof createServer$1;
|
||||
};
|
||||
type createSecureHttp2Options = {
|
||||
serverOptions?: SecureServerOptions;
|
||||
createServer?: typeof createSecureServer;
|
||||
};
|
||||
type ServerOptions$3 = createHttpOptions | createHttpsOptions | createHttp2Options | createSecureHttp2Options;
|
||||
type Options = {
|
||||
fetch: FetchCallback;
|
||||
overrideGlobalObjects?: boolean;
|
||||
autoCleanupIncoming?: boolean;
|
||||
port?: number;
|
||||
hostname?: string;
|
||||
websocket?: {
|
||||
server: WebSocketServer;
|
||||
};
|
||||
} & ServerOptions$3;
|
||||
type CustomErrorHandler = (err: unknown) => void | Response | Promise<void | Response>;
|
||||
//#endregion
|
||||
//#region src/server.d.ts
|
||||
declare const createAdaptorServer: (options: Options) => ServerType;
|
||||
declare const serve: (options: Options, listeningListener?: (info: AddressInfo) => void) => ServerType;
|
||||
//#endregion
|
||||
//#region src/websocket.d.ts
|
||||
type UpgradeWebSocketOptions = {
|
||||
onError: (err: unknown) => void;
|
||||
};
|
||||
declare const upgradeWebSocket: UpgradeWebSocket<WebSocket, UpgradeWebSocketOptions>;
|
||||
//#endregion
|
||||
//#region src/listener.d.ts
|
||||
declare const getRequestListener: (fetchCallback: FetchCallback, options?: {
|
||||
hostname?: string;
|
||||
errorHandler?: CustomErrorHandler;
|
||||
overrideGlobalObjects?: boolean;
|
||||
autoCleanupIncoming?: boolean;
|
||||
}) => (incoming: IncomingMessage | Http2ServerRequest, outgoing: ServerResponse | Http2ServerResponse) => Promise<void>;
|
||||
//#endregion
|
||||
//#region src/error.d.ts
|
||||
declare class RequestError extends Error {
|
||||
constructor(message: string, options?: {
|
||||
cause?: unknown;
|
||||
});
|
||||
}
|
||||
//#endregion
|
||||
export { type Http2Bindings, type HttpBindings, RequestError, type ServerType, createAdaptorServer, getRequestListener, serve, upgradeWebSocket };
|
||||
+1001
File diff suppressed because it is too large
Load Diff
+135
@@ -0,0 +1,135 @@
|
||||
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
||||
let node_stream = require("node:stream");
|
||||
let hono_utils_mime = require("hono/utils/mime");
|
||||
let node_fs = require("node:fs");
|
||||
let node_path = require("node:path");
|
||||
let node_process = require("node:process");
|
||||
|
||||
//#region src/serve-static.ts
|
||||
const COMPRESSIBLE_CONTENT_TYPE_REGEX = /^\s*(?:text\/[^;\s]+|application\/(?:javascript|json|xml|xml-dtd|ecmascript|dart|postscript|rtf|tar|toml|vnd\.dart|vnd\.ms-fontobject|vnd\.ms-opentype|wasm|x-httpd-php|x-javascript|x-ns-proxy-autoconfig|x-sh|x-tar|x-virtualbox-hdd|x-virtualbox-ova|x-virtualbox-ovf|x-virtualbox-vbox|x-virtualbox-vdi|x-virtualbox-vhd|x-virtualbox-vmdk|x-www-form-urlencoded)|font\/(?:otf|ttf)|image\/(?:bmp|vnd\.adobe\.photoshop|vnd\.microsoft\.icon|vnd\.ms-dds|x-icon|x-ms-bmp)|message\/rfc822|model\/gltf-binary|x-shader\/x-fragment|x-shader\/x-vertex|[^;\s]+?\+(?:json|text|xml|yaml))(?:[;\s]|$)/i;
|
||||
const ENCODINGS = {
|
||||
br: ".br",
|
||||
zstd: ".zst",
|
||||
gzip: ".gz"
|
||||
};
|
||||
const ENCODINGS_ORDERED_KEYS = Object.keys(ENCODINGS);
|
||||
const pr54206Applied = () => {
|
||||
const [major, minor] = node_process.versions.node.split(".").map((component) => parseInt(component));
|
||||
return major >= 23 || major === 22 && minor >= 7 || major === 20 && minor >= 18;
|
||||
};
|
||||
const useReadableToWeb = pr54206Applied();
|
||||
const createStreamBody = (stream) => {
|
||||
if (useReadableToWeb) return node_stream.Readable.toWeb(stream);
|
||||
return new ReadableStream({
|
||||
start(controller) {
|
||||
stream.on("data", (chunk) => {
|
||||
controller.enqueue(chunk);
|
||||
});
|
||||
stream.on("error", (err) => {
|
||||
controller.error(err);
|
||||
});
|
||||
stream.on("end", () => {
|
||||
controller.close();
|
||||
});
|
||||
},
|
||||
cancel() {
|
||||
stream.destroy();
|
||||
}
|
||||
});
|
||||
};
|
||||
const getStats = (path) => {
|
||||
let stats;
|
||||
try {
|
||||
stats = (0, node_fs.statSync)(path);
|
||||
} catch {}
|
||||
return stats;
|
||||
};
|
||||
const tryDecode = (str, decoder) => {
|
||||
try {
|
||||
return decoder(str);
|
||||
} catch {
|
||||
return str.replace(/(?:%[0-9A-Fa-f]{2})+/g, (match) => {
|
||||
try {
|
||||
return decoder(match);
|
||||
} catch {
|
||||
return match;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
const tryDecodeURI = (str) => tryDecode(str, decodeURI);
|
||||
const serveStatic = (options = { root: "" }) => {
|
||||
const root = options.root || "";
|
||||
const optionPath = options.path;
|
||||
if (root !== "" && !(0, node_fs.existsSync)(root)) console.error(`serveStatic: root path '${root}' is not found, are you sure it's correct?`);
|
||||
return async (c, next) => {
|
||||
if (c.finalized) return next();
|
||||
let filename;
|
||||
if (optionPath) filename = optionPath;
|
||||
else try {
|
||||
filename = tryDecodeURI(c.req.path);
|
||||
if (/(?:^|[\/\\])\.{1,2}(?:$|[\/\\])|[\/\\]{2,}/.test(filename)) throw new Error();
|
||||
} catch {
|
||||
await options.onNotFound?.(c.req.path, c);
|
||||
return next();
|
||||
}
|
||||
let path = (0, node_path.join)(root, !optionPath && options.rewriteRequestPath ? options.rewriteRequestPath(filename, c) : filename);
|
||||
let stats = getStats(path);
|
||||
if (stats && stats.isDirectory()) {
|
||||
const indexFile = options.index ?? "index.html";
|
||||
path = (0, node_path.join)(path, indexFile);
|
||||
stats = getStats(path);
|
||||
}
|
||||
if (!stats) {
|
||||
await options.onNotFound?.(path, c);
|
||||
return next();
|
||||
}
|
||||
const mimeType = (0, hono_utils_mime.getMimeType)(path);
|
||||
c.header("Content-Type", mimeType || "application/octet-stream");
|
||||
if (options.precompressed && (!mimeType || COMPRESSIBLE_CONTENT_TYPE_REGEX.test(mimeType))) {
|
||||
const acceptEncodingSet = new Set(c.req.header("Accept-Encoding")?.split(",").map((encoding) => encoding.trim()));
|
||||
for (const encoding of ENCODINGS_ORDERED_KEYS) {
|
||||
if (!acceptEncodingSet.has(encoding)) continue;
|
||||
const precompressedStats = getStats(path + ENCODINGS[encoding]);
|
||||
if (precompressedStats) {
|
||||
c.header("Content-Encoding", encoding);
|
||||
c.header("Vary", "Accept-Encoding", { append: true });
|
||||
stats = precompressedStats;
|
||||
path = path + ENCODINGS[encoding];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
let result;
|
||||
const size = stats.size;
|
||||
const range = c.req.header("range") || "";
|
||||
if (c.req.method == "HEAD" || c.req.method == "OPTIONS") {
|
||||
c.header("Content-Length", size.toString());
|
||||
c.status(200);
|
||||
result = c.body(null);
|
||||
} else if (!range) {
|
||||
c.header("Content-Length", size.toString());
|
||||
result = c.body(createStreamBody((0, node_fs.createReadStream)(path)), 200);
|
||||
} else {
|
||||
c.header("Accept-Ranges", "bytes");
|
||||
c.header("Date", stats.birthtime.toUTCString());
|
||||
const parts = range.replace(/bytes=/, "").split("-", 2);
|
||||
const start = parseInt(parts[0], 10) || 0;
|
||||
let end = parseInt(parts[1], 10) || size - 1;
|
||||
if (size < end - start + 1) end = size - 1;
|
||||
const chunksize = end - start + 1;
|
||||
const stream = (0, node_fs.createReadStream)(path, {
|
||||
start,
|
||||
end
|
||||
});
|
||||
c.header("Content-Length", chunksize.toString());
|
||||
c.header("Content-Range", `bytes ${start}-${end}/${stats.size}`);
|
||||
result = c.body(createStreamBody(stream), 206);
|
||||
}
|
||||
await options.onFound?.(path, c);
|
||||
return result;
|
||||
};
|
||||
};
|
||||
|
||||
//#endregion
|
||||
exports.serveStatic = serveStatic;
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
import { Context, Env, MiddlewareHandler } from "hono";
|
||||
|
||||
//#region src/serve-static.d.ts
|
||||
type ServeStaticOptions<E extends Env = Env> = {
|
||||
/**
|
||||
* Root path, relative to current working directory from which the app was started. Absolute paths are not supported.
|
||||
*/
|
||||
root?: string;
|
||||
path?: string;
|
||||
index?: string;
|
||||
precompressed?: boolean;
|
||||
rewriteRequestPath?: (path: string, c: Context<E>) => string;
|
||||
onFound?: (path: string, c: Context<E>) => void | Promise<void>;
|
||||
onNotFound?: (path: string, c: Context<E>) => void | Promise<void>;
|
||||
};
|
||||
declare const serveStatic: <E extends Env = any>(options?: ServeStaticOptions<E>) => MiddlewareHandler<E>;
|
||||
//#endregion
|
||||
export { ServeStaticOptions, serveStatic };
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
import { Context, Env, MiddlewareHandler } from "hono";
|
||||
|
||||
//#region src/serve-static.d.ts
|
||||
type ServeStaticOptions<E extends Env = Env> = {
|
||||
/**
|
||||
* Root path, relative to current working directory from which the app was started. Absolute paths are not supported.
|
||||
*/
|
||||
root?: string;
|
||||
path?: string;
|
||||
index?: string;
|
||||
precompressed?: boolean;
|
||||
rewriteRequestPath?: (path: string, c: Context<E>) => string;
|
||||
onFound?: (path: string, c: Context<E>) => void | Promise<void>;
|
||||
onNotFound?: (path: string, c: Context<E>) => void | Promise<void>;
|
||||
};
|
||||
declare const serveStatic: <E extends Env = any>(options?: ServeStaticOptions<E>) => MiddlewareHandler<E>;
|
||||
//#endregion
|
||||
export { ServeStaticOptions, serveStatic };
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
import { Readable } from "node:stream";
|
||||
import { getMimeType } from "hono/utils/mime";
|
||||
import { createReadStream, existsSync, statSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { versions } from "node:process";
|
||||
|
||||
//#region src/serve-static.ts
|
||||
const COMPRESSIBLE_CONTENT_TYPE_REGEX = /^\s*(?:text\/[^;\s]+|application\/(?:javascript|json|xml|xml-dtd|ecmascript|dart|postscript|rtf|tar|toml|vnd\.dart|vnd\.ms-fontobject|vnd\.ms-opentype|wasm|x-httpd-php|x-javascript|x-ns-proxy-autoconfig|x-sh|x-tar|x-virtualbox-hdd|x-virtualbox-ova|x-virtualbox-ovf|x-virtualbox-vbox|x-virtualbox-vdi|x-virtualbox-vhd|x-virtualbox-vmdk|x-www-form-urlencoded)|font\/(?:otf|ttf)|image\/(?:bmp|vnd\.adobe\.photoshop|vnd\.microsoft\.icon|vnd\.ms-dds|x-icon|x-ms-bmp)|message\/rfc822|model\/gltf-binary|x-shader\/x-fragment|x-shader\/x-vertex|[^;\s]+?\+(?:json|text|xml|yaml))(?:[;\s]|$)/i;
|
||||
const ENCODINGS = {
|
||||
br: ".br",
|
||||
zstd: ".zst",
|
||||
gzip: ".gz"
|
||||
};
|
||||
const ENCODINGS_ORDERED_KEYS = Object.keys(ENCODINGS);
|
||||
const pr54206Applied = () => {
|
||||
const [major, minor] = versions.node.split(".").map((component) => parseInt(component));
|
||||
return major >= 23 || major === 22 && minor >= 7 || major === 20 && minor >= 18;
|
||||
};
|
||||
const useReadableToWeb = pr54206Applied();
|
||||
const createStreamBody = (stream) => {
|
||||
if (useReadableToWeb) return Readable.toWeb(stream);
|
||||
return new ReadableStream({
|
||||
start(controller) {
|
||||
stream.on("data", (chunk) => {
|
||||
controller.enqueue(chunk);
|
||||
});
|
||||
stream.on("error", (err) => {
|
||||
controller.error(err);
|
||||
});
|
||||
stream.on("end", () => {
|
||||
controller.close();
|
||||
});
|
||||
},
|
||||
cancel() {
|
||||
stream.destroy();
|
||||
}
|
||||
});
|
||||
};
|
||||
const getStats = (path) => {
|
||||
let stats;
|
||||
try {
|
||||
stats = statSync(path);
|
||||
} catch {}
|
||||
return stats;
|
||||
};
|
||||
const tryDecode = (str, decoder) => {
|
||||
try {
|
||||
return decoder(str);
|
||||
} catch {
|
||||
return str.replace(/(?:%[0-9A-Fa-f]{2})+/g, (match) => {
|
||||
try {
|
||||
return decoder(match);
|
||||
} catch {
|
||||
return match;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
const tryDecodeURI = (str) => tryDecode(str, decodeURI);
|
||||
const serveStatic = (options = { root: "" }) => {
|
||||
const root = options.root || "";
|
||||
const optionPath = options.path;
|
||||
if (root !== "" && !existsSync(root)) console.error(`serveStatic: root path '${root}' is not found, are you sure it's correct?`);
|
||||
return async (c, next) => {
|
||||
if (c.finalized) return next();
|
||||
let filename;
|
||||
if (optionPath) filename = optionPath;
|
||||
else try {
|
||||
filename = tryDecodeURI(c.req.path);
|
||||
if (/(?:^|[\/\\])\.{1,2}(?:$|[\/\\])|[\/\\]{2,}/.test(filename)) throw new Error();
|
||||
} catch {
|
||||
await options.onNotFound?.(c.req.path, c);
|
||||
return next();
|
||||
}
|
||||
let path = join(root, !optionPath && options.rewriteRequestPath ? options.rewriteRequestPath(filename, c) : filename);
|
||||
let stats = getStats(path);
|
||||
if (stats && stats.isDirectory()) {
|
||||
const indexFile = options.index ?? "index.html";
|
||||
path = join(path, indexFile);
|
||||
stats = getStats(path);
|
||||
}
|
||||
if (!stats) {
|
||||
await options.onNotFound?.(path, c);
|
||||
return next();
|
||||
}
|
||||
const mimeType = getMimeType(path);
|
||||
c.header("Content-Type", mimeType || "application/octet-stream");
|
||||
if (options.precompressed && (!mimeType || COMPRESSIBLE_CONTENT_TYPE_REGEX.test(mimeType))) {
|
||||
const acceptEncodingSet = new Set(c.req.header("Accept-Encoding")?.split(",").map((encoding) => encoding.trim()));
|
||||
for (const encoding of ENCODINGS_ORDERED_KEYS) {
|
||||
if (!acceptEncodingSet.has(encoding)) continue;
|
||||
const precompressedStats = getStats(path + ENCODINGS[encoding]);
|
||||
if (precompressedStats) {
|
||||
c.header("Content-Encoding", encoding);
|
||||
c.header("Vary", "Accept-Encoding", { append: true });
|
||||
stats = precompressedStats;
|
||||
path = path + ENCODINGS[encoding];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
let result;
|
||||
const size = stats.size;
|
||||
const range = c.req.header("range") || "";
|
||||
if (c.req.method == "HEAD" || c.req.method == "OPTIONS") {
|
||||
c.header("Content-Length", size.toString());
|
||||
c.status(200);
|
||||
result = c.body(null);
|
||||
} else if (!range) {
|
||||
c.header("Content-Length", size.toString());
|
||||
result = c.body(createStreamBody(createReadStream(path)), 200);
|
||||
} else {
|
||||
c.header("Accept-Ranges", "bytes");
|
||||
c.header("Date", stats.birthtime.toUTCString());
|
||||
const parts = range.replace(/bytes=/, "").split("-", 2);
|
||||
const start = parseInt(parts[0], 10) || 0;
|
||||
let end = parseInt(parts[1], 10) || size - 1;
|
||||
if (size < end - start + 1) end = size - 1;
|
||||
const chunksize = end - start + 1;
|
||||
const stream = createReadStream(path, {
|
||||
start,
|
||||
end
|
||||
});
|
||||
c.header("Content-Length", chunksize.toString());
|
||||
c.header("Content-Range", `bytes ${start}-${end}/${stats.size}`);
|
||||
result = c.body(createStreamBody(stream), 206);
|
||||
}
|
||||
await options.onFound?.(path, c);
|
||||
return result;
|
||||
};
|
||||
};
|
||||
|
||||
//#endregion
|
||||
export { serveStatic };
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
||||
const require_constants = require('../constants-BXAKTxRC.cjs');
|
||||
|
||||
//#region src/utils/response.ts
|
||||
const RESPONSE_ALREADY_SENT = new Response(null, { headers: { [require_constants.X_ALREADY_SENT]: "true" } });
|
||||
|
||||
//#endregion
|
||||
exports.RESPONSE_ALREADY_SENT = RESPONSE_ALREADY_SENT;
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
//#region src/utils/response.d.ts
|
||||
declare const RESPONSE_ALREADY_SENT: Response;
|
||||
//#endregion
|
||||
export { RESPONSE_ALREADY_SENT };
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
//#region src/utils/response.d.ts
|
||||
declare const RESPONSE_ALREADY_SENT: Response;
|
||||
//#endregion
|
||||
export { RESPONSE_ALREADY_SENT };
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import { t as X_ALREADY_SENT } from "../constants-BLSFu_RU.mjs";
|
||||
|
||||
//#region src/utils/response.ts
|
||||
const RESPONSE_ALREADY_SENT = new Response(null, { headers: { [X_ALREADY_SENT]: "true" } });
|
||||
|
||||
//#endregion
|
||||
export { RESPONSE_ALREADY_SENT };
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
{
|
||||
"name": "@hono/node-server",
|
||||
"version": "2.0.0",
|
||||
"description": "Node.js Adapter for Hono",
|
||||
"main": "dist/index.mjs",
|
||||
"type": "module",
|
||||
"types": "dist/index.d.mts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"import": {
|
||||
"types": "./dist/index.d.mts",
|
||||
"default": "./dist/index.mjs"
|
||||
},
|
||||
"require": {
|
||||
"types": "./dist/index.d.cts",
|
||||
"default": "./dist/index.cjs"
|
||||
}
|
||||
},
|
||||
"./serve-static": {
|
||||
"import": {
|
||||
"types": "./dist/serve-static.d.mts",
|
||||
"default": "./dist/serve-static.mjs"
|
||||
},
|
||||
"require": {
|
||||
"types": "./dist/serve-static.d.cts",
|
||||
"default": "./dist/serve-static.cjs"
|
||||
}
|
||||
},
|
||||
"./utils/*": {
|
||||
"import": {
|
||||
"types": "./dist/utils/*.d.mts",
|
||||
"default": "./dist/utils/*.mjs"
|
||||
},
|
||||
"require": {
|
||||
"types": "./dist/utils/*.d.cts",
|
||||
"default": "./dist/utils/*.cjs"
|
||||
}
|
||||
},
|
||||
"./conninfo": {
|
||||
"import": {
|
||||
"types": "./dist/conninfo.d.mts",
|
||||
"default": "./dist/conninfo.mjs"
|
||||
},
|
||||
"require": {
|
||||
"types": "./dist/conninfo.d.cts",
|
||||
"default": "./dist/conninfo.cjs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
".": [
|
||||
"./dist/index.d.mts"
|
||||
],
|
||||
"serve-static": [
|
||||
"./dist/serve-static.d.mts"
|
||||
],
|
||||
"utils/*": [
|
||||
"./dist/utils/*.d.mts"
|
||||
],
|
||||
"conninfo": [
|
||||
"./dist/conninfo.d.mts"
|
||||
]
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"test": "vitest",
|
||||
"build": "tsdown --external hono",
|
||||
"watch": "tsdown --watch",
|
||||
"postbuild": "publint",
|
||||
"prerelease": "bun run build && bun run test --run",
|
||||
"release": "np",
|
||||
"lint": "eslint src test",
|
||||
"lint:fix": "eslint src test --fix",
|
||||
"format": "prettier --check \"src/**/*.{js,ts}\" \"test/**/*.{js,ts}\"",
|
||||
"format:fix": "prettier --write \"src/**/*.{js,ts}\" \"test/**/*.{js,ts}\""
|
||||
},
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/honojs/node-server.git"
|
||||
},
|
||||
"homepage": "https://github.com/honojs/node-server",
|
||||
"author": "Yusuke Wada <yusuke@kamawada.com> (https://github.com/yusukebe)",
|
||||
"publishConfig": {
|
||||
"registry": "https://registry.npmjs.org",
|
||||
"access": "public"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hono/eslint-config": "^1.0.1",
|
||||
"@types/node": "^20.10.0",
|
||||
"@types/supertest": "^2.0.12",
|
||||
"@types/ws": "^8.18.1",
|
||||
"@whatwg-node/fetch": "^0.9.14",
|
||||
"eslint": "^9.10.0",
|
||||
"hono": "^4.12.8",
|
||||
"np": "^7.7.0",
|
||||
"prettier": "^3.2.4",
|
||||
"publint": "^0.3.18",
|
||||
"supertest": "^6.3.3",
|
||||
"tsdown": "^0.20.3",
|
||||
"typescript": "^5.3.2",
|
||||
"vitest": "^4.0.18",
|
||||
"ws": "^8.19.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"hono": "^4"
|
||||
},
|
||||
"packageManager": "bun@1.2.20"
|
||||
}
|
||||
Reference in New Issue
Block a user