hoa 0.3.3 → 0.3.5
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 +9 -0
- package/dist/cjs/context.cjs +152 -0
- package/dist/cjs/hoa.cjs +158 -0
- package/dist/cjs/lib/compose.cjs +35 -0
- package/dist/cjs/lib/http-error.cjs +42 -0
- package/dist/cjs/lib/utils.cjs +214 -0
- package/dist/cjs/request.cjs +516 -0
- package/dist/cjs/response.cjs +335 -0
- package/dist/esm/context.mjs +152 -0
- package/dist/esm/hoa.mjs +148 -0
- package/dist/esm/lib/compose.mjs +34 -0
- package/dist/esm/lib/http-error.mjs +42 -0
- package/dist/esm/lib/utils.mjs +207 -0
- package/dist/esm/request.mjs +516 -0
- package/dist/esm/response.mjs +335 -0
- package/package.json +12 -12
- package/dist/cjs/context.js +0 -177
- package/dist/cjs/hoa.js +0 -192
- package/dist/cjs/lib/compose.js +0 -40
- package/dist/cjs/lib/http-error.js +0 -61
- package/dist/cjs/lib/utils.js +0 -190
- package/dist/cjs/request.js +0 -546
- package/dist/cjs/response.js +0 -379
- package/dist/esm/context.js +0 -148
- package/dist/esm/hoa.js +0 -151
- package/dist/esm/lib/compose.js +0 -21
- package/dist/esm/lib/http-error.js +0 -42
- package/dist/esm/lib/utils.js +0 -161
- package/dist/esm/request.js +0 -527
- package/dist/esm/response.js +0 -360
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { statusTextMapping } from "./utils.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/lib/http-error.js
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {Object} HttpErrorOptions
|
|
6
|
+
* @property {string} [message] - Custom error message
|
|
7
|
+
* @property {Error} [cause] - The underlying cause of this error
|
|
8
|
+
* @property {boolean} [expose] - Whether to expose the error message to clients (defaults based on status)
|
|
9
|
+
* @property {HeadersInit} [headers] - Additional response headers
|
|
10
|
+
*/
|
|
11
|
+
var HttpError = class HttpError extends Error {
|
|
12
|
+
/**
|
|
13
|
+
* Create a new HttpError instance.
|
|
14
|
+
*
|
|
15
|
+
* @param {number} status - HTTP status code (400-599, invalid codes become 500)
|
|
16
|
+
* @param {string|HttpErrorOptions} [message] - Error message or options object
|
|
17
|
+
* @param {HttpErrorOptions} [options] - Additional options when second param is string
|
|
18
|
+
* @throws {TypeError}
|
|
19
|
+
*/
|
|
20
|
+
constructor(status, message, options) {
|
|
21
|
+
if (!Number.isInteger(status)) throw new TypeError("status code must be an integer");
|
|
22
|
+
if (status < 400 || status >= 600) status = 500;
|
|
23
|
+
let finalOptions = {};
|
|
24
|
+
if (typeof message === "string") {
|
|
25
|
+
finalOptions.message = message;
|
|
26
|
+
if (options && typeof options === "object") finalOptions = {
|
|
27
|
+
...finalOptions,
|
|
28
|
+
...options
|
|
29
|
+
};
|
|
30
|
+
} else if (message && typeof message === "object") finalOptions = message;
|
|
31
|
+
message = finalOptions.message ?? statusTextMapping[status] ?? "Unknown error";
|
|
32
|
+
super(message, { cause: finalOptions.cause });
|
|
33
|
+
this.name = "HttpError";
|
|
34
|
+
this.status = this.statusCode = status;
|
|
35
|
+
this.expose = finalOptions.expose ?? status < 500;
|
|
36
|
+
if (finalOptions.headers) this.headers = Object.fromEntries(new Headers(finalOptions.headers).entries());
|
|
37
|
+
if (Error.captureStackTrace) Error.captureStackTrace(this, HttpError);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
//#endregion
|
|
42
|
+
export { HttpError as default };
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
//#region src/lib/utils.js
|
|
2
|
+
/**
|
|
3
|
+
* Parse URLSearchParams into a query object, handling multiple values for the same key.
|
|
4
|
+
* When a key appears multiple times, values are collected into an array.
|
|
5
|
+
*
|
|
6
|
+
* @param {URLSearchParams} searchParams - The URLSearchParams object to parse
|
|
7
|
+
* @returns {Record<string, string|string[]>} Query object with string values or arrays for multiple values
|
|
8
|
+
* @public
|
|
9
|
+
*/
|
|
10
|
+
function parseSearchParamsToQuery(searchParams) {
|
|
11
|
+
const query = {};
|
|
12
|
+
for (const [key, value] of searchParams) if (query[key] !== void 0) query[key] = [].concat(query[key], value);
|
|
13
|
+
else query[key] = value;
|
|
14
|
+
return query;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Convert a query object to a URL query string.
|
|
18
|
+
* Handles arrays by appending multiple parameters with the same key.
|
|
19
|
+
*
|
|
20
|
+
* @param {Record<string, string|string[]|undefined|null>} query - Query object to stringify
|
|
21
|
+
* @returns {string} URL-encoded query string (without leading '?')
|
|
22
|
+
* @public
|
|
23
|
+
*/
|
|
24
|
+
function stringifyQueryToString(query) {
|
|
25
|
+
if (!query) return "";
|
|
26
|
+
const params = new URLSearchParams();
|
|
27
|
+
for (const [key, value] of Object.entries(query)) if (Array.isArray(value)) value.forEach((v) => params.append(key, v ?? ""));
|
|
28
|
+
else params.append(key, value ?? "");
|
|
29
|
+
return params.toString();
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Mapping of HTTP status codes to their standard reason phrases.
|
|
33
|
+
*
|
|
34
|
+
* @type {Record<number, string>}
|
|
35
|
+
* @public
|
|
36
|
+
*/
|
|
37
|
+
const statusTextMapping = {
|
|
38
|
+
100: "Continue",
|
|
39
|
+
101: "Switching Protocols",
|
|
40
|
+
102: "Processing",
|
|
41
|
+
103: "Early Hints",
|
|
42
|
+
200: "OK",
|
|
43
|
+
201: "Created",
|
|
44
|
+
202: "Accepted",
|
|
45
|
+
203: "Non-Authoritative Information",
|
|
46
|
+
204: "No Content",
|
|
47
|
+
205: "Reset Content",
|
|
48
|
+
206: "Partial Content",
|
|
49
|
+
207: "Multi-Status",
|
|
50
|
+
208: "Already Reported",
|
|
51
|
+
226: "IM Used",
|
|
52
|
+
300: "Multiple Choices",
|
|
53
|
+
301: "Moved Permanently",
|
|
54
|
+
302: "Found",
|
|
55
|
+
303: "See Other",
|
|
56
|
+
304: "Not Modified",
|
|
57
|
+
305: "Use Proxy",
|
|
58
|
+
307: "Temporary Redirect",
|
|
59
|
+
308: "Permanent Redirect",
|
|
60
|
+
400: "Bad Request",
|
|
61
|
+
401: "Unauthorized",
|
|
62
|
+
402: "Payment Required",
|
|
63
|
+
403: "Forbidden",
|
|
64
|
+
404: "Not Found",
|
|
65
|
+
405: "Method Not Allowed",
|
|
66
|
+
406: "Not Acceptable",
|
|
67
|
+
407: "Proxy Authentication Required",
|
|
68
|
+
408: "Request Timeout",
|
|
69
|
+
409: "Conflict",
|
|
70
|
+
410: "Gone",
|
|
71
|
+
411: "Length Required",
|
|
72
|
+
412: "Precondition Failed",
|
|
73
|
+
413: "Payload Too Large",
|
|
74
|
+
414: "URI Too Long",
|
|
75
|
+
415: "Unsupported Media Type",
|
|
76
|
+
416: "Range Not Satisfiable",
|
|
77
|
+
417: "Expectation Failed",
|
|
78
|
+
418: "I'm a Teapot",
|
|
79
|
+
421: "Misdirected Request",
|
|
80
|
+
422: "Unprocessable Entity",
|
|
81
|
+
423: "Locked",
|
|
82
|
+
424: "Failed Dependency",
|
|
83
|
+
425: "Too Early",
|
|
84
|
+
426: "Upgrade Required",
|
|
85
|
+
428: "Precondition Required",
|
|
86
|
+
429: "Too Many Requests",
|
|
87
|
+
431: "Request Header Fields Too Large",
|
|
88
|
+
451: "Unavailable For Legal Reasons",
|
|
89
|
+
500: "Internal Server Error",
|
|
90
|
+
501: "Not Implemented",
|
|
91
|
+
502: "Bad Gateway",
|
|
92
|
+
503: "Service Unavailable",
|
|
93
|
+
504: "Gateway Timeout",
|
|
94
|
+
505: "HTTP Version Not Supported",
|
|
95
|
+
506: "Variant Also Negotiates",
|
|
96
|
+
507: "Insufficient Storage",
|
|
97
|
+
508: "Loop Detected",
|
|
98
|
+
509: "Bandwidth Limit Exceeded",
|
|
99
|
+
510: "Not Extended",
|
|
100
|
+
511: "Network Authentication Required"
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* Mapping of HTTP status codes that indicate redirects.
|
|
104
|
+
* Used to determine if a response should trigger a redirect.
|
|
105
|
+
*
|
|
106
|
+
* @type {Record<number, boolean>}
|
|
107
|
+
* @readonly
|
|
108
|
+
* @public
|
|
109
|
+
*/
|
|
110
|
+
const statusRedirectMapping = {
|
|
111
|
+
300: true,
|
|
112
|
+
301: true,
|
|
113
|
+
302: true,
|
|
114
|
+
303: true,
|
|
115
|
+
305: true,
|
|
116
|
+
307: true,
|
|
117
|
+
308: true
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* Mapping of HTTP status codes that should have empty response bodies.
|
|
121
|
+
* These status codes by specification should not include a message body.
|
|
122
|
+
*
|
|
123
|
+
* @type {Record<number, boolean>}
|
|
124
|
+
* @readonly
|
|
125
|
+
* @public
|
|
126
|
+
*/
|
|
127
|
+
const statusEmptyMapping = {
|
|
128
|
+
204: true,
|
|
129
|
+
205: true,
|
|
130
|
+
304: true
|
|
131
|
+
};
|
|
132
|
+
/**
|
|
133
|
+
* Mapping of common content type aliases to their full MIME types.
|
|
134
|
+
* Provides convenient shortcuts for setting response content types.
|
|
135
|
+
*
|
|
136
|
+
* @type {Record<string, string>}
|
|
137
|
+
* @readonly
|
|
138
|
+
* @public
|
|
139
|
+
*/
|
|
140
|
+
const commonTypeMapping = {
|
|
141
|
+
html: "text/html;charset=UTF-8",
|
|
142
|
+
text: "text/plain;charset=UTF-8",
|
|
143
|
+
xml: "text/xml;charset=UTF-8",
|
|
144
|
+
md: "text/markdown;charset=UTF-8",
|
|
145
|
+
json: "application/json",
|
|
146
|
+
form: "application/x-www-form-urlencoded;charset=UTF-8",
|
|
147
|
+
pdf: "application/pdf",
|
|
148
|
+
zip: "application/zip",
|
|
149
|
+
wasm: "application/wasm",
|
|
150
|
+
webmanifest: "application/manifest+json",
|
|
151
|
+
js: "application/javascript;charset=UTF-8",
|
|
152
|
+
ts: "application/typescript;charset=UTF-8",
|
|
153
|
+
png: "image/png",
|
|
154
|
+
jpg: "image/jpeg",
|
|
155
|
+
jpeg: "image/jpeg",
|
|
156
|
+
gif: "image/gif",
|
|
157
|
+
svg: "image/svg+xml",
|
|
158
|
+
webp: "image/webp",
|
|
159
|
+
avif: "image/avif",
|
|
160
|
+
ico: "image/x-icon",
|
|
161
|
+
mp3: "audio/mpeg",
|
|
162
|
+
wav: "audio/wav",
|
|
163
|
+
ogg: "audio/ogg",
|
|
164
|
+
mp4: "video/mp4",
|
|
165
|
+
webm: "video/webm",
|
|
166
|
+
avi: "video/x-msvideo",
|
|
167
|
+
mov: "video/quicktime",
|
|
168
|
+
woff: "font/woff",
|
|
169
|
+
woff2: "font/woff2",
|
|
170
|
+
ttf: "font/ttf",
|
|
171
|
+
otf: "font/otf",
|
|
172
|
+
bin: "application/octet-stream"
|
|
173
|
+
};
|
|
174
|
+
const ENCODE_CHARS_REGEXP = /(?:[^\x21\x23-\x3B\x3D\x3F-\x5F\x61-\x7A\x7C\x7E]|%(?:[^0-9A-Fa-f]|[0-9A-Fa-f][^0-9A-Fa-f]|$))+/g;
|
|
175
|
+
/**
|
|
176
|
+
* RegExp to match unmatched surrogate pair.
|
|
177
|
+
* @private
|
|
178
|
+
*/
|
|
179
|
+
const UNMATCHED_SURROGATE_PAIR_REGEXP = /(^|[^\uD800-\uDBFF])[\uDC00-\uDFFF]|[\uD800-\uDBFF]([^\uDC00-\uDFFF]|$)/g;
|
|
180
|
+
/**
|
|
181
|
+
* String to replace unmatched surrogate pair with.
|
|
182
|
+
* @private
|
|
183
|
+
*/
|
|
184
|
+
const UNMATCHED_SURROGATE_PAIR_REPLACE = "$1�$2";
|
|
185
|
+
/**
|
|
186
|
+
* Encode a URL to a percent-encoded form, excluding already-encoded sequences.
|
|
187
|
+
*
|
|
188
|
+
* This function will take an already-encoded URL and encode all the non-URL
|
|
189
|
+
* code points. This function will not encode the "%" character unless it is
|
|
190
|
+
* not part of a valid sequence (`%20` will be left as-is, but `%foo` will
|
|
191
|
+
* be encoded as `%25foo`).
|
|
192
|
+
*
|
|
193
|
+
* This encode is meant to be "safe" and does not throw errors. It will try as
|
|
194
|
+
* hard as it can to properly encode the given URL, including replacing any raw,
|
|
195
|
+
* unpaired surrogate pairs with the Unicode replacement character prior to
|
|
196
|
+
* encoding.
|
|
197
|
+
*
|
|
198
|
+
* @param {string} url - URL string to encode
|
|
199
|
+
* @return {string} Encoded URL string
|
|
200
|
+
* @public
|
|
201
|
+
*/
|
|
202
|
+
function encodeUrl(url) {
|
|
203
|
+
return String(url).replace(UNMATCHED_SURROGATE_PAIR_REGEXP, UNMATCHED_SURROGATE_PAIR_REPLACE).replace(ENCODE_CHARS_REGEXP, encodeURI);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
//#endregion
|
|
207
|
+
export { commonTypeMapping, encodeUrl, parseSearchParamsToQuery, statusEmptyMapping, statusRedirectMapping, statusTextMapping, stringifyQueryToString };
|