hoa 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## v0.3.0 / 2025-10-16
2
+
3
+ - feat: move respond() to ctx.response
4
+
5
+ ## v0.2.1 / 2025-10-15
6
+
7
+ - fix: rename HoaExtend to HoaExtension
8
+
1
9
  ## v0.2.0 / 2025-10-12
2
10
 
3
11
  - refactor: rename Application to Hoa
@@ -97,6 +97,70 @@ class HoaContext {
97
97
  headers: res._headers
98
98
  });
99
99
  }
100
+ /**
101
+ * Build Web Standard Response from current context state.
102
+ * Handles various body types, HEAD requests, and status-specific behaviors.
103
+ *
104
+ * @returns {Response} Web Standard Response object
105
+ * @public
106
+ */
107
+ get response() {
108
+ const { res, req } = this;
109
+ let body = res.body;
110
+ if (req.method === "HEAD") {
111
+ if (!res.has("Content-Length")) {
112
+ const contentLength = res.length;
113
+ if (Number.isInteger(contentLength)) {
114
+ res.length = contentLength;
115
+ }
116
+ }
117
+ return new Response(null, {
118
+ status: res.status,
119
+ statusText: res.statusText,
120
+ headers: res._headers
121
+ });
122
+ }
123
+ if (import_utils.statusEmptyMapping[res.status]) {
124
+ res.body = null;
125
+ return new Response(null, {
126
+ status: res.status,
127
+ statusText: res.statusText,
128
+ headers: res._headers
129
+ });
130
+ }
131
+ if (body == null) {
132
+ if (res._explicitNullBody) {
133
+ res.delete("Content-Type");
134
+ res.delete("Transfer-Encoding");
135
+ res.set("Content-Length", "0");
136
+ }
137
+ return new Response(null, {
138
+ status: res.status,
139
+ statusText: res.statusText,
140
+ headers: res._headers
141
+ });
142
+ }
143
+ if (typeof body === "string" || body instanceof Blob || body instanceof ArrayBuffer || ArrayBuffer.isView(body) || body instanceof ReadableStream || body instanceof FormData || body instanceof URLSearchParams) {
144
+ return new Response(body, {
145
+ status: res.status,
146
+ statusText: res.statusText,
147
+ headers: res._headers
148
+ });
149
+ }
150
+ if (body instanceof Response) {
151
+ return new Response(body.body, {
152
+ status: res.status,
153
+ statusText: res.statusText,
154
+ headers: res._headers
155
+ });
156
+ }
157
+ body = JSON.stringify(body);
158
+ return new Response(body, {
159
+ status: res.status,
160
+ statusText: res.statusText,
161
+ headers: res._headers
162
+ });
163
+ }
100
164
  /**
101
165
  * Return JSON representation of the context.
102
166
  *
package/dist/cjs/hoa.js CHANGED
@@ -38,7 +38,6 @@ __export(hoa_exports, {
38
38
  module.exports = __toCommonJS(hoa_exports);
39
39
  var import_compose = __toESM(require("./lib/compose.js"), 1);
40
40
  var import_http_error = __toESM(require("./lib/http-error.js"), 1);
41
- var import_utils = require("./lib/utils.js");
42
41
  var import_context = __toESM(require("./context.js"), 1);
43
42
  var import_request = __toESM(require("./request.js"), 1);
44
43
  var import_response = __toESM(require("./response.js"), 1);
@@ -60,7 +59,7 @@ class Hoa {
60
59
  /**
61
60
  * Extend the application with a plugin initializer.
62
61
  *
63
- * @param {HoaExtend} fn - Plugin function that receives the app instance
62
+ * @param {HoaExtension} fn - Plugin function that receives the app instance
64
63
  * @returns {Hoa} The Hoa instance for method chaining
65
64
  * @throws {TypeError}
66
65
  * @public
@@ -113,9 +112,7 @@ class Hoa {
113
112
  * @private
114
113
  */
115
114
  handleRequest(ctx, middlewareFn) {
116
- const onerror = (err) => ctx.onerror(err);
117
- const handleResponse = () => respond(ctx);
118
- return middlewareFn(ctx).then(handleResponse).catch(onerror);
115
+ return middlewareFn(ctx).then(() => ctx.response).catch((err) => ctx.onerror(err));
119
116
  }
