@boo-labs/server 0.0.0-stage → 1.0.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/README.md +229 -2
- package/index.mjs +23 -0
- package/package.json +71 -4
- package/source/application/Kernel/index.mjs +1137 -0
- package/source/application/Server/index.mjs +1201 -0
- package/source/application/configurations/Http2CleartextServerConfiguration/index.mjs +168 -0
- package/source/application/configurations/Http2SecureServerConfiguration/index.mjs +172 -0
- package/source/application/configurations/HttpServerConfiguration/index.mjs +169 -0
- package/source/application/configurations/HttpsServerConfiguration/index.mjs +172 -0
- package/source/application/configurations/ServerConfiguration/Options/CORSOptions/CORSOptionsValue.mjs +158 -0
- package/source/application/configurations/ServerConfiguration/Options/CORSOptions/index.mjs +87 -0
- package/source/application/configurations/ServerConfiguration/Options/CSRFOptions/CSRFOptionsValue.mjs +178 -0
- package/source/application/configurations/ServerConfiguration/Options/CSRFOptions/index.mjs +80 -0
- package/source/application/configurations/ServerConfiguration/Options/CompressOptions/CompressOptionsValue.mjs +126 -0
- package/source/application/configurations/ServerConfiguration/Options/CompressOptions/index.mjs +81 -0
- package/source/application/configurations/ServerConfiguration/Options/CookieParserOptions/CookieParserOptionsValue.mjs +107 -0
- package/source/application/configurations/ServerConfiguration/Options/CookieParserOptions/index.mjs +82 -0
- package/source/application/configurations/ServerConfiguration/Options/CookieSessionOptions/CookieSessionOptionsValue.mjs +182 -0
- package/source/application/configurations/ServerConfiguration/Options/CookieSessionOptions/index.mjs +86 -0
- package/source/application/configurations/ServerConfiguration/Options/ListenOptions/ListenOptionsValue.mjs +95 -0
- package/source/application/configurations/ServerConfiguration/Options/ListenOptions/index.mjs +47 -0
- package/source/application/configurations/ServerConfiguration/Options/MiddlewareOption.mjs +102 -0
- package/source/application/configurations/ServerConfiguration/Options/Options.mjs +105 -0
- package/source/application/configurations/ServerConfiguration/Options/RateLimitOptions/RateLimitOptionsValue.mjs +164 -0
- package/source/application/configurations/ServerConfiguration/Options/RateLimitOptions/index.mjs +84 -0
- package/source/application/configurations/ServerConfiguration/Options/RequestOptions/RequestOptionsValue.mjs +108 -0
- package/source/application/configurations/ServerConfiguration/Options/RequestOptions/index.mjs +47 -0
- package/source/application/configurations/ServerConfiguration/Options/ResponseOptions/ResponseOptionsValue.mjs +101 -0
- package/source/application/configurations/ServerConfiguration/Options/ResponseOptions/index.mjs +47 -0
- package/source/application/configurations/ServerConfiguration/Options/SecurityHeadersOptions/SecurityHeadersOptionsValue.mjs +272 -0
- package/source/application/configurations/ServerConfiguration/Options/SecurityHeadersOptions/index.mjs +85 -0
- package/source/application/configurations/ServerConfiguration/Options/ServerOption.mjs +50 -0
- package/source/application/configurations/ServerConfiguration/Options/StaticOptions/StaticOptionsValue.mjs +164 -0
- package/source/application/configurations/ServerConfiguration/Options/StaticOptions/index.mjs +89 -0
- package/source/application/configurations/ServerConfiguration/index.mjs +781 -0
- package/source/application/resolvers/TransportResolver/index.mjs +119 -0
- package/source/application/use-cases/CloseServer/index.mjs +85 -0
- package/source/application/use-cases/ListenServer/index.mjs +98 -0
- package/source/core/EventSystem/index.mjs +110 -0
- package/source/core/Exchange/ExchangeContext.mjs +291 -0
- package/source/core/Exchange/Telemetry.mjs +193 -0
- package/source/core/Exchange/index.mjs +604 -0
- package/source/core/ExchangeLayerBuilder/index.mjs +133 -0
- package/source/core/IPCMain/IPCMain.mjs +507 -0
- package/source/core/IPCMain/IPCProtocol.mjs +441 -0
- package/source/core/IPCMain/IPCServerChannel.mjs +331 -0
- package/source/core/IPCMain/WebSocketAdapter.mjs +189 -0
- package/source/core/Request/BodyParser/BodyParser.mjs +197 -0
- package/source/core/Request/BodyParser/JSONParser.mjs +70 -0
- package/source/core/Request/BodyParser/MultipartParser.mjs +612 -0
- package/source/core/Request/BodyParser/URLEncodedParser.mjs +71 -0
- package/source/core/Request/RequestHeaders.mjs +177 -0
- package/source/core/Request/index.mjs +805 -0
- package/source/core/Response/ResponseHeaders.mjs +402 -0
- package/source/core/Response/index.mjs +1370 -0
- package/source/core/middlewares/CORSMiddleware/index.mjs +289 -0
- package/source/core/middlewares/CSRFMiddleware/index.mjs +284 -0
- package/source/core/middlewares/CompressMiddleware/index.mjs +460 -0
- package/source/core/middlewares/CookieParserMiddleware/index.mjs +194 -0
- package/source/core/middlewares/CookieSessionMiddleware/index.mjs +460 -0
- package/source/core/middlewares/RateLimitMiddleware/index.mjs +245 -0
- package/source/core/middlewares/SecurityHeadersMiddleware/index.mjs +151 -0
- package/source/core/middlewares/StaticMiddleware/index.mjs +628 -0
- package/source/core/routing/ExchangeLayer/ExchangeMatcher.mjs +127 -0
- package/source/core/routing/ExchangeLayer/index.mjs +254 -0
- package/source/core/routing/Pipeline/index.mjs +157 -0
- package/source/core/routing/Router/Route.mjs +184 -0
- package/source/core/routing/Router/index.mjs +523 -0
- package/source/core/routing/RouterRegister/OptionsMiddleware.mjs +147 -0
- package/source/core/routing/RouterRegister/index.mjs +150 -0
- package/source/core/routing/RouterSystem/index.mjs +225 -0
- package/source/core/value-objects/Address/index.mjs +281 -0
- package/source/core/value-objects/Endpoint/index.mjs +293 -0
- package/source/index.mjs +62 -0
- package/source/infrastructure/adapters/Http2Adapter/index.mjs +144 -0
- package/source/infrastructure/adapters/Http2CleartextAdapter/index.mjs +147 -0
- package/source/infrastructure/adapters/HttpAdapter/index.mjs +124 -0
- package/source/infrastructure/adapters/HttpsAdapter/index.mjs +124 -0
- package/source/infrastructure/adapters/TransportAdapter/createWebSocketOpenRequest.mjs +843 -0
- package/source/infrastructure/adapters/TransportAdapter/index.mjs +555 -0
- package/source/shared/Identifier/index.mjs +671 -0
- package/source/shared/constants/symbols/brands/index.mjs +45 -0
- package/source/shared/domain/UUIDV7/index.mjs +346 -0
- package/source/shared/errors/BaseError/index.mjs +157 -0
- package/source/shared/errors/IPCError/index.mjs +97 -0
- package/source/shared/errors/MiddlewareError/index.mjs +106 -0
- package/source/shared/errors/PathnamePatternError/index.mjs +103 -0
- package/source/shared/errors/RequestError/index.mjs +116 -0
- package/source/shared/errors/ResponseError/index.mjs +140 -0
- package/source/shared/errors/RouterError/index.mjs +90 -0
- package/source/shared/errors/ServerConfigurationError/index.mjs +551 -0
- package/source/shared/errors/ServerError/index.mjs +127 -0
- package/source/shared/errors/WebSocketError/index.mjs +58 -0
- package/source/shared/outcome/ErrorSchema.mjs +361 -0
- package/source/shared/outcome/Result/index.mjs +239 -0
- package/source/shared/outcome/Validation/index.mjs +208 -0
- package/source/shared/validations/Middleware/index.mjs +160 -0
- package/source/shared/validations/PathnamePattern/index.mjs +256 -0
- package/source/shared/validations/Server/NotFoundHandler/index.mjs +94 -0
- package/source/shared/validations/Server/Router/index.mjs +68 -0
- package/source/shared/validations/Server/ServerConfiguration/index.mjs +68 -0
- package/source/shared/validations/Server/SystemErrorHandler/index.mjs +94 -0
- package/source/shared/validations/ServerConfiguration/CORSOptions/index.mjs +310 -0
- package/source/shared/validations/ServerConfiguration/CSRFOptions/index.mjs +299 -0
- package/source/shared/validations/ServerConfiguration/CompressOptions/index.mjs +204 -0
- package/source/shared/validations/ServerConfiguration/CookieParserOptions/index.mjs +102 -0
- package/source/shared/validations/ServerConfiguration/CookieSessionOptions/index.mjs +332 -0
- package/source/shared/validations/ServerConfiguration/ListenOptions/index.mjs +178 -0
- package/source/shared/validations/ServerConfiguration/RateLimitOptions/index.mjs +229 -0
- package/source/shared/validations/ServerConfiguration/RequestOptions/index.mjs +132 -0
- package/source/shared/validations/ServerConfiguration/ResponseOptions/index.mjs +122 -0
- package/source/shared/validations/ServerConfiguration/SecurityHeadersOptions/index.mjs +137 -0
- package/source/shared/validations/ServerConfiguration/StaticOptions/index.mjs +248 -0
- package/types/index.d.ts +719 -0
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/**
|
|
3
|
+
* @file Defines the mutable response headers abstraction.
|
|
4
|
+
*
|
|
5
|
+
* @description `ResponseHeaders` owns the mutable header map of an outgoing
|
|
6
|
+
* HTTP response. It validates header names and values, normalizes names to
|
|
7
|
+
* lowercase, and enforces the `Content-Type` lock once the response body has
|
|
8
|
+
* been described.
|
|
9
|
+
*
|
|
10
|
+
* The class does not know about the response body. It only knows when its
|
|
11
|
+
* `Content-Type` becomes locked, which is signaled by `Response` through
|
|
12
|
+
* `lockContentType()`.
|
|
13
|
+
*
|
|
14
|
+
* @module ResponseHeaders
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
// ━━ IMPORT MODULES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
18
|
+
// » IMPORT LOCAL MODULES
|
|
19
|
+
import ResponseError from '#shared/errors/ResponseError';
|
|
20
|
+
|
|
21
|
+
// ━━ TYPE DEFINITIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
22
|
+
/**
|
|
23
|
+
* Represents a supported response header value.
|
|
24
|
+
*
|
|
25
|
+
* @typedef {string|number|(string|number)[]} ResponseHeaderValue
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Represents the collection of response headers as a plain object.
|
|
30
|
+
*
|
|
31
|
+
* Used by `toObject()` and by the native `writeHead` call. The internal
|
|
32
|
+
* representation of `ResponseHeaders` is a `Map`, not this type.
|
|
33
|
+
*
|
|
34
|
+
* @typedef {Record<string, ResponseHeaderValue>} ResponseHeadersData
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Identifies the operation being performed on a response header.
|
|
39
|
+
*
|
|
40
|
+
* @typedef {'set'|'append'|'remove'} HeaderOperation
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Represents an iterator over response header entries.
|
|
45
|
+
*
|
|
46
|
+
* @typedef {Iterator<[string, ResponseHeaderValue]>} ResponseHeadersIterator
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
// ━━ CONSTANTS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
50
|
+
/**
|
|
51
|
+
* Validates HTTP field names according to the HTTP token syntax.
|
|
52
|
+
*
|
|
53
|
+
* @type {RegExp}
|
|
54
|
+
*/
|
|
55
|
+
const HEADER_NAME_PATTERN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Validates HTTP field values supported by this response abstraction.
|
|
59
|
+
*
|
|
60
|
+
* @type {RegExp}
|
|
61
|
+
*/
|
|
62
|
+
const HEADER_VALUE_PATTERN = /^[\t\x20-\x7E\x80-\xFF]*$/;
|
|
63
|
+
|
|
64
|
+
// ━━ MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
65
|
+
/**
|
|
66
|
+
* Mutable, validated collection of outgoing HTTP response headers.
|
|
67
|
+
*
|
|
68
|
+
* @classdesc Response header names are normalized to lowercase. Values may
|
|
69
|
+
* be strings, finite numbers, or arrays of strings and finite numbers.
|
|
70
|
+
*
|
|
71
|
+
* Once `lockContentType()` has been called, the `Content-Type` header can
|
|
72
|
+
* no longer be set, appended, or removed.
|
|
73
|
+
*/
|
|
74
|
+
class ResponseHeaders {
|
|
75
|
+
/**
|
|
76
|
+
* Private snapshot of the normalized response headers.
|
|
77
|
+
*
|
|
78
|
+
* @type {Map<string, ResponseHeaderValue>}
|
|
79
|
+
*/
|
|
80
|
+
#headers = new Map();
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Whether the `Content-Type` header is locked.
|
|
84
|
+
*
|
|
85
|
+
* @type {boolean}
|
|
86
|
+
*/
|
|
87
|
+
#contentTypeLocked = false;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Sets a response header, replacing any existing value.
|
|
91
|
+
*
|
|
92
|
+
* @param {string} name - The HTTP header name.
|
|
93
|
+
* @param {ResponseHeaderValue} value - The HTTP header value.
|
|
94
|
+
* @returns {void}
|
|
95
|
+
* @throws {ResponseError} If the header name or value is invalid.
|
|
96
|
+
* @throws {ResponseError} If `Content-Type` is already locked.
|
|
97
|
+
* @example
|
|
98
|
+
* ```js
|
|
99
|
+
* headers.set('Content-Type', 'application/json');
|
|
100
|
+
* headers.set('Content-Length', 42);
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
set(name, value) {
|
|
104
|
+
this.#assertHeader(name, value);
|
|
105
|
+
this.#assertContentTypeNotLocked(name, 'set');
|
|
106
|
+
this.#headers.set(name.toLowerCase(), value);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Sets multiple response headers.
|
|
111
|
+
*
|
|
112
|
+
* @param {ResponseHeadersData} headers - The response headers to set.
|
|
113
|
+
* @returns {void}
|
|
114
|
+
* @throws {ResponseError} If the headers value is not an object.
|
|
115
|
+
* @throws {ResponseError} If any header name or value is invalid.
|
|
116
|
+
* @throws {ResponseError} If `Content-Type` is already locked.
|
|
117
|
+
* @example
|
|
118
|
+
* ```js
|
|
119
|
+
* headers.setMany({
|
|
120
|
+
* 'Content-Type': 'application/json',
|
|
121
|
+
* 'Cache-Control': 'no-cache',
|
|
122
|
+
* 'Content-Length': 42,
|
|
123
|
+
* });
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
setMany(headers) {
|
|
127
|
+
if (headers === null || typeof headers !== 'object' || Array.isArray(headers)) {
|
|
128
|
+
throw new ResponseError('INVALID_HEADERS');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
132
|
+
this.set(name, value);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Appends a value to an existing header or creates the header when it
|
|
138
|
+
* does not exist.
|
|
139
|
+
*
|
|
140
|
+
* Existing scalar values are converted to an array when another value is
|
|
141
|
+
* appended.
|
|
142
|
+
*
|
|
143
|
+
* @param {string} name - The HTTP header name.
|
|
144
|
+
* @param {string|number} value - The value to append.
|
|
145
|
+
* @returns {void}
|
|
146
|
+
* @throws {ResponseError} If the header name or value is invalid.
|
|
147
|
+
* @throws {ResponseError} If `Content-Type` is already locked.
|
|
148
|
+
* @example
|
|
149
|
+
* ```js
|
|
150
|
+
* headers.append('Set-Cookie', 'session=abc');
|
|
151
|
+
* headers.append('Set-Cookie', 'theme=dark');
|
|
152
|
+
*
|
|
153
|
+
* headers.get('Set-Cookie');
|
|
154
|
+
* // ['session=abc', 'theme=dark']
|
|
155
|
+
* ```
|
|
156
|
+
*/
|
|
157
|
+
append(name, value) {
|
|
158
|
+
this.#assertHeader(name, value);
|
|
159
|
+
this.#assertContentTypeNotLocked(name, 'append');
|
|
160
|
+
|
|
161
|
+
const key = name.toLowerCase();
|
|
162
|
+
const current = this.#headers.get(key);
|
|
163
|
+
|
|
164
|
+
if (current === undefined) {
|
|
165
|
+
this.#headers.set(key, value);
|
|
166
|
+
} else if (Array.isArray(current)) {
|
|
167
|
+
current.push(value);
|
|
168
|
+
} else {
|
|
169
|
+
this.#headers.set(key, [current, value]);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Returns the value of a response header.
|
|
175
|
+
*
|
|
176
|
+
* When the header value is an array, a defensive copy is returned.
|
|
177
|
+
*
|
|
178
|
+
* @param {string} name - The HTTP header name.
|
|
179
|
+
* @returns {ResponseHeaderValue|undefined} The header value, or `undefined` when the header does not exist.
|
|
180
|
+
* @example
|
|
181
|
+
* ```js
|
|
182
|
+
* const contentType = headers.get('Content-Type');
|
|
183
|
+
* ```
|
|
184
|
+
*/
|
|
185
|
+
get(name) {
|
|
186
|
+
const value = this.#headers.get(name.toLowerCase());
|
|
187
|
+
return Array.isArray(value) ? [...value] : value;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Checks whether a response header exists.
|
|
192
|
+
*
|
|
193
|
+
* @param {string} name - The HTTP header name.
|
|
194
|
+
* @returns {boolean} `true` when the header exists; otherwise, `false`.
|
|
195
|
+
* @example
|
|
196
|
+
* ```js
|
|
197
|
+
* if (headers.has('Content-Type')) {
|
|
198
|
+
* console.log('Content-Type is set');
|
|
199
|
+
* }
|
|
200
|
+
* ```
|
|
201
|
+
*/
|
|
202
|
+
has(name) {
|
|
203
|
+
return this.#headers.has(name.toLowerCase());
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Removes a response header.
|
|
208
|
+
*
|
|
209
|
+
* Removing a header that does not exist has no effect.
|
|
210
|
+
*
|
|
211
|
+
* @param {string} name - The HTTP header name.
|
|
212
|
+
* @returns {void}
|
|
213
|
+
* @throws {ResponseError} If `Content-Type` is already locked.
|
|
214
|
+
* @example
|
|
215
|
+
* ```js
|
|
216
|
+
* headers.remove('Cache-Control');
|
|
217
|
+
* ```
|
|
218
|
+
*/
|
|
219
|
+
remove(name) {
|
|
220
|
+
this.#assertContentTypeNotLocked(name, 'remove');
|
|
221
|
+
this.#headers.delete(name.toLowerCase());
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Sets a default `Content-Type` header when one has not already been set.
|
|
226
|
+
*
|
|
227
|
+
* @param {string} value - The default content type.
|
|
228
|
+
* @returns {void}
|
|
229
|
+
* @throws {ResponseError} If the value is not a string.
|
|
230
|
+
* @throws {ResponseError} If `Content-Type` is already locked.
|
|
231
|
+
* @example
|
|
232
|
+
* ```js
|
|
233
|
+
* headers.ensureContentType('application/json');
|
|
234
|
+
* ```
|
|
235
|
+
*/
|
|
236
|
+
ensureContentType(value) {
|
|
237
|
+
if (typeof value !== 'string') {
|
|
238
|
+
throw new ResponseError('INVALID_CONTENT_TYPE', {
|
|
239
|
+
values: {
|
|
240
|
+
expected: 'string',
|
|
241
|
+
received: value === null ? 'null' : typeof value,
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (!this.#headers.has('content-type')) {
|
|
247
|
+
this.set('content-type', value);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Locks the `Content-Type` header.
|
|
253
|
+
*
|
|
254
|
+
* After this method is called, the `Content-Type` header can no longer be
|
|
255
|
+
* set, appended, or removed.
|
|
256
|
+
*
|
|
257
|
+
* @returns {void}
|
|
258
|
+
* @example
|
|
259
|
+
* ```js
|
|
260
|
+
* headers.lockContentType();
|
|
261
|
+
* ```
|
|
262
|
+
*/
|
|
263
|
+
lockContentType() {
|
|
264
|
+
this.#contentTypeLocked = true;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Returns the names of all response headers.
|
|
269
|
+
*
|
|
270
|
+
* @returns {string[]} The normalized response header names.
|
|
271
|
+
* @example
|
|
272
|
+
* ```js
|
|
273
|
+
* headers.set('Content-Type', 'application/json');
|
|
274
|
+
* headers.set('Cache-Control', 'no-cache');
|
|
275
|
+
*
|
|
276
|
+
* const names = headers.getNames();
|
|
277
|
+
* // ['content-type', 'cache-control']
|
|
278
|
+
* ```
|
|
279
|
+
*/
|
|
280
|
+
getNames() {
|
|
281
|
+
return [...this.#headers.keys()];
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Returns an iterator over the response headers as `[name, value]` pairs.
|
|
286
|
+
*
|
|
287
|
+
* Array values are copied so the caller cannot mutate the internal snapshot
|
|
288
|
+
* through the iterator.
|
|
289
|
+
*
|
|
290
|
+
* @yields {[string, ResponseHeaderValue]} The response header entry.
|
|
291
|
+
* @returns {ResponseHeadersIterator} The response header entries.
|
|
292
|
+
* @example
|
|
293
|
+
* ```js
|
|
294
|
+
* for (const [name, value] of headers.entries()) {
|
|
295
|
+
* console.log(name, value);
|
|
296
|
+
* }
|
|
297
|
+
* ```
|
|
298
|
+
*/
|
|
299
|
+
*entries() {
|
|
300
|
+
for (const [name, value] of this.#headers) {
|
|
301
|
+
yield [name, Array.isArray(value) ? [...value] : value];
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Returns an iterator over the response headers as `[name, value]` pairs.
|
|
307
|
+
*
|
|
308
|
+
* Makes `ResponseHeaders` directly iterable with `for...of`, spread, and
|
|
309
|
+
* `Object.fromEntries`.
|
|
310
|
+
*
|
|
311
|
+
* @returns {ResponseHeadersIterator} The response header entries iterator.
|
|
312
|
+
* @example
|
|
313
|
+
* ```js
|
|
314
|
+
* for (const [name, value] of headers) {
|
|
315
|
+
* console.log(name, value);
|
|
316
|
+
* }
|
|
317
|
+
* ```
|
|
318
|
+
*/
|
|
319
|
+
[Symbol.iterator]() {
|
|
320
|
+
return this.entries();
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Returns a plain object representation of the response headers.
|
|
325
|
+
*
|
|
326
|
+
* The returned object is a shallow copy. Array values are copied so the
|
|
327
|
+
* caller cannot mutate the internal state through the result.
|
|
328
|
+
*
|
|
329
|
+
* @returns {ResponseHeadersData} A plain object representation of the headers.
|
|
330
|
+
* @example
|
|
331
|
+
* ```js
|
|
332
|
+
* const headersObject = headers.toObject();
|
|
333
|
+
* ```
|
|
334
|
+
*/
|
|
335
|
+
toObject() {
|
|
336
|
+
/** @type {ResponseHeadersData} */
|
|
337
|
+
const result = Object.create(null);
|
|
338
|
+
|
|
339
|
+
for (const [name, value] of this.#headers) {
|
|
340
|
+
result[name] = Array.isArray(value) ? [...value] : value;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return result;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Validates a response header name and value.
|
|
348
|
+
*
|
|
349
|
+
* @param {string} name - The HTTP header name.
|
|
350
|
+
* @param {ResponseHeaderValue} value - The HTTP header value.
|
|
351
|
+
* @throws {ResponseError} If the header name or value is invalid.
|
|
352
|
+
*/
|
|
353
|
+
#assertHeader(name, value) {
|
|
354
|
+
if (typeof name !== 'string' || !HEADER_NAME_PATTERN.test(name)) {
|
|
355
|
+
throw new ResponseError('INVALID_HEADER_NAME');
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const items = Array.isArray(value) ? value : [value];
|
|
359
|
+
|
|
360
|
+
for (const item of items) {
|
|
361
|
+
this.#assertHeaderValue(item);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Validates a response header value.
|
|
367
|
+
*
|
|
368
|
+
* @param {string|number} value - The header value to validate.
|
|
369
|
+
* @throws {ResponseError} If the value is not a valid header value.
|
|
370
|
+
*/
|
|
371
|
+
#assertHeaderValue(value) {
|
|
372
|
+
if (typeof value !== 'string' && typeof value !== 'number') {
|
|
373
|
+
throw new ResponseError('INVALID_HEADER_VALUE');
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (typeof value === 'number' && !Number.isFinite(value)) {
|
|
377
|
+
throw new ResponseError('INVALID_HEADER_VALUE');
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (typeof value === 'string' && !HEADER_VALUE_PATTERN.test(value)) {
|
|
381
|
+
throw new ResponseError('INVALID_HEADER_VALUE');
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Ensures that the `Content-Type` header cannot be mutated once locked.
|
|
387
|
+
*
|
|
388
|
+
* @param {string} name - The HTTP header name being modified.
|
|
389
|
+
* @param {HeaderOperation} operation - The operation being performed.
|
|
390
|
+
* @throws {ResponseError} If `Content-Type` is already locked.
|
|
391
|
+
*/
|
|
392
|
+
#assertContentTypeNotLocked(name, operation) {
|
|
393
|
+
if (name.toLowerCase() === 'content-type' && this.#contentTypeLocked) {
|
|
394
|
+
throw new ResponseError('CONTENT_TYPE_LOCKED', {
|
|
395
|
+
values: { operation },
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// ━━ EXPORT MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
402
|
+
export default ResponseHeaders;
|