quickwin 1.0.2 → 1.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 (78) hide show
  1. package/README.en.md +204 -204
  2. package/README.md +203 -203
  3. package/examples/PdfCanvas.tsx +83 -83
  4. package/examples/exec_server.ts +35 -35
  5. package/examples/pdf_preview2.ts +375 -375
  6. package/examples/pdf_viewer.tsx +279 -279
  7. package/examples/test_gallery.tsx +285 -285
  8. package/examples/test_react_lazy.tsx +63 -63
  9. package/examples/test_react_listbox.tsx +71 -71
  10. package/examples/test_root_window.tsx +41 -41
  11. package/examples/test_tab.tsx +75 -75
  12. package/examples/tray_demo.tsx +81 -81
  13. package/lib/assert.ts +6 -6
  14. package/lib/cache-utils.ts +40 -40
  15. package/lib/dumpRects.ts +25 -25
  16. package/lib/fetch.ts +502 -502
  17. package/lib/http-common.ts +399 -399
  18. package/lib/http-server.ts +424 -424
  19. package/lib/polyfill.ts +171 -171
  20. package/lib/react-qw/components/index.ts +17 -17
  21. package/lib/react-qw/index.ts +90 -90
  22. package/lib/react-qw/jsx.d.ts +34 -34
  23. package/lib/react-qw/layout.ts +111 -111
  24. package/lib/react-qw/props.ts +36 -36
  25. package/lib/react-qw/reconciler.ts +373 -373
  26. package/lib/stream.ts +21 -21
  27. package/lib/text-codec.ts +171 -171
  28. package/lib/text-measure.ts +54 -54
  29. package/lib/url.ts +201 -201
  30. package/lib/vendor/web-streams/ponyfill.d.mts +829 -829
  31. package/lib/vendor/web-streams/ponyfill.mjs +8 -8
  32. package/lib/websocket.js +8 -0
  33. package/lib/websocket.ts +563 -555
  34. package/package.json +50 -44
  35. package/qwin-nowasm-x86.exe +0 -0
  36. package/qwin-nowasm.exe +0 -0
  37. package/qwin-x86.exe +0 -0
  38. package/qwin.exe +0 -0
  39. package/test/run.ts +96 -96
  40. package/test/test_basic.ts +9 -9
  41. package/test/test_basic_fetch.ts +33 -33
  42. package/test/test_brotli.ts +52 -52
  43. package/test/test_fetch_cache.ts +187 -187
  44. package/test/test_ffi.ts +174 -174
  45. package/test/test_frame_encoding.ts +132 -132
  46. package/test/test_helper.ts +80 -80
  47. package/test/test_http_import.ts +74 -74
  48. package/test/test_http_server.ts +136 -136
  49. package/test/test_import_dir.ts +5 -5
  50. package/test/test_ipv6.ts +129 -129
  51. package/test/test_meta_url.ts +4 -4
  52. package/test/test_mupdf_render.ts +74 -74
  53. package/test/test_mupdf_twice.ts +81 -81
  54. package/test/test_mupdf_wasm.ts +30 -30
  55. package/test/test_net_event.ts +55 -55
  56. package/test/test_net_fetch.ts +238 -238
  57. package/test/test_net_websocket.ts +164 -164
  58. package/test/test_polyfill.ts +159 -159
  59. package/test/test_url.ts +183 -183
  60. package/test/test_wasm_basic.ts +73 -73
  61. package/test/test_wasm_import_global.ts +45 -45
  62. package/test/test_wasm_sjlj.ts +125 -125
  63. package/test/test_wasm_types.ts +124 -124
  64. package/test/test_worker.ts +24 -24
  65. package/test/test_worker_fetch.ts +27 -27
  66. package/test/test_worker_net.ts +54 -54
  67. package/test/test_worker_wasm_concurrent.ts +45 -45
  68. package/test/worker_fetch.ts +13 -13
  69. package/test/worker_helper.ts +1 -1
  70. package/test/worker_http.ts +20 -20
  71. package/test/worker_import_lib.ts +13 -13
  72. package/test/worker_module.ts +17 -17
  73. package/test/worker_wasm_concurrent.ts +22 -22
  74. package/tsconfig.json +27 -27
  75. package/vendor/mupdf-wasm/mupdf-wasm.d.ts +571 -571
  76. package/vendor/mupdf-wasm/mupdf-wasm.js +2749 -2749
  77. package/vendor/mupdf-wasm/mupdf.d.ts +939 -939
  78. package/vendor/mupdf-wasm/mupdf.js +3317 -3317
