@ccheever/exact-ibex-runtime 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.
Files changed (161) hide show
  1. package/package.json +63 -0
  2. package/src/abort/AbortController.ts +23 -0
  3. package/src/abort/AbortSignal.ts +152 -0
  4. package/src/abort/index.ts +2 -0
  5. package/src/accessibility.ts +12 -0
  6. package/src/arraybuffer-detach.ts +109 -0
  7. package/src/base64/base64.ts +168 -0
  8. package/src/base64/index.ts +1 -0
  9. package/src/blob/Blob.ts +259 -0
  10. package/src/blob/File.ts +59 -0
  11. package/src/blob/FormData.ts +323 -0
  12. package/src/blob/index.ts +3 -0
  13. package/src/bootstrap.ts +1946 -0
  14. package/src/broadcast/BroadcastChannel.ts +280 -0
  15. package/src/broadcast/index.ts +5 -0
  16. package/src/cache/Cache.ts +349 -0
  17. package/src/cache/CacheStorage.ts +89 -0
  18. package/src/cache/index.ts +27 -0
  19. package/src/camera/index.ts +6202 -0
  20. package/src/camera/processor.worker.ts +194 -0
  21. package/src/camera/scene.ts +195 -0
  22. package/src/clipboard/Clipboard.ts +129 -0
  23. package/src/clipboard/ClipboardItem.ts +97 -0
  24. package/src/clipboard/index.ts +6 -0
  25. package/src/clone/index.ts +1 -0
  26. package/src/clone/structuredClone.ts +389 -0
  27. package/src/clone/transferableSymbols.ts +2 -0
  28. package/src/compression/CompressionStream.ts +146 -0
  29. package/src/compression/DecompressionStream.ts +342 -0
  30. package/src/compression/index.ts +4 -0
  31. package/src/console/Console.ts +341 -0
  32. package/src/console/index.ts +2 -0
  33. package/src/core/accessibility-state.ts +263 -0
  34. package/src/core/accessibility.ts +184 -0
  35. package/src/core/agent-state.ts +37 -0
  36. package/src/core/diagnostics-logs.ts +144 -0
  37. package/src/core/host-call-bridge.ts +16 -0
  38. package/src/core/i18n-helpers.ts +189 -0
  39. package/src/core/locale-state.ts +253 -0
  40. package/src/core/locale.ts +95 -0
  41. package/src/crypto/Crypto.ts +2743 -0
  42. package/src/crypto/index.ts +1 -0
  43. package/src/diagnostics/logs.ts +7 -0
  44. package/src/encoding/TextDecoder.ts +1181 -0
  45. package/src/encoding/TextDecoderStream.ts +58 -0
  46. package/src/encoding/TextEncoder.ts +180 -0
  47. package/src/encoding/TextEncoderStream.ts +39 -0
  48. package/src/encoding/index.ts +8 -0
  49. package/src/events/CloseEvent.ts +91 -0
  50. package/src/events/DOMException.ts +409 -0
  51. package/src/events/ErrorEvent.ts +39 -0
  52. package/src/events/Event.ts +151 -0
  53. package/src/events/EventTarget.ts +280 -0
  54. package/src/events/FocusEvent.ts +27 -0
  55. package/src/events/KeyboardEvent.ts +46 -0
  56. package/src/events/MessageEvent.ts +61 -0
  57. package/src/events/ProgressEvent.ts +33 -0
  58. package/src/events/PromiseRejectionEvent.ts +31 -0
  59. package/src/events/index.ts +52 -0
  60. package/src/eventsource/EventSource.ts +371 -0
  61. package/src/eventsource/index.ts +2 -0
  62. package/src/fetch/Headers.ts +642 -0
  63. package/src/fetch/Request.ts +760 -0
  64. package/src/fetch/Response.ts +543 -0
  65. package/src/fetch/body.ts +1256 -0
  66. package/src/fetch/cookie-jar.ts +566 -0
  67. package/src/fetch/demo.ts +207 -0
  68. package/src/fetch/errors.ts +101 -0
  69. package/src/fetch/fetch.ts +2610 -0
  70. package/src/fetch/index.ts +101 -0
  71. package/src/fetch/native-bridge.ts +65 -0
  72. package/src/fetch/types.ts +258 -0
  73. package/src/filereader/FileReader.ts +236 -0
  74. package/src/filereader/index.ts +1 -0
  75. package/src/fs/Dirent.ts +39 -0
  76. package/src/fs/ExactFile.ts +450 -0
  77. package/src/fs/Stats.ts +80 -0
  78. package/src/fs/index.ts +944 -0
  79. package/src/fs/promises.ts +386 -0
  80. package/src/fs/shared.ts +328 -0
  81. package/src/http-server/index.js +697 -0
  82. package/src/http-server/index.ts +27 -0
  83. package/src/identity.generated.ts +14 -0
  84. package/src/index.ts +283 -0
  85. package/src/indexeddb/IDBCursor.ts +188 -0
  86. package/src/indexeddb/IDBDatabase.ts +343 -0
  87. package/src/indexeddb/IDBFactory.ts +269 -0
  88. package/src/indexeddb/IDBIndex.ts +194 -0
  89. package/src/indexeddb/IDBKeyRange.ts +109 -0
  90. package/src/indexeddb/IDBObjectStore.ts +468 -0
  91. package/src/indexeddb/IDBRequest.ts +163 -0
  92. package/src/indexeddb/IDBTransaction.ts +207 -0
  93. package/src/indexeddb/index.ts +34 -0
  94. package/src/indexeddb/utils.ts +52 -0
  95. package/src/inspect/index.ts +1 -0
  96. package/src/inspect/inspect.ts +465 -0
  97. package/src/internal/detect.ts +104 -0
  98. package/src/locale.ts +10 -0
  99. package/src/location/index.ts +1059 -0
  100. package/src/locks/LockManager.ts +460 -0
  101. package/src/locks/index.ts +12 -0
  102. package/src/media/VideoFrame.ts +58 -0
  103. package/src/messaging/MessageChannel.ts +31 -0
  104. package/src/messaging/MessagePort.ts +180 -0
  105. package/src/messaging/index.ts +2 -0
  106. package/src/messaging.ts +247 -0
  107. package/src/native/NativeModules.ts +354 -0
  108. package/src/native/index.ts +1 -0
  109. package/src/navigator/Navigator.ts +351 -0
  110. package/src/navigator/index.ts +1 -0
  111. package/src/node/Buffer.ts +1786 -0
  112. package/src/node/index.ts +4 -0
  113. package/src/node/path.ts +495 -0
  114. package/src/node/process.ts +2528 -0
  115. package/src/performance/Performance.ts +532 -0
  116. package/src/performance/index.ts +21 -0
  117. package/src/polyfills/array.ts +236 -0
  118. package/src/polyfills/arraybuffer.ts +172 -0
  119. package/src/polyfills/groupby.ts +85 -0
  120. package/src/polyfills/index.ts +85 -0
  121. package/src/polyfills/intl.ts +1956 -0
  122. package/src/polyfills/iterator.ts +479 -0
  123. package/src/polyfills/promise.ts +37 -0
  124. package/src/polyfills/set.ts +245 -0
  125. package/src/polyfills/string.ts +85 -0
  126. package/src/polyfills/typedarray.ts +110 -0
  127. package/src/promise-rejection-tracking.ts +464 -0
  128. package/src/react-native/index.ts +388 -0
  129. package/src/runtime-entry.ts +55 -0
  130. package/src/scheduling/AnimationFrame.ts +105 -0
  131. package/src/scheduling/IdleCallback.ts +167 -0
  132. package/src/scheduling/index.ts +13 -0
  133. package/src/security/Capabilities.ts +1146 -0
  134. package/src/security/Permissions.ts +392 -0
  135. package/src/security/capability-bits.generated.ts +63 -0
  136. package/src/security/index.ts +16 -0
  137. package/src/sqlite/Database.ts +456 -0
  138. package/src/sqlite/Statement.ts +206 -0
  139. package/src/sqlite/constants.ts +79 -0
  140. package/src/sqlite/errors.ts +25 -0
  141. package/src/sqlite/index.ts +34 -0
  142. package/src/sqlite/module.js +438 -0
  143. package/src/storage/Storage.ts +291 -0
  144. package/src/storage/StorageManager.ts +91 -0
  145. package/src/storage/index.ts +3 -0
  146. package/src/stream-compat.ts +47 -0
  147. package/src/streams/ReadableStream.ts +4131 -0
  148. package/src/streams/TransformStream.ts +375 -0
  149. package/src/streams/WritableStream.ts +866 -0
  150. package/src/streams/index.ts +41 -0
  151. package/src/timers/Timers.ts +296 -0
  152. package/src/timers/index.ts +11 -0
  153. package/src/url/URL.ts +656 -0
  154. package/src/url/URLPattern.ts +850 -0
  155. package/src/url/URLSearchParams.ts +244 -0
  156. package/src/url/index.ts +9 -0
  157. package/src/websocket/WebSocket.ts +770 -0
  158. package/src/websocket/WebSocketError.ts +52 -0
  159. package/src/websocket/WebSocketStream.ts +628 -0
  160. package/src/websocket/index.ts +7 -0
  161. package/src/window/index.ts +872 -0
