@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,259 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Blob implementation for Ibex runtime
4
+ *
5
+ * Implements the WHATWG Blob API for handling binary data.
6
+ * https://w3c.github.io/FileAPI/#blob-section
7
+ */
8
+
9
+ import { ReadableStream, type UnderlyingByteSource } from '../streams';
10
+
11
+ export interface BlobPropertyBag {
12
+ type?: string;
13
+ endings?: 'transparent' | 'native';
14
+ }
15
+
16
+ export type BlobPart = ArrayBuffer | ArrayBufferView | Blob | string;
17
+
18
+ /**
19
+ * Blob represents immutable raw binary data.
20
+ */
21
+ export class Blob {
22
+ readonly #parts: Uint8Array[];
23
+ readonly #type: string;
24
+ readonly #size: number;
25
+
26
+ constructor(blobParts?: BlobPart[], options?: BlobPropertyBag) {
27
+ this.#parts = [];
28
+ this.#type = normalizeType(options?.type ?? '');
29
+ const endings = options?.endings ?? 'transparent';
30
+
31
+ if (blobParts) {
32
+ const parts = Array.from(blobParts);
33
+ for (let i = 0; i < parts.length; i++) {
34
+ this.#parts.push(convertToBytes(parts[i], endings));
35
+ }
36
+ }
37
+
38
+ this.#size = this.#parts.reduce((acc, part) => acc + part.byteLength, 0);
39
+ }
40
+
41
+ /**
42
+ * The size of the Blob in bytes.
43
+ */
44
+ get size(): number {
45
+ return this.#size;
46
+ }
47
+
48
+ /**
49
+ * The MIME type of the Blob.
50
+ */
51
+ get type(): string {
52
+ return this.#type;
53
+ }
54
+
55
+ /**
56
+ * Returns a new Blob containing a subset of this Blob's data.
57
+ */
58
+ slice(start?: number, end?: number, contentType?: string): Blob {
59
+ const size = this.#size;
60
+
61
+ // Normalize start
62
+ let relativeStart: number;
63
+ if (start === undefined) {
64
+ relativeStart = 0;
65
+ } else if (start < 0) {
66
+ relativeStart = Math.max(size + start, 0);
67
+ } else {
68
+ relativeStart = Math.min(start, size);
69
+ }
70
+
71
+ // Normalize end
72
+ let relativeEnd: number;
73
+ if (end === undefined) {
74
+ relativeEnd = size;
75
+ } else if (end < 0) {
76
+ relativeEnd = Math.max(size + end, 0);
77
+ } else {
78
+ relativeEnd = Math.min(end, size);
79
+ }
80
+
81
+ // Calculate span
82
+ const span = Math.max(relativeEnd - relativeStart, 0);
83
+
84
+ if (span === 0) {
85
+ return new Blob([], { type: normalizeType(contentType ?? '') });
86
+ }
87
+
88
+ // Extract the slice
89
+ const allBytes = this.#concatenateBytes();
90
+ const slicedBytes = allBytes.slice(relativeStart, relativeStart + span);
91
+
92
+ return new Blob([slicedBytes], { type: normalizeType(contentType ?? '') });
93
+ }
94
+
95
+ /**
96
+ * Returns a Promise that resolves with the contents as an ArrayBuffer.
97
+ */
98
+ async arrayBuffer(): Promise<ArrayBuffer> {
99
+ const bytes = this.#concatenateBytes();
100
+ // Return a copy to ensure immutability
101
+ return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
102
+ }
103
+
104
+ /**
105
+ * Returns a Promise that resolves with the contents as a string.
106
+ */
107
+ async text(): Promise<string> {
108
+ const bytes = this.#concatenateBytes();
109
+ return new TextDecoder().decode(bytes);
110
+ }
111
+
112
+ /**
113
+ * Returns a Promise that resolves with the parsed JSON contents.
114
+ */
115
+ async json(): Promise<unknown> {
116
+ const text = await this.text();
117
+ const normalized = text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
118
+ return JSON.parse(normalized);
119
+ }
120
+
121
+ /**
122
+ * Returns a Promise that resolves with parsed application/x-www-form-urlencoded data.
123
+ */
124
+ async formData(): Promise<FormData> {
125
+ const text = await this.text();
126
+ const normalized = text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
127
+ const params = new URLSearchParams(normalized);
128
+ const form = new FormData();
129
+ params.forEach((value, key) => {
130
+ form.append(key, value);
131
+ });
132
+ return form;
133
+ }
134
+
135
+ /**
136
+ * Returns a Promise that resolves with the Blob contents as a Uint8Array.
137
+ */
138
+ async bytes(): Promise<Uint8Array> {
139
+ return this.#concatenateBytes();
140
+ }
141
+
142
+ /**
143
+ * Returns a ReadableStream that can be used to read the Blob's contents.
144
+ *
145
+ * Uses byte stream type (type: 'bytes') so that BYOB readers work.
146
+ */
147
+ stream(): ReadableStream<Uint8Array> {
148
+ const bytes = this.#concatenateBytes();
149
+ let position = 0;
150
+ const chunkSize = 65536; // 64KB chunks
151
+
152
+ const source: UnderlyingByteSource = {
153
+ type: 'bytes',
154
+ pull(controller) {
155
+ if (position >= bytes.length) {
156
+ controller.close();
157
+ return;
158
+ }
159
+ const chunk = bytes.slice(position, position + chunkSize);
160
+ position += chunkSize;
161
+ controller.enqueue(chunk);
162
+ },
163
+ };
164
+
165
+ // Prefer the global ReadableStream when available so that
166
+ // `instanceof ReadableStream` checks work against the platform class.
167
+ const RS = (globalThis as any).ReadableStream ?? ReadableStream;
168
+ return new RS(source);
169
+ }
170
+
171
+ /**
172
+ * Returns a new Blob with the given bytes appended.
173
+ * Non-standard extension for internal use.
174
+ */
175
+ #concatenateBytes(): Uint8Array {
176
+ if (this.#parts.length === 0) {
177
+ return new Uint8Array(0);
178
+ }
179
+ if (this.#parts.length === 1) {
180
+ return this.#parts[0];
181
+ }
182
+
183
+ const result = new Uint8Array(this.#size);
184
+ let offset = 0;
185
+ for (const part of this.#parts) {
186
+ result.set(part, offset);
187
+ offset += part.byteLength;
188
+ }
189
+ return result;
190
+ }
191
+
192
+ /**
193
+ * Returns the raw bytes for internal use.
194
+ * Non-standard - used by File and other internal APIs.
195
+ */
196
+ _getBytes(): Uint8Array {
197
+ return this.#concatenateBytes();
198
+ }
199
+
200
+ get [Symbol.toStringTag](): string {
201
+ return 'Blob';
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Normalize a MIME type string.
207
+ * Per spec: lowercase, only ASCII printable chars (0x20-0x7E) excluding certain chars.
208
+ */
209
+ function normalizeType(type: string): string {
210
+ if (!type) return '';
211
+
212
+ // Convert to lowercase
213
+ const lowered = type.toLowerCase();
214
+
215
+ // Check for invalid characters
216
+ for (let i = 0; i < lowered.length; i++) {
217
+ const code = lowered.charCodeAt(i);
218
+ if (code < 0x20 || code > 0x7e) {
219
+ return '';
220
+ }
221
+ }
222
+
223
+ return lowered;
224
+ }
225
+
226
+ /**
227
+ * Convert a BlobPart to bytes.
228
+ */
229
+ function convertToBytes(part: BlobPart, endings: 'transparent' | 'native'): Uint8Array {
230
+ if (typeof part === 'string') {
231
+ let str = part;
232
+ if (endings === 'native') {
233
+ // Convert line endings to platform-native
234
+ // On most platforms this is \n, but could be \r\n on Windows
235
+ const nativeLineEnding = '\n'; // Mobile platforms use \n
236
+ str = str.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
237
+ if (nativeLineEnding !== '\n') {
238
+ str = str.replace(/\n/g, nativeLineEnding);
239
+ }
240
+ }
241
+ return new TextEncoder().encode(str);
242
+ }
243
+
244
+ if (part instanceof Blob) {
245
+ return part._getBytes();
246
+ }
247
+
248
+ if (part instanceof ArrayBuffer) {
249
+ return new Uint8Array(part);
250
+ }
251
+
252
+ if (ArrayBuffer.isView(part)) {
253
+ return new Uint8Array(part.buffer, part.byteOffset, part.byteLength);
254
+ }
255
+
256
+ throw new TypeError('Invalid blob part type');
257
+ }
258
+
259
+ export default Blob;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * File implementation for Ibex runtime
3
+ *
4
+ * Implements the WHATWG File API.
5
+ * https://w3c.github.io/FileAPI/#file-section
6
+ */
7
+
8
+ import { Blob, BlobPart, BlobPropertyBag } from './Blob';
9
+
10
+ export interface FilePropertyBag extends BlobPropertyBag {
11
+ lastModified?: number;
12
+ }
13
+
14
+ /**
15
+ * File represents a file with a name and optional last modified date.
16
+ * Extends Blob with file-specific metadata.
17
+ */
18
+ export class File extends Blob {
19
+ readonly #name: string;
20
+ readonly #lastModified: number;
21
+
22
+ constructor(fileBits: BlobPart[], fileName: string, options?: FilePropertyBag) {
23
+ super(fileBits, options);
24
+
25
+ // Per spec, convert to string
26
+ this.#name = String(fileName);
27
+
28
+ // Default to current time if not specified
29
+ this.#lastModified = options?.lastModified ?? Date.now();
30
+ }
31
+
32
+ /**
33
+ * The name of the file.
34
+ */
35
+ get name(): string {
36
+ return this.#name;
37
+ }
38
+
39
+ /**
40
+ * The last modified time of the file, in milliseconds since the Unix epoch.
41
+ */
42
+ get lastModified(): number {
43
+ return this.#lastModified;
44
+ }
45
+
46
+ /**
47
+ * The relative path of the file (empty for files not from a directory picker).
48
+ * This is a legacy property, always returns empty string.
49
+ */
50
+ get webkitRelativePath(): string {
51
+ return '';
52
+ }
53
+
54
+ get [Symbol.toStringTag](): string {
55
+ return 'File';
56
+ }
57
+ }
58
+
59
+ export default File;
@@ -0,0 +1,323 @@
1
+ /**
2
+ * FormData implementation for Ibex runtime
3
+ *
4
+ * Implements the WHATWG FormData API with multipart/form-data encoding.
5
+ * https://xhr.spec.whatwg.org/#interface-formdata
6
+ */
7
+
8
+ import { Blob } from './Blob';
9
+ import { File } from './File';
10
+
11
+ export type FormDataEntryValue = string | File;
12
+
13
+ interface BlobLikeValue {
14
+ type?: string;
15
+ name?: string;
16
+ lastModified?: number;
17
+ _getBytes(): Uint8Array;
18
+ }
19
+
20
+ /**
21
+ * Result of encoding FormData to multipart/form-data
22
+ */
23
+ export interface MultipartEncodingResult {
24
+ /** The encoded body as bytes */
25
+ body: Uint8Array;
26
+ /** The Content-Type header value including boundary */
27
+ contentType: string;
28
+ /** The boundary string used */
29
+ boundary: string;
30
+ }
31
+
32
+ /**
33
+ * FormData provides a way to construct a set of key/value pairs
34
+ * representing form fields and their values.
35
+ */
36
+ export class FormData {
37
+ readonly #entries: Array<[string, FormDataEntryValue]> = [];
38
+
39
+ constructor(form?: unknown) {
40
+ // Note: In a browser, this would accept an HTMLFormElement.
41
+ // In Ibex runtime, we only support creating empty FormData.
42
+ if (form !== undefined && form !== null) {
43
+ throw new TypeError('FormData constructor with form element is not supported');
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Appends a new value to an existing key, or adds the key if it doesn't exist.
49
+ */
50
+ append(name: string, value: string): void;
51
+ append(name: string, value: Blob, filename?: string): void;
52
+ append(name: string, value: string | Blob, filename?: string): void {
53
+ const normalizedName = String(name);
54
+ const entry = normalizeValue(value, filename);
55
+ this.#entries.push([normalizedName, entry]);
56
+ }
57
+
58
+ /**
59
+ * Deletes all entries with the given name.
60
+ */
61
+ delete(name: string): void {
62
+ const normalizedName = String(name);
63
+ for (let i = this.#entries.length - 1; i >= 0; i--) {
64
+ if (this.#entries[i][0] === normalizedName) {
65
+ this.#entries.splice(i, 1);
66
+ }
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Returns the first value associated with a given key.
72
+ */
73
+ get(name: string): FormDataEntryValue | null {
74
+ const normalizedName = String(name);
75
+ for (const [entryName, value] of this.#entries) {
76
+ if (entryName === normalizedName) {
77
+ return value;
78
+ }
79
+ }
80
+ return null;
81
+ }
82
+
83
+ /**
84
+ * Returns all values associated with a given key.
85
+ */
86
+ getAll(name: string): FormDataEntryValue[] {
87
+ const normalizedName = String(name);
88
+ const result: FormDataEntryValue[] = [];
89
+ for (const [entryName, value] of this.#entries) {
90
+ if (entryName === normalizedName) {
91
+ result.push(value);
92
+ }
93
+ }
94
+ return result;
95
+ }
96
+
97
+ /**
98
+ * Returns whether a key exists in the FormData.
99
+ */
100
+ has(name: string): boolean {
101
+ const normalizedName = String(name);
102
+ for (const [entryName] of this.#entries) {
103
+ if (entryName === normalizedName) {
104
+ return true;
105
+ }
106
+ }
107
+ return false;
108
+ }
109
+
110
+ /**
111
+ * Sets a key to a value, replacing any existing entries with that key.
112
+ */
113
+ set(name: string, value: string): void;
114
+ set(name: string, value: Blob, filename?: string): void;
115
+ set(name: string, value: string | Blob, filename?: string): void {
116
+ const normalizedName = String(name);
117
+ const entry = normalizeValue(value, filename);
118
+
119
+ // Find first occurrence and replace
120
+ let found = false;
121
+ for (let i = 0; i < this.#entries.length; i++) {
122
+ if (this.#entries[i][0] === normalizedName) {
123
+ if (!found) {
124
+ this.#entries[i] = [normalizedName, entry];
125
+ found = true;
126
+ } else {
127
+ // Remove subsequent entries
128
+ this.#entries.splice(i, 1);
129
+ i--;
130
+ }
131
+ }
132
+ }
133
+
134
+ // If not found, append
135
+ if (!found) {
136
+ this.#entries.push([normalizedName, entry]);
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Returns an iterator of all keys.
142
+ */
143
+ *keys(): IterableIterator<string> {
144
+ for (const [name] of this.#entries) {
145
+ yield name;
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Returns an iterator of all values.
151
+ */
152
+ *values(): IterableIterator<FormDataEntryValue> {
153
+ for (const [, value] of this.#entries) {
154
+ yield value;
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Returns an iterator of all key/value pairs.
160
+ */
161
+ *entries(): IterableIterator<[string, FormDataEntryValue]> {
162
+ for (const entry of this.#entries) {
163
+ yield entry;
164
+ }
165
+ }
166
+
167
+ /**
168
+ * Calls a function for each key/value pair.
169
+ */
170
+ forEach(
171
+ callback: (value: FormDataEntryValue, key: string, parent: FormData) => void,
172
+ thisArg?: unknown
173
+ ): void {
174
+ // Use indexed loop to avoid the Rolldown for-of → forEach transform which
175
+ // rewrites `this` inside the callback to the global object.
176
+ const entries = this.#entries;
177
+ for (let i = 0; i < entries.length; i++) {
178
+ callback.call(thisArg, entries[i][1], entries[i][0], this);
179
+ }
180
+ }
181
+
182
+ [Symbol.iterator](): IterableIterator<[string, FormDataEntryValue]> {
183
+ return this.entries();
184
+ }
185
+
186
+ get [Symbol.toStringTag](): string {
187
+ return 'FormData';
188
+ }
189
+
190
+ /**
191
+ * Internal method to get all entries for serialization.
192
+ * Used by fetch() to build multipart/form-data body.
193
+ */
194
+ _getEntries(): Array<[string, FormDataEntryValue]> {
195
+ return [...this.#entries];
196
+ }
197
+
198
+ /**
199
+ * Encode FormData as multipart/form-data.
200
+ *
201
+ * This method is used by fetch() when sending FormData as request body.
202
+ *
203
+ * @returns Object containing body bytes, content-type header, and boundary
204
+ *
205
+ * @example
206
+ * const formData = new FormData();
207
+ * formData.append('name', 'John');
208
+ * formData.append('file', new File(['content'], 'test.txt'));
209
+ * const { body, contentType } = formData._encode();
210
+ * // contentType = 'multipart/form-data; boundary=----ExactFormBoundary...'
211
+ */
212
+ _encode(): MultipartEncodingResult {
213
+ // Generate a unique boundary
214
+ const boundary = generateBoundary();
215
+ const encoder = new TextEncoder();
216
+ const parts: Uint8Array[] = [];
217
+ const CRLF = '\r\n';
218
+
219
+ for (const [name, value] of this.#entries) {
220
+ // Add boundary
221
+ parts.push(encoder.encode(`--${boundary}${CRLF}`));
222
+
223
+ if (typeof value === 'string') {
224
+ // Text field
225
+ parts.push(encoder.encode(
226
+ `Content-Disposition: form-data; name="${escapeQuotes(name)}"${CRLF}` +
227
+ `${CRLF}` +
228
+ `${value}${CRLF}`
229
+ ));
230
+ } else {
231
+ // File field
232
+ const filename = value.name || 'blob';
233
+ const contentType = value.type || 'application/octet-stream';
234
+
235
+ parts.push(encoder.encode(
236
+ `Content-Disposition: form-data; name="${escapeQuotes(name)}"; filename="${escapeQuotes(filename)}"${CRLF}` +
237
+ `Content-Type: ${contentType}${CRLF}` +
238
+ `${CRLF}`
239
+ ));
240
+
241
+ // Add file content
242
+ parts.push(value._getBytes());
243
+ parts.push(encoder.encode(CRLF));
244
+ }
245
+ }
246
+
247
+ // Add closing boundary
248
+ parts.push(encoder.encode(`--${boundary}--${CRLF}`));
249
+
250
+ // Concatenate all parts
251
+ const totalLength = parts.reduce((acc, part) => acc + part.byteLength, 0);
252
+ const body = new Uint8Array(totalLength);
253
+ let offset = 0;
254
+ for (const part of parts) {
255
+ body.set(part, offset);
256
+ offset += part.byteLength;
257
+ }
258
+
259
+ return {
260
+ body,
261
+ contentType: `multipart/form-data; boundary=${boundary}`,
262
+ boundary,
263
+ };
264
+ }
265
+ }
266
+
267
+ /**
268
+ * Generate a unique boundary string for multipart encoding.
269
+ */
270
+ function generateBoundary(): string {
271
+ // Use a combination of timestamp and random values
272
+ const timestamp = Date.now().toString(36);
273
+ const random = Math.random().toString(36).slice(2, 10);
274
+ const random2 = Math.random().toString(36).slice(2, 10);
275
+ return `----ExactFormBoundary${timestamp}${random}${random2}`;
276
+ }
277
+
278
+ /**
279
+ * Escape quotes in a string for use in Content-Disposition header.
280
+ */
281
+ function escapeQuotes(str: string): string {
282
+ return str.replace(/"/g, '\\"').replace(/\r/g, '%0D').replace(/\n/g, '%0A');
283
+ }
284
+
285
+ function isBlobLikeValue(value: unknown): value is BlobLikeValue {
286
+ return (
287
+ typeof value === 'object' &&
288
+ value !== null &&
289
+ typeof (value as BlobLikeValue)._getBytes === 'function'
290
+ );
291
+ }
292
+
293
+ /**
294
+ * Normalize a value for storage in FormData.
295
+ * Strings are stored as-is, Blobs are converted to Files.
296
+ */
297
+ function normalizeValue(value: string | Blob | BlobLikeValue, filename?: string): FormDataEntryValue {
298
+ if (typeof value === 'string') {
299
+ return value;
300
+ }
301
+
302
+ // If it's already a File and no filename override, use as-is
303
+ if (value instanceof File && filename === undefined) {
304
+ return value;
305
+ }
306
+
307
+ if (!isBlobLikeValue(value)) {
308
+ throw new TypeError("FormData value must be a string, Blob, or File");
309
+ }
310
+
311
+ // Convert Blob to File
312
+ const name = filename ?? (typeof value.name === 'string' ? value.name : 'blob');
313
+ const options = {
314
+ type: value.type,
315
+ lastModified: typeof value.lastModified === 'number' ? value.lastModified : Date.now(),
316
+ };
317
+
318
+ // Need to extract the bytes from the Blob
319
+ const bytes = value._getBytes();
320
+ return new File([bytes], name, options);
321
+ }
322
+
323
+ export default FormData;
@@ -0,0 +1,3 @@
1
+ export { Blob, type BlobPart, type BlobPropertyBag } from './Blob';
2
+ export { File, type FilePropertyBag } from './File';
3
+ export { FormData, type FormDataEntryValue, type MultipartEncodingResult } from './FormData';