@hono/node-server 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -77,7 +77,7 @@ app.get('/', (c) => c.json({ 'Hono meets': 'Node.js' }))
77
77
  serve(app)
78
78
  ```
79
79
 
80
- ### Serve Static Middleware
80
+ ## Serve Static Middleware
81
81
 
82
82
  Use Serve Static Middleware that has been created for Node.js.
83
83
 
@@ -89,7 +89,23 @@ import { serveStatic } from '@hono/node-server/serve-static'
89
89
  app.use('/static/*', serveStatic({ root: './' }))
90
90
  ```
91
91
 
92
- Note that `root` must be *relative* to the current working directory - absolute paths are not supported.
92
+ Note that `root` must be _relative_ to the current working directory - absolute paths are not supported.
93
+
94
+ ### Options
95
+
96
+ #### `rewriteRequestPath`
97
+
98
+ If you want to serve files in `./.foojs` with the request path `/__foo/*`, you can write like the following.
99
+
100
+ ```ts
101
+ app.use(
102
+ '/__foo/*',
103
+ serveStatic({
104
+ root: './.foojs/',
105
+ rewriteRequestPath: (path: string) => path.replace(/^\/__foo/, ''),
106
+ })
107
+ )
108
+ ```
93
109
 
94
110
  ## Related projects
95
111
 
package/dist/globals.d.ts CHANGED
@@ -16,6 +16,9 @@ declare global {
16
16
  FormData: typeof FormData;
17
17
  ReadableStream: typeof ReadableStream;
18
18
  WritableStream: typeof WritableStream;
19
+ TransformStream: typeof TransformStream;
20
+ TextDecoderStream: typeof TextDecoderStream;
21
+ TextEncoderStream: typeof TextEncoderStream;
19
22
  crypto: Crypto;
20
23
  }
21
24
  }
package/dist/globals.js CHANGED
@@ -9,9 +9,12 @@ const web_stream_1 = require("@remix-run/web-stream");
9
9
  const base64_1 = require("./base64");
10
10
  const fetch_1 = require("./fetch");