@@ -0,0 +1,642 @@
1
+ /**
2
+ * Headers API Implementation
3
+ *
4
+ * Implements the WHATWG Fetch Standard Headers interface.
5
+ * @see https://fetch.spec.whatwg.org/#headers-class
6
+ */
7
+
8
+ import type { HeadersInit } from './types.js';
9
+
10
+ /**
11
+ * Header guard modes per the Fetch spec.
12
+ */
13
+ export type HeaderGuard = 'none' | 'request' | 'request-no-cors' | 'response' | 'immutable';
14
+
15
+ /**
16
+ * Normalize a header name to lowercase.
17
+ * HTTP header names are case-insensitive.
18
+ */
19
+ function normalizeHeaderName(name: string): string {
20
+ if (typeof name !== 'string') {
21
+ name = String(name);
22
+ }
23
+ if (!/^[\w!#$%&'*+.^`|~-]+$/.test(name)) {
24
+ throw new TypeError(
25
+ isBunCompatEnv() ? `Invalid header name: '${name}'` : `Invalid header name: ${name}`
26
+ );
27
+ }
28
+ return name.toLowerCase();
29
+ }
30
+
31
+ function isBunCompatEnv(): boolean {
32
+ if ((globalThis as { __exactRuntimeContext?: string }).__exactRuntimeContext === 'shell') {
33
+ return false;
34
+ }
35
+ if (typeof process !== 'object' || !process || typeof process.env !== 'object') {
36
+ return false;
37
+ }
38
+
39
+ return process.env.EXACT_COMPAT_TEST === '1' && process.env.EXACT_TEST_SECTION === 'bun';
40
+ }
41
+
42
+ function ensureRequiredArguments(
43
+ methodName: string,
44
+ actualCount: number,
45
+ requiredCount: number
46
+ ): void {
47
+ if (actualCount < requiredCount) {
48
+ throw new TypeError(
49
+ `Failed to execute '${methodName}' on 'Headers': ${requiredCount} argument${
50
+ requiredCount === 1 ? '' : 's'
51
+ } required, but only ${actualCount} present.`
52
+ );
53
+ }
54
+ }
55
+
56
+ function ensureHeadersBrand(value: unknown, methodName: string): Headers {
57
+ if (!(value instanceof Headers)) {
58
+ throw new TypeError(`Failed to execute '${methodName}' on 'Headers': Illegal invocation`);
59
+ }
60
+ return value;
61
+ }
62
+
63
+ function formatHeadersForInspect(entries: ReadonlyArray<[string, string]>): string {
64
+ if (entries.length === 0) {
65
+ return 'Headers {}';
66
+ }
67
+
68
+ const lines = ['Headers {'];
69
+ for (let i = 0; i < entries.length; i++) {
70
+ const [name, value] = entries[i];
71
+ lines.push(` ${JSON.stringify(name)}: ${JSON.stringify(value)},`);
72
+ }
73
+ lines.push('}');
74
+ return lines.join('\n');
75
+ }
76
+
77
+ /**
78
+ * CORS-safelisted request header names.
79
+ */
80
+ const CORS_SAFELISTED_HEADERS = new Set([
81
+ 'accept',
82
+ 'accept-language',
83
+ 'content-language',
84
+ 'content-type',
85
+ ]);
86
+
87
+ /**
88
+ * Forbidden response header names.
89
+ */
90
+ const FORBIDDEN_RESPONSE_HEADERS = new Set([
91
+ 'set-cookie',
92
+ 'set-cookie2',
93
+ ]);
94
+
95
+ /**
96
+ * Normalize a header value.
97
+ */
98
+ function normalizeHeaderValue(value: string): string {
99
+ if (typeof value === 'symbol') {
100
+ throw new TypeError(`Invalid header value`);
101
+ }
102
+ if (typeof value !== 'string') {
103
+ value = String(value);
104
+ }
105
+ const rawValue = value;
106
+ // Per Fetch spec: the empty string is a valid header value
107
+ if (value.length === 0) {
108
+ return value;
109
+ }
110
+ // Per Fetch spec: reject null bytes anywhere
111
+ if (value.includes('\x00')) {
112
+ if (isBunCompatEnv()) {
113
+ throw new TypeError(`Header 'x-test' has invalid value: '${rawValue}'`);
114
+ }
115
+ throw new TypeError(`Invalid header value`);
116
+ }
117
+ // Per Fetch spec: reject characters > 0xFF (header values must be byte strings)
118
+ for (let i = 0; i < value.length; i++) {
119
+ if (value.charCodeAt(i) > 0xFF) {
120
+ if (isBunCompatEnv()) {
121
+ throw new TypeError(`Header 'x-test' has invalid value: '${rawValue}'`);
122
+ }
123
+ throw new TypeError(`Invalid header value`);
124
+ }
125
+ }
126
+ // Strip leading and trailing HTTP whitespace without regex backtracking on
127
+ // large attacker-controlled values.
128
+ let start = 0;
129
+ let end = value.length;
130
+ while (start < end) {
131
+ const code = value.charCodeAt(start);
132
+ if (code !== 0x09 && code !== 0x0a && code !== 0x0d && code !== 0x20) {
133
+ break;
134
+ }
135
+ start += 1;
136
+ }
137
+ while (end > start) {
138
+ const code = value.charCodeAt(end - 1);
139
+ if (code !== 0x09 && code !== 0x0a && code !== 0x0d && code !== 0x20) {
140
+ break;
141
+ }
142
+ end -= 1;
143
+ }
144
+ value = start === 0 && end === value.length ? value : value.slice(start, end);
145
+ // After stripping, reject any remaining bare CR or LF in the middle
146
+ if (/[\r\n]/.test(value)) {
147
+ if (isBunCompatEnv()) {
148
+ throw new TypeError(`Header 'x-test' has invalid value: '${rawValue}'`);
149
+ }
150
+ throw new TypeError(`Invalid header value`);
151
+ }
152
+ return value;
153
+ }
154
+
155
+ /**
156
+ * Check if a header name is forbidden for modification.
157
+ */
158
+ function isForbiddenHeaderName(name: string): boolean {
159
+ const lower = name.toLowerCase();
160
+ // Per Fetch spec: https://fetch.spec.whatwg.org/#forbidden-request-header
161
+ // Note: set-cookie IS forbidden for requests; set-cookie2 is NOT (only forbidden for responses)
162
+ const forbidden = [
163
+ 'accept-charset',
164
+ 'accept-encoding',
165
+ 'access-control-request-headers',
166
+ 'access-control-request-method',
167
+ 'access-control-request-private-network',
168
+ 'connection',
169
+ 'content-length',
170
+ 'cookie',
171
+ 'cookie2',
172
+ 'date',
173
+ 'dnt',
174
+ 'expect',
175
+ 'host',
176
+ 'keep-alive',
177
+ 'origin',
178
+ 'referer',
179
+ 'set-cookie',
180
+ 'te',
181
+ 'trailer',
182
+ 'transfer-encoding',
183
+ 'upgrade',
184
+ 'via',
185
+ ];
186
+ return forbidden.includes(lower) || lower.startsWith('proxy-') || lower.startsWith('sec-');
187
+ }
188
+
189
+ function isBunCompatAllowedRequestHeader(name: string): boolean {
190
+ if (!isBunCompatEnv()) {
191
+ return false;
192
+ }
193
+
194
+ const normalized = name.toLowerCase();
195
+ return normalized === 'connection' || normalized === 'accept-encoding';
196
+ }
197
+
198
+ /**
199
+ * Check if a string contains CORS-unsafe request-header bytes.
200
+ * Per Fetch spec: https://fetch.spec.whatwg.org/#cors-unsafe-request-header-byte
201
+ */
202
+ function hasCORSUnsafeBytes(value: string): boolean {
203
+ for (let i = 0; i < value.length; i++) {
204
+ const c = value.charCodeAt(i);
205
+ if ((c >= 0x00 && c <= 0x08) || (c >= 0x0A && c <= 0x1F) ||
206
+ c === 0x22 || c === 0x28 || c === 0x29 || c === 0x3A ||
207
+ c === 0x3C || c === 0x3E || c === 0x3F || c === 0x40 ||
208
+ c === 0x5B || c === 0x5C || c === 0x5D || c === 0x7B ||
209
+ c === 0x7D || c === 0x7F) {
210
+ return true;
211
+ }
212
+ }
213
+ return false;
214
+ }
215
+
216
+ /**
217
+ * The Headers interface of the Fetch API allows you to perform
218
+ * various actions on HTTP request and response headers.
219
+ */
220
+ /**
221
+ * Get the %IteratorPrototype% from the runtime.
222
+ */
223
+ const iteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()));
224
+
225
+ /**
226
+ * Create a Headers iterator prototype for the given value mapper.
227
+ */
228
+ function createHeadersIteratorPrototype<T>(mapFn: (entry: [string, string]) => T) {
229
+ const proto = Object.create(iteratorPrototype);
230
+ Object.defineProperty(proto, 'next', {
231
+ value: function(this: any): IteratorResult<T> {
232
+ const entries = this._headers._flatSortedEntries();
233
+ if (this._index >= entries.length) return { value: undefined as any, done: true };
234
+ return { value: mapFn(entries[this._index++]), done: false };
235
+ },
236
+ writable: true,
237
+ enumerable: true,
238
+ configurable: true,
239
+ });
240
+ Object.defineProperty(proto, Symbol.toStringTag, {
241
+ value: 'Iterator',
242
+ configurable: true,
243
+ });
244
+ return proto;
245
+ }
246
+
247
+ const entriesIteratorProto = createHeadersIteratorPrototype<[string, string]>((e) => e);
248
+ const keysIteratorProto = createHeadersIteratorPrototype<string>((e) => e[0]);
249
+ const valuesIteratorProto = createHeadersIteratorPrototype<string>((e) => e[1]);
250
+
251
+ export class Headers implements Iterable<[string, string]> {
252
+ private _map: Map<string, string[]> = new Map();
253
+ /** @internal */
254
+ _guard: HeaderGuard = 'none';
255
+
256
+ constructor(init?: HeadersInit) {
257
+ if (init !== undefined) {
258
+ if (init === null || (typeof init !== 'object' && typeof init !== 'function')) {
259
+ throw new TypeError("Failed to construct 'Headers': The provided value is not of type '(sequence<sequence<ByteString>> or record<ByteString, ByteString>)'");
260
+ }
261
+ this._initFromInput(init);
262
+ }
263
+ }
264
+
265
+ private _initFromInput(init: HeadersInit): void {
266
+ // Per WebIDL spec: check [[Get]](@@iterator) to determine if it's a sequence.
267
+ // This must be the first observable operation on the init value.
268
+ const iteratorMethod = (init as any)[Symbol.iterator];
269
+ if (
270
+ (iteratorMethod === null || iteratorMethod === undefined) &&
271
+ typeof init === 'object' &&
272
+ init !== null &&
273
+ Symbol.iterator in init
274
+ ) {
275
+ throw new TypeError("Failed to construct 'Headers': The provided value is not of type '(sequence<sequence<ByteString>> or record<ByteString, ByteString>)'");
276
+ }
277
+ if (typeof iteratorMethod === 'function') {
278
+ // It's iterable - treat as sequence<sequence<ByteString>> or Headers
279
+ const iterator = iteratorMethod.call(init);
280
+ const entries = Array.from(iterator as Iterable<any>);
281
+ for (let i = 0; i < entries.length; i++) {
282
+ const entry = entries[i];
283
+ // Each entry must be iterable and yield exactly 2 items
284
+ if (typeof entry === 'string') {
285
+ // A string is iterable but yields characters, which makes each pair invalid
286
+ throw new TypeError("Failed to construct 'Headers': Each header pair must be a name/value pair");
287
+ }
288
+ if (Array.isArray(entry)) {
289
+ if (entry.length !== 2) {
290
+ throw new TypeError("Failed to construct 'Headers': Each header pair must be a name/value pair");
291
+ }
292
+ this.append(entry[0], entry[1]);
293
+ } else if (typeof entry === 'object' && entry !== null && Symbol.iterator in entry) {
294
+ const pair = [...(entry as Iterable<string>)];
295
+ if (pair.length !== 2) {
296
+ throw new TypeError("Failed to construct 'Headers': Each header pair must be a name/value pair");
297
+ }
298
+ this.append(pair[0], pair[1]);
299
+ } else {
300
+ throw new TypeError("Failed to construct 'Headers': Each header pair must be an iterable");
301
+ }
302
+ }
303
+ } else if (typeof init === 'object' && init !== null) {
304
+ // WebIDL es-to-record algorithm:
305
+ // 1. [[OwnPropertyKeys]] to get all keys
306
+ // 2. For each key: [[GetOwnPropertyDescriptor]], check enumerable,
307
+ // convert key to ByteString (may throw), THEN [[Get]] value
308
+ const keys = (typeof Reflect !== 'undefined' && Reflect.ownKeys)
309
+ ? Reflect.ownKeys(init as object)
310
+ : Object.getOwnPropertyNames(init as object);
311
+ for (const key of keys) {
312
+ const desc = Object.getOwnPropertyDescriptor(init as object, key as PropertyKey);
313
+ if (!desc || !desc.enumerable) continue;
314
+ // Per WebIDL: convert key to ByteString BEFORE [[Get]]
315
+ if (typeof key === 'symbol') {
316
+ throw new TypeError("Failed to construct 'Headers': Invalid header name");
317
+ }
318
+ normalizeHeaderName(key); // throws for invalid names before [[Get]]
319
+ const value = (init as any)[key];
320
+ this.append(key, value);
321
+ }
322
+ }
323
+ }
324
+
325
+ /**
326
+ * Appends a new value onto an existing header, or adds the header
327
+ * if it does not already exist.
328
+ */
329
+ append(name: string, value: string): void {
330
+ const headers = ensureHeadersBrand(this, 'append');
331
+ ensureRequiredArguments('append', arguments.length, 2);
332
+ const normalizedName = normalizeHeaderName(name);
333
+ const normalizedValue = normalizeHeaderValue(value);
334
+
335
+ if (!headers._guardAllows(normalizedName, normalizedValue, false)) return;
336
+
337
+ const existing = headers._map.get(normalizedName);
338
+ if (existing) {
339
+ existing.push(normalizedValue);
340
+ } else {
341
+ headers._map.set(normalizedName, [normalizedValue]);
342
+ }
343
+ }
344
+
345
+ /**
346
+ * Deletes a header from the Headers object.
347
+ */
348
+ delete(name: string): void {
349
+ const headers = ensureHeadersBrand(this, 'delete');
350
+ ensureRequiredArguments('delete', arguments.length, 1);
351
+ const normalizedName = normalizeHeaderName(name);
352
+ if (!headers._guardAllows(normalizedName)) return;
353
+ headers._map.delete(normalizedName);
354
+ }
355
+
356
+ /**
357
+ * Returns a String containing all the values of a header with a given name.
358
+ */
359
+ get(name: string): string | null {
360
+ const headers = ensureHeadersBrand(this, 'get');
361
+ ensureRequiredArguments('get', arguments.length, 1);
362
+ const normalizedName = normalizeHeaderName(name);
363
+ const values = headers._map.get(normalizedName);
364
+ if (!values || values.length === 0) {
365
+ return null;
366
+ }
367
+ return values.join(', ');
368
+ }
369
+
370
+ /**
371
+ * Returns all values of a header with a given name as an array.
372
+ */
373
+ getSetCookie(): string[] {
374
+ const headers = ensureHeadersBrand(this, 'getSetCookie');
375
+ // Set-Cookie headers should not be combined
376
+ const values = headers._map.get('set-cookie');
377
+ return values ? [...values] : [];
378
+ }
379
+
380
+ /**
381
+ * Bun compatibility extension that returns all stored values for a header.
382
+ */
383
+ getAll(name: string): string[] {
384
+ const headers = ensureHeadersBrand(this, 'getAll');
385
+ ensureRequiredArguments('getAll', arguments.length, 1);
386
+ const normalizedName = normalizeHeaderName(name);
387
+ const values = headers._map.get(normalizedName);
388
+ return values ? [...values] : [];
389
+ }
390
+
391
+ /**
392
+ * Bun compatibility extension that reports the total number of stored header entries.
393
+ */
394
+ get count(): number {
395
+ const headers = ensureHeadersBrand(this, 'count');
396
+ let total = 0;
397
+ for (const values of headers._map.values()) {
398
+ total += values.length;
399
+ }
400
+ return total;
401
+ }
402
+
403
+ /**
404
+ * Returns a boolean stating whether a Headers object contains a certain header.
405
+ */
406
+ has(name: string): boolean {
407
+ const headers = ensureHeadersBrand(this, 'has');
408
+ ensureRequiredArguments('has', arguments.length, 1);
409
+ const normalizedName = normalizeHeaderName(name);
410
+ return headers._map.has(normalizedName);
411
+ }
412
+
413
+ /**
414
+ * Sets a new value for an existing header, or adds the header if it does not exist.
415
+ */
416
+ set(name: string, value: string): void {
417
+ const headers = ensureHeadersBrand(this, 'set');
418
+ ensureRequiredArguments('set', arguments.length, 2);
419
+ const normalizedName = normalizeHeaderName(name);
420
+ const normalizedValue = normalizeHeaderValue(value);
421
+ if (!headers._guardAllows(normalizedName, normalizedValue, true)) return;
422
+ headers._map.set(normalizedName, [normalizedValue]);
423
+ }
424
+
425
+ /**
426
+ * Executes a provided function once for each header.
427
+ */
428
+ forEach(
429
+ callback: (value: string, name: string, parent: Headers) => void,
430
+ thisArg?: unknown
431
+ ): void {
432
+ const headers = ensureHeadersBrand(this, 'forEach');
433
+ ensureRequiredArguments('forEach', arguments.length, 1);
434
+ if (typeof callback !== 'function') {
435
+ throw new TypeError("Failed to execute 'forEach' on 'Headers': parameter 1 is not of type 'Function'");
436
+ }
437
+ // Per spec: forEach must iterate in sorted order with Set-Cookie un-combined.
438
+ // NOTE: Use an indexed loop instead of for...of because the Rolldown bundler
439
+ // rewrites `for (const [name, value] of ...)` into
440
+ // `Array.from(...).forEach(function([name, value]) { ... })`.
441
+ // Inside that rewritten regular function, `this` is no longer the Headers
442
+ // instance — it becomes the global object (sloppy mode) or undefined (strict
443
+ // mode). The third argument passed to the caller's callback would then be
444
+ // wrong, causing the WPT "Check forEach method" assertion to fail.
445
+ const entries = headers._flatSortedEntries();
446
+ for (let i = 0; i < entries.length; i++) {
447
+ callback.call(thisArg, entries[i][1], entries[i][0], headers);
448
+ }
449
+ }
450
+
451
+ /**
452
+ * Returns a live iterator of all header entries.
453
+ * Re-sorts on each next() call per the Fetch spec.
454
+ */
455
+ entries(): IterableIterator<[string, string]> {
456
+ const headers = ensureHeadersBrand(this, 'entries');
457
+ const iter = Object.create(entriesIteratorProto);
458
+ iter._headers = headers;
459
+ iter._index = 0;
460
+ return iter;
461
+ }
462
+
463
+ /**
464
+ * Returns a live iterator of all header names.
465
+ */
466
+ keys(): IterableIterator<string> {
467
+ const headers = ensureHeadersBrand(this, 'keys');
468
+ const iter = Object.create(keysIteratorProto);
469
+ iter._headers = headers;
470
+ iter._index = 0;
471
+ return iter;
472
+ }
473
+
474
+ /**
475
+ * Returns a live iterator of all header values.
476
+ */
477
+ values(): IterableIterator<string> {
478
+ const headers = ensureHeadersBrand(this, 'values');
479
+ const iter = Object.create(valuesIteratorProto);
480
+ iter._headers = headers;
481
+ iter._index = 0;
482
+ return iter;
483
+ }
484
+
485
+ /**
486
+ * Returns an iterator of all header entries (sorted by name).
487
+ */
488
+ [Symbol.iterator](): IterableIterator<[string, string]> {
489
+ const headers = ensureHeadersBrand(this, 'entries');
490
+ return headers.entries();
491
+ }
492
+
493
+ /**
494
+ * Bun compatibility extension that materializes the headers as a plain object.
495
+ */
496
+ toJSON(): Record<string, string | string[]> {
497
+ const headers = ensureHeadersBrand(this, 'toJSON');
498
+ const result: Record<string, string | string[]> = {};
499
+ for (const [name, values] of headers._sortedEntries()) {
500
+ if (name === 'set-cookie' && isBunCompatEnv()) {
501
+ result[name] = [...values];
502
+ } else {
503
+ result[name] = values.join(', ');
504
+ }
505
+ }
506
+ return result;
507
+ }
508
+
509
+ /**
510
+ * Check if a header modification is allowed by the current guard.
511
+ * Returns true if allowed. Throws TypeError for immutable guard.
512
+ * Returns false (silently fails) for other guard violations per spec.
513
+ * @param normalizedName The lowercased header name
514
+ * @param normalizedValue The value being set/appended (for no-cors value checks)
515
+ * @param isSet Whether this is a set (replace) vs append operation
516
+ */
517
+ private _guardAllows(normalizedName: string, normalizedValue?: string, isSet?: boolean): boolean {
518
+ switch (this._guard) {
519
+ case 'immutable':
520
+ throw new TypeError('Failed to execute on \'Headers\': Headers are immutable');
521
+ case 'request':
522
+ if (isForbiddenHeaderName(normalizedName) && !isBunCompatAllowedRequestHeader(normalizedName)) return false;
523
+ break;
524
+ case 'request-no-cors':
525
+ if (!CORS_SAFELISTED_HEADERS.has(normalizedName)) return false;
526
+ // Per spec: CORS-safelisted headers have value constraints
527
+ if (normalizedValue !== undefined) {
528
+ // Check for CORS-unsafe request-header bytes in the value
529
+ if (hasCORSUnsafeBytes(normalizedValue)) return false;
530
+ // Compute what the combined value would be after this operation
531
+ let combinedValue: string;
532
+ if (isSet) {
533
+ combinedValue = normalizedValue;
534
+ } else {
535
+ const existing = this._map.get(normalizedName);
536
+ combinedValue = existing ? existing.join(', ') + ', ' + normalizedValue : normalizedValue;
537
+ }
538
+ // Combined value must not exceed 128 bytes
539
+ if (combinedValue.length > 128) return false;
540
+ // For content-type, must be a CORS-safelisted MIME type
541
+ if (normalizedName === 'content-type') {
542
+ const mime = combinedValue.split(';')[0].trim().toLowerCase();
543
+ if (mime !== 'application/x-www-form-urlencoded' &&
544
+ mime !== 'multipart/form-data' &&
545
+ mime !== 'text/plain') {
546
+ return false;
547
+ }
548
+ }
549
+ }
550
+ break;
551
+ case 'response':
552
+ if (FORBIDDEN_RESPONSE_HEADERS.has(normalizedName)) return false;
553
+ break;
554
+ case 'none':
555
+ default:
556
+ break;
557
+ }
558
+ return true;
559
+ }
560
+
561
+ /**
562
+ * Get entries sorted alphabetically by name.
563
+ */
564
+ private _sortedEntries(): [string, string[]][] {
565
+ const entries = [...this._map.entries()].sort((a, b) => a[0].localeCompare(b[0]));
566
+ if (!isBunCompatEnv()) {
567
+ return entries;
568
+ }
569
+
570
+ const normalEntries: [string, string[]][] = [];
571
+ const setCookieEntries: [string, string[]][] = [];
572
+ for (let i = 0; i < entries.length; i++) {
573
+ if (entries[i][0] === 'set-cookie') {
574
+ setCookieEntries.push(entries[i]);
575
+ } else {
576
+ normalEntries.push(entries[i]);
577
+ }
578
+ }
579
+ return normalEntries.concat(setCookieEntries);
580
+ }
581
+
582
+ /**
583
+ * Get flattened sorted entries per the Fetch spec.
584
+ * Most headers combine values with ", ". Set-Cookie headers are NOT combined —
585
+ * each value is returned as a separate entry.
586
+ */
587
+ private _flatSortedEntries(): [string, string][] {
588
+ const result: [string, string][] = [];
589
+ for (const [name, values] of this._sortedEntries()) {
590
+ if (name === 'set-cookie') {
591
+ for (const v of values) {
592
+ result.push([name, v]);
593
+ }
594
+ } else {
595
+ result.push([name, values.join(', ')]);
596
+ }
597
+ }
598
+ return result;
599
+ }
600
+
601
+ /**
602
+ * Convert headers to a tuple array for native bridge.
603
+ */
604
+ toTupleArray(): [string, string][] {
605
+ const headers = ensureHeadersBrand(this, 'toTupleArray');
606
+ const result: [string, string][] = [];
607
+ for (const [name, values] of headers._map) {
608
+ // For Set-Cookie, each value needs to be a separate header
609
+ if (name === 'set-cookie') {
610
+ for (const value of values) {
611
+ result.push([name, value]);
612
+ }
613
+ } else {
614
+ result.push([name, values.join(', ')]);
615
+ }
616
+ }
617
+ return result;
618
+ }
619
+
620
+ [Symbol.for('nodejs.util.inspect.custom')](): string {
621
+ const headers = ensureHeadersBrand(this, 'Symbol(nodejs.util.inspect.custom)');
622
+ return formatHeadersForInspect(headers._flatSortedEntries());
623
+ }
624
+
625
+ /**
626
+ * Create Headers from a tuple array (from native bridge).
627
+ */
628
+ static fromTupleArray(tuples: [string, string][]): Headers {
629
+ const headers = new Headers();
630
+ for (const [name, value] of tuples) {
631
+ headers.append(name, value);
632
+ }
633
+ return headers;
634
+ }
635
+ }
636
+
637
+ // Ensure constructor.name survives minification
638
+ Object.defineProperty(Headers, 'name', { value: 'Headers', configurable: true });
639
+ Object.defineProperty(Headers.prototype, Symbol.toStringTag, {
640
+ value: 'Headers',
641
+ configurable: true,
642
+ });