@@ -1,399 +1,399 @@
1
- import './polyfill.js'
2
- import './stream.js'
3
-
4
- // ── Byte helpers ──
5
-
6
- export function _concat(parts: Uint8Array[]): Uint8Array<ArrayBuffer> {
7
- if (parts.length === 0) return new Uint8Array(0)
8
- let totalLen = 0
9
- for (const p of parts) totalLen += p.length
10
- const result = new Uint8Array(totalLen)
11
- let offset = 0
12
- for (const p of parts) { result.set(p, offset); offset += p.length }
13
- return result
14
- }
15
-
16
- export function _toReadableStream(body: BodyInit): ReadableStream<Uint8Array> {
17
- if (body instanceof ReadableStream) return body
18
- const bytes = (typeof body === 'string')
19
- ? new TextEncoder().encode(body)
20
- : (body instanceof ArrayBuffer)
21
- ? new Uint8Array(body)
22
- : new Uint8Array(body.buffer, body.byteOffset, body.byteLength)
23
- return new ReadableStream<Uint8Array>({
24
- start(ctrl) {
25
- ctrl.enqueue(bytes)
26
- ctrl.close()
27
- }
28
- })
29
- }
30
-
31
- export async function _readStream(stream: ReadableStream<Uint8Array>): Promise<Uint8Array<ArrayBuffer>> {
32
- const reader = stream.getReader()
33
- const chunks: Uint8Array[] = []
34
- while (true) {
35
- const { done, value } = await reader.read()
36
- if (done) break
37
- chunks.push(value)
38
- }
39
- return _concat(chunks)
40
- }
41
-
42
- export function _hasChunkedEnd(data: Uint8Array): boolean {
43
- if (data.length < 7) return false
44
- let i = data.length - 7
45
- return data[i] === 0x0D && data[i+1] === 0x0A && data[i+2] === 0x30 && data[i+3] === 0x0D && data[i+4] === 0x0A && data[i+5] === 0x0D && data[i+6] === 0x0A
46
- }
47
-
48
- export function decodeChunked(data: Uint8Array): Uint8Array {
49
- const chunks: Uint8Array[] = []
50
- let pos = 0
51
- while (pos < data.length) {
52
- let crlf = -1
53
- for (let i = pos; i < data.length - 1; i++) {
54
- if (data[i] === 0x0D && data[i + 1] === 0x0A) { crlf = i; break }
55
- }
56
- if (crlf < 0) break
57
-
58
- let size = 0
59
- for (let i = pos; i < crlf; i++) {
60
- const b = data[i]!
61
- if (b >= 0x30 && b <= 0x39) size = size * 16 + (b - 0x30)
62
- else if (b >= 0x41 && b <= 0x46) size = size * 16 + (b - 0x37)
63
- else if (b >= 0x61 && b <= 0x66) size = size * 16 + (b - 0x57)
64
- else break
65
- }
66
-
67
- const dataStart = crlf + 2
68
- if (size === 0) {
69
- for (let i = dataStart; i < data.length - 3; i++) {
70
- if (data[i] === 0x0D && data[i+1] === 0x0A && data[i+2] === 0x0D && data[i+3] === 0x0A) {
71
- pos = i + 4; break
72
- }
73
- }
74
- if (pos === dataStart) break
75
- break
76
- }
77
-
78
- if (dataStart + size > data.length) break
79
- chunks.push(data.subarray(dataStart, dataStart + size))
80
- pos = dataStart + size + 2
81
- }
82
- return _concat(chunks)
83
- }
84
-
85
- // ── Headers ──
86
-
87
- // Flat array storage: [name0, value0, name1, value1, ...]
88
- // Pairs at even/odd indices. Strings are immutable, so [...this._headers] is a correct deep copy.
89
-
90
- export class HeadersImpl {
91
- private _headers: string[] = []
92
-
93
- constructor(init?: HeadersInit) {
94
- if (init) {
95
- if (init instanceof HeadersImpl) {
96
- this._headers = [...init._headers]
97
- } else if (Array.isArray(init)) {
98
- this._headers = init.flatMap(([k, v]) => [k.toLowerCase(), v])
99
- } else if (typeof init === 'object') {
100
- this._headers = Object.entries(init).flatMap(([k, v]) => [k.toLowerCase(), v])
101
- }
102
- }
103
- }
104
-
105
- append(name: string, value: string): void {
106
- this._headers.push(name.toLowerCase(), value)
107
- }
108
-
109
- delete(name: string): void {
110
- const key = name.toLowerCase()
111
- const h = this._headers
112
- let i = h.length - 2
113
- while (i >= 0) {
114
- if (h[i] === key) { h.splice(i, 2); i -= 2 } else i -= 2
115
- }
116
- }
117
-
118
- get(name: string): string | null {
119
- const key = name.toLowerCase()
120
- const matches = [...this.entries()].filter(([k]) => k === key).map(([, v]) => v)
121
- return matches.length ? matches.join(', ') : null
122
- }
123
-
124
- has(name: string): boolean {
125
- return [...this.keys()].includes(name.toLowerCase())
126
- }
127
-
128
- set(name: string, value: string): void {
129
- const key = name.toLowerCase()
130
- const h = this._headers
131
- let found = false
132
- let i = h.length - 2
133
- while (i >= 0) {
134
- if (h[i] === key) {
135
- if (!found) {
136
- h[i + 1] = value
137
- found = true
138
- } else {
139
- h.splice(i, 2)
140
- }
141
- }
142
- i -= 2
143
- }
144
- if (!found) h.push(key, value)
145
- }
146
-
147
- forEach(callback: (value: string, name: string, headers: HeadersImpl) => void): void {
148
- for (const [name, value] of this) {
149
- callback(value, name, this)
150
- }
151
- }
152
-
153
- *entries(): IterableIterator<[string, string]> {
154
- const h = this._headers
155
- for (let i = 0; i < h.length; i += 2) {
156
- yield [h[i]!, h[i + 1]!]
157
- }
158
- }
159
-
160
- *keys(): IterableIterator<string> {
161
- for (const [k] of this) yield k
162
- }
163
-
164
- *values(): IterableIterator<string> {
165
- for (const [, v] of this) yield v
166
- }
167
-
168
- [Symbol.iterator](): IterableIterator<[string, string]> {
169
- return this.entries()
170
- }
171
- }
172
-
173
- // ── Request ──
174
-
175
- export class RequestImpl {
176
- readonly url: string
177
- readonly method: string
178
- readonly headers: HeadersImpl
179
- private _body: ReadableStream<Uint8Array> | null
180
- readonly redirect: RequestRedirect
181
- readonly timeout: number
182
- readonly maxRedirects: number
183
- private _bodyConsumed: boolean = false
184
- readonly integrity: string = ''
185
- readonly keepalive: boolean = false
186
- readonly mode: string = 'cors'
187
- readonly cache: string = 'default'
188
- readonly credentials: string = 'same-origin'
189
- readonly referrer: string = 'about:client'
190
- readonly referrerPolicy: string = ''
191
- readonly signal: AbortSignal | null = null
192
- readonly destination: string = ''
193
-
194
- get body(): ReadableStream<Uint8Array> | null { return this._body }
195
-
196
- get bodyUsed(): boolean {
197
- return this._bodyConsumed || (this._body?.locked ?? false)
198
- }
199
-
200
- constructor(input: string | RequestImpl, init: RequestInit = {}) {
201
- const initBody = init.body
202
- if (input instanceof RequestImpl) {
203
- this.url = input.url
204
- this.method = init.method || input.method
205
- this.headers = new HeadersImpl(init.headers || input.headers)
206
- if (initBody !== undefined && initBody !== null) {
207
- this._body = _toReadableStream(initBody)
208
- } else {
209
- this._body = input._body
210
- }
211
- this.redirect = init.redirect || input.redirect
212
- this.timeout = init.timeout || input.timeout
213
- this.maxRedirects = init.maxRedirects || input.maxRedirects
214
- } else {
215
- this.url = input
216
- this.method = init.method || 'GET'
217
- this.headers = new HeadersImpl(init.headers)
218
- this._body = initBody !== null && initBody !== undefined ? _toReadableStream(initBody) : null
219
- this.redirect = init.redirect || 'follow'
220
- this.timeout = init.timeout || 30000
221
- this.maxRedirects = init.maxRedirects || 5
222
- }
223
- }
224
-
225
- async arrayBuffer(): Promise<ArrayBuffer> {
226
- if (!this.body) return new ArrayBuffer(0)
227
- if (this._bodyConsumed) throw new TypeError('Body already used')
228
- this._bodyConsumed = true
229
- const bytes = await _readStream(this.body)
230
- return bytes.buffer
231
- }
232
-
233
- async json<T = unknown>(): Promise<T> {
234
- return JSON.parse(await this.text())
235
- }
236
-
237
- async text(): Promise<string> {
238
- if (!this.body) return ''
239
- if (this._bodyConsumed) throw new TypeError('Body already used')
240
- this._bodyConsumed = true
241
- const bytes = await _readStream(this.body)
242
- return new TextDecoder('utf-8').decode(bytes)
243
- }
244
-
245
- clone(): RequestImpl {
246
- if (this.bodyUsed) throw new TypeError('Body already used')
247
- if (this._body) {
248
- const [branch1, branch2] = this._body.tee()
249
- this._body = branch1
250
- const req = new RequestImpl(this.url, {
251
- method: this.method,
252
- headers: this.headers,
253
- body: branch2,
254
- redirect: this.redirect,
255
- timeout: this.timeout,
256
- maxRedirects: this.maxRedirects,
257
- })
258
- return req
259
- }
260
- return new RequestImpl(this.url, {
261
- method: this.method,
262
- headers: this.headers,
263
- redirect: this.redirect,
264
- timeout: this.timeout,
265
- maxRedirects: this.maxRedirects,
266
- })
267
- }
268
- }
269
-
270
- // ── Response ──
271
-
272
- export class ResponseImpl {
273
- readonly status: number
274
- readonly statusText: string
275
- readonly ok: boolean
276
-
277
- private _headers: HeadersImpl
278
- _redirected: boolean = false
279
- private _type: ResponseType = 'basic'
280
- _url: string = ''
281
- private _body: ReadableStream<Uint8Array>
282
- private _bodyConsumed: boolean = false
283
-
284
- get body(): ReadableStream<Uint8Array> { return this._body }
285
- get bodyUsed(): boolean { return this._bodyConsumed || this._body.locked }
286
- get headers(): HeadersImpl { return this._headers }
287
- get redirected(): boolean { return this._redirected }
288
- get type(): ResponseType { return this._type }
289
- get url(): string { return this._url }
290
-
291
- constructor(body?: BodyInit | null, init?: ResponseInit) {
292
- const status = init?.status ?? 200
293
- const statusText = init?.statusText ?? ''
294
- const headers = new HeadersImpl(init?.headers)
295
- this.status = status
296
- this.statusText = statusText
297
- this._headers = headers
298
- this.ok = status >= 200 && status < 300
299
- this._body = body != null ? _toReadableStream(body) : new ReadableStream<Uint8Array>({
300
- start(ctrl) { ctrl.close() }
301
- })
302
- }
303
-
304
- async text(): Promise<string> {
305
- if (this.bodyUsed) throw new TypeError('Body already used')
306
- this._bodyConsumed = true
307
- const bytes = await _readStream(this._body)
308
- return new TextDecoder('utf-8').decode(bytes)
309
- }
310
-
311
- async json<T = unknown>(): Promise<T> {
312
- return JSON.parse(await this.text())
313
- }
314
-
315
- async arrayBuffer(): Promise<ArrayBuffer> {
316
- if (this.bodyUsed) throw new TypeError('Body already used')
317
- this._bodyConsumed = true
318
- const bytes = await _readStream(this._body)
319
- return bytes.buffer
320
- }
321
-
322
- async bytes(): Promise<Uint8Array> {
323
- if (this.bodyUsed) throw new TypeError('Body already used')
324
- this._bodyConsumed = true
325
- return await _readStream(this._body)
326
- }
327
-
328
- clone(): Response {
329
- if (this.bodyUsed) throw new TypeError('Body already used')
330
- const [branch1, branch2] = this._body.tee()
331
- this._body = branch1
332
- return new ResponseImpl(branch2, { status: this.status, statusText: this.statusText, headers: this._headers })
333
- }
334
-
335
- _applyDecompressedBody(stream: ReadableStream<Uint8Array>, _body: ArrayBuffer, newHeaders: HeadersImpl): void {
336
- this._bodyConsumed = false
337
- this._body = stream
338
- this._headers = newHeaders
339
- }
340
- }
341
-
342
- // ── Global declarations ──
343
-
344
- declare global {
345
- type HeadersInit = [string, string][] | Record<string, string> | Headers
346
- type BodyInit = ReadableStream<Uint8Array> | string | ArrayBuffer | ArrayBufferView
347
- type RequestRedirect = 'error' | 'follow' | 'manual'
348
- type RequestCache = 'default' | 'force-cache' | 'no-cache' | 'no-store' | 'only-if-cached' | 'reload'
349
- type RequestCredentials = 'include' | 'omit' | 'same-origin'
350
- type RequestMode = 'cors' | 'navigate' | 'no-cors' | 'same-origin'
351
- type ResponseType = 'basic' | 'cors' | 'default' | 'error' | 'opaque' | 'opaqueredirect'
352
-
353
- interface AbortSignal {
354
- readonly aborted: boolean;
355
- readonly reason: unknown;
356
- onabort: ((event: Event) => void) | null;
357
- throwIfAborted(): void;
358
- }
359
-
360
- interface RequestInit {
361
- body?: BodyInit | null;
362
- cache?: RequestCache;
363
- credentials?: RequestCredentials;
364
- headers?: HeadersInit;
365
- integrity?: string;
366
- keepalive?: boolean;
367
- method?: string;
368
- mode?: RequestMode;
369
- redirect?: RequestRedirect;
370
- referrer?: string;
371
- referrerPolicy?: string;
372
- signal?: AbortSignal | null;
373
- window?: null;
374
- timeout?: number;
375
- maxRedirects?: number;
376
- }
377
-
378
- interface ResponseInit {
379
- status?: number;
380
- statusText?: string;
381
- headers?: HeadersInit;
382
- }
383
-
384
- interface Headers extends HeadersImpl {}
385
- var Headers: typeof HeadersImpl
386
- interface Response extends Omit<ResponseImpl, '_url' | '_redirected' | '_applyDecompressedBody'> {}
387
- interface ResponseConstructor {
388
- new(body?: BodyInit | null, init?: ResponseInit): Response
389
- }
390
- var Response: ResponseConstructor
391
- interface Request extends RequestImpl {}
392
- var Request: typeof RequestImpl
393
- }
394
-
395
- // ── Register globals ──
396
-
397
- globalThis.Headers = HeadersImpl
398
- globalThis.Request = RequestImpl
399
- globalThis.Response = ResponseImpl
1
+ import './polyfill.js'
2
+ import './stream.js'
3
+
4
+ // ── Byte helpers ──
5
+
6
+ export function _concat(parts: Uint8Array[]): Uint8Array<ArrayBuffer> {
7
+ if (parts.length === 0) return new Uint8Array(0)
8
+ let totalLen = 0
9
+ for (const p of parts) totalLen += p.length
10
+ const result = new Uint8Array(totalLen)
11
+ let offset = 0
12
+ for (const p of parts) { result.set(p, offset); offset += p.length }
13
+ return result
14
+ }
15
+
16
+ export function _toReadableStream(body: BodyInit): ReadableStream<Uint8Array> {
17
+ if (body instanceof ReadableStream) return body
18
+ const bytes = (typeof body === 'string')
19
+ ? new TextEncoder().encode(body)
20
+ : (body instanceof ArrayBuffer)
21
+ ? new Uint8Array(body)
22
+ : new Uint8Array(body.buffer, body.byteOffset, body.byteLength)
23
+ return new ReadableStream<Uint8Array>({
24
+ start(ctrl) {
25
+ ctrl.enqueue(bytes)
26
+ ctrl.close()
27
+ }
28
+ })
29
+ }
30
+
31
+ export async function _readStream(stream: ReadableStream<Uint8Array>): Promise<Uint8Array<ArrayBuffer>> {
32
+ const reader = stream.getReader()
33
+ const chunks: Uint8Array[] = []
34
+ while (true) {
35
+ const { done, value } = await reader.read()
36
+ if (done) break
37
+ chunks.push(value)
38
+ }
39
+ return _concat(chunks)
40
+ }
41
+
42
+ export function _hasChunkedEnd(data: Uint8Array): boolean {
43
+ if (data.length < 7) return false
44
+ let i = data.length - 7
45
+ return data[i] === 0x0D && data[i+1] === 0x0A && data[i+2] === 0x30 && data[i+3] === 0x0D && data[i+4] === 0x0A && data[i+5] === 0x0D && data[i+6] === 0x0A
46
+ }
47
+
48
+ export function decodeChunked(data: Uint8Array): Uint8Array {
49
+ const chunks: Uint8Array[] = []
50
+ let pos = 0
51
+ while (pos < data.length) {
52
+ let crlf = -1
53
+ for (let i = pos; i < data.length - 1; i++) {
54
+ if (data[i] === 0x0D && data[i + 1] === 0x0A) { crlf = i; break }
55
+ }
56
+ if (crlf < 0) break
57
+
58
+ let size = 0
59
+ for (let i = pos; i < crlf; i++) {
60
+ const b = data[i]!
61
+ if (b >= 0x30 && b <= 0x39) size = size * 16 + (b - 0x30)
62
+ else if (b >= 0x41 && b <= 0x46) size = size * 16 + (b - 0x37)
63
+ else if (b >= 0x61 && b <= 0x66) size = size * 16 + (b - 0x57)
64
+ else break
65
+ }
66
+
67
+ const dataStart = crlf + 2
68
+ if (size === 0) {
69
+ for (let i = dataStart; i < data.length - 3; i++) {
70
+ if (data[i] === 0x0D && data[i+1] === 0x0A && data[i+2] === 0x0D && data[i+3] === 0x0A) {
71
+ pos = i + 4; break
72
+ }
73
+ }
74
+ if (pos === dataStart) break
75
+ break
76
+ }
77
+
78
+ if (dataStart + size > data.length) break
79
+ chunks.push(data.subarray(dataStart, dataStart + size))
80
+ pos = dataStart + size + 2
81
+ }
82
+ return _concat(chunks)
83
+ }
84
+
85
+ // ── Headers ──
86
+
87
+ // Flat array storage: [name0, value0, name1, value1, ...]
88
+ // Pairs at even/odd indices. Strings are immutable, so [...this._headers] is a correct deep copy.
89
+
90
+ export class HeadersImpl {
91
+ private _headers: string[] = []
92
+
93
+ constructor(init?: HeadersInit) {
94
+ if (init) {
95
+ if (init instanceof HeadersImpl) {
96
+ this._headers = [...init._headers]
97
+ } else if (Array.isArray(init)) {
98
+ this._headers = init.flatMap(([k, v]) => [k.toLowerCase(), v])
99
+ } else if (typeof init === 'object') {
100
+ this._headers = Object.entries(init).flatMap(([k, v]) => [k.toLowerCase(), v])
101
+ }
102
+ }
103
+ }
104
+
105
+ append(name: string, value: string): void {
106
+ this._headers.push(name.toLowerCase(), value)
107
+ }
108
+
109
+ delete(name: string): void {
110
+ const key = name.toLowerCase()
111
+ const h = this._headers
112
+ let i = h.length - 2
113
+ while (i >= 0) {
114
+ if (h[i] === key) { h.splice(i, 2); i -= 2 } else i -= 2
115
+ }
116
+ }
117
+
118
+ get(name: string): string | null {
119
+ const key = name.toLowerCase()
120
+ const matches = [...this.entries()].filter(([k]) => k === key).map(([, v]) => v)
121
+ return matches.length ? matches.join(', ') : null
122
+ }
123
+
124
+ has(name: string): boolean {
125
+ return [...this.keys()].includes(name.toLowerCase())
126
+ }
127
+
128
+ set(name: string, value: string): void {
129
+ const key = name.toLowerCase()
130
+ const h = this._headers
131
+ let found = false
132
+ let i = h.length - 2
133
+ while (i >= 0) {
134
+ if (h[i] === key) {
135
+ if (!found) {
136
+ h[i + 1] = value
137
+ found = true
138
+ } else {
139
+ h.splice(i, 2)
140
+ }
141
+ }
142
+ i -= 2
143
+ }
144
+ if (!found) h.push(key, value)
145
+ }
146
+
147
+ forEach(callback: (value: string, name: string, headers: HeadersImpl) => void): void {
148
+ for (const [name, value] of this) {
149
+ callback(value, name, this)
150
+ }
151
+ }
152
+
153
+ *entries(): IterableIterator<[string, string]> {
154
+ const h = this._headers
155
+ for (let i = 0; i < h.length; i += 2) {
156
+ yield [h[i]!, h[i + 1]!]
157
+ }
158
+ }
159
+
160
+ *keys(): IterableIterator<string> {
161
+ for (const [k] of this) yield k
162
+ }
163
+
164
+ *values(): IterableIterator<string> {
165
+ for (const [, v] of this) yield v
166
+ }
167
+
168
+ [Symbol.iterator](): IterableIterator<[string, string]> {
169
+ return this.entries()
170
+ }
171
+ }
172
+
173
+ // ── Request ──
174
+
175
+ export class RequestImpl {
176
+ readonly url: string
177
+ readonly method: string
178
+ readonly headers: HeadersImpl
179
+ private _body: ReadableStream<Uint8Array> | null
180
+ readonly redirect: RequestRedirect
181
+ readonly timeout: number
182
+ readonly maxRedirects: number
183
+ private _bodyConsumed: boolean = false
184
+ readonly integrity: string = ''
185
+ readonly keepalive: boolean = false
186
+ readonly mode: string = 'cors'
187
+ readonly cache: string = 'default'
188
+ readonly credentials: string = 'same-origin'
189
+ readonly referrer: string = 'about:client'
190
+ readonly referrerPolicy: string = ''
191
+ readonly signal: AbortSignal | null = null
192
+ readonly destination: string = ''
193
+
194
+ get body(): ReadableStream<Uint8Array> | null { return this._body }
195
+
196
+ get bodyUsed(): boolean {
197
+ return this._bodyConsumed || (this._body?.locked ?? false)
198
+ }
199
+
200
+ constructor(input: string | RequestImpl, init: RequestInit = {}) {
201
+ const initBody = init.body
202
+ if (input instanceof RequestImpl) {
203
+ this.url = input.url
204
+ this.method = init.method || input.method
205
+ this.headers = new HeadersImpl(init.headers || input.headers)
206
+ if (initBody !== undefined && initBody !== null) {
207
+ this._body = _toReadableStream(initBody)
208
+ } else {
209
+ this._body = input._body
210
+ }
211
+ this.redirect = init.redirect || input.redirect
212
+ this.timeout = init.timeout || input.timeout
213
+ this.maxRedirects = init.maxRedirects || input.maxRedirects
214
+ } else {
215
+ this.url = input
216
+ this.method = init.method || 'GET'
217
+ this.headers = new HeadersImpl(init.headers)
218
+ this._body = initBody !== null && initBody !== undefined ? _toReadableStream(initBody) : null
219
+ this.redirect = init.redirect || 'follow'
220
+ this.timeout = init.timeout || 30000
221
+ this.maxRedirects = init.maxRedirects || 5
222
+ }
223
+ }
224
+
225
+ async arrayBuffer(): Promise<ArrayBuffer> {
226
+ if (!this.body) return new ArrayBuffer(0)
227
+ if (this._bodyConsumed) throw new TypeError('Body already used')
228
+ this._bodyConsumed = true
229
+ const bytes = await _readStream(this.body)
230
+ return bytes.buffer
231
+ }
232
+
233
+ async json<T = unknown>(): Promise<T> {
234
+ return JSON.parse(await this.text())
235
+ }
236
+
237
+ async text(): Promise<string> {
238
+ if (!this.body) return ''
239
+ if (this._bodyConsumed) throw new TypeError('Body already used')
240
+ this._bodyConsumed = true
241
+ const bytes = await _readStream(this.body)
242
+ return new TextDecoder('utf-8').decode(bytes)
243
+ }
244
+
245
+ clone(): RequestImpl {
246
+ if (this.bodyUsed) throw new TypeError('Body already used')
247
+ if (this._body) {
248
+ const [branch1, branch2] = this._body.tee()
249
+ this._body = branch1
250
+ const req = new RequestImpl(this.url, {
251
+ method: this.method,
252
+ headers: this.headers,
253
+ body: branch2,
254
+ redirect: this.redirect,
255
+ timeout: this.timeout,
256
+ maxRedirects: this.maxRedirects,
257
+ })
258
+ return req
259
+ }
260
+ return new RequestImpl(this.url, {
261
+ method: this.method,
262
+ headers: this.headers,
263
+ redirect: this.redirect,
264
+ timeout: this.timeout,
265
+ maxRedirects: this.maxRedirects,
266
+ })
267
+ }
268
+ }
269
+
270
+ // ── Response ──
271
+
272
+ export class ResponseImpl {
273
+ readonly status: number
274
+ readonly statusText: string
275
+ readonly ok: boolean
276
+
277
+ private _headers: HeadersImpl
278
+ _redirected: boolean = false
279
+ private _type: ResponseType = 'basic'
280
+ _url: string = ''
281
+ private _body: ReadableStream<Uint8Array>
282
+ private _bodyConsumed: boolean = false
283
+
284
+ get body(): ReadableStream<Uint8Array> { return this._body }
285
+ get bodyUsed(): boolean { return this._bodyConsumed || this._body.locked }
286
+ get headers(): HeadersImpl { return this._headers }
287
+ get redirected(): boolean { return this._redirected }
288
+ get type(): ResponseType { return this._type }
289
+ get url(): string { return this._url }
290
+
291
+ constructor(body?: BodyInit | null, init?: ResponseInit) {
292
+ const status = init?.status ?? 200
293
+ const statusText = init?.statusText ?? ''
294
+ const headers = new HeadersImpl(init?.headers)
295
+ this.status = status
296
+ this.statusText = statusText
297
+ this._headers = headers
298
+ this.ok = status >= 200 && status < 300
299
+ this._body = body != null ? _toReadableStream(body) : new ReadableStream<Uint8Array>({
300
+ start(ctrl) { ctrl.close() }
301
+ })
302
+ }
303
+
304
+ async text(): Promise<string> {
305
+ if (this.bodyUsed) throw new TypeError('Body already used')
306
+ this._bodyConsumed = true
307
+ const bytes = await _readStream(this._body)
308
+ return new TextDecoder('utf-8').decode(bytes)
309
+ }
310
+
311
+ async json<T = unknown>(): Promise<T> {
312
+ return JSON.parse(await this.text())
313
+ }
314
+
315
+ async arrayBuffer(): Promise<ArrayBuffer> {
316
+ if (this.bodyUsed) throw new TypeError('Body already used')
317
+ this._bodyConsumed = true
318
+ const bytes = await _readStream(this._body)
319
+ return bytes.buffer
320
+ }
321
+
322
+ async bytes(): Promise<Uint8Array> {
323
+ if (this.bodyUsed) throw new TypeError('Body already used')
324
+ this._bodyConsumed = true
325
+ return await _readStream(this._body)
326
+ }
327
+
328
+ clone(): Response {
329
+ if (this.bodyUsed) throw new TypeError('Body already used')
330
+ const [branch1, branch2] = this._body.tee()
331
+ this._body = branch1
332
+ return new ResponseImpl(branch2, { status: this.status, statusText: this.statusText, headers: this._headers })
333
+ }
334
+
335
+ _applyDecompressedBody(stream: ReadableStream<Uint8Array>, _body: ArrayBuffer, newHeaders: HeadersImpl): void {
336
+ this._bodyConsumed = false
337
+ this._body = stream
338
+ this._headers = newHeaders
339
+ }
340
+ }
341
+
342
+ // ── Global declarations ──
343
+
344
+ declare global {
345
+ type HeadersInit = [string, string][] | Record<string, string> | Headers
346
+ type BodyInit = ReadableStream<Uint8Array> | string | ArrayBuffer | ArrayBufferView
347
+ type RequestRedirect = 'error' | 'follow' | 'manual'
348
+ type RequestCache = 'default' | 'force-cache' | 'no-cache' | 'no-store' | 'only-if-cached' | 'reload'
349
+ type RequestCredentials = 'include' | 'omit' | 'same-origin'
350
+ type RequestMode = 'cors' | 'navigate' | 'no-cors' | 'same-origin'
351
+ type ResponseType = 'basic' | 'cors' | 'default' | 'error' | 'opaque' | 'opaqueredirect'
352
+
353
+ interface AbortSignal {
354
+ readonly aborted: boolean;
355
+ readonly reason: unknown;
356
+ onabort: ((event: Event) => void) | null;
357
+ throwIfAborted(): void;
358
+ }
359
+
360
+ interface RequestInit {
361
+ body?: BodyInit | null;
362
+ cache?: RequestCache;
363
+ credentials?: RequestCredentials;
364
+ headers?: HeadersInit;
365
+ integrity?: string;
366
+ keepalive?: boolean;
367
+ method?: string;
368
+ mode?: RequestMode;
369
+ redirect?: RequestRedirect;
370
+ referrer?: string;
371
+ referrerPolicy?: string;
372
+ signal?: AbortSignal | null;
373
+ window?: null;
374
+ timeout?: number;
375
+ maxRedirects?: number;
376
+ }
377
+
378
+ interface ResponseInit {
379
+ status?: number;
380
+ statusText?: string;
381
+ headers?: HeadersInit;
382
+ }
383
+
384
+ interface Headers extends HeadersImpl {}
385
+ var Headers: typeof HeadersImpl
386
+ interface Response extends Omit<ResponseImpl, '_url' | '_redirected' | '_applyDecompressedBody'> {}
387
+ interface ResponseConstructor {
388
+ new(body?: BodyInit | null, init?: ResponseInit): Response
389
+ }
390
+ var Response: ResponseConstructor
391
+ interface Request extends RequestImpl {}
392
+ var Request: typeof RequestImpl
393
+ }
394
+
395
+ // ── Register globals ──
396
+
397
+ globalThis.Headers = HeadersImpl
398
+ globalThis.Request = RequestImpl
399
+ globalThis.Response = ResponseImpl