11
11
  function installGlobals() {
12
- global.atob = base64_1.atob;
13
- global.btoa = base64_1.btoa;
14
- global.Blob = fetch_1.Blob;
12
+ if (typeof base64_1.atob === 'undefined')
13
+ global.atob = base64_1.atob;
14
+ if (typeof base64_1.btoa === 'undefined')
15
+ global.btoa = base64_1.btoa;
16
+ if (typeof Blob === 'undefined')
17
+ global.Blob = fetch_1.Blob;
15
18
  global.File = fetch_1.File;
16
19
  global.Headers = fetch_1.Headers;
17
20
  global.Request = fetch_1.Request;
@@ -20,8 +23,18 @@ function installGlobals() {
20
23
  global.FormData = fetch_1.FormData;
21
24
  global.ReadableStream = web_stream_1.ReadableStream;
22
25
  global.WritableStream = web_stream_1.WritableStream;
23
- if (typeof global.crypto === "undefined") {
24
- global.crypto = node_crypto_1.default;
26
+ global.TransformStream = web_stream_1.TransformStream;
27
+ global.TextDecoderStream = web_stream_1.TextDecoderStream;
28
+ global.TextEncoderStream = web_stream_1.TextEncoderStream;
29
+ if (typeof global.crypto === 'undefined') {
30
+ // If crypto.subtle is undefined, we're in a Node.js v16 environment
31
+ if (typeof node_crypto_1.default.subtle === 'undefined') {
32
+ // We can use the webcrypto polyfill
33
+ global.crypto = require('crypto').webcrypto;
34
+ }
35
+ else {
36
+ global.crypto = node_crypto_1.default;
37
+ }
25
38
  }
26
39
  }
27
40
  exports.installGlobals = installGlobals;
package/dist/listener.js CHANGED
@@ -18,17 +18,11 @@ const getRequestListener = (fetchCallback) => {
18
18
  const init = {
19
19
  method: method,
20
20
  headers: headerRecord,
21
+ // duplex: 'half', should used in nodejs 18
21
22
  };
22
23
  if (!(method === 'GET' || method === 'HEAD')) {
23
24
  // lazy-consume request body
24
- init.body = new ReadableStream({
25
- start: async (controller) => {
26
- for await (const chunk of incoming) {
27
- controller.enqueue(chunk);
28
- }
29
- controller.close();
30
- }
31
- });
25
+ init.body = (0, stream_1.nodeReadableToWebReadableStream)(incoming);
32
26
  }
33
27
  let res;
34
28
  try {
@@ -7,5 +7,6 @@ export declare type ServeStaticOptions = {
7
7
  root?: string;
8
8
  path?: string;
9
9
  index?: string;
10
+ rewriteRequestPath?: (path: string) => string;
10
11
  };
11
12
  export declare const serveStatic: (options?: ServeStaticOptions) => (c: Context, next: Next) => Promise<Response | undefined>;
@@ -11,8 +11,9 @@ const serveStatic = (options = { root: '' }) => {
11
11
  await next();
12
12
  }
13
13
  const url = new URL(c.req.url);
14
+ const filename = options.path ?? url.pathname;
14
15
  let path = (0, filepath_1.getFilePath)({
15
- filename: options.path ?? url.pathname,
16
+ filename: options.rewriteRequestPath ? options.rewriteRequestPath(filename) : filename,
16
17
  root: options.root,
17
18
  defaultDocument: options.index ?? 'index.html',
18
19
  });
package/dist/stream.d.ts CHANGED
@@ -1,3 +1,7 @@
1
1
  /// <reference types="node" />
2
- import type { Writable } from 'node:stream';
2
+ import { Writable, Readable } from 'node:stream';
3
+ /** pipeline will assure the backpressure and reduce huge memory usage */
3
4
  export declare function writeReadableStreamToWritable(stream: ReadableStream, writable: Writable): Promise<void>;
5
+ /** This implementation use nodejs Readable::fromWeb as references */
6
+ export declare function webReadableStreamToNodeReadable(stream: ReadableStream): Readable;
7
+ export declare function nodeReadableToWebReadableStream(readable: Readable): ReadableStream<Uint8Array>;
package/dist/stream.js CHANGED
@@ -1,25 +1,107 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.writeReadableStreamToWritable = void 0;
3
+ exports.nodeReadableToWebReadableStream = exports.webReadableStreamToNodeReadable = exports.writeReadableStreamToWritable = void 0;
4
+ const node_stream_1 = require("node:stream");
5
+ const node_util_1 = require("node:util");
6
+ const pipelinePromise = (0, node_util_1.promisify)(node_stream_1.pipeline);
7
+ /** pipeline will assure the backpressure and reduce huge memory usage */
4
8
  async function writeReadableStreamToWritable(stream, writable) {
9
+ const readable = webReadableStreamToNodeReadable(stream);
10
+ return pipelinePromise(readable, writable);
11
+ }
12
+ exports.writeReadableStreamToWritable = writeReadableStreamToWritable;
13
+ /** This implementation use nodejs Readable::fromWeb as references */
14
+ function webReadableStreamToNodeReadable(stream) {
5
15
  const reader = stream.getReader();
6
- function onClose() {
7
- reader.cancel(new Error('Response writer closed'));
8
- }
9
- writable.once('close', onClose);
10
- try {
11
- while (true) {
12
- const { done, value } = await reader.read();
13
- if (done) {
14
- writable.end();
16
+ let closed = false;
17
+ const readable = new node_stream_1.Readable({
18
+ read() {
19
+ reader
20
+ .read()
21
+ .then(({ done, value }) => {
22
+ if (done) {
23
+ this.push(null);
24
+ }
25
+ else {
26
+ this.push(value);
27
+ }
28
+ })
29
+ .catch(e => {
30
+ readable.destroy(e);
31
+ });
32
+ },
33
+ destroy(error, callback) {
34
+ const done = () => {
35
+ try {
36
+ callback(error);
37
+ }
38
+ catch (err) {
39
+ process.nextTick(() => {
40
+ throw err;
41
+ });
42
+ }
43
+ };
44
+ if (!closed) {
45
+ reader.cancel(error).then(done, done);
15
46
  return;
16
47
  }
17
- writable.write(value);
18
- }
19
- }
20
- finally {
21
- writable.off('close', onClose);
22
- reader.releaseLock();
48
+ done();
49
+ },
50
+ });
51
+ reader.closed.then(() => {
52
+ closed = true;
53
+ }, error => {
54
+ readable.destroy(error);
55
+ });
56
+ return readable;
57
+ }
58
+ exports.webReadableStreamToNodeReadable = webReadableStreamToNodeReadable;
59
+ function nodeReadableToWebReadableStream(readable) {
60
+ if (readable.destroyed) {
61
+ const stream = new ReadableStream();
62
+ stream.cancel();
63
+ return stream;
23
64
  }
65
+ const highWaterMark = readable.readableHighWaterMark;
66
+ const strategy = { highWaterMark };
67
+ let controller;
68
+ const onData = (chunk) => {
69
+ // Copy the Buffer to detach it from the pool.
70
+ if (Buffer.isBuffer(chunk)) {
71
+ chunk = new Uint8Array(chunk);
72
+ }
73
+ controller.enqueue(chunk);
74
+ if (controller.desiredSize !== null && controller.desiredSize <= 0) {
75
+ readable.pause();
76
+ }
77
+ };
78
+ readable.pause();
79
+ const cleanup = (0, node_stream_1.finished)(readable, error => {
80
+ if (error?.code === 'ERR_STREAM_PREMATURE_CLOSE') {
81
+ const err = new Error(undefined, { cause: error });
82
+ Object.defineProperty(err, 'name', 'AbortError');
83
+ error = err;
84
+ }
85
+ cleanup();
86
+ // This is a protection against non-standard, legacy streams
87
+ // that happen to emit an error event again after finished is called.
88
+ readable.on('error', () => { });
89
+ if (error) {
90
+ return controller.error(error);
91
+ }
92
+ controller.close();
93
+ });
94
+ readable.on('data', onData);
95
+ return new ReadableStream({
96
+ start(c) {
97
+ controller = c;
98
+ },
99
+ pull() {
100
+ readable.resume();
101
+ },
102
+ cancel(reason) {
103
+ readable.destroy(reason);
104
+ },
105
+ }, strategy);
24
106
  }
25
- exports.writeReadableStreamToWritable = writeReadableStreamToWritable;
107
+ exports.nodeReadableToWebReadableStream = nodeReadableToWebReadableStream;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hono/node-server",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "description": "HTTP Server for Hono on Node.js",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -48,7 +48,7 @@
48
48
  "@types/jest": "^29.0.1",
49
49
  "@types/node": "^18.7.16",
50
50
  "@types/supertest": "^2.0.12",
51
- "hono": "^2.7.1",
51
+ "hono": "^3.1.5",
52
52
  "jest": "^29.0.3",
53
53
  "next": "13.1.6",
54
54
  "np": "^7.6.2",