hoa 0.2.1 → 0.3.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/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## v0.3.1 / 2025-11-08
2
+
3
+ - fix: app.onerror print original error
4
+
5
+ ## v0.3.0 / 2025-10-16
6
+
7
+ - feat: move respond() to ctx.response
8
+
1
9
  ## v0.2.1 / 2025-10-15
2
10
 
3
11
  - fix: rename HoaExtend to HoaExtension
@@ -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);
@@ -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.
@@ -154,10 +151,7 @@ class Hoa {
154
151
  }
155
152
  if (err.status === 404 || err.expose) return;
156
153
  if (this.silent) return;
157
- const msg = err.stack || err.toString();
158
- console.error(`
159
- ${msg.replace(/^/gm, " ")}
160
- `);
154
+ console.error(err);
161
155
  }
162
156
  /**
163
157
  * ESM/CJS interop helper for default exports.
@@ -180,63 +174,6 @@ ${msg.replace(/^/gm, " ")}
180
174
  };
181
175
  }
182
176
  }
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
177
  // Annotate the CommonJS export names for ESM import in node:
241
178
  0 && (module.exports = {
242
179
  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";
@@ -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.
@@ -116,10 +113,7 @@ class Hoa {
116
113
  }
117
114
  if (err.status === 404 || err.expose) return;
118
115
  if (this.silent) return;
119
- const msg = err.stack || err.toString();
120
- console.error(`
121
- ${msg.replace(/^/gm, " ")}
122
- `);
116
+ console.error(err);
123
117
  }
124
118
  /**
125
119
  * ESM/CJS interop helper for default exports.
@@ -142,63 +136,6 @@ ${msg.replace(/^/gm, " ")}
142
136
  };
143
137
  }
144
138
  }
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
139
  export {
203
140
  Hoa,
204
141
  HoaContext,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hoa",
3
- "version": "0.2.1",
3
+ "version": "0.3.1",
4
4
  "description": "A minimal web framework built on Web Standards",
5
5
  "main": "./dist/cjs/hoa.js",
6
6
  "type": "module",
@@ -50,8 +50,8 @@
50
50
  "devDependencies": {
51
51
  "@commitlint/cli": "20.1.0",
52
52
  "@commitlint/config-conventional": "20.0.0",
53
- "eslint": "9.37.0",
54
- "globals": "16.4.0",
53
+ "eslint": "9.39.1",
54
+ "globals": "16.5.0",
55
55
  "husky": "9.1.7",
56
56
  "jest": "30.2.0",
57
57
  "neostandard": "0.12.2",
package/types/index.d.ts CHANGED
@@ -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 {