120
117
  /**
121
118
  * Create context for incoming request with linked request/response objects.
@@ -180,63 +177,6 @@ ${msg.replace(/^/gm, " ")}
180
177
  };
181
178
  }
182
179
  }
183
- function respond(ctx) {
184
- const { res, req } = ctx;
185
- let body = res.body;
186
- if (req.method === "HEAD") {
187
- if (!res.has("Content-Length")) {
188
- const contentLength = res.length;
189
- if (Number.isInteger(contentLength)) {
190
- res.length = contentLength;
191
- }
192
- }
193
- return new Response(null, {
194
- status: res.status,
195
- statusText: res.statusText,
196
- headers: res._headers
197
- });
198
- }
199
- if (import_utils.statusEmptyMapping[res.status]) {
200
- res.body = null;
201
- return new Response(null, {
202
- status: res.status,
203
- statusText: res.statusText,
204
- headers: res._headers
205
- });
206
- }
207
- if (body == null) {
208
- if (res._explicitNullBody) {
209
- res.delete("Content-Type");
210
- res.delete("Transfer-Encoding");
211
- res.set("Content-Length", "0");
212
- }
213
- return new Response(null, {
214
- status: res.status,
215
- statusText: res.statusText,
216
- headers: res._headers
217
- });
218
- }
219
- if (typeof body === "string" || body instanceof Blob || body instanceof ArrayBuffer || ArrayBuffer.isView(body) || body instanceof ReadableStream || body instanceof FormData || body instanceof URLSearchParams) {
220
- return new Response(body, {
221
- status: res.status,
222
- statusText: res.statusText,
223
- headers: res._headers
224
- });
225
- }
226
- if (body instanceof Response) {
227
- return new Response(body.body, {
228
- status: res.status,
229
- statusText: res.statusText,
230
- headers: res._headers
231
- });
232
- }
233
- body = JSON.stringify(body);
234
- return new Response(body, {
235
- status: res.status,
236
- statusText: res.statusText,
237
- headers: res._headers
238
- });
239
- }
240
180
  // Annotate the CommonJS export names for ESM import in node:
241
181
  0 && (module.exports = {
242
182
  Hoa,
@@ -1,5 +1,5 @@
1
1
  import HttpError from "./lib/http-error.js";
2
- import { statusTextMapping } from "./lib/utils.js";
2
+ import { statusTextMapping, statusEmptyMapping } from "./lib/utils.js";
3
3
  class HoaContext {
4
4
  /**
5
5
  * Create a context for a single HTTP request.
@@ -65,6 +65,70 @@ class HoaContext {
65
65
  headers: res._headers
66
66
  });
67
67
  }
68
+ /**
69
+ * Build Web Standard Response from current context state.
70
+ * Handles various body types, HEAD requests, and status-specific behaviors.
71
+ *
72
+ * @returns {Response} Web Standard Response object
73
+ * @public
74
+ */
75
+ get response() {
76
+ const { res, req } = this;
77
+ let body = res.body;
78
+ if (req.method === "HEAD") {
79
+ if (!res.has("Content-Length")) {
80
+ const contentLength = res.length;
81
+ if (Number.isInteger(contentLength)) {
82
+ res.length = contentLength;
83
+ }
84
+ }
85
+ return new Response(null, {
86
+ status: res.status,
87
+ statusText: res.statusText,
88
+ headers: res._headers
89
+ });
90
+ }
91
+ if (statusEmptyMapping[res.status]) {
92
+ res.body = null;
93
+ return new Response(null, {
94
+ status: res.status,
95
+ statusText: res.statusText,
96
+ headers: res._headers
97
+ });
98
+ }
99
+ if (body == null) {
100
+ if (res._explicitNullBody) {
101
+ res.delete("Content-Type");
102
+ res.delete("Transfer-Encoding");
103
+ res.set("Content-Length", "0");
104
+ }
105
+ return new Response(null, {
106
+ status: res.status,
107
+ statusText: res.statusText,
108
+ headers: res._headers
109
+ });
110
+ }
111
+ if (typeof body === "string" || body instanceof Blob || body instanceof ArrayBuffer || ArrayBuffer.isView(body) || body instanceof ReadableStream || body instanceof FormData || body instanceof URLSearchParams) {
112
+ return new Response(body, {
113
+ status: res.status,
114
+ statusText: res.statusText,
115
+ headers: res._headers
116
+ });
117
+ }
118
+ if (body instanceof Response) {
119
+ return new Response(body.body, {
120
+ status: res.status,
121
+ statusText: res.statusText,
122
+ headers: res._headers
123
+ });
124
+ }
125
+ body = JSON.stringify(body);
126
+ return new Response(body, {
127
+ status: res.status,
128
+ statusText: res.statusText,
129
+ headers: res._headers
130
+ });
131
+ }
68
132
  /**
69
133
  * Return JSON representation of the context.
70
134
  *
package/dist/esm/hoa.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import compose from "./lib/compose.js";
2
2
  import HttpError from "./lib/http-error.js";
3
- import { statusEmptyMapping } from "./lib/utils.js";
4
3
  import HoaContext from "./context.js";
5
4
  import HoaRequest from "./request.js";
6
5
  import HoaResponse from "./response.js";
@@ -22,7 +21,7 @@ class Hoa {
22
21
  /**
23
22
  * Extend the application with a plugin initializer.
24
23
  *
25
- * @param {HoaExtend} fn - Plugin function that receives the app instance
24
+ * @param {HoaExtension} fn - Plugin function that receives the app instance
26
25
  * @returns {Hoa} The Hoa instance for method chaining
27
26
  * @throws {TypeError}
28
27
  * @public
@@ -75,9 +74,7 @@ class Hoa {
75
74
  * @private
76
75
  */
