hono 3.9.1 → 3.10.0-rc.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.
File without changes
@@ -5,6 +5,40 @@ globalThis.crypto ?? (globalThis.crypto = crypto);
5
5
  var getRequestContext = (event) => {
6
6
  return event.requestContext;
7
7
  };
8
+ var streamToNodeStream = async (reader, writer) => {
9
+ let readResult = await reader.read();
10
+ while (!readResult.done) {
11
+ writer.write(readResult.value);
12
+ readResult = await reader.read();
13
+ }
14
+ writer.end();
15
+ };
16
+ var streamHandle = (app) => {
17
+ return awslambda.streamifyResponse(
18
+ async (event, responseStream, context) => {
19
+ try {
20
+ const req = createRequest(event);
21
+ const requestContext = getRequestContext(event);
22
+ const res = await app.fetch(req, {
23
+ requestContext,
24
+ context
25
+ });
26
+ const contentType = res.headers.get("content-type");
27
+ if (!contentType) {
28
+ console.warn("Content Type is not set in the response.");
29
+ }
30
+ if (res.body) {
31
+ await streamToNodeStream(res.body.getReader(), responseStream);
32
+ }
33
+ } catch (error) {
34
+ console.error("Error processing request:", error);
35
+ responseStream.write("Internal Server Error");
36
+ } finally {
37
+ responseStream.end();
38
+ }
39
+ }
40
+ );
41
+ };
8
42
  var handle = (app) => {
9
43
  return async (event, lambdaContext) => {
10
44
  const req = createRequest(event);
@@ -108,5 +142,6 @@ var isContentEncodingBinary = (contentEncoding) => {
108
142
  export {
109
143
  handle,
110
144
  isContentEncodingBinary,
111
- isContentTypeBinary
145
+ isContentTypeBinary,
146
+ streamHandle
112
147
  };
@@ -1,5 +1,6 @@
1
1
  // src/adapter/aws-lambda/index.ts
2
- import { handle } from "./handler.js";
2
+ import { handle, streamHandle } from "./handler.js";
3
3
  export {
4
- handle
4
+ handle,
5
+ streamHandle
5
6
  };
@@ -0,0 +1 @@
1
+ "use strict";
@@ -26,7 +26,8 @@ var handler_exports = {};
26
26
  __export(handler_exports, {
27
27
  handle: () => handle,
28
28
  isContentEncodingBinary: () => isContentEncodingBinary,
29
- isContentTypeBinary: () => isContentTypeBinary
29
+ isContentTypeBinary: () => isContentTypeBinary,
30
+ streamHandle: () => streamHandle
30
31
  });
31
32
  module.exports = __toCommonJS(handler_exports);
32
33
  var import_crypto = __toESM(require("crypto"), 1);
@@ -35,6 +36,40 @@ globalThis.crypto ?? (globalThis.crypto = import_crypto.default);
35
36
  const getRequestContext = (event) => {
36
37
  return event.requestContext;
37
38
  };
39
+ const streamToNodeStream = async (reader, writer) => {
40
+ let readResult = await reader.read();
41
+ while (!readResult.done) {
42
+ writer.write(readResult.value);
43
+ readResult = await reader.read();
44
+ }
45
+ writer.end();
46
+ };
47
+ const streamHandle = (app) => {
48
+ return awslambda.streamifyResponse(
49
+ async (event, responseStream, context) => {
50
+ try {
51
+ const req = createRequest(event);
52
+ const requestContext = getRequestContext(event);
53
+ const res = await app.fetch(req, {
54
+ requestContext,
55
+ context
56
+ });
57
+ const contentType = res.headers.get("content-type");
58
+ if (!contentType) {
59
+ console.warn("Content Type is not set in the response.");
60
+ }
61
+ if (res.body) {
62
+ await streamToNodeStream(res.body.getReader(), responseStream);
63
+ }
64
+ } catch (error) {
65
+ console.error("Error processing request:", error);
66
+ responseStream.write("Internal Server Error");
67
+ } finally {
68
+ responseStream.end();
69
+ }
70
+ }
71
+ );
72
+ };
38
73
  const handle = (app) => {
39
74
  return async (event, lambdaContext) => {
40
75
  const req = createRequest(event);
@@ -139,5 +174,6 @@ const isContentEncodingBinary = (contentEncoding) => {
139
174
  0 && (module.exports = {
140
175
  handle,
141
176
  isContentEncodingBinary,
142
- isContentTypeBinary
177
+ isContentTypeBinary,
178
+ streamHandle
143
179
  });
@@ -18,11 +18,13 @@ var __copyProps = (to, from, except, desc) => {
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
  var aws_lambda_exports = {};
20
20
  __export(aws_lambda_exports, {
21
- handle: () => import_handler.handle
21
+ handle: () => import_handler.handle,
22
+ streamHandle: () => import_handler.streamHandle
22
23
  });
23
24
  module.exports = __toCommonJS(aws_lambda_exports);
24
25
  var import_handler = require("./handler");
25
26
  // Annotate the CommonJS export names for ESM import in node:
26
27
  0 && (module.exports = {
27
- handle
28
+ handle,
29
+ streamHandle
28
30
  });
@@ -162,6 +162,16 @@ class Context {
162
162
  this.html = (html, arg, headers) => {
163
163
  this._pH ?? (this._pH = {});
164
164
  this._pH["content-type"] = "text/html; charset=UTF-8";
165
+ if (typeof html === "object") {
166
+ if (!(html instanceof Promise)) {
167
+ html = html.toString();
168
+ }
169
+ if (html instanceof Promise) {
170
+ return html.then((html2) => {
171
+ return typeof arg === "number" ? this.newResponse(html2, arg, headers) : this.newResponse(html2, arg);
172
+ });
173
+ }
174
+ }
165
175
  return typeof arg === "number" ? this.newResponse(html, arg, headers) : this.newResponse(html, arg);
166
176
  };
167
177
  this.redirect = (location, status = 302) => {
@@ -40,14 +40,19 @@ const html = (strings, ...values) => {
40
40
  } else if (typeof child === "boolean" || child === null || child === void 0) {
41
41
  continue;
42
42
  } else if (typeof child === "object" && child.isEscaped || typeof child === "number") {
43
- buffer[0] += child;
43
+ const tmp = child.toString();
44
+ if (tmp instanceof Promise) {
45
+ buffer.unshift("", tmp);
46
+ } else {
47
+ buffer[0] += tmp;
48
+ }
44
49
  } else {
45
50
  (0, import_html.escapeToBuffer)(child.toString(), buffer);
46
51
  }
47
52
  }
48
53
  }
49
54
  buffer[0] += strings[strings.length - 1];
50
- return raw(buffer[0]);
55
+ return buffer.length === 1 ? raw(buffer[0]) : (0, import_html.stringBufferToString)(buffer).then((str) => raw(str));
51
56
  };
52
57
  // Annotate the CommonJS export names for ESM import in node:
53
58
  0 && (module.exports = {
@@ -81,7 +81,10 @@ const childrenToStringToBuffer = (children, buffer) => {
81
81
  } else if (child instanceof JSXNode) {
82
82
  child.toStringToBuffer(buffer);
83
83
  } else if (typeof child === "number" || child.isEscaped) {
84
+ ;
84
85
  buffer[0] += child;
86
+ } else if (child instanceof Promise) {
87
+ buffer.unshift("", child);
85
88
  } else {
86
89
  childrenToStringToBuffer(child, buffer);
87
90
  }
@@ -97,7 +100,7 @@ class JSXNode {
97
100
  toString() {
98
101
  const buffer = [""];
99
102
  this.toStringToBuffer(buffer);
100
- return buffer[0];
103
+ return buffer.length === 1 ? buffer[0] : (0, import_html.stringBufferToString)(buffer);
101
104
  }
102
105
  toStringToBuffer(buffer) {
103
106
  const tag = this.tag;
@@ -154,7 +157,9 @@ class JSXFunctionNode extends JSXNode {
154
157
  ...this.props,
155
158
  children: children.length <= 1 ? children[0] : children
156
159
  });
157
- if (res instanceof JSXNode) {
160
+ if (res instanceof Promise) {
161
+ buffer.unshift("", res);
162
+ } else if (res instanceof JSXNode) {
158
163
  res.toStringToBuffer(buffer);
159
164
  } else if (typeof res === "number" || res.isEscaped) {
160
165
  buffer[0] += res;
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var streaming_exports = {};
20
+ __export(streaming_exports, {
21
+ Suspense: () => Suspense,
22
+ renderToReadableStream: () => renderToReadableStream
23
+ });
24
+ module.exports = __toCommonJS(streaming_exports);
25
+ let suspenseCounter = 0;
26
+ async function childrenToString(children) {
27
+ try {
28
+ return children.toString();
29
+ } catch (e) {
30
+ if (e instanceof Promise) {
31
+ await e;
32
+ return childrenToString(children);
33
+ } else {
34
+ throw e;
35
+ }
36
+ }
37
+ }
38
+ const Suspense = async ({ children, fallback }) => {
39
+ if (!children) {
40
+ return fallback.toString();
41
+ }
42
+ let res;
43
+ try {
44
+ res = children.toString();
45
+ } catch (e) {
46
+ if (e instanceof Promise) {
47
+ res = e;
48
+ } else {
49
+ throw e;
50
+ }
51
+ } finally {
52
+ const index = suspenseCounter++;
53
+ if (res instanceof Promise) {
54
+ const promise = res;
55
+ res = new String(
56
+ `<template id="H:${index}"></template>${fallback.toString()}<!--/$-->`
57
+ );
58
+ res.isEscaped = true;
59
+ res.promises = [
60
+ promise.then(async () => {
61
+ return `<template>${await childrenToString(children)}</template><script>
62
+ ((d,c,n) => {
63
+ c=d.currentScript.previousSibling
64
+ d=d.getElementById('H:${index}')
65
+ do{n=d.nextSibling;n.remove()}while(n.nodeType!=8||n.nodeValue!='/$')
66
+ d.replaceWith(c.content)
67
+ })(document)
68
+ <\/script>`;
69
+ })
70
+ ];
71
+ }
72
+ }
73
+ return res;
74
+ };
75
+ const textEncoder = new TextEncoder();
76
+ const renderToReadableStream = (str) => {
77
+ const reader = new ReadableStream({
78
+ async start(controller) {
79
+ const resolved = await str.toString();
80
+ controller.enqueue(textEncoder.encode(resolved));
81
+ let unresolvedCount = resolved.promises?.length || 0;
82
+ if (!unresolvedCount) {
83
+ controller.close();
84
+ return;
85
+ }
86
+ for (let i = 0; i < unresolvedCount; i++) {
87
+ ;
88
+ resolved.promises[i].catch((err) => {
89
+ console.trace(err);
90
+ return "";
91
+ }).then((res) => {
92
+ controller.enqueue(textEncoder.encode(res));
93
+ if (!--unresolvedCount) {
94
+ controller.close();
95
+ }
96
+ });
97
+ }
98
+ }
99
+ });
100
+ return reader;
101
+ };
102
+ // Annotate the CommonJS export names for ESM import in node:
103
+ 0 && (module.exports = {
104
+ Suspense,
105
+ renderToReadableStream
106
+ });
@@ -23,7 +23,7 @@ __export(filepath_exports, {
23
23
  module.exports = __toCommonJS(filepath_exports);
24
24
  const getFilePath = (options) => {
25
25
  let filename = options.filename;
26
- if (/(?:^|\/)\.\.(?:$|\/)/.test(filename))
26
+ if (/(?:^|[\/\\])\.\.(?:$|[\/\\])/.test(filename))
27
27
  return;
28
28
  let root = options.root || "";
29
29
  const defaultDocument = options.defaultDocument || "index.html";
@@ -32,7 +32,8 @@ const getFilePath = (options) => {
32
32
  } else if (!filename.match(/\.[a-zA-Z0-9]+$/)) {
33
33
  filename = filename.concat("/" + defaultDocument);
34
34
  }
35
- filename = filename.replace(/^\.?\//, "");
35
+ filename = filename.replace(/^\.?[\/\\]/, "");
36
+ filename = filename.replace(/\\/, "/");
36
37
  root = root.replace(/\/$/, "");
37
38
  let path = root ? root + "/" + filename : filename;
38
39
  path = path.replace(/^\.?\//, "");
@@ -18,10 +18,30 @@ var __copyProps = (to, from, except, desc) => {
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
  var html_exports = {};
20
20
  __export(html_exports, {
21
- escapeToBuffer: () => escapeToBuffer
21
+ escapeToBuffer: () => escapeToBuffer,
22
+ stringBufferToString: () => stringBufferToString
22
23
  });
23
24
  module.exports = __toCommonJS(html_exports);
24
25
  const escapeRe = /[&<>'"]/;
26
+ const stringBufferToString = async (buffer) => {
27
+ let str = "";
28
+ const promises = [];
29
+ for (let i = buffer.length - 1; i >= 0; i--) {
30
+ let r = await buffer[i];
31
+ if (typeof r === "object") {
32
+ promises.push(...r.promises || []);
33
+ }
34
+ r = await (typeof r === "object" ? r.toString() : r);
35
+ if (typeof r === "object") {
36
+ promises.push(...r.promises || []);
37
+ }
38
+ str += r;
39
+ }
40
+ const res = new String(str);
41
+ res.isEscaped = true;
42
+ res.promises = promises;
43
+ return res;
44
+ };
25
45
  const escapeToBuffer = (str, buffer) => {
26
46
  const match = str.search(escapeRe);
27
47
  if (match === -1) {
@@ -58,5 +78,6 @@ const escapeToBuffer = (str, buffer) => {
58
78
  };
59
79
  // Annotate the CommonJS export names for ESM import in node:
60
80
  0 && (module.exports = {
61
- escapeToBuffer
81
+ escapeToBuffer,
82
+ stringBufferToString
62
83
  });
package/dist/context.js CHANGED
@@ -140,6 +140,16 @@ var Context = class {
140
140
  this.html = (html, arg, headers) => {
141
141
  this._pH ?? (this._pH = {});
142
142
  this._pH["content-type"] = "text/html; charset=UTF-8";
143
+ if (typeof html === "object") {
144
+ if (!(html instanceof Promise)) {
145
+ html = html.toString();
146
+ }
147
+ if (html instanceof Promise) {
148
+ return html.then((html2) => {
149
+ return typeof arg === "number" ? this.newResponse(html2, arg, headers) : this.newResponse(html2, arg);
150
+ });
151
+ }
152
+ }
143
153
  return typeof arg === "number" ? this.newResponse(html, arg, headers) : this.newResponse(html, arg);
144
154
  };
145
155
  this.redirect = (location, status = 302) => {
@@ -1,5 +1,5 @@
1
1
  // src/helper/html/index.ts
2
- import { escapeToBuffer } from "../../utils/html.js";
2
+ import { escapeToBuffer, stringBufferToString } from "../../utils/html.js";
3
3
  var raw = (value) => {
4
4
  const escapedString = new String(value);
5
5
  escapedString.isEscaped = true;
@@ -17,14 +17,19 @@ var html = (strings, ...values) => {
17
17
  } else if (typeof child === "boolean" || child === null || child === void 0) {
18
18
  continue;
19
19
  } else if (typeof child === "object" && child.isEscaped || typeof child === "number") {
20
- buffer[0] += child;
20
+ const tmp = child.toString();
21
+ if (tmp instanceof Promise) {
22
+ buffer.unshift("", tmp);
23
+ } else {
24
+ buffer[0] += tmp;
25
+ }
21
26
  } else {
22
27
  escapeToBuffer(child.toString(), buffer);
23
28
  }
24
29
  }
25
30
  }
26
31
  buffer[0] += strings[strings.length - 1];
27
- return raw(buffer[0]);
32
+ return buffer.length === 1 ? raw(buffer[0]) : stringBufferToString(buffer).then((str) => raw(str));
28
33
  };
29
34
  export {
30
35
  html,
package/dist/jsx/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/jsx/index.ts
2
- import { escapeToBuffer } from "../utils/html.js";
2
+ import { escapeToBuffer, stringBufferToString } from "../utils/html.js";
3
3
  var emptyTags = [
4
4
  "area",
5
5
  "base",
@@ -54,7 +54,10 @@ var childrenToStringToBuffer = (children, buffer) => {
54
54
  } else if (child instanceof JSXNode) {
55
55
  child.toStringToBuffer(buffer);
56
56
  } else if (typeof child === "number" || child.isEscaped) {
57
+ ;
57
58
  buffer[0] += child;
59
+ } else if (child instanceof Promise) {
60
+ buffer.unshift("", child);
58
61
  } else {
59
62
  childrenToStringToBuffer(child, buffer);
60
63
  }
@@ -70,7 +73,7 @@ var JSXNode = class {
70
73
  toString() {
71
74
  const buffer = [""];
72
75
  this.toStringToBuffer(buffer);
73
- return buffer[0];
76
+ return buffer.length === 1 ? buffer[0] : stringBufferToString(buffer);
74
77
  }
75
78
  toStringToBuffer(buffer) {
76
79
  const tag = this.tag;
@@ -127,7 +130,9 @@ var JSXFunctionNode = class extends JSXNode {
127
130
  ...this.props,
128
131
  children: children.length <= 1 ? children[0] : children
129
132
  });
130
- if (res instanceof JSXNode) {
133
+ if (res instanceof Promise) {
134
+ buffer.unshift("", res);
135
+ } else if (res instanceof JSXNode) {
131
136
  res.toStringToBuffer(buffer);
132
137
  } else if (typeof res === "number" || res.isEscaped) {
133
138
  buffer[0] += res;
@@ -0,0 +1,82 @@
1
+ // src/jsx/streaming.ts
2
+ var suspenseCounter = 0;
3
+ async function childrenToString(children) {
4
+ try {
5
+ return children.toString();
6
+ } catch (e) {
7
+ if (e instanceof Promise) {
8
+ await e;
9
+ return childrenToString(children);
10
+ } else {
11
+ throw e;
12
+ }
13
+ }
14
+ }
15
+ var Suspense = async ({ children, fallback }) => {
16
+ if (!children) {
17
+ return fallback.toString();
18
+ }
19
+ let res;
20
+ try {
21
+ res = children.toString();
22
+ } catch (e) {
23
+ if (e instanceof Promise) {
24
+ res = e;
25
+ } else {
26
+ throw e;
27
+ }
28
+ } finally {
29
+ const index = suspenseCounter++;
30
+ if (res instanceof Promise) {
31
+ const promise = res;
32
+ res = new String(
33
+ `<template id="H:${index}"></template>${fallback.toString()}<!--/$-->`
34
+ );
35
+ res.isEscaped = true;
36
+ res.promises = [
37
+ promise.then(async () => {
38
+ return `<template>${await childrenToString(children)}</template><script>
39
+ ((d,c,n) => {
40
+ c=d.currentScript.previousSibling
41
+ d=d.getElementById('H:${index}')
42
+ do{n=d.nextSibling;n.remove()}while(n.nodeType!=8||n.nodeValue!='/$')
43
+ d.replaceWith(c.content)
44
+ })(document)
45
+ <\/script>`;
46
+ })
47
+ ];
48
+ }
49
+ }
50
+ return res;
51
+ };
52
+ var textEncoder = new TextEncoder();
53
+ var renderToReadableStream = (str) => {
54
+ const reader = new ReadableStream({
55
+ async start(controller) {
56
+ const resolved = await str.toString();
57
+ controller.enqueue(textEncoder.encode(resolved));
58
+ let unresolvedCount = resolved.promises?.length || 0;
59
+ if (!unresolvedCount) {
60
+ controller.close();
61
+ return;
62
+ }
63
+ for (let i = 0; i < unresolvedCount; i++) {
64
+ ;
65
+ resolved.promises[i].catch((err) => {
66
+ console.trace(err);
67
+ return "";
68
+ }).then((res) => {
69
+ controller.enqueue(textEncoder.encode(res));
70
+ if (!--unresolvedCount) {
71
+ controller.close();
72
+ }
73
+ });
74
+ }
75
+ }
76
+ });
77
+ return reader;
78
+ };
79
+ export {
80
+ Suspense,
81
+ renderToReadableStream
82
+ };
@@ -2,7 +2,7 @@ import type { Hono } from '../../hono';
2
2
  import type { Env, Schema } from '../../types';
3
3
  import type { ApiGatewayRequestContext, LambdaFunctionUrlRequestContext } from './custom-context';
4
4
  import type { LambdaContext } from './types';
5
- interface APIGatewayProxyEventV2 {
5
+ export interface APIGatewayProxyEventV2 {
6
6
  httpMethod: string;
7
7
  headers: Record<string, string | undefined>;
8
8
  cookies?: string[];
@@ -12,7 +12,7 @@ interface APIGatewayProxyEventV2 {
12
12
  isBase64Encoded: boolean;
13
13
  requestContext: ApiGatewayRequestContext;
14
14
  }
15
- interface APIGatewayProxyEvent {
15
+ export interface APIGatewayProxyEvent {
16
16
  httpMethod: string;
17
17
  headers: Record<string, string | undefined>;
18
18
  multiValueHeaders?: {
@@ -24,7 +24,7 @@ interface APIGatewayProxyEvent {
24
24
  queryStringParameters?: Record<string, string | undefined>;
25
25
  requestContext: ApiGatewayRequestContext;
26
26
  }
27
- interface LambdaFunctionUrlEvent {
27
+ export interface LambdaFunctionUrlEvent {
28
28
  headers: Record<string, string | undefined>;
29
29
  rawPath: string;
30
30
  rawQueryString: string;
@@ -42,6 +42,7 @@ interface APIGatewayProxyResult {
42
42
  };
43
43
  isBase64Encoded: boolean;
44
44
  }
45
+ export declare const streamHandle: <E extends Env = Env, S extends Schema = {}, BasePath extends string = "/">(app: Hono<E, S, BasePath>) => import("./types").Handler<any, any>;
45
46
  /**
46
47
  * Accepts events from API Gateway/ELB(`APIGatewayProxyEvent`) and directly through Function Url(`APIGatewayProxyEventV2`)
47
48
  */
@@ -1,3 +1,3 @@
1
- export { handle } from './handler';
1
+ export { handle, streamHandle } from './handler';
2
2
  export type { ApiGatewayRequestContext, LambdaFunctionUrlRequestContext } from './custom-context';
3
3
  export type { LambdaContext } from './types';
@@ -38,3 +38,6 @@ export interface LambdaContext {
38
38
  clientContext?: ClientContext | undefined;
39
39
  getRemainingTimeInMillis(): number;
40
40
  }
41
+ declare type Callback<TResult = any> = (error?: Error | string | null, result?: TResult) => void;
42
+ export declare type Handler<TEvent = any, TResult = any> = (event: TEvent, context: LambdaContext, callback: Callback<TResult>) => void | Promise<TResult>;
43
+ export {};
@@ -16,7 +16,7 @@ export interface ContextVariableMap {
16
16
  export interface ContextRenderer {
17
17
  }
18
18
  interface DefaultRenderer {
19
- (content: string): Response | Promise<Response>;
19
+ (content: string | Promise<string>): Response | Promise<Response>;
20
20
  }
21
21
  export declare type Renderer = ContextRenderer extends Function ? ContextRenderer : DefaultRenderer;
22
22
  interface Get<E extends Env> {
@@ -46,8 +46,8 @@ interface JSONTRespond {
46
46
  <T>(object: InterfaceToType<T> extends JSONValue ? T : JSONValue, init?: ResponseInit): TypedResponse<InterfaceToType<T> extends JSONValue ? JSONValue extends InterfaceToType<T> ? never : T : never>;
47
47
  }
48
48
  interface HTMLRespond {
49
- (html: string, status?: StatusCode, headers?: HeaderRecord): Response;
50
- (html: string, init?: ResponseInit): Response;
49
+ (html: string | Promise<string>, status?: StatusCode, headers?: HeaderRecord): Response | Promise<Response>;
50
+ (html: string | Promise<string>, init?: ResponseInit): Response | Promise<Response>;
51
51
  }
52
52
  declare type ContextOptions<E extends Env> = {
53
53
  env: E['Bindings'];
@@ -1,3 +1,3 @@
1
1
  import type { HtmlEscapedString } from '../../utils/html';
2
2
  export declare const raw: (value: unknown) => HtmlEscapedString;
3
- export declare const html: (strings: TemplateStringsArray, ...values: unknown[]) => HtmlEscapedString;
3
+ export declare const html: (strings: TemplateStringsArray, ...values: unknown[]) => HtmlEscapedString | Promise<HtmlEscapedString>;
@@ -3,7 +3,7 @@ import type { IntrinsicElements as IntrinsicElementsDefined } from './intrinsic-
3
3
  declare type Props = Record<string, any>;
4
4
  declare global {
5
5
  namespace JSX {
6
- type Element = HtmlEscapedString;
6
+ type Element = HtmlEscapedString | Promise<HtmlEscapedString>;
7
7
  interface ElementChildrenAttribute {
8
8
  children: Child;
9
9
  }
@@ -12,21 +12,21 @@ declare global {
12
12
  }
13
13
  }
14
14
  }
15
- declare type Child = string | number | JSXNode | Child[];
15
+ export declare type Child = string | Promise<string> | number | JSXNode | Child[];
16
16
  export declare class JSXNode implements HtmlEscaped {
17
17
  tag: string | Function;
18
18
  props: Props;
19
19
  children: Child[];
20
20
  isEscaped: true;
21
21
  constructor(tag: string | Function, props: Props, children: Child[]);
22
- toString(): string;
22
+ toString(): string | Promise<string>;
23
23
  toStringToBuffer(buffer: StringBuffer): void;
24
24
  }
25
25
  export { jsxFn as jsx };
26
26
  declare const jsxFn: (tag: string | Function, props: Props, ...children: (string | HtmlEscapedString)[]) => JSXNode;
27
27
  export declare type FC<T = Props> = (props: T & {
28
28
  children?: Child;
29
- }) => HtmlEscapedString;
29
+ }) => HtmlEscapedString | Promise<HtmlEscapedString>;
30
30
  export declare const memo: <T>(component: FC<T>, propsAreEqual?: (prevProps: Readonly<T>, nextProps: Readonly<T>) => boolean) => FC<T>;
31
31
  export declare const Fragment: (props: {
32
32
  key?: string;
@@ -0,0 +1,16 @@
1
+ import type { HtmlEscapedString } from '../utils/html';
2
+ import type { FC } from './index';
3
+ /**
4
+ * @experimental
5
+ * `Suspense` is an experimental feature.
6
+ * The API might be changed.
7
+ */
8
+ export declare const Suspense: FC<{
9
+ fallback: any;
10
+ }>;
11
+ /**
12
+ * @experimental
13
+ * `renderToReadableStream()` is an experimental feature.
14
+ * The API might be changed.
15
+ */
16
+ export declare const renderToReadableStream: (str: HtmlEscapedString | Promise<HtmlEscapedString>) => ReadableStream<Uint8Array>;
@@ -1,6 +1,8 @@
1
1
  export declare type HtmlEscaped = {
2
2
  isEscaped: true;
3
+ promises?: Promise<string>[];
3
4
  };
4
5
  export declare type HtmlEscapedString = string & HtmlEscaped;
5
- export declare type StringBuffer = [string];
6
+ export declare type StringBuffer = (string | Promise<string>)[];
7
+ export declare const stringBufferToString: (buffer: StringBuffer) => Promise<HtmlEscapedString>;
6
8
  export declare const escapeToBuffer: (str: string, buffer: StringBuffer) => void;
@@ -1,7 +1,7 @@
1
1
  // src/utils/filepath.ts
2
2
  var getFilePath = (options) => {
3
3
  let filename = options.filename;
4
- if (/(?:^|\/)\.\.(?:$|\/)/.test(filename))
4
+ if (/(?:^|[\/\\])\.\.(?:$|[\/\\])/.test(filename))
5
5
  return;
6
6
  let root = options.root || "";
7
7
  const defaultDocument = options.defaultDocument || "index.html";
@@ -10,7 +10,8 @@ var getFilePath = (options) => {
10
10
  } else if (!filename.match(/\.[a-zA-Z0-9]+$/)) {
11
11
  filename = filename.concat("/" + defaultDocument);
12
12
  }
13
- filename = filename.replace(/^\.?\//, "");
13
+ filename = filename.replace(/^\.?[\/\\]/, "");
14
+ filename = filename.replace(/\\/, "/");
14
15
  root = root.replace(/\/$/, "");
15
16
  let path = root ? root + "/" + filename : filename;
16
17
  path = path.replace(/^\.?\//, "");
@@ -1,5 +1,24 @@
1
1
  // src/utils/html.ts
2
2
  var escapeRe = /[&<>'"]/;
3
+ var stringBufferToString = async (buffer) => {
4
+ let str = "";
5
+ const promises = [];
6
+ for (let i = buffer.length - 1; i >= 0; i--) {
7
+ let r = await buffer[i];
8
+ if (typeof r === "object") {
9
+ promises.push(...r.promises || []);
10
+ }
11
+ r = await (typeof r === "object" ? r.toString() : r);
12
+ if (typeof r === "object") {
13
+ promises.push(...r.promises || []);
14
+ }
15
+ str += r;
16
+ }
17
+ const res = new String(str);
18
+ res.isEscaped = true;
19
+ res.promises = promises;
20
+ return res;
21
+ };
3
22
  var escapeToBuffer = (str, buffer) => {
4
23
  const match = str.search(escapeRe);
5
24
  if (match === -1) {
@@ -35,5 +54,6 @@ var escapeToBuffer = (str, buffer) => {
35
54
  buffer[0] += str.substring(lastIndex, index);
36
55
  };
37
56
  export {
38
- escapeToBuffer
57
+ escapeToBuffer,
58
+ stringBufferToString
39
59
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hono",
3
- "version": "3.9.1",
3
+ "version": "3.10.0-rc.1",
4
4
  "description": "Ultrafast web framework for the Edges",
5
5
  "main": "dist/cjs/index.js",
6
6
  "type": "module",
@@ -119,6 +119,11 @@
119
119
  "import": "./dist/jsx/jsx-runtime.js",
120
120
  "require": "./dist/cjs/jsx/jsx-runtime.js"
121
121
  },
122
+ "./jsx/streaming": {
123
+ "types": "./dist/types/jsx/streaming.d.ts",
124
+ "import": "./dist/jsx/streaming.js",
125
+ "require": "./dist/cjs/jsx/streaming.js"
126
+ },
122
127
  "./jsx-renderer": {
123
128
  "types": "./dist/types/middleware/jsx-renderer/index.d.ts",
124
129
  "import": "./dist/middleware/jsx-renderer/index.js",
@@ -310,6 +315,9 @@
310
315
  "jsx/jsx-dev-runtime": [
311
316
  "./dist/types/jsx/jsx-dev-runtime.d.ts"
312
317
  ],
318
+ "jsx/streaming": [
319
+ "./dist/types/jsx/streaming.d.ts"
320
+ ],
313
321
  "jsx-renderer": [
314
322
  "./dist/types/middleware/jsx-renderer"
315
323
  ],
@@ -430,6 +438,7 @@
430
438
  "@types/crypto-js": "^4.1.1",
431
439
  "@types/glob": "^8.0.0",
432
440
  "@types/jest": "^29.4.0",
441
+ "@types/jsdom": "^21.1.4",
433
442
  "@types/node": "^20.8.2",
434
443
  "@types/node-fetch": "^2.6.2",
435
444
  "@types/supertest": "^2.0.12",
@@ -451,6 +460,7 @@
451
460
  "form-data": "^4.0.0",
452
461
  "jest": "^29.6.4",
453
462
  "jest-preset-fastly-js-compute": "^1.3.0",
463
+ "jsdom": "^22.1.0",
454
464
  "msw": "^1.0.0",
455
465
  "node-fetch": "2",
456
466
  "np": "^7.7.0",