hoa 0.0.1 → 0.1.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.
@@ -0,0 +1,42 @@
1
+ import { statusTextMapping } from "./utils.js";
2
+ class HttpError extends Error {
3
+ /**
4
+ * Create a new HttpError instance.
5
+ *
6
+ * @param {number} status - HTTP status code (400-599, invalid codes become 500)
7
+ * @param {string|HttpErrorOptions} [message] - Error message or options object
8
+ * @param {HttpErrorOptions} [options] - Additional options when second param is string
9
+ * @throws {TypeError} When status is not an integer
10
+ */
11
+ constructor(status, message, options) {
12
+ if (!Number.isInteger(status)) {
13
+ throw new TypeError("status code must be an integer");
14
+ }
15
+ if (status < 400 || status >= 600) {
16
+ status = 500;
17
+ }
18
+ let finalOptions = {};
19
+ if (typeof message === "string") {
20
+ finalOptions.message = message;
21
+ if (options && typeof options === "object") {
22
+ finalOptions = { ...finalOptions, ...options };
23
+ }
24
+ } else if (message && typeof message === "object") {
25
+ finalOptions = message;
26
+ }
27
+ message = finalOptions.message ?? statusTextMapping[status] ?? "Unknown error";
28
+ super(message, { cause: finalOptions.cause });
29
+ this.name = "HttpError";
30
+ this.status = this.statusCode = status;
31
+ this.expose = finalOptions.expose ?? status < 500;
32
+ if (finalOptions.headers) {
33
+ this.headers = Object.fromEntries(new Headers(finalOptions.headers).entries());
34
+ }
35
+ if (Error.captureStackTrace) {
36
+ Error.captureStackTrace(this, HttpError);
37
+ }
38
+ }
39
+ }
40
+ export {
41
+ HttpError as default
42
+ };
@@ -0,0 +1,161 @@
1
+ function parseSearchParamsToQuery(searchParams) {
2
+ const query = {};
3
+ for (const [key, value] of searchParams) {
4
+ if (query[key] !== void 0) {
5
+ query[key] = [].concat(query[key], value);
6
+ } else {
7
+ query[key] = value;
8
+ }
9
+ }
10
+ return query;
11
+ }
12
+ function stringifyQueryToString(query) {
13
+ if (!query) {
14
+ return "";
15
+ }
16
+ const params = new URLSearchParams();
17
+ for (const [key, value] of Object.entries(query)) {
18
+ if (Array.isArray(value)) {
19
+ value.forEach((v) => params.append(key, v ?? ""));
20
+ } else {
21
+ params.append(key, value ?? "");
22
+ }
23
+ }
24
+ return params.toString();
25
+ }
26
+ const statusTextMapping = {
27
+ 100: "Continue",
28
+ 101: "Switching Protocols",
29
+ 102: "Processing",
30
+ 103: "Early Hints",
31
+ 200: "OK",
32
+ 201: "Created",
33
+ 202: "Accepted",
34
+ 203: "Non-Authoritative Information",
35
+ 204: "No Content",
36
+ 205: "Reset Content",
37
+ 206: "Partial Content",
38
+ 207: "Multi-Status",
39
+ 208: "Already Reported",
40
+ 226: "IM Used",
41
+ 300: "Multiple Choices",
42
+ 301: "Moved Permanently",
43
+ 302: "Found",
44
+ 303: "See Other",
45
+ 304: "Not Modified",
46
+ 305: "Use Proxy",
47
+ 307: "Temporary Redirect",
48
+ 308: "Permanent Redirect",
49
+ 400: "Bad Request",
50
+ 401: "Unauthorized",
51
+ 402: "Payment Required",
52
+ 403: "Forbidden",
53
+ 404: "Not Found",
54
+ 405: "Method Not Allowed",
55
+ 406: "Not Acceptable",
56
+ 407: "Proxy Authentication Required",
57
+ 408: "Request Timeout",
58
+ 409: "Conflict",
59
+ 410: "Gone",
60
+ 411: "Length Required",
61
+ 412: "Precondition Failed",
62
+ 413: "Payload Too Large",
63
+ 414: "URI Too Long",
64
+ 415: "Unsupported Media Type",
65
+ 416: "Range Not Satisfiable",
66
+ 417: "Expectation Failed",
67
+ 418: "I'm a Teapot",
68
+ 421: "Misdirected Request",
69
+ 422: "Unprocessable Entity",
70
+ 423: "Locked",
71
+ 424: "Failed Dependency",
72
+ 425: "Too Early",
73
+ 426: "Upgrade Required",
74
+ 428: "Precondition Required",
75
+ 429: "Too Many Requests",
76
+ 431: "Request Header Fields Too Large",
77
+ 451: "Unavailable For Legal Reasons",
78
+ 500: "Internal Server Error",
79
+ 501: "Not Implemented",
80
+ 502: "Bad Gateway",
81
+ 503: "Service Unavailable",
82
+ 504: "Gateway Timeout",
83
+ 505: "HTTP Version Not Supported",
84
+ 506: "Variant Also Negotiates",
85
+ 507: "Insufficient Storage",
86
+ 508: "Loop Detected",
87
+ 509: "Bandwidth Limit Exceeded",
88
+ 510: "Not Extended",
89
+ 511: "Network Authentication Required"
90
+ };
91
+ const statusRedirectMapping = {
92
+ 300: true,
93
+ 301: true,
94
+ 302: true,
95
+ 303: true,
96
+ 305: true,
97
+ 307: true,
98
+ 308: true
99
+ };
100
+ const statusEmptyMapping = {
101
+ 204: true,
102
+ 205: true,
103
+ 304: true
104
+ };
105
+ const commonTypeMapping = {
106
+ // Text types
107
+ html: "text/html;charset=UTF-8",
108
+ text: "text/plain;charset=UTF-8",
109
+ xml: "text/xml;charset=UTF-8",
110
+ md: "text/markdown;charset=UTF-8",
111
+ // Application types
112
+ json: "application/json",
113
+ form: "application/x-www-form-urlencoded;charset=UTF-8",
114
+ pdf: "application/pdf",
115
+ zip: "application/zip",
116
+ wasm: "application/wasm",
117
+ webmanifest: "application/manifest+json",
118
+ // JavaScript/TypeScript
119
+ js: "application/javascript;charset=UTF-8",
120
+ ts: "application/typescript;charset=UTF-8",
121
+ // Image types
122
+ png: "image/png",
123
+ jpg: "image/jpeg",
124
+ jpeg: "image/jpeg",
125
+ gif: "image/gif",
126
+ svg: "image/svg+xml",
127
+ webp: "image/webp",
128
+ avif: "image/avif",
129
+ ico: "image/x-icon",
130
+ // Audio types
131
+ mp3: "audio/mpeg",
132
+ wav: "audio/wav",
133
+ ogg: "audio/ogg",
134
+ // Video types
135
+ mp4: "video/mp4",
136
+ webm: "video/webm",
137
+ avi: "video/x-msvideo",
138
+ mov: "video/quicktime",
139
+ // Font types
140
+ woff: "font/woff",
141
+ woff2: "font/woff2",
142
+ ttf: "font/ttf",
143
+ otf: "font/otf",
144
+ // Binary
145
+ bin: "application/octet-stream"
146
+ };
147
+ 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;
148
+ const UNMATCHED_SURROGATE_PAIR_REGEXP = /(^|[^\uD800-\uDBFF])[\uDC00-\uDFFF]|[\uD800-\uDBFF]([^\uDC00-\uDFFF]|$)/g;
149
+ const UNMATCHED_SURROGATE_PAIR_REPLACE = "$1\uFFFD$2";
150
+ function encodeUrl(url) {
151
+ return String(url).replace(UNMATCHED_SURROGATE_PAIR_REGEXP, UNMATCHED_SURROGATE_PAIR_REPLACE).replace(ENCODE_CHARS_REGEXP, encodeURI);
152
+ }
153
+ export {
154
+ commonTypeMapping,
155
+ encodeUrl,
156
+ parseSearchParamsToQuery,
157
+ statusEmptyMapping,
158
+ statusRedirectMapping,
159
+ statusTextMapping,
160
+ stringifyQueryToString
161
+ };