77
76
  handleRequest(ctx, middlewareFn) {
78
- const onerror = (err) => ctx.onerror(err);
79
- const handleResponse = () => respond(ctx);
80
- return middlewareFn(ctx).then(handleResponse).catch(onerror);
77
+ return middlewareFn(ctx).then(() => ctx.response).catch((err) => ctx.onerror(err));
81
78
  }
82
79
  /**
83
80
  * Create context for incoming request with linked request/response objects.
@@ -142,63 +139,6 @@ ${msg.replace(/^/gm, " ")}
142
139
  };
143
140
  }
144
141
  }
145
- function respond(ctx) {
146
- const { res, req } = ctx;
147
- let body = res.body;
148
- if (req.method === "HEAD") {
149
- if (!res.has("Content-Length")) {
150
- const contentLength = res.length;
151
- if (Number.isInteger(contentLength)) {
152
- res.length = contentLength;
153
- }
154
- }
155
- return new Response(null, {
156
- status: res.status,
157
- statusText: res.statusText,
158
- headers: res._headers
159
- });
160
- }
161
- if (statusEmptyMapping[res.status]) {
162
- res.body = null;
163
- return new Response(null, {
164
- status: res.status,
165
- statusText: res.statusText,
166
- headers: res._headers
167
- });
168
- }
169
- if (body == null) {
170
- if (res._explicitNullBody) {
171
- res.delete("Content-Type");
172
- res.delete("Transfer-Encoding");
173
- res.set("Content-Length", "0");
174
- }
175
- return new Response(null, {
176
- status: res.status,
177
- statusText: res.statusText,
178
- headers: res._headers
179
- });
180
- }
181
- if (typeof body === "string" || body instanceof Blob || body instanceof ArrayBuffer || ArrayBuffer.isView(body) || body instanceof ReadableStream || body instanceof FormData || body instanceof URLSearchParams) {
182
- return new Response(body, {
183
- status: res.status,
184
- statusText: res.statusText,
185
- headers: res._headers
186
- });
187
- }
188
- if (body instanceof Response) {
189
- return new Response(body.body, {
190
- status: res.status,
191
- statusText: res.statusText,
192
- headers: res._headers
193
- });
194
- }
195
- body = JSON.stringify(body);
196
- return new Response(body, {
197
- status: res.status,
198
- statusText: res.statusText,
199
- headers: res._headers
200
- });
201
- }
202
142
  export {
203
143
  Hoa,
204
144
  HoaContext,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hoa",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "A minimal web framework built on Web Standards",
5
5
  "main": "./dist/cjs/hoa.js",
6
6
  "type": "module",
package/types/index.d.ts CHANGED
@@ -20,7 +20,7 @@ interface ResJSON {
20
20
  headers: Record<string, string>;
21
21
  }
22
22
 
23
- export type HoaExtend = (app: Hoa) => void;
23
+ export type HoaExtension = (app: Hoa) => void;
24
24
 
25
25
  export type HoaMiddleware = (ctx: HoaContext, next?: () => Promise<void>) => Promise<void> | void;
26
26
 
@@ -34,7 +34,7 @@ export declare class Hoa {
34
34
  readonly HoaResponse: typeof HoaResponse;
35
35
  readonly middlewares: HoaMiddleware[];
36
36
 
37
- extend(fn: HoaExtend): this;
37
+ extend(fn: HoaExtension): this;
38
38
  use(fn: HoaMiddleware): this;
39
39
  fetch(request: Request, env?: any, executionCtx?: any): Promise<Response>;
40
40
  protected handleRequest(ctx: HoaContext, middlewareFn: HoaMiddleware): Promise<Response>;
@@ -58,6 +58,7 @@ export declare class HoaContext {
58
58
  assert<T>(value: T, status: number, message?: string | { message?: string; cause?: unknown; expose?: boolean; headers?: Headers | Record<string, string> | Iterable<readonly [string, string]> }, options?: { message?: string; cause?: unknown; expose?: boolean; headers?: Headers | Record<string, string> | Iterable<readonly [string, string]> }): asserts value is NonNullable<T>;
59
59
  onerror(err: unknown): Response;
60
60
  toJSON(): CtxJSON;
61
+ readonly response: Response;
61
62
  }
62
63
 
63
64
  export declare class HoaRequest {