@ricsam/quickjs-fetch 0.2.4 → 0.2.5
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/dist/cjs/globals/form-data.cjs +114 -8
- package/dist/cjs/globals/form-data.cjs.map +3 -3
- package/dist/cjs/globals/request.cjs +51 -6
- package/dist/cjs/globals/request.cjs.map +3 -3
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/setup.cjs +3 -1
- package/dist/cjs/setup.cjs.map +3 -3
- package/dist/mjs/globals/form-data.mjs +115 -9
- package/dist/mjs/globals/form-data.mjs.map +3 -3
- package/dist/mjs/globals/request.mjs +51 -6
- package/dist/mjs/globals/request.mjs.map +3 -3
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/setup.mjs +5 -3
- package/dist/mjs/setup.mjs.map +3 -3
- package/dist/types/globals/form-data.d.ts +14 -0
- package/dist/types/globals/request.d.ts +9 -0
- package/dist/types/types.d.ts +1 -0
- package/package.json +2 -2
|
@@ -33,12 +33,15 @@ __export(exports_form_data, {
|
|
|
33
33
|
serializeFormData: () => serializeFormData,
|
|
34
34
|
parseUrlEncodedFormData: () => parseUrlEncodedFormData,
|
|
35
35
|
parseMultipartFormData: () => parseMultipartFormData,
|
|
36
|
-
createFormDataClass: () => createFormDataClass
|
|
36
|
+
createFormDataClass: () => createFormDataClass,
|
|
37
|
+
addFormDataFileMethods: () => addFormDataFileMethods,
|
|
38
|
+
FORMDATA_FILE_MARKER: () => FORMDATA_FILE_MARKER
|
|
37
39
|
});
|
|
38
40
|
module.exports = __toCommonJS(exports_form_data);
|
|
39
41
|
var import_quickjs_core = require("@ricsam/quickjs-core");
|
|
42
|
+
var FORMDATA_FILE_MARKER = "__formDataFile__";
|
|
40
43
|
function isFileValue(value) {
|
|
41
|
-
return value !== null && typeof value === "object" &&
|
|
44
|
+
return value !== null && typeof value === "object" && FORMDATA_FILE_MARKER in value && value[FORMDATA_FILE_MARKER] === true;
|
|
42
45
|
}
|
|
43
46
|
function createFormDataClass(context, stateMap) {
|
|
44
47
|
return import_quickjs_core.defineClass(context, stateMap, {
|
|
@@ -49,21 +52,54 @@ function createFormDataClass(context, stateMap) {
|
|
|
49
52
|
methods: {
|
|
50
53
|
append(name, value, filename) {
|
|
51
54
|
const nameStr = String(name);
|
|
55
|
+
if (import_quickjs_core.isInstanceOf(value, "File")) {
|
|
56
|
+
const fileState = import_quickjs_core.getClassInstanceState(value);
|
|
57
|
+
if (fileState) {
|
|
58
|
+
const data = concatenateParts(fileState.parts);
|
|
59
|
+
const fileValue = {
|
|
60
|
+
[FORMDATA_FILE_MARKER]: true,
|
|
61
|
+
data,
|
|
62
|
+
filename: filename !== undefined ? String(filename) : fileState.name,
|
|
63
|
+
type: fileState.type || "application/octet-stream"
|
|
64
|
+
};
|
|
65
|
+
this.entries.push({ name: nameStr, value: fileValue });
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (import_quickjs_core.isInstanceOf(value, "Blob")) {
|
|
70
|
+
const blobState = import_quickjs_core.getClassInstanceState(value);
|
|
71
|
+
if (blobState) {
|
|
72
|
+
const data = concatenateParts(blobState.parts);
|
|
73
|
+
const fileValue = {
|
|
74
|
+
[FORMDATA_FILE_MARKER]: true,
|
|
75
|
+
data,
|
|
76
|
+
filename: filename !== undefined ? String(filename) : "blob",
|
|
77
|
+
type: blobState.type || "application/octet-stream"
|
|
78
|
+
};
|
|
79
|
+
this.entries.push({ name: nameStr, value: fileValue });
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
52
83
|
if (value && typeof value === "object" && "parts" in value) {
|
|
53
84
|
const blobLike = value;
|
|
54
85
|
const data = concatenateParts(blobLike.parts);
|
|
55
86
|
const fileValue = {
|
|
87
|
+
[FORMDATA_FILE_MARKER]: true,
|
|
56
88
|
data,
|
|
57
89
|
filename: filename !== undefined ? String(filename) : blobLike.name || "blob",
|
|
58
90
|
type: blobLike.type || "application/octet-stream"
|
|
59
91
|
};
|
|
60
92
|
this.entries.push({ name: nameStr, value: fileValue });
|
|
61
93
|
} else if (value && typeof value === "object" && "data" in value && "filename" in value) {
|
|
94
|
+
const fileVal = value;
|
|
95
|
+
const data = Array.isArray(fileVal.data) ? new Uint8Array(fileVal.data) : fileVal.data;
|
|
62
96
|
this.entries.push({
|
|
63
97
|
name: nameStr,
|
|
64
98
|
value: {
|
|
65
|
-
|
|
66
|
-
|
|
99
|
+
[FORMDATA_FILE_MARKER]: true,
|
|
100
|
+
data,
|
|
101
|
+
filename: filename !== undefined ? String(filename) : fileVal.filename,
|
|
102
|
+
type: fileVal.type
|
|
67
103
|
}
|
|
68
104
|
});
|
|
69
105
|
} else {
|
|
@@ -90,21 +126,54 @@ function createFormDataClass(context, stateMap) {
|
|
|
90
126
|
set(name, value, filename) {
|
|
91
127
|
const nameStr = String(name);
|
|
92
128
|
this.entries = this.entries.filter((e) => e.name !== nameStr);
|
|
129
|
+
if (import_quickjs_core.isInstanceOf(value, "File")) {
|
|
130
|
+
const fileState = import_quickjs_core.getClassInstanceState(value);
|
|
131
|
+
if (fileState) {
|
|
132
|
+
const data = concatenateParts(fileState.parts);
|
|
133
|
+
const fileValue = {
|
|
134
|
+
[FORMDATA_FILE_MARKER]: true,
|
|
135
|
+
data,
|
|
136
|
+
filename: filename !== undefined ? String(filename) : fileState.name,
|
|
137
|
+
type: fileState.type || "application/octet-stream"
|
|
138
|
+
};
|
|
139
|
+
this.entries.push({ name: nameStr, value: fileValue });
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (import_quickjs_core.isInstanceOf(value, "Blob")) {
|
|
144
|
+
const blobState = import_quickjs_core.getClassInstanceState(value);
|
|
145
|
+
if (blobState) {
|
|
146
|
+
const data = concatenateParts(blobState.parts);
|
|
147
|
+
const fileValue = {
|
|
148
|
+
[FORMDATA_FILE_MARKER]: true,
|
|
149
|
+
data,
|
|
150
|
+
filename: filename !== undefined ? String(filename) : "blob",
|
|
151
|
+
type: blobState.type || "application/octet-stream"
|
|
152
|
+
};
|
|
153
|
+
this.entries.push({ name: nameStr, value: fileValue });
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
93
157
|
if (value && typeof value === "object" && "parts" in value) {
|
|
94
158
|
const blobLike = value;
|
|
95
159
|
const data = concatenateParts(blobLike.parts);
|
|
96
160
|
const fileValue = {
|
|
161
|
+
[FORMDATA_FILE_MARKER]: true,
|
|
97
162
|
data,
|
|
98
163
|
filename: filename !== undefined ? String(filename) : blobLike.name || "blob",
|
|
99
164
|
type: blobLike.type || "application/octet-stream"
|
|
100
165
|
};
|
|
101
166
|
this.entries.push({ name: nameStr, value: fileValue });
|
|
102
167
|
} else if (value && typeof value === "object" && "data" in value && "filename" in value) {
|
|
168
|
+
const fileVal = value;
|
|
169
|
+
const data = Array.isArray(fileVal.data) ? new Uint8Array(fileVal.data) : fileVal.data;
|
|
103
170
|
this.entries.push({
|
|
104
171
|
name: nameStr,
|
|
105
172
|
value: {
|
|
106
|
-
|
|
107
|
-
|
|
173
|
+
[FORMDATA_FILE_MARKER]: true,
|
|
174
|
+
data,
|
|
175
|
+
filename: filename !== undefined ? String(filename) : fileVal.filename,
|
|
176
|
+
type: fileVal.type
|
|
108
177
|
}
|
|
109
178
|
});
|
|
110
179
|
} else {
|
|
@@ -198,7 +267,7 @@ function parseMultipartFormData(body, contentType) {
|
|
|
198
267
|
const type = headers["content-type"] || "application/octet-stream";
|
|
199
268
|
entries.push({
|
|
200
269
|
name,
|
|
201
|
-
value: { data: content, filename, type }
|
|
270
|
+
value: { [FORMDATA_FILE_MARKER]: true, data: content, filename, type }
|
|
202
271
|
});
|
|
203
272
|
} else {
|
|
204
273
|
entries.push({
|
|
@@ -284,6 +353,43 @@ function serializeFormData(state) {
|
|
|
284
353
|
contentType: `multipart/form-data; boundary=${boundary}`
|
|
285
354
|
};
|
|
286
355
|
}
|
|
356
|
+
function addFormDataFileMethods(context) {
|
|
357
|
+
const result = context.evalCode(`
|
|
358
|
+
(function() {
|
|
359
|
+
const FILE_MARKER = "${FORMDATA_FILE_MARKER}";
|
|
360
|
+
|
|
361
|
+
function toFile(value) {
|
|
362
|
+
if (!value || typeof value !== "object") return value;
|
|
363
|
+
if (value instanceof File) return value;
|
|
364
|
+
if (value[FILE_MARKER] === true) {
|
|
365
|
+
// Reconstruct File from stored data
|
|
366
|
+
const data = Array.isArray(value.data)
|
|
367
|
+
? new Uint8Array(value.data)
|
|
368
|
+
: value.data;
|
|
369
|
+
return new File([data], value.filename, { type: value.type });
|
|
370
|
+
}
|
|
371
|
+
return value;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const originalGet = FormData.prototype.get;
|
|
375
|
+
FormData.prototype.get = function(name) {
|
|
376
|
+
return toFile(originalGet.call(this, name));
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
const originalGetAll = FormData.prototype.getAll;
|
|
380
|
+
FormData.prototype.getAll = function(name) {
|
|
381
|
+
return originalGetAll.call(this, name).map(toFile);
|
|
382
|
+
};
|
|
383
|
+
})();
|
|
384
|
+
`);
|
|
385
|
+
if (result.error) {
|
|
386
|
+
const errorMsg = context.dump(result.error);
|
|
387
|
+
result.error.dispose();
|
|
388
|
+
throw new Error(`Failed to add FormData file methods: ${JSON.stringify(errorMsg)}`);
|
|
389
|
+
} else {
|
|
390
|
+
result.value.dispose();
|
|
391
|
+
}
|
|
392
|
+
}
|
|
287
393
|
})
|
|
288
394
|
|
|
289
|
-
//# debugId=
|
|
395
|
+
//# debugId=E3D4034C748F5E3564756E2164756E21
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/globals/form-data.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import type { QuickJSContext, QuickJSHandle } from \"quickjs-emscripten\";\nimport type { StateMap } from \"@ricsam/quickjs-core\";\nimport { defineClass } from \"@ricsam/quickjs-core\";\n\n/**\n * Internal state for FormData entries\n * Each entry can have multiple values (same key appended multiple times)\n */\nexport interface FormDataEntry {\n name: string;\n value: string | FormDataFileValue;\n}\n\nexport interface FormDataFileValue {\n data: Uint8Array;\n filename: string;\n type: string;\n}\n\nexport interface FormDataState {\n entries: FormDataEntry[];\n}\n\nfunction isFileValue(value: unknown): value is FormDataFileValue {\n return (\n value !== null &&\n typeof value === \"object\" &&\n \"data\" in value &&\n \"filename\" in value\n );\n}\n\n/**\n * Create the FormData class for QuickJS\n */\nexport function createFormDataClass(\n context: QuickJSContext,\n stateMap: StateMap\n): QuickJSHandle {\n return defineClass<FormDataState>(context, stateMap, {\n name: \"FormData\",\n construct: () => {\n return { entries: [] };\n },\n methods: {\n append(this: FormDataState, name: unknown, value: unknown, filename?: unknown) {\n const nameStr = String(name);\n\n if (value && typeof value === \"object\" && \"parts\" in value) {\n // Blob-like value\n const blobLike = value as { parts: Uint8Array[]; type?: string; name?: string };\n const data = concatenateParts(blobLike.parts);\n const fileValue: FormDataFileValue = {\n data,\n filename: filename !== undefined ? String(filename) : (blobLike.name || \"blob\"),\n type: blobLike.type || \"application/octet-stream\",\n };\n this.entries.push({ name: nameStr, value: fileValue });\n } else if (value && typeof value === \"object\" && \"data\" in value && \"filename\" in value) {\n // Already a FormDataFileValue\n this.entries.push({\n name: nameStr,\n value: {\n ...(value as FormDataFileValue),\n filename: filename !== undefined ? String(filename) : (value as FormDataFileValue).filename,\n },\n });\n } else {\n // String value\n this.entries.push({ name: nameStr, value: String(value) });\n }\n },\n delete(this: FormDataState, name: unknown) {\n const nameStr = String(name);\n this.entries = this.entries.filter((e) => e.name !== nameStr);\n },\n get(this: FormDataState, name: unknown): string | FormDataFileValue | null {\n const nameStr = String(name);\n const entry = this.entries.find((e) => e.name === nameStr);\n return entry ? entry.value : null;\n },\n getAll(this: FormDataState, name: unknown): Array<string | FormDataFileValue> {\n const nameStr = String(name);\n return this.entries\n .filter((e) => e.name === nameStr)\n .map((e) => e.value);\n },\n has(this: FormDataState, name: unknown): boolean {\n const nameStr = String(name);\n return this.entries.some((e) => e.name === nameStr);\n },\n set(this: FormDataState, name: unknown, value: unknown, filename?: unknown) {\n const nameStr = String(name);\n // Remove all existing entries with this name\n this.entries = this.entries.filter((e) => e.name !== nameStr);\n\n // Add the new entry (using append logic)\n if (value && typeof value === \"object\" && \"parts\" in value) {\n const blobLike = value as { parts: Uint8Array[]; type?: string; name?: string };\n const data = concatenateParts(blobLike.parts);\n const fileValue: FormDataFileValue = {\n data,\n filename: filename !== undefined ? String(filename) : (blobLike.name || \"blob\"),\n type: blobLike.type || \"application/octet-stream\",\n };\n this.entries.push({ name: nameStr, value: fileValue });\n } else if (value && typeof value === \"object\" && \"data\" in value && \"filename\" in value) {\n this.entries.push({\n name: nameStr,\n value: {\n ...(value as FormDataFileValue),\n filename: filename !== undefined ? String(filename) : (value as FormDataFileValue).filename,\n },\n });\n } else {\n this.entries.push({ name: nameStr, value: String(value) });\n }\n },\n entries(this: FormDataState): Array<[string, string | FormDataFileValue]> {\n return this.entries.map((e) => [e.name, e.value]);\n },\n keys(this: FormDataState): string[] {\n // Return unique keys in order of first appearance\n const seen = new Set<string>();\n const result: string[] = [];\n for (const entry of this.entries) {\n if (!seen.has(entry.name)) {\n seen.add(entry.name);\n result.push(entry.name);\n }\n }\n return result;\n },\n values(this: FormDataState): Array<string | FormDataFileValue> {\n return this.entries.map((e) => e.value);\n },\n forEach(this: FormDataState, callback: unknown) {\n if (typeof callback !== \"function\") {\n throw new TypeError(\"callback must be a function\");\n }\n for (const entry of this.entries) {\n (callback as (value: string | FormDataFileValue, key: string, parent: FormDataState) => void)(\n entry.value,\n entry.name,\n this\n );\n }\n },\n },\n });\n}\n\n/**\n * Concatenate Uint8Arrays into a single Uint8Array\n */\nfunction concatenateParts(parts: Uint8Array[]): Uint8Array {\n const totalLength = parts.reduce((sum, part) => sum + part.length, 0);\n const result = new Uint8Array(totalLength);\n let offset = 0;\n for (const part of parts) {\n result.set(part, offset);\n offset += part.length;\n }\n return result;\n}\n\n/**\n * Parse multipart/form-data body\n */\nexport function parseMultipartFormData(\n body: Uint8Array,\n contentType: string\n): FormDataState {\n const entries: FormDataEntry[] = [];\n\n // Extract boundary from content-type\n const boundaryMatch = contentType.match(/boundary=([^;]+)/i);\n if (!boundaryMatch || !boundaryMatch[1]) {\n return { entries };\n }\n\n const boundary = boundaryMatch[1].replace(/^[\"']|[\"']$/g, \"\");\n const boundaryBytes = new TextEncoder().encode(`--${boundary}`);\n const endBoundaryBytes = new TextEncoder().encode(`--${boundary}--`);\n\n // Find all parts\n const decoder = new TextDecoder();\n let pos = 0;\n\n // Skip preamble and first boundary\n pos = findSequence(body, boundaryBytes, pos);\n if (pos === -1) return { entries };\n pos += boundaryBytes.length;\n\n while (pos < body.length) {\n // Skip CRLF after boundary\n if (body[pos] === 0x0d && body[pos + 1] === 0x0a) {\n pos += 2;\n } else if (body[pos] === 0x0a) {\n pos += 1;\n }\n\n // Check for end boundary\n if (pos + 2 <= body.length && body[pos] === 0x2d && body[pos + 1] === 0x2d) {\n break; // End of multipart\n }\n\n // Parse headers\n const headersEnd = findSequence(body, new Uint8Array([0x0d, 0x0a, 0x0d, 0x0a]), pos);\n if (headersEnd === -1) break;\n\n const headersText = decoder.decode(body.slice(pos, headersEnd));\n const headers = parseHeaders(headersText);\n pos = headersEnd + 4;\n\n // Find next boundary\n const nextBoundary = findSequence(body, boundaryBytes, pos);\n if (nextBoundary === -1) break;\n\n // Content is between current position and next boundary (minus CRLF)\n let contentEnd = nextBoundary;\n if (contentEnd > 0 && body[contentEnd - 1] === 0x0a) contentEnd--;\n if (contentEnd > 0 && body[contentEnd - 1] === 0x0d) contentEnd--;\n\n const content = body.slice(pos, contentEnd);\n\n // Parse Content-Disposition header\n const disposition = headers[\"content-disposition\"] || \"\";\n const nameMatch = disposition.match(/name=\"([^\"]+)\"/);\n const filenameMatch = disposition.match(/filename=\"([^\"]+)\"/);\n\n if (nameMatch && nameMatch[1]) {\n const name = nameMatch[1];\n if (filenameMatch && filenameMatch[1]) {\n // File entry\n const filename = filenameMatch[1];\n const type = headers[\"content-type\"] || \"application/octet-stream\";\n entries.push({\n name,\n value: { data: content, filename, type },\n });\n } else {\n // String entry\n entries.push({\n name,\n value: decoder.decode(content),\n });\n }\n }\n\n pos = nextBoundary + boundaryBytes.length;\n }\n\n return { entries };\n}\n\n/**\n * Parse URL-encoded form data\n */\nexport function parseUrlEncodedFormData(body: Uint8Array): FormDataState {\n const text = new TextDecoder().decode(body);\n const entries: FormDataEntry[] = [];\n\n const params = new URLSearchParams(text);\n for (const [name, value] of params) {\n entries.push({ name, value });\n }\n\n return { entries };\n}\n\nfunction findSequence(haystack: Uint8Array, needle: Uint8Array, start: number): number {\n outer: for (let i = start; i <= haystack.length - needle.length; i++) {\n for (let j = 0; j < needle.length; j++) {\n if (haystack[i + j] !== needle[j]) continue outer;\n }\n return i;\n }\n return -1;\n}\n\nfunction parseHeaders(text: string): Record<string, string> {\n const headers: Record<string, string> = {};\n const lines = text.split(/\\r?\\n/);\n for (const line of lines) {\n const colonIndex = line.indexOf(\":\");\n if (colonIndex > 0) {\n const name = line.slice(0, colonIndex).trim().toLowerCase();\n const value = line.slice(colonIndex + 1).trim();\n headers[name] = value;\n }\n }\n return headers;\n}\n\n/**\n * Serialize FormData to multipart/form-data format\n */\nexport function serializeFormData(state: FormDataState): { body: Uint8Array; contentType: string } {\n const boundary = `----FormDataBoundary${Math.random().toString(36).slice(2)}`;\n const encoder = new TextEncoder();\n const parts: Uint8Array[] = [];\n\n for (const entry of state.entries) {\n const headerLines: string[] = [];\n headerLines.push(`--${boundary}`);\n\n if (isFileValue(entry.value)) {\n headerLines.push(\n `Content-Disposition: form-data; name=\"${entry.name}\"; filename=\"${entry.value.filename}\"`\n );\n headerLines.push(`Content-Type: ${entry.value.type}`);\n headerLines.push(\"\");\n\n parts.push(encoder.encode(headerLines.join(\"\\r\\n\") + \"\\r\\n\"));\n parts.push(entry.value.data);\n parts.push(encoder.encode(\"\\r\\n\"));\n } else {\n headerLines.push(`Content-Disposition: form-data; name=\"${entry.name}\"`);\n headerLines.push(\"\");\n headerLines.push(entry.value);\n\n parts.push(encoder.encode(headerLines.join(\"\\r\\n\") + \"\\r\\n\"));\n }\n }\n\n parts.push(encoder.encode(`--${boundary}--\\r\\n`));\n\n // Concatenate all parts\n const totalLength = parts.reduce((sum, part) => sum + part.length, 0);\n const body = new Uint8Array(totalLength);\n let offset = 0;\n for (const part of parts) {\n body.set(part, offset);\n offset += part.length;\n }\n\n return {\n body,\n contentType: `multipart/form-data; boundary=${boundary}`,\n };\n}\n"
|
|
5
|
+
"import type { QuickJSContext, QuickJSHandle } from \"quickjs-emscripten\";\nimport type { StateMap } from \"@ricsam/quickjs-core\";\nimport { defineClass, isInstanceOf, getClassInstanceState } from \"@ricsam/quickjs-core\";\n\n/**\n * Marker to identify FormData file entries that need File reconstruction\n */\nexport const FORMDATA_FILE_MARKER = \"__formDataFile__\";\n\n/**\n * Internal state for Blob/File classes (from packages/core)\n */\ninterface BlobInternalState {\n parts: Uint8Array[];\n type: string;\n size: number;\n}\n\ninterface FileInternalState extends BlobInternalState {\n name: string;\n lastModified: number;\n webkitRelativePath: string;\n}\n\n/**\n * Internal state for FormData entries\n * Each entry can have multiple values (same key appended multiple times)\n */\nexport interface FormDataEntry {\n name: string;\n value: string | FormDataFileValue;\n}\n\nexport interface FormDataFileValue {\n [FORMDATA_FILE_MARKER]: true;\n data: Uint8Array;\n filename: string;\n type: string;\n}\n\nexport interface FormDataState {\n entries: FormDataEntry[];\n}\n\nfunction isFileValue(value: unknown): value is FormDataFileValue {\n return (\n value !== null &&\n typeof value === \"object\" &&\n FORMDATA_FILE_MARKER in value &&\n (value as FormDataFileValue)[FORMDATA_FILE_MARKER] === true\n );\n}\n\n/**\n * Create the FormData class for QuickJS\n */\nexport function createFormDataClass(\n context: QuickJSContext,\n stateMap: StateMap\n): QuickJSHandle {\n return defineClass<FormDataState>(context, stateMap, {\n name: \"FormData\",\n construct: () => {\n return { entries: [] };\n },\n methods: {\n append(this: FormDataState, name: unknown, value: unknown, filename?: unknown) {\n const nameStr = String(name);\n\n // Check for File instance using class identity (preferred method)\n if (isInstanceOf(value, \"File\")) {\n const fileState = getClassInstanceState<FileInternalState>(value);\n if (fileState) {\n const data = concatenateParts(fileState.parts);\n const fileValue: FormDataFileValue = {\n [FORMDATA_FILE_MARKER]: true,\n data,\n filename: filename !== undefined ? String(filename) : fileState.name,\n type: fileState.type || \"application/octet-stream\",\n };\n this.entries.push({ name: nameStr, value: fileValue });\n return;\n }\n }\n\n // Check for Blob instance using class identity\n if (isInstanceOf(value, \"Blob\")) {\n const blobState = getClassInstanceState<BlobInternalState>(value);\n if (blobState) {\n const data = concatenateParts(blobState.parts);\n const fileValue: FormDataFileValue = {\n [FORMDATA_FILE_MARKER]: true,\n data,\n filename: filename !== undefined ? String(filename) : \"blob\",\n type: blobState.type || \"application/octet-stream\",\n };\n this.entries.push({ name: nameStr, value: fileValue });\n return;\n }\n }\n\n // Fallback: duck-typing for Blob-like objects (backwards compatibility)\n if (value && typeof value === \"object\" && \"parts\" in value) {\n const blobLike = value as { parts: Uint8Array[]; type?: string; name?: string };\n const data = concatenateParts(blobLike.parts);\n const fileValue: FormDataFileValue = {\n [FORMDATA_FILE_MARKER]: true,\n data,\n filename: filename !== undefined ? String(filename) : (blobLike.name || \"blob\"),\n type: blobLike.type || \"application/octet-stream\",\n };\n this.entries.push({ name: nameStr, value: fileValue });\n } else if (value && typeof value === \"object\" && \"data\" in value && \"filename\" in value) {\n // Already a FormDataFileValue (or similar structure with data, filename, type)\n const fileVal = value as { data: Uint8Array | number[]; filename: string; type: string };\n // Handle both Uint8Array and number array (for safe cross-context marshalling)\n const data = Array.isArray(fileVal.data)\n ? new Uint8Array(fileVal.data)\n : fileVal.data;\n this.entries.push({\n name: nameStr,\n value: {\n [FORMDATA_FILE_MARKER]: true,\n data,\n filename: filename !== undefined ? String(filename) : fileVal.filename,\n type: fileVal.type,\n },\n });\n } else {\n // String value\n this.entries.push({ name: nameStr, value: String(value) });\n }\n },\n delete(this: FormDataState, name: unknown) {\n const nameStr = String(name);\n this.entries = this.entries.filter((e) => e.name !== nameStr);\n },\n get(this: FormDataState, name: unknown): string | FormDataFileValue | null {\n const nameStr = String(name);\n const entry = this.entries.find((e) => e.name === nameStr);\n return entry ? entry.value : null;\n },\n getAll(this: FormDataState, name: unknown): Array<string | FormDataFileValue> {\n const nameStr = String(name);\n return this.entries\n .filter((e) => e.name === nameStr)\n .map((e) => e.value);\n },\n has(this: FormDataState, name: unknown): boolean {\n const nameStr = String(name);\n return this.entries.some((e) => e.name === nameStr);\n },\n set(this: FormDataState, name: unknown, value: unknown, filename?: unknown) {\n const nameStr = String(name);\n // Remove all existing entries with this name\n this.entries = this.entries.filter((e) => e.name !== nameStr);\n\n // Check for File instance using class identity (preferred method)\n if (isInstanceOf(value, \"File\")) {\n const fileState = getClassInstanceState<FileInternalState>(value);\n if (fileState) {\n const data = concatenateParts(fileState.parts);\n const fileValue: FormDataFileValue = {\n [FORMDATA_FILE_MARKER]: true,\n data,\n filename: filename !== undefined ? String(filename) : fileState.name,\n type: fileState.type || \"application/octet-stream\",\n };\n this.entries.push({ name: nameStr, value: fileValue });\n return;\n }\n }\n\n // Check for Blob instance using class identity\n if (isInstanceOf(value, \"Blob\")) {\n const blobState = getClassInstanceState<BlobInternalState>(value);\n if (blobState) {\n const data = concatenateParts(blobState.parts);\n const fileValue: FormDataFileValue = {\n [FORMDATA_FILE_MARKER]: true,\n data,\n filename: filename !== undefined ? String(filename) : \"blob\",\n type: blobState.type || \"application/octet-stream\",\n };\n this.entries.push({ name: nameStr, value: fileValue });\n return;\n }\n }\n\n // Fallback: duck-typing for Blob-like objects (backwards compatibility)\n if (value && typeof value === \"object\" && \"parts\" in value) {\n const blobLike = value as { parts: Uint8Array[]; type?: string; name?: string };\n const data = concatenateParts(blobLike.parts);\n const fileValue: FormDataFileValue = {\n [FORMDATA_FILE_MARKER]: true,\n data,\n filename: filename !== undefined ? String(filename) : (blobLike.name || \"blob\"),\n type: blobLike.type || \"application/octet-stream\",\n };\n this.entries.push({ name: nameStr, value: fileValue });\n } else if (value && typeof value === \"object\" && \"data\" in value && \"filename\" in value) {\n // Already a FormDataFileValue (or similar structure with data, filename, type)\n const fileVal = value as { data: Uint8Array | number[]; filename: string; type: string };\n // Handle both Uint8Array and number array (for safe cross-context marshalling)\n const data = Array.isArray(fileVal.data)\n ? new Uint8Array(fileVal.data)\n : fileVal.data;\n this.entries.push({\n name: nameStr,\n value: {\n [FORMDATA_FILE_MARKER]: true,\n data,\n filename: filename !== undefined ? String(filename) : fileVal.filename,\n type: fileVal.type,\n },\n });\n } else {\n this.entries.push({ name: nameStr, value: String(value) });\n }\n },\n entries(this: FormDataState): Array<[string, string | FormDataFileValue]> {\n return this.entries.map((e) => [e.name, e.value]);\n },\n keys(this: FormDataState): string[] {\n // Return unique keys in order of first appearance\n const seen = new Set<string>();\n const result: string[] = [];\n for (const entry of this.entries) {\n if (!seen.has(entry.name)) {\n seen.add(entry.name);\n result.push(entry.name);\n }\n }\n return result;\n },\n values(this: FormDataState): Array<string | FormDataFileValue> {\n return this.entries.map((e) => e.value);\n },\n forEach(this: FormDataState, callback: unknown) {\n if (typeof callback !== \"function\") {\n throw new TypeError(\"callback must be a function\");\n }\n for (const entry of this.entries) {\n (callback as (value: string | FormDataFileValue, key: string, parent: FormDataState) => void)(\n entry.value,\n entry.name,\n this\n );\n }\n },\n },\n });\n}\n\n/**\n * Concatenate Uint8Arrays into a single Uint8Array\n */\nfunction concatenateParts(parts: Uint8Array[]): Uint8Array {\n const totalLength = parts.reduce((sum, part) => sum + part.length, 0);\n const result = new Uint8Array(totalLength);\n let offset = 0;\n for (const part of parts) {\n result.set(part, offset);\n offset += part.length;\n }\n return result;\n}\n\n/**\n * Parse multipart/form-data body\n */\nexport function parseMultipartFormData(\n body: Uint8Array,\n contentType: string\n): FormDataState {\n const entries: FormDataEntry[] = [];\n\n // Extract boundary from content-type\n const boundaryMatch = contentType.match(/boundary=([^;]+)/i);\n if (!boundaryMatch || !boundaryMatch[1]) {\n return { entries };\n }\n\n const boundary = boundaryMatch[1].replace(/^[\"']|[\"']$/g, \"\");\n const boundaryBytes = new TextEncoder().encode(`--${boundary}`);\n const endBoundaryBytes = new TextEncoder().encode(`--${boundary}--`);\n\n // Find all parts\n const decoder = new TextDecoder();\n let pos = 0;\n\n // Skip preamble and first boundary\n pos = findSequence(body, boundaryBytes, pos);\n if (pos === -1) return { entries };\n pos += boundaryBytes.length;\n\n while (pos < body.length) {\n // Skip CRLF after boundary\n if (body[pos] === 0x0d && body[pos + 1] === 0x0a) {\n pos += 2;\n } else if (body[pos] === 0x0a) {\n pos += 1;\n }\n\n // Check for end boundary\n if (pos + 2 <= body.length && body[pos] === 0x2d && body[pos + 1] === 0x2d) {\n break; // End of multipart\n }\n\n // Parse headers\n const headersEnd = findSequence(body, new Uint8Array([0x0d, 0x0a, 0x0d, 0x0a]), pos);\n if (headersEnd === -1) break;\n\n const headersText = decoder.decode(body.slice(pos, headersEnd));\n const headers = parseHeaders(headersText);\n pos = headersEnd + 4;\n\n // Find next boundary\n const nextBoundary = findSequence(body, boundaryBytes, pos);\n if (nextBoundary === -1) break;\n\n // Content is between current position and next boundary (minus CRLF)\n let contentEnd = nextBoundary;\n if (contentEnd > 0 && body[contentEnd - 1] === 0x0a) contentEnd--;\n if (contentEnd > 0 && body[contentEnd - 1] === 0x0d) contentEnd--;\n\n const content = body.slice(pos, contentEnd);\n\n // Parse Content-Disposition header\n const disposition = headers[\"content-disposition\"] || \"\";\n const nameMatch = disposition.match(/name=\"([^\"]+)\"/);\n const filenameMatch = disposition.match(/filename=\"([^\"]+)\"/);\n\n if (nameMatch && nameMatch[1]) {\n const name = nameMatch[1];\n if (filenameMatch && filenameMatch[1]) {\n // File entry\n const filename = filenameMatch[1];\n const type = headers[\"content-type\"] || \"application/octet-stream\";\n entries.push({\n name,\n value: { [FORMDATA_FILE_MARKER]: true, data: content, filename, type },\n });\n } else {\n // String entry\n entries.push({\n name,\n value: decoder.decode(content),\n });\n }\n }\n\n pos = nextBoundary + boundaryBytes.length;\n }\n\n return { entries };\n}\n\n/**\n * Parse URL-encoded form data\n */\nexport function parseUrlEncodedFormData(body: Uint8Array): FormDataState {\n const text = new TextDecoder().decode(body);\n const entries: FormDataEntry[] = [];\n\n const params = new URLSearchParams(text);\n for (const [name, value] of params) {\n entries.push({ name, value });\n }\n\n return { entries };\n}\n\nfunction findSequence(haystack: Uint8Array, needle: Uint8Array, start: number): number {\n outer: for (let i = start; i <= haystack.length - needle.length; i++) {\n for (let j = 0; j < needle.length; j++) {\n if (haystack[i + j] !== needle[j]) continue outer;\n }\n return i;\n }\n return -1;\n}\n\nfunction parseHeaders(text: string): Record<string, string> {\n const headers: Record<string, string> = {};\n const lines = text.split(/\\r?\\n/);\n for (const line of lines) {\n const colonIndex = line.indexOf(\":\");\n if (colonIndex > 0) {\n const name = line.slice(0, colonIndex).trim().toLowerCase();\n const value = line.slice(colonIndex + 1).trim();\n headers[name] = value;\n }\n }\n return headers;\n}\n\n/**\n * Serialize FormData to multipart/form-data format\n */\nexport function serializeFormData(state: FormDataState): { body: Uint8Array; contentType: string } {\n const boundary = `----FormDataBoundary${Math.random().toString(36).slice(2)}`;\n const encoder = new TextEncoder();\n const parts: Uint8Array[] = [];\n\n for (const entry of state.entries) {\n const headerLines: string[] = [];\n headerLines.push(`--${boundary}`);\n\n if (isFileValue(entry.value)) {\n headerLines.push(\n `Content-Disposition: form-data; name=\"${entry.name}\"; filename=\"${entry.value.filename}\"`\n );\n headerLines.push(`Content-Type: ${entry.value.type}`);\n headerLines.push(\"\");\n\n parts.push(encoder.encode(headerLines.join(\"\\r\\n\") + \"\\r\\n\"));\n parts.push(entry.value.data);\n parts.push(encoder.encode(\"\\r\\n\"));\n } else {\n headerLines.push(`Content-Disposition: form-data; name=\"${entry.name}\"`);\n headerLines.push(\"\");\n headerLines.push(entry.value);\n\n parts.push(encoder.encode(headerLines.join(\"\\r\\n\") + \"\\r\\n\"));\n }\n }\n\n parts.push(encoder.encode(`--${boundary}--\\r\\n`));\n\n // Concatenate all parts\n const totalLength = parts.reduce((sum, part) => sum + part.length, 0);\n const body = new Uint8Array(totalLength);\n let offset = 0;\n for (const part of parts) {\n body.set(part, offset);\n offset += part.length;\n }\n\n return {\n body,\n contentType: `multipart/form-data; boundary=${boundary}`,\n };\n}\n\n/**\n * Add JavaScript overrides for FormData.get() and FormData.getAll() to reconstruct File instances\n *\n * This is needed because defineClass methods can only return plain objects when marshalling,\n * but we want formData.get(\"file\") to return actual File instances with working methods.\n *\n * @param context The QuickJS context (must have FormData and File classes already defined)\n */\nexport function addFormDataFileMethods(context: QuickJSContext): void {\n const result = context.evalCode(`\n (function() {\n const FILE_MARKER = \"${FORMDATA_FILE_MARKER}\";\n\n function toFile(value) {\n if (!value || typeof value !== \"object\") return value;\n if (value instanceof File) return value;\n if (value[FILE_MARKER] === true) {\n // Reconstruct File from stored data\n const data = Array.isArray(value.data)\n ? new Uint8Array(value.data)\n : value.data;\n return new File([data], value.filename, { type: value.type });\n }\n return value;\n }\n\n const originalGet = FormData.prototype.get;\n FormData.prototype.get = function(name) {\n return toFile(originalGet.call(this, name));\n };\n\n const originalGetAll = FormData.prototype.getAll;\n FormData.prototype.getAll = function(name) {\n return originalGetAll.call(this, name).map(toFile);\n };\n })();\n `);\n if (result.error) {\n const errorMsg = context.dump(result.error);\n result.error.dispose();\n throw new Error(`Failed to add FormData file methods: ${JSON.stringify(errorMsg)}`);\n } else {\n result.value.dispose();\n }\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "E3D4034C748F5E3564756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -31,7 +31,8 @@ var __export = (target, all) => {
|
|
|
31
31
|
var exports_request = {};
|
|
32
32
|
__export(exports_request, {
|
|
33
33
|
createRequestStateFromNative: () => createRequestStateFromNative,
|
|
34
|
-
createRequestClass: () => createRequestClass
|
|
34
|
+
createRequestClass: () => createRequestClass,
|
|
35
|
+
addRequestFormDataMethod: () => addRequestFormDataMethod
|
|
35
36
|
});
|
|
36
37
|
module.exports = __toCommonJS(exports_request);
|
|
37
38
|
var import_quickjs_core = require("@ricsam/quickjs-core");
|
|
@@ -271,7 +272,7 @@ function createRequestClass(context, stateMap, createStream) {
|
|
|
271
272
|
}
|
|
272
273
|
return new TextDecoder().decode(this.body);
|
|
273
274
|
},
|
|
274
|
-
async
|
|
275
|
+
async __getFormDataEntries__() {
|
|
275
276
|
if (this.bodyUsed) {
|
|
276
277
|
throw new TypeError("Body has already been consumed");
|
|
277
278
|
}
|
|
@@ -280,16 +281,60 @@ function createRequestClass(context, stateMap, createStream) {
|
|
|
280
281
|
return { entries: [] };
|
|
281
282
|
}
|
|
282
283
|
const contentType = this.headersState.headers.get("content-type")?.[0] || "";
|
|
284
|
+
let formDataState;
|
|
283
285
|
if (contentType.includes("multipart/form-data")) {
|
|
284
|
-
|
|
286
|
+
formDataState = import_form_data.parseMultipartFormData(this.body, contentType);
|
|
285
287
|
} else if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
286
|
-
|
|
288
|
+
formDataState = import_form_data.parseUrlEncodedFormData(this.body);
|
|
289
|
+
} else {
|
|
290
|
+
throw new TypeError("Could not parse content as FormData");
|
|
287
291
|
}
|
|
288
|
-
|
|
292
|
+
return {
|
|
293
|
+
entries: formDataState.entries.map((entry) => {
|
|
294
|
+
if (typeof entry.value === "string") {
|
|
295
|
+
return { name: entry.name, value: entry.value };
|
|
296
|
+
}
|
|
297
|
+
return {
|
|
298
|
+
name: entry.name,
|
|
299
|
+
value: {
|
|
300
|
+
__formDataFile__: true,
|
|
301
|
+
data: Array.from(entry.value.data),
|
|
302
|
+
filename: entry.value.filename,
|
|
303
|
+
type: entry.value.type
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
})
|
|
307
|
+
};
|
|
289
308
|
}
|
|
290
309
|
}
|
|
291
310
|
});
|
|
292
311
|
}
|
|
312
|
+
function addRequestFormDataMethod(context) {
|
|
313
|
+
const result = context.evalCode(`
|
|
314
|
+
Request.prototype.formData = async function() {
|
|
315
|
+
// Get raw entries from private method
|
|
316
|
+
// Note: File data comes as plain number arrays (converted in host side)
|
|
317
|
+
const rawData = await this.__getFormDataEntries__();
|
|
318
|
+
|
|
319
|
+
// Create a proper FormData instance
|
|
320
|
+
const formData = new FormData();
|
|
321
|
+
|
|
322
|
+
// Populate with entries
|
|
323
|
+
// FormData.append handles both string values and file-like objects
|
|
324
|
+
// with number arrays (converted to Uint8Array on the host side)
|
|
325
|
+
for (const entry of rawData.entries) {
|
|
326
|
+
formData.append(entry.name, entry.value);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return formData;
|
|
330
|
+
};
|
|
331
|
+
`);
|
|
332
|
+
if (result.error) {
|
|
333
|
+
result.error.dispose();
|
|
334
|
+
} else {
|
|
335
|
+
result.value.dispose();
|
|
336
|
+
}
|
|
337
|
+
}
|
|
293
338
|
async function createRequestStateFromNative(request) {
|
|
294
339
|
const body = request.body ? new Uint8Array(await request.arrayBuffer()) : null;
|
|
295
340
|
return {
|
|
@@ -312,4 +357,4 @@ async function createRequestStateFromNative(request) {
|
|
|
312
357
|
}
|
|
313
358
|
})
|
|
314
359
|
|
|
315
|
-
//# debugId=
|
|
360
|
+
//# debugId=C39079D8C2092BD064756E2164756E21
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/globals/request.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import type { QuickJSContext, QuickJSHandle } from \"quickjs-emscripten\";\nimport type { StateMap } from \"@ricsam/quickjs-core\";\nimport { defineClass } from \"@ricsam/quickjs-core\";\nimport type { RequestState, HeadersState, AbortSignalState, FormDataState } from \"../types.cjs\";\nimport { createHeadersStateFromNative, createHeadersLike } from \"./headers.cjs\";\nimport { parseMultipartFormData, parseUrlEncodedFormData } from \"./form-data.cjs\";\n\n/**\n * Type for the stream factory function\n */\ntype StreamFactory = (source: UnderlyingSource) => QuickJSHandle;\n\n/**\n * Create the Request class for QuickJS\n */\nexport function createRequestClass(\n context: QuickJSContext,\n stateMap: StateMap,\n createStream?: StreamFactory\n): QuickJSHandle {\n return defineClass<RequestState>(context, stateMap, {\n name: \"Request\",\n construct: (args) => {\n const input = args[0];\n const init = args[1] as {\n method?: string;\n headers?: object;\n body?: unknown;\n cache?: string;\n credentials?: string;\n integrity?: string;\n keepalive?: boolean;\n mode?: string;\n redirect?: string;\n referrer?: string;\n referrerPolicy?: string;\n signal?: AbortSignalState;\n } | undefined;\n\n let url = \"\";\n let method = \"GET\";\n let headersState: HeadersState = { headers: new Map() };\n let body: Uint8Array | null = null;\n let signal: AbortSignalState | null = null;\n\n // Handle input\n if (typeof input === \"string\") {\n url = input;\n } else if (input && typeof input === \"object\") {\n // Could be URL or Request-like\n if (\"url\" in input) {\n url = String((input as { url: string }).url);\n }\n if (\"method\" in input) {\n method = String((input as { method: string }).method);\n }\n if (\"headersState\" in input) {\n const inputHeaders = (input as RequestState).headersState;\n headersState = {\n headers: new Map(inputHeaders.headers),\n };\n }\n if (\"body\" in input && (input as RequestState).body) {\n body = (input as RequestState).body;\n }\n if (\"signal\" in input) {\n signal = (input as RequestState).signal;\n }\n }\n\n // Apply init options\n if (init) {\n if (init.method) {\n method = init.method.toUpperCase();\n }\n if (init.headers) {\n if (init.headers && typeof init.headers === \"object\") {\n if (\"headers\" in init.headers && init.headers.headers instanceof Map) {\n headersState = {\n headers: new Map((init.headers as HeadersState).headers),\n };\n } else {\n headersState = { headers: new Map() };\n for (const [key, value] of Object.entries(init.headers)) {\n headersState.headers.set(key.toLowerCase(), [String(value)]);\n }\n }\n }\n }\n if (init.body !== undefined && init.body !== null) {\n if (typeof init.body === \"string\") {\n body = new TextEncoder().encode(init.body);\n } else if (init.body instanceof ArrayBuffer) {\n body = new Uint8Array(init.body);\n } else if (init.body instanceof Uint8Array) {\n body = init.body;\n }\n }\n if (init.signal) {\n signal = init.signal;\n }\n }\n\n return {\n method,\n url,\n headersState,\n body,\n bodyUsed: false,\n cache: init?.cache || \"default\",\n credentials: init?.credentials || \"same-origin\",\n destination: \"\",\n integrity: init?.integrity || \"\",\n keepalive: init?.keepalive || false,\n mode: init?.mode || \"cors\",\n redirect: init?.redirect || \"follow\",\n referrer: init?.referrer || \"about:client\",\n referrerPolicy: init?.referrerPolicy || \"\",\n signal,\n };\n },\n properties: {\n method: {\n get(this: RequestState) {\n return this.method;\n },\n },\n url: {\n get(this: RequestState) {\n return this.url;\n },\n },\n headers: {\n get(this: RequestState) {\n return createHeadersLike(this.headersState);\n },\n },\n body: {\n get(this: RequestState) {\n if (!this.body) return null;\n if (!createStream) {\n // Fallback: return raw body if no stream factory\n return this.body;\n }\n // Create a ReadableStream from the body data\n const bodyData = this.body;\n let offset = 0;\n const chunkSize = 65536; // 64KB chunks\n return createStream({\n pull(controller) {\n if (offset >= bodyData.length) {\n controller.close();\n return;\n }\n const chunk = bodyData.slice(offset, Math.min(offset + chunkSize, bodyData.length));\n offset += chunk.length;\n controller.enqueue(chunk);\n },\n });\n },\n },\n bodyUsed: {\n get(this: RequestState) {\n return this.bodyUsed;\n },\n },\n cache: {\n get(this: RequestState) {\n return this.cache;\n },\n },\n credentials: {\n get(this: RequestState) {\n return this.credentials;\n },\n },\n destination: {\n get(this: RequestState) {\n return this.destination;\n },\n },\n integrity: {\n get(this: RequestState) {\n return this.integrity;\n },\n },\n keepalive: {\n get(this: RequestState) {\n return this.keepalive;\n },\n },\n mode: {\n get(this: RequestState) {\n return this.mode;\n },\n },\n redirect: {\n get(this: RequestState) {\n return this.redirect;\n },\n },\n referrer: {\n get(this: RequestState) {\n return this.referrer;\n },\n },\n referrerPolicy: {\n get(this: RequestState) {\n return this.referrerPolicy;\n },\n },\n signal: {\n get(this: RequestState) {\n return this.signal;\n },\n },\n },\n methods: {\n async arrayBuffer(this: RequestState): Promise<ArrayBuffer> {\n if (this.bodyUsed) {\n throw new TypeError(\"Body has already been consumed\");\n }\n this.bodyUsed = true;\n if (!this.body) {\n return new ArrayBuffer(0);\n }\n return this.body.buffer.slice(\n this.body.byteOffset,\n this.body.byteOffset + this.body.byteLength\n ) as ArrayBuffer;\n },\n async blob(this: RequestState): Promise<object> {\n if (this.bodyUsed) {\n throw new TypeError(\"Body has already been consumed\");\n }\n this.bodyUsed = true;\n const contentType = this.headersState.headers.get(\"content-type\")?.[0] || \"\";\n return {\n parts: this.body ? [this.body] : [],\n type: contentType,\n size: this.body?.length || 0,\n };\n },\n clone(this: RequestState): RequestState {\n if (this.bodyUsed) {\n throw new TypeError(\"Body has already been consumed\");\n }\n return {\n ...this,\n headersState: {\n headers: new Map(this.headersState.headers),\n },\n body: this.body ? new Uint8Array(this.body) : null,\n bodyUsed: false,\n };\n },\n async json(this: RequestState): Promise<unknown> {\n if (this.bodyUsed) {\n throw new TypeError(\"Body has already been consumed\");\n }\n this.bodyUsed = true;\n if (!this.body) {\n return JSON.parse(\"\");\n }\n const text = new TextDecoder().decode(this.body);\n return JSON.parse(text);\n },\n async text(this: RequestState): Promise<string> {\n if (this.bodyUsed) {\n throw new TypeError(\"Body has already been consumed\");\n }\n this.bodyUsed = true;\n if (!this.body) {\n return \"\";\n }\n return new TextDecoder().decode(this.body);\n },\n async formData(this: RequestState): Promise<FormDataState> {\n if (this.bodyUsed) {\n throw new TypeError(\"Body has already been consumed\");\n }\n this.bodyUsed = true;\n if (!this.body) {\n return { entries: [] };\n }\n\n const contentType = this.headersState.headers.get(\"content-type\")?.[0] || \"\";\n\n if (contentType.includes(\"multipart/form-data\")) {\n return parseMultipartFormData(this.body, contentType);\n } else if (contentType.includes(\"application/x-www-form-urlencoded\")) {\n return parseUrlEncodedFormData(this.body);\n }\n\n throw new TypeError(\"Could not parse content as FormData\");\n },\n },\n });\n}\n\n/**\n * Create a RequestState from a native Request object\n */\nexport async function createRequestStateFromNative(\n request: Request\n): Promise<RequestState> {\n const body = request.body\n ? new Uint8Array(await request.arrayBuffer())\n : null;\n\n return {\n method: request.method,\n url: request.url,\n headersState: createHeadersStateFromNative(request.headers),\n body,\n bodyUsed: false,\n cache: request.cache,\n credentials: request.credentials,\n destination: request.destination,\n integrity: request.integrity,\n keepalive: request.keepalive,\n mode: request.mode,\n redirect: request.redirect,\n referrer: request.referrer,\n referrerPolicy: request.referrerPolicy,\n signal: null, // Signal handling is complex, simplified here\n };\n}\n"
|
|
5
|
+
"import type { QuickJSContext, QuickJSHandle } from \"quickjs-emscripten\";\nimport type { StateMap } from \"@ricsam/quickjs-core\";\nimport { defineClass } from \"@ricsam/quickjs-core\";\nimport type { RequestState, HeadersState, AbortSignalState, FormDataState } from \"../types.cjs\";\nimport { createHeadersStateFromNative, createHeadersLike } from \"./headers.cjs\";\nimport { parseMultipartFormData, parseUrlEncodedFormData } from \"./form-data.cjs\";\n\n/**\n * Type for the stream factory function\n */\ntype StreamFactory = (source: UnderlyingSource) => QuickJSHandle;\n\n/**\n * Create the Request class for QuickJS\n */\nexport function createRequestClass(\n context: QuickJSContext,\n stateMap: StateMap,\n createStream?: StreamFactory\n): QuickJSHandle {\n return defineClass<RequestState>(context, stateMap, {\n name: \"Request\",\n construct: (args) => {\n const input = args[0];\n const init = args[1] as {\n method?: string;\n headers?: object;\n body?: unknown;\n cache?: string;\n credentials?: string;\n integrity?: string;\n keepalive?: boolean;\n mode?: string;\n redirect?: string;\n referrer?: string;\n referrerPolicy?: string;\n signal?: AbortSignalState;\n } | undefined;\n\n let url = \"\";\n let method = \"GET\";\n let headersState: HeadersState = { headers: new Map() };\n let body: Uint8Array | null = null;\n let signal: AbortSignalState | null = null;\n\n // Handle input\n if (typeof input === \"string\") {\n url = input;\n } else if (input && typeof input === \"object\") {\n // Could be URL or Request-like\n if (\"url\" in input) {\n url = String((input as { url: string }).url);\n }\n if (\"method\" in input) {\n method = String((input as { method: string }).method);\n }\n if (\"headersState\" in input) {\n const inputHeaders = (input as RequestState).headersState;\n headersState = {\n headers: new Map(inputHeaders.headers),\n };\n }\n if (\"body\" in input && (input as RequestState).body) {\n body = (input as RequestState).body;\n }\n if (\"signal\" in input) {\n signal = (input as RequestState).signal;\n }\n }\n\n // Apply init options\n if (init) {\n if (init.method) {\n method = init.method.toUpperCase();\n }\n if (init.headers) {\n if (init.headers && typeof init.headers === \"object\") {\n if (\"headers\" in init.headers && init.headers.headers instanceof Map) {\n headersState = {\n headers: new Map((init.headers as HeadersState).headers),\n };\n } else {\n headersState = { headers: new Map() };\n for (const [key, value] of Object.entries(init.headers)) {\n headersState.headers.set(key.toLowerCase(), [String(value)]);\n }\n }\n }\n }\n if (init.body !== undefined && init.body !== null) {\n if (typeof init.body === \"string\") {\n body = new TextEncoder().encode(init.body);\n } else if (init.body instanceof ArrayBuffer) {\n body = new Uint8Array(init.body);\n } else if (init.body instanceof Uint8Array) {\n body = init.body;\n }\n }\n if (init.signal) {\n signal = init.signal;\n }\n }\n\n return {\n method,\n url,\n headersState,\n body,\n bodyUsed: false,\n cache: init?.cache || \"default\",\n credentials: init?.credentials || \"same-origin\",\n destination: \"\",\n integrity: init?.integrity || \"\",\n keepalive: init?.keepalive || false,\n mode: init?.mode || \"cors\",\n redirect: init?.redirect || \"follow\",\n referrer: init?.referrer || \"about:client\",\n referrerPolicy: init?.referrerPolicy || \"\",\n signal,\n };\n },\n properties: {\n method: {\n get(this: RequestState) {\n return this.method;\n },\n },\n url: {\n get(this: RequestState) {\n return this.url;\n },\n },\n headers: {\n get(this: RequestState) {\n return createHeadersLike(this.headersState);\n },\n },\n body: {\n get(this: RequestState) {\n if (!this.body) return null;\n if (!createStream) {\n // Fallback: return raw body if no stream factory\n return this.body;\n }\n // Create a ReadableStream from the body data\n const bodyData = this.body;\n let offset = 0;\n const chunkSize = 65536; // 64KB chunks\n return createStream({\n pull(controller) {\n if (offset >= bodyData.length) {\n controller.close();\n return;\n }\n const chunk = bodyData.slice(offset, Math.min(offset + chunkSize, bodyData.length));\n offset += chunk.length;\n controller.enqueue(chunk);\n },\n });\n },\n },\n bodyUsed: {\n get(this: RequestState) {\n return this.bodyUsed;\n },\n },\n cache: {\n get(this: RequestState) {\n return this.cache;\n },\n },\n credentials: {\n get(this: RequestState) {\n return this.credentials;\n },\n },\n destination: {\n get(this: RequestState) {\n return this.destination;\n },\n },\n integrity: {\n get(this: RequestState) {\n return this.integrity;\n },\n },\n keepalive: {\n get(this: RequestState) {\n return this.keepalive;\n },\n },\n mode: {\n get(this: RequestState) {\n return this.mode;\n },\n },\n redirect: {\n get(this: RequestState) {\n return this.redirect;\n },\n },\n referrer: {\n get(this: RequestState) {\n return this.referrer;\n },\n },\n referrerPolicy: {\n get(this: RequestState) {\n return this.referrerPolicy;\n },\n },\n signal: {\n get(this: RequestState) {\n return this.signal;\n },\n },\n },\n methods: {\n async arrayBuffer(this: RequestState): Promise<ArrayBuffer> {\n if (this.bodyUsed) {\n throw new TypeError(\"Body has already been consumed\");\n }\n this.bodyUsed = true;\n if (!this.body) {\n return new ArrayBuffer(0);\n }\n return this.body.buffer.slice(\n this.body.byteOffset,\n this.body.byteOffset + this.body.byteLength\n ) as ArrayBuffer;\n },\n async blob(this: RequestState): Promise<object> {\n if (this.bodyUsed) {\n throw new TypeError(\"Body has already been consumed\");\n }\n this.bodyUsed = true;\n const contentType = this.headersState.headers.get(\"content-type\")?.[0] || \"\";\n return {\n parts: this.body ? [this.body] : [],\n type: contentType,\n size: this.body?.length || 0,\n };\n },\n clone(this: RequestState): RequestState {\n if (this.bodyUsed) {\n throw new TypeError(\"Body has already been consumed\");\n }\n return {\n ...this,\n headersState: {\n headers: new Map(this.headersState.headers),\n },\n body: this.body ? new Uint8Array(this.body) : null,\n bodyUsed: false,\n };\n },\n async json(this: RequestState): Promise<unknown> {\n if (this.bodyUsed) {\n throw new TypeError(\"Body has already been consumed\");\n }\n this.bodyUsed = true;\n if (!this.body) {\n return JSON.parse(\"\");\n }\n const text = new TextDecoder().decode(this.body);\n return JSON.parse(text);\n },\n async text(this: RequestState): Promise<string> {\n if (this.bodyUsed) {\n throw new TypeError(\"Body has already been consumed\");\n }\n this.bodyUsed = true;\n if (!this.body) {\n return \"\";\n }\n return new TextDecoder().decode(this.body);\n },\n /**\n * Private method that returns raw FormData entries.\n * Used by the formData() method added via evalCode (see addRequestFormDataMethod).\n *\n * Note: File data is converted to plain number arrays to avoid memory issues\n * when marshalling Uint8Array between host and QuickJS contexts.\n */\n async __getFormDataEntries__(this: RequestState): Promise<{\n entries: Array<{\n name: string;\n value: string | { __formDataFile__: true; data: number[]; filename: string; type: string };\n }>;\n }> {\n if (this.bodyUsed) {\n throw new TypeError(\"Body has already been consumed\");\n }\n this.bodyUsed = true;\n if (!this.body) {\n return { entries: [] };\n }\n\n const contentType = this.headersState.headers.get(\"content-type\")?.[0] || \"\";\n\n let formDataState: FormDataState;\n if (contentType.includes(\"multipart/form-data\")) {\n formDataState = parseMultipartFormData(this.body, contentType);\n } else if (contentType.includes(\"application/x-www-form-urlencoded\")) {\n formDataState = parseUrlEncodedFormData(this.body);\n } else {\n throw new TypeError(\"Could not parse content as FormData\");\n }\n\n // Convert Uint8Array data to plain number arrays for safe marshalling\n // Include marker so FormData.get() can reconstruct File instances\n type SerializedFileValue = { __formDataFile__: true; data: number[]; filename: string; type: string };\n type SerializedEntry = { name: string; value: string | SerializedFileValue };\n return {\n entries: formDataState.entries.map((entry): SerializedEntry => {\n if (typeof entry.value === \"string\") {\n return { name: entry.name, value: entry.value };\n }\n // Convert Uint8Array to number array and include marker\n return {\n name: entry.name,\n value: {\n __formDataFile__: true,\n data: Array.from(entry.value.data),\n filename: entry.value.filename,\n type: entry.value.type,\n },\n };\n }),\n };\n },\n },\n });\n}\n\n/**\n * Add the formData() method to Request.prototype via evalCode.\n *\n * This must be called AFTER both Request and FormData classes are on global.\n * The method creates a proper FormData instance with all entries.\n *\n * @see PATTERNS.md section 2 (Class Methods That Return Instances)\n */\nexport function addRequestFormDataMethod(context: QuickJSContext): void {\n const result = context.evalCode(`\n Request.prototype.formData = async function() {\n // Get raw entries from private method\n // Note: File data comes as plain number arrays (converted in host side)\n const rawData = await this.__getFormDataEntries__();\n\n // Create a proper FormData instance\n const formData = new FormData();\n\n // Populate with entries\n // FormData.append handles both string values and file-like objects\n // with number arrays (converted to Uint8Array on the host side)\n for (const entry of rawData.entries) {\n formData.append(entry.name, entry.value);\n }\n\n return formData;\n };\n `);\n\n if (result.error) {\n result.error.dispose();\n } else {\n result.value.dispose();\n }\n}\n\n/**\n * Create a RequestState from a native Request object\n */\nexport async function createRequestStateFromNative(\n request: Request\n): Promise<RequestState> {\n const body = request.body\n ? new Uint8Array(await request.arrayBuffer())\n : null;\n\n return {\n method: request.method,\n url: request.url,\n headersState: createHeadersStateFromNative(request.headers),\n body,\n bodyUsed: false,\n cache: request.cache,\n credentials: request.credentials,\n destination: request.destination,\n integrity: request.integrity,\n keepalive: request.keepalive,\n mode: request.mode,\n redirect: request.redirect,\n referrer: request.referrer,\n referrerPolicy: request.referrerPolicy,\n signal: null, // Signal handling is complex, simplified here\n };\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "C39079D8C2092BD064756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/dist/cjs/package.json
CHANGED
package/dist/cjs/setup.cjs
CHANGED
|
@@ -94,6 +94,8 @@ function setupFetch(context, options = {}) {
|
|
|
94
94
|
} else {
|
|
95
95
|
formDataIteratorResult.value.dispose();
|
|
96
96
|
}
|
|
97
|
+
import_request.addRequestFormDataMethod(context);
|
|
98
|
+
import_form_data.addFormDataFileMethods(context);
|
|
97
99
|
const ServerWebSocketClass = import_serve.createServerWebSocketClass(context, stateMap, dispatchWsCommand);
|
|
98
100
|
context.setProp(context.global, "__ServerWebSocket__", ServerWebSocketClass);
|
|
99
101
|
ServerWebSocketClass.dispose();
|
|
@@ -116,4 +118,4 @@ function setupFetch(context, options = {}) {
|
|
|
116
118
|
}
|
|
117
119
|
})
|
|
118
120
|
|
|
119
|
-
//# debugId=
|
|
121
|
+
//# debugId=E9774DA93B26BC8B64756E2164756E21
|
package/dist/cjs/setup.cjs.map
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/setup.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import type { QuickJSContext, QuickJSHandle } from \"quickjs-emscripten\";\nimport { setupCore, createStateMap, createReadableStream } from \"@ricsam/quickjs-core\";\nimport type { SetupFetchOptions, FetchHandle, ServeState } from \"./types.cjs\";\nimport { createHeadersClass } from \"./globals/headers.cjs\";\nimport { createRequestClass } from \"./globals/request.cjs\";\nimport { createResponseClass, addResponseStaticMethods } from \"./globals/response.cjs\";\nimport { setupAbortControllerAndSignal } from \"./globals/abort-controller.cjs\";\nimport { createFormDataClass } from \"./globals/form-data.cjs\";\nimport { createFetchFunction } from \"./globals/fetch.cjs\";\nimport {\n createServeFunction,\n createServerClass,\n createServerWebSocketClass,\n} from \"./globals/serve.cjs\";\nimport { createFetchHandle } from \"./handle.cjs\";\n\n/**\n * Setup Fetch API in a QuickJS context\n *\n * Injects the following globals:\n * - fetch\n * - Request\n * - Response\n * - Headers\n * - AbortController\n * - AbortSignal\n * - serve\n * - FormData\n *\n * Also sets up Core APIs (Streams, Blob, File) if not already present.\n *\n * **Private globals (internal use):**\n * - `__Server__` - Server class for serve() handler, instantiated via evalCode\n * - `__ServerWebSocket__` - WebSocket class for connection handling\n * - `__scheduleTimeout__` - Host function to schedule AbortSignal.timeout()\n * - `__checkTimeout__` - Host function to check if timeout elapsed\n *\n * These private globals follow the `__Name__` convention and are required for\n * JavaScript code in QuickJS to create class instances via evalCode.\n * See PATTERNS.md section 5 for details.\n *\n * @example\n * const handle = setupFetch(context, {\n * onFetch: async (request) => {\n * // Proxy to real fetch\n * return fetch(request);\n * }\n * });\n *\n * context.evalCode(`\n * serve({\n * fetch(request, server) {\n * return new Response(\"Hello!\");\n * }\n * });\n * `);\n *\n * const response = await handle.dispatchRequest(\n * new Request(\"http://localhost/\")\n * );\n */\nexport function setupFetch(\n context: QuickJSContext,\n options: SetupFetchOptions = {}\n): FetchHandle {\n // Setup core if not already done\n const coreHandle =\n options.coreHandle ??\n setupCore(context, {\n stateMap: options.stateMap,\n });\n\n const stateMap = options.stateMap ?? coreHandle.stateMap;\n\n // Create serve state\n const serveState: ServeState = {\n fetchHandler: null,\n websocketHandlers: {},\n pendingUpgrade: null,\n activeConnections: new Map(),\n };\n\n // WebSocket command dispatcher\n const wsCommandCallbacks = new Set<\n (cmd: import(\"./types.cjs\").WebSocketCommand) => void\n >();\n const dispatchWsCommand = (cmd: import(\"./types.cjs\").WebSocketCommand) => {\n for (const cb of wsCommandCallbacks) {\n cb(cmd);\n }\n };\n\n // Create stream factory for Request/Response body\n const streamFactory = (source: UnderlyingSource) =>\n createReadableStream(context, stateMap, source);\n\n // Create Headers class\n const HeadersClass = createHeadersClass(context, stateMap);\n context.setProp(context.global, \"Headers\", HeadersClass);\n HeadersClass.dispose();\n\n // Add Symbol.iterator support for Headers (for...of, Array.from, spread)\n const iteratorResult = context.evalCode(`\n Headers.prototype[Symbol.iterator] = function() {\n return this.entries()[Symbol.iterator]();\n };\n `);\n if (iteratorResult.error) {\n iteratorResult.error.dispose();\n } else {\n iteratorResult.value.dispose();\n }\n\n // Create Request class\n const RequestClass = createRequestClass(context, stateMap, streamFactory);\n context.setProp(context.global, \"Request\", RequestClass);\n RequestClass.dispose();\n\n // Create Response class\n const ResponseClass = createResponseClass(context, stateMap, streamFactory);\n context.setProp(context.global, \"Response\", ResponseClass);\n ResponseClass.dispose();\n\n // Add Response static methods (must be after Response and Headers are on global)\n addResponseStaticMethods(context);\n\n // Create AbortSignal and AbortController classes (pure QuickJS implementation)\n setupAbortControllerAndSignal(context);\n\n // Create FormData class\n const FormDataClass = createFormDataClass(context, stateMap);\n context.setProp(context.global, \"FormData\", FormDataClass);\n FormDataClass.dispose();\n\n // Add Symbol.iterator support for FormData (for...of, Array.from, spread)\n const formDataIteratorResult = context.evalCode(`\n FormData.prototype[Symbol.iterator] = function() {\n return this.entries()[Symbol.iterator]();\n };\n `);\n if (formDataIteratorResult.error) {\n formDataIteratorResult.error.dispose();\n } else {\n formDataIteratorResult.value.dispose();\n }\n\n // Create ServerWebSocket class (internal, for WebSocket handling)\n const ServerWebSocketClass = createServerWebSocketClass(\n context,\n stateMap,\n dispatchWsCommand\n );\n // Set on global with internal name so we can use evalCode to instantiate\n context.setProp(context.global, \"__ServerWebSocket__\", ServerWebSocketClass);\n ServerWebSocketClass.dispose();\n // Note: ServerWebSocketClass handle is now owned by global\n\n // Create Server class (internal, passed to fetch handler)\n const ServerClass = createServerClass(context, stateMap, serveState);\n // Set on global with internal name so we can use evalCode to instantiate\n context.setProp(context.global, \"__Server__\", ServerClass);\n ServerClass.dispose();\n // Note: ServerClass handle is now owned by global\n\n // Create fetch function\n const fetchFn = createFetchFunction(context, options.onFetch);\n context.setProp(context.global, \"fetch\", fetchFn);\n fetchFn.dispose();\n\n // Create serve function\n const serveFn = createServeFunction(context, stateMap, serveState);\n context.setProp(context.global, \"serve\", serveFn);\n serveFn.dispose();\n\n // Create and return the handle\n const fetchHandle = createFetchHandle(\n context,\n stateMap,\n serveState\n );\n\n // Wire up WebSocket command callbacks\n const originalOnWebSocketCommand = fetchHandle.onWebSocketCommand;\n fetchHandle.onWebSocketCommand = (callback) => {\n wsCommandCallbacks.add(callback);\n return () => wsCommandCallbacks.delete(callback);\n };\n\n return fetchHandle;\n}\n"
|
|
5
|
+
"import type { QuickJSContext, QuickJSHandle } from \"quickjs-emscripten\";\nimport { setupCore, createStateMap, createReadableStream } from \"@ricsam/quickjs-core\";\nimport type { SetupFetchOptions, FetchHandle, ServeState } from \"./types.cjs\";\nimport { createHeadersClass } from \"./globals/headers.cjs\";\nimport { createRequestClass, addRequestFormDataMethod } from \"./globals/request.cjs\";\nimport { createResponseClass, addResponseStaticMethods } from \"./globals/response.cjs\";\nimport { setupAbortControllerAndSignal } from \"./globals/abort-controller.cjs\";\nimport { createFormDataClass, addFormDataFileMethods } from \"./globals/form-data.cjs\";\nimport { createFetchFunction } from \"./globals/fetch.cjs\";\nimport {\n createServeFunction,\n createServerClass,\n createServerWebSocketClass,\n} from \"./globals/serve.cjs\";\nimport { createFetchHandle } from \"./handle.cjs\";\n\n/**\n * Setup Fetch API in a QuickJS context\n *\n * Injects the following globals:\n * - fetch\n * - Request\n * - Response\n * - Headers\n * - AbortController\n * - AbortSignal\n * - serve\n * - FormData\n *\n * Also sets up Core APIs (Streams, Blob, File) if not already present.\n *\n * **Private globals (internal use):**\n * - `__Server__` - Server class for serve() handler, instantiated via evalCode\n * - `__ServerWebSocket__` - WebSocket class for connection handling\n * - `__scheduleTimeout__` - Host function to schedule AbortSignal.timeout()\n * - `__checkTimeout__` - Host function to check if timeout elapsed\n *\n * These private globals follow the `__Name__` convention and are required for\n * JavaScript code in QuickJS to create class instances via evalCode.\n * See PATTERNS.md section 5 for details.\n *\n * @example\n * const handle = setupFetch(context, {\n * onFetch: async (request) => {\n * // Proxy to real fetch\n * return fetch(request);\n * }\n * });\n *\n * context.evalCode(`\n * serve({\n * fetch(request, server) {\n * return new Response(\"Hello!\");\n * }\n * });\n * `);\n *\n * const response = await handle.dispatchRequest(\n * new Request(\"http://localhost/\")\n * );\n */\nexport function setupFetch(\n context: QuickJSContext,\n options: SetupFetchOptions = {}\n): FetchHandle {\n // Setup core if not already done\n const coreHandle =\n options.coreHandle ??\n setupCore(context, {\n stateMap: options.stateMap,\n });\n\n const stateMap = options.stateMap ?? coreHandle.stateMap;\n\n // Create serve state\n const serveState: ServeState = {\n fetchHandler: null,\n websocketHandlers: {},\n pendingUpgrade: null,\n activeConnections: new Map(),\n };\n\n // WebSocket command dispatcher\n const wsCommandCallbacks = new Set<\n (cmd: import(\"./types.cjs\").WebSocketCommand) => void\n >();\n const dispatchWsCommand = (cmd: import(\"./types.cjs\").WebSocketCommand) => {\n for (const cb of wsCommandCallbacks) {\n cb(cmd);\n }\n };\n\n // Create stream factory for Request/Response body\n const streamFactory = (source: UnderlyingSource) =>\n createReadableStream(context, stateMap, source);\n\n // Create Headers class\n const HeadersClass = createHeadersClass(context, stateMap);\n context.setProp(context.global, \"Headers\", HeadersClass);\n HeadersClass.dispose();\n\n // Add Symbol.iterator support for Headers (for...of, Array.from, spread)\n const iteratorResult = context.evalCode(`\n Headers.prototype[Symbol.iterator] = function() {\n return this.entries()[Symbol.iterator]();\n };\n `);\n if (iteratorResult.error) {\n iteratorResult.error.dispose();\n } else {\n iteratorResult.value.dispose();\n }\n\n // Create Request class\n const RequestClass = createRequestClass(context, stateMap, streamFactory);\n context.setProp(context.global, \"Request\", RequestClass);\n RequestClass.dispose();\n\n // Create Response class\n const ResponseClass = createResponseClass(context, stateMap, streamFactory);\n context.setProp(context.global, \"Response\", ResponseClass);\n ResponseClass.dispose();\n\n // Add Response static methods (must be after Response and Headers are on global)\n addResponseStaticMethods(context);\n\n // Create AbortSignal and AbortController classes (pure QuickJS implementation)\n setupAbortControllerAndSignal(context);\n\n // Create FormData class\n const FormDataClass = createFormDataClass(context, stateMap);\n context.setProp(context.global, \"FormData\", FormDataClass);\n FormDataClass.dispose();\n\n // Add Symbol.iterator support for FormData (for...of, Array.from, spread)\n const formDataIteratorResult = context.evalCode(`\n FormData.prototype[Symbol.iterator] = function() {\n return this.entries()[Symbol.iterator]();\n };\n `);\n if (formDataIteratorResult.error) {\n formDataIteratorResult.error.dispose();\n } else {\n formDataIteratorResult.value.dispose();\n }\n\n // Add Request.formData() method that returns a proper FormData instance\n // Must be after both Request and FormData are on global\n addRequestFormDataMethod(context);\n\n // Add FormData.get()/getAll() overrides to reconstruct File instances\n // Must be after both FormData and File are on global\n addFormDataFileMethods(context);\n\n // Create ServerWebSocket class (internal, for WebSocket handling)\n const ServerWebSocketClass = createServerWebSocketClass(\n context,\n stateMap,\n dispatchWsCommand\n );\n // Set on global with internal name so we can use evalCode to instantiate\n context.setProp(context.global, \"__ServerWebSocket__\", ServerWebSocketClass);\n ServerWebSocketClass.dispose();\n // Note: ServerWebSocketClass handle is now owned by global\n\n // Create Server class (internal, passed to fetch handler)\n const ServerClass = createServerClass(context, stateMap, serveState);\n // Set on global with internal name so we can use evalCode to instantiate\n context.setProp(context.global, \"__Server__\", ServerClass);\n ServerClass.dispose();\n // Note: ServerClass handle is now owned by global\n\n // Create fetch function\n const fetchFn = createFetchFunction(context, options.onFetch);\n context.setProp(context.global, \"fetch\", fetchFn);\n fetchFn.dispose();\n\n // Create serve function\n const serveFn = createServeFunction(context, stateMap, serveState);\n context.setProp(context.global, \"serve\", serveFn);\n serveFn.dispose();\n\n // Create and return the handle\n const fetchHandle = createFetchHandle(\n context,\n stateMap,\n serveState\n );\n\n // Wire up WebSocket command callbacks\n const originalOnWebSocketCommand = fetchHandle.onWebSocketCommand;\n fetchHandle.onWebSocketCommand = (callback) => {\n wsCommandCallbacks.add(callback);\n return () => wsCommandCallbacks.delete(callback);\n };\n\n return fetchHandle;\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACgE,IAAhE;AAEmC,IAAnC;
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACgE,IAAhE;AAEmC,IAAnC;AAC6D,IAA7D;AAC8D,IAA9D;AAC8C,IAA9C;AAC4D,IAA5D;AACoC,IAApC;AAKO,IAJP;AAKkC,IAAlC;AA+CO,SAAS,UAAU,CACxB,SACA,UAA6B,CAAC,GACjB;AAAA,EAEb,MAAM,aACJ,QAAQ,cACR,8BAAU,SAAS;AAAA,IACjB,UAAU,QAAQ;AAAA,EACpB,CAAC;AAAA,EAEH,MAAM,WAAW,QAAQ,YAAY,WAAW;AAAA,EAGhD,MAAM,aAAyB;AAAA,IAC7B,cAAc;AAAA,IACd,mBAAmB,CAAC;AAAA,IACpB,gBAAgB;AAAA,IAChB,mBAAmB,IAAI;AAAA,EACzB;AAAA,EAGA,MAAM,qBAAqB,IAAI;AAAA,EAG/B,MAAM,oBAAoB,CAAC,QAAgD;AAAA,IACzE,WAAW,MAAM,oBAAoB;AAAA,MACnC,GAAG,GAAG;AAAA,IACR;AAAA;AAAA,EAIF,MAAM,gBAAgB,CAAC,WACrB,yCAAqB,SAAS,UAAU,MAAM;AAAA,EAGhD,MAAM,eAAe,kCAAmB,SAAS,QAAQ;AAAA,EACzD,QAAQ,QAAQ,QAAQ,QAAQ,WAAW,YAAY;AAAA,EACvD,aAAa,QAAQ;AAAA,EAGrB,MAAM,iBAAiB,QAAQ,SAAS;AAAA;AAAA;AAAA;AAAA,GAIvC;AAAA,EACD,IAAI,eAAe,OAAO;AAAA,IACxB,eAAe,MAAM,QAAQ;AAAA,EAC/B,EAAO;AAAA,IACL,eAAe,MAAM,QAAQ;AAAA;AAAA,EAI/B,MAAM,eAAe,kCAAmB,SAAS,UAAU,aAAa;AAAA,EACxE,QAAQ,QAAQ,QAAQ,QAAQ,WAAW,YAAY;AAAA,EACvD,aAAa,QAAQ;AAAA,EAGrB,MAAM,gBAAgB,oCAAoB,SAAS,UAAU,aAAa;AAAA,EAC1E,QAAQ,QAAQ,QAAQ,QAAQ,YAAY,aAAa;AAAA,EACzD,cAAc,QAAQ;AAAA,EAGtB,yCAAyB,OAAO;AAAA,EAGhC,sDAA8B,OAAO;AAAA,EAGrC,MAAM,gBAAgB,qCAAoB,SAAS,QAAQ;AAAA,EAC3D,QAAQ,QAAQ,QAAQ,QAAQ,YAAY,aAAa;AAAA,EACzD,cAAc,QAAQ;AAAA,EAGtB,MAAM,yBAAyB,QAAQ,SAAS;AAAA;AAAA;AAAA;AAAA,GAI/C;AAAA,EACD,IAAI,uBAAuB,OAAO;AAAA,IAChC,uBAAuB,MAAM,QAAQ;AAAA,EACvC,EAAO;AAAA,IACL,uBAAuB,MAAM,QAAQ;AAAA;AAAA,EAKvC,wCAAyB,OAAO;AAAA,EAIhC,wCAAuB,OAAO;AAAA,EAG9B,MAAM,uBAAuB,wCAC3B,SACA,UACA,iBACF;AAAA,EAEA,QAAQ,QAAQ,QAAQ,QAAQ,uBAAuB,oBAAoB;AAAA,EAC3E,qBAAqB,QAAQ;AAAA,EAI7B,MAAM,cAAc,+BAAkB,SAAS,UAAU,UAAU;AAAA,EAEnE,QAAQ,QAAQ,QAAQ,QAAQ,cAAc,WAAW;AAAA,EACzD,YAAY,QAAQ;AAAA,EAIpB,MAAM,UAAU,iCAAoB,SAAS,QAAQ,OAAO;AAAA,EAC5D,QAAQ,QAAQ,QAAQ,QAAQ,SAAS,OAAO;AAAA,EAChD,QAAQ,QAAQ;AAAA,EAGhB,MAAM,UAAU,iCAAoB,SAAS,UAAU,UAAU;AAAA,EACjE,QAAQ,QAAQ,QAAQ,QAAQ,SAAS,OAAO;AAAA,EAChD,QAAQ,QAAQ;AAAA,EAGhB,MAAM,cAAc,gCAClB,SACA,UACA,UACF;AAAA,EAGA,MAAM,6BAA6B,YAAY;AAAA,EAC/C,YAAY,qBAAqB,CAAC,aAAa;AAAA,IAC7C,mBAAmB,IAAI,QAAQ;AAAA,IAC/B,OAAO,MAAM,mBAAmB,OAAO,QAAQ;AAAA;AAAA,EAGjD,OAAO;AAAA;",
|
|
8
|
+
"debugId": "E9774DA93B26BC8B64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// packages/fetch/src/globals/form-data.ts
|
|
3
|
-
import { defineClass } from "@ricsam/quickjs-core";
|
|
3
|
+
import { defineClass, isInstanceOf, getClassInstanceState } from "@ricsam/quickjs-core";
|
|
4
|
+
var FORMDATA_FILE_MARKER = "__formDataFile__";
|
|
4
5
|
function isFileValue(value) {
|
|
5
|
-
return value !== null && typeof value === "object" &&
|
|
6
|
+
return value !== null && typeof value === "object" && FORMDATA_FILE_MARKER in value && value[FORMDATA_FILE_MARKER] === true;
|
|
6
7
|
}
|
|
7
8
|
function createFormDataClass(context, stateMap) {
|
|
8
9
|
return defineClass(context, stateMap, {
|
|
@@ -13,21 +14,54 @@ function createFormDataClass(context, stateMap) {
|
|
|
13
14
|
methods: {
|
|
14
15
|
append(name, value, filename) {
|
|
15
16
|
const nameStr = String(name);
|
|
17
|
+
if (isInstanceOf(value, "File")) {
|
|
18
|
+
const fileState = getClassInstanceState(value);
|
|
19
|
+
if (fileState) {
|
|
20
|
+
const data = concatenateParts(fileState.parts);
|
|
21
|
+
const fileValue = {
|
|
22
|
+
[FORMDATA_FILE_MARKER]: true,
|
|
23
|
+
data,
|
|
24
|
+
filename: filename !== undefined ? String(filename) : fileState.name,
|
|
25
|
+
type: fileState.type || "application/octet-stream"
|
|
26
|
+
};
|
|
27
|
+
this.entries.push({ name: nameStr, value: fileValue });
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (isInstanceOf(value, "Blob")) {
|
|
32
|
+
const blobState = getClassInstanceState(value);
|
|
33
|
+
if (blobState) {
|
|
34
|
+
const data = concatenateParts(blobState.parts);
|
|
35
|
+
const fileValue = {
|
|
36
|
+
[FORMDATA_FILE_MARKER]: true,
|
|
37
|
+
data,
|
|
38
|
+
filename: filename !== undefined ? String(filename) : "blob",
|
|
39
|
+
type: blobState.type || "application/octet-stream"
|
|
40
|
+
};
|
|
41
|
+
this.entries.push({ name: nameStr, value: fileValue });
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
16
45
|
if (value && typeof value === "object" && "parts" in value) {
|
|
17
46
|
const blobLike = value;
|
|
18
47
|
const data = concatenateParts(blobLike.parts);
|
|
19
48
|
const fileValue = {
|
|
49
|
+
[FORMDATA_FILE_MARKER]: true,
|
|
20
50
|
data,
|
|
21
51
|
filename: filename !== undefined ? String(filename) : blobLike.name || "blob",
|
|
22
52
|
type: blobLike.type || "application/octet-stream"
|
|
23
53
|
};
|
|
24
54
|
this.entries.push({ name: nameStr, value: fileValue });
|
|
25
55
|
} else if (value && typeof value === "object" && "data" in value && "filename" in value) {
|
|
56
|
+
const fileVal = value;
|
|
57
|
+
const data = Array.isArray(fileVal.data) ? new Uint8Array(fileVal.data) : fileVal.data;
|
|
26
58
|
this.entries.push({
|
|
27
59
|
name: nameStr,
|
|
28
60
|
value: {
|
|
29
|
-
|
|
30
|
-
|
|
61
|
+
[FORMDATA_FILE_MARKER]: true,
|
|
62
|
+
data,
|
|
63
|
+
filename: filename !== undefined ? String(filename) : fileVal.filename,
|
|
64
|
+
type: fileVal.type
|
|
31
65
|
}
|
|
32
66
|
});
|
|
33
67
|
} else {
|
|
@@ -54,21 +88,54 @@ function createFormDataClass(context, stateMap) {
|
|
|
54
88
|
set(name, value, filename) {
|
|
55
89
|
const nameStr = String(name);
|
|
56
90
|
this.entries = this.entries.filter((e) => e.name !== nameStr);
|
|
91
|
+
if (isInstanceOf(value, "File")) {
|
|
92
|
+
const fileState = getClassInstanceState(value);
|
|
93
|
+
if (fileState) {
|
|
94
|
+
const data = concatenateParts(fileState.parts);
|
|
95
|
+
const fileValue = {
|
|
96
|
+
[FORMDATA_FILE_MARKER]: true,
|
|
97
|
+
data,
|
|
98
|
+
filename: filename !== undefined ? String(filename) : fileState.name,
|
|
99
|
+
type: fileState.type || "application/octet-stream"
|
|
100
|
+
};
|
|
101
|
+
this.entries.push({ name: nameStr, value: fileValue });
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (isInstanceOf(value, "Blob")) {
|
|
106
|
+
const blobState = getClassInstanceState(value);
|
|
107
|
+
if (blobState) {
|
|
108
|
+
const data = concatenateParts(blobState.parts);
|
|
109
|
+
const fileValue = {
|
|
110
|
+
[FORMDATA_FILE_MARKER]: true,
|
|
111
|
+
data,
|
|
112
|
+
filename: filename !== undefined ? String(filename) : "blob",
|
|
113
|
+
type: blobState.type || "application/octet-stream"
|
|
114
|
+
};
|
|
115
|
+
this.entries.push({ name: nameStr, value: fileValue });
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
57
119
|
if (value && typeof value === "object" && "parts" in value) {
|
|
58
120
|
const blobLike = value;
|
|
59
121
|
const data = concatenateParts(blobLike.parts);
|
|
60
122
|
const fileValue = {
|
|
123
|
+
[FORMDATA_FILE_MARKER]: true,
|
|
61
124
|
data,
|
|
62
125
|
filename: filename !== undefined ? String(filename) : blobLike.name || "blob",
|
|
63
126
|
type: blobLike.type || "application/octet-stream"
|
|
64
127
|
};
|
|
65
128
|
this.entries.push({ name: nameStr, value: fileValue });
|
|
66
129
|
} else if (value && typeof value === "object" && "data" in value && "filename" in value) {
|
|
130
|
+
const fileVal = value;
|
|
131
|
+
const data = Array.isArray(fileVal.data) ? new Uint8Array(fileVal.data) : fileVal.data;
|
|
67
132
|
this.entries.push({
|
|
68
133
|
name: nameStr,
|
|
69
134
|
value: {
|
|
70
|
-
|
|
71
|
-
|
|
135
|
+
[FORMDATA_FILE_MARKER]: true,
|
|
136
|
+
data,
|
|
137
|
+
filename: filename !== undefined ? String(filename) : fileVal.filename,
|
|
138
|
+
type: fileVal.type
|
|
72
139
|
}
|
|
73
140
|
});
|
|
74
141
|
} else {
|
|
@@ -162,7 +229,7 @@ function parseMultipartFormData(body, contentType) {
|
|
|
162
229
|
const type = headers["content-type"] || "application/octet-stream";
|
|
163
230
|
entries.push({
|
|
164
231
|
name,
|
|
165
|
-
value: { data: content, filename, type }
|
|
232
|
+
value: { [FORMDATA_FILE_MARKER]: true, data: content, filename, type }
|
|
166
233
|
});
|
|
167
234
|
} else {
|
|
168
235
|
entries.push({
|
|
@@ -248,11 +315,50 @@ function serializeFormData(state) {
|
|
|
248
315
|
contentType: `multipart/form-data; boundary=${boundary}`
|
|
249
316
|
};
|
|
250
317
|
}
|
|
318
|
+
function addFormDataFileMethods(context) {
|
|
319
|
+
const result = context.evalCode(`
|
|
320
|
+
(function() {
|
|
321
|
+
const FILE_MARKER = "${FORMDATA_FILE_MARKER}";
|
|
322
|
+
|
|
323
|
+
function toFile(value) {
|
|
324
|
+
if (!value || typeof value !== "object") return value;
|
|
325
|
+
if (value instanceof File) return value;
|
|
326
|
+
if (value[FILE_MARKER] === true) {
|
|
327
|
+
// Reconstruct File from stored data
|
|
328
|
+
const data = Array.isArray(value.data)
|
|
329
|
+
? new Uint8Array(value.data)
|
|
330
|
+
: value.data;
|
|
331
|
+
return new File([data], value.filename, { type: value.type });
|
|
332
|
+
}
|
|
333
|
+
return value;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const originalGet = FormData.prototype.get;
|
|
337
|
+
FormData.prototype.get = function(name) {
|
|
338
|
+
return toFile(originalGet.call(this, name));
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
const originalGetAll = FormData.prototype.getAll;
|
|
342
|
+
FormData.prototype.getAll = function(name) {
|
|
343
|
+
return originalGetAll.call(this, name).map(toFile);
|
|
344
|
+
};
|
|
345
|
+
})();
|
|
346
|
+
`);
|
|
347
|
+
if (result.error) {
|
|
348
|
+
const errorMsg = context.dump(result.error);
|
|
349
|
+
result.error.dispose();
|
|
350
|
+
throw new Error(`Failed to add FormData file methods: ${JSON.stringify(errorMsg)}`);
|
|
351
|
+
} else {
|
|
352
|
+
result.value.dispose();
|
|
353
|
+
}
|
|
354
|
+
}
|
|
251
355
|
export {
|
|
252
356
|
serializeFormData,
|
|
253
357
|
parseUrlEncodedFormData,
|
|
254
358
|
parseMultipartFormData,
|
|
255
|
-
createFormDataClass
|
|
359
|
+
createFormDataClass,
|
|
360
|
+
addFormDataFileMethods,
|
|
361
|
+
FORMDATA_FILE_MARKER
|
|
256
362
|
};
|
|
257
363
|
|
|
258
|
-
//# debugId=
|
|
364
|
+
//# debugId=D50F63F639F0720E64756E2164756E21
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/globals/form-data.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import type { QuickJSContext, QuickJSHandle } from \"quickjs-emscripten\";\nimport type { StateMap } from \"@ricsam/quickjs-core\";\nimport { defineClass } from \"@ricsam/quickjs-core\";\n\n/**\n * Internal state for FormData entries\n * Each entry can have multiple values (same key appended multiple times)\n */\nexport interface FormDataEntry {\n name: string;\n value: string | FormDataFileValue;\n}\n\nexport interface FormDataFileValue {\n data: Uint8Array;\n filename: string;\n type: string;\n}\n\nexport interface FormDataState {\n entries: FormDataEntry[];\n}\n\nfunction isFileValue(value: unknown): value is FormDataFileValue {\n return (\n value !== null &&\n typeof value === \"object\" &&\n \"data\" in value &&\n \"filename\" in value\n );\n}\n\n/**\n * Create the FormData class for QuickJS\n */\nexport function createFormDataClass(\n context: QuickJSContext,\n stateMap: StateMap\n): QuickJSHandle {\n return defineClass<FormDataState>(context, stateMap, {\n name: \"FormData\",\n construct: () => {\n return { entries: [] };\n },\n methods: {\n append(this: FormDataState, name: unknown, value: unknown, filename?: unknown) {\n const nameStr = String(name);\n\n if (value && typeof value === \"object\" && \"parts\" in value) {\n // Blob-like value\n const blobLike = value as { parts: Uint8Array[]; type?: string; name?: string };\n const data = concatenateParts(blobLike.parts);\n const fileValue: FormDataFileValue = {\n data,\n filename: filename !== undefined ? String(filename) : (blobLike.name || \"blob\"),\n type: blobLike.type || \"application/octet-stream\",\n };\n this.entries.push({ name: nameStr, value: fileValue });\n } else if (value && typeof value === \"object\" && \"data\" in value && \"filename\" in value) {\n // Already a FormDataFileValue\n this.entries.push({\n name: nameStr,\n value: {\n ...(value as FormDataFileValue),\n filename: filename !== undefined ? String(filename) : (value as FormDataFileValue).filename,\n },\n });\n } else {\n // String value\n this.entries.push({ name: nameStr, value: String(value) });\n }\n },\n delete(this: FormDataState, name: unknown) {\n const nameStr = String(name);\n this.entries = this.entries.filter((e) => e.name !== nameStr);\n },\n get(this: FormDataState, name: unknown): string | FormDataFileValue | null {\n const nameStr = String(name);\n const entry = this.entries.find((e) => e.name === nameStr);\n return entry ? entry.value : null;\n },\n getAll(this: FormDataState, name: unknown): Array<string | FormDataFileValue> {\n const nameStr = String(name);\n return this.entries\n .filter((e) => e.name === nameStr)\n .map((e) => e.value);\n },\n has(this: FormDataState, name: unknown): boolean {\n const nameStr = String(name);\n return this.entries.some((e) => e.name === nameStr);\n },\n set(this: FormDataState, name: unknown, value: unknown, filename?: unknown) {\n const nameStr = String(name);\n // Remove all existing entries with this name\n this.entries = this.entries.filter((e) => e.name !== nameStr);\n\n // Add the new entry (using append logic)\n if (value && typeof value === \"object\" && \"parts\" in value) {\n const blobLike = value as { parts: Uint8Array[]; type?: string; name?: string };\n const data = concatenateParts(blobLike.parts);\n const fileValue: FormDataFileValue = {\n data,\n filename: filename !== undefined ? String(filename) : (blobLike.name || \"blob\"),\n type: blobLike.type || \"application/octet-stream\",\n };\n this.entries.push({ name: nameStr, value: fileValue });\n } else if (value && typeof value === \"object\" && \"data\" in value && \"filename\" in value) {\n this.entries.push({\n name: nameStr,\n value: {\n ...(value as FormDataFileValue),\n filename: filename !== undefined ? String(filename) : (value as FormDataFileValue).filename,\n },\n });\n } else {\n this.entries.push({ name: nameStr, value: String(value) });\n }\n },\n entries(this: FormDataState): Array<[string, string | FormDataFileValue]> {\n return this.entries.map((e) => [e.name, e.value]);\n },\n keys(this: FormDataState): string[] {\n // Return unique keys in order of first appearance\n const seen = new Set<string>();\n const result: string[] = [];\n for (const entry of this.entries) {\n if (!seen.has(entry.name)) {\n seen.add(entry.name);\n result.push(entry.name);\n }\n }\n return result;\n },\n values(this: FormDataState): Array<string | FormDataFileValue> {\n return this.entries.map((e) => e.value);\n },\n forEach(this: FormDataState, callback: unknown) {\n if (typeof callback !== \"function\") {\n throw new TypeError(\"callback must be a function\");\n }\n for (const entry of this.entries) {\n (callback as (value: string | FormDataFileValue, key: string, parent: FormDataState) => void)(\n entry.value,\n entry.name,\n this\n );\n }\n },\n },\n });\n}\n\n/**\n * Concatenate Uint8Arrays into a single Uint8Array\n */\nfunction concatenateParts(parts: Uint8Array[]): Uint8Array {\n const totalLength = parts.reduce((sum, part) => sum + part.length, 0);\n const result = new Uint8Array(totalLength);\n let offset = 0;\n for (const part of parts) {\n result.set(part, offset);\n offset += part.length;\n }\n return result;\n}\n\n/**\n * Parse multipart/form-data body\n */\nexport function parseMultipartFormData(\n body: Uint8Array,\n contentType: string\n): FormDataState {\n const entries: FormDataEntry[] = [];\n\n // Extract boundary from content-type\n const boundaryMatch = contentType.match(/boundary=([^;]+)/i);\n if (!boundaryMatch || !boundaryMatch[1]) {\n return { entries };\n }\n\n const boundary = boundaryMatch[1].replace(/^[\"']|[\"']$/g, \"\");\n const boundaryBytes = new TextEncoder().encode(`--${boundary}`);\n const endBoundaryBytes = new TextEncoder().encode(`--${boundary}--`);\n\n // Find all parts\n const decoder = new TextDecoder();\n let pos = 0;\n\n // Skip preamble and first boundary\n pos = findSequence(body, boundaryBytes, pos);\n if (pos === -1) return { entries };\n pos += boundaryBytes.length;\n\n while (pos < body.length) {\n // Skip CRLF after boundary\n if (body[pos] === 0x0d && body[pos + 1] === 0x0a) {\n pos += 2;\n } else if (body[pos] === 0x0a) {\n pos += 1;\n }\n\n // Check for end boundary\n if (pos + 2 <= body.length && body[pos] === 0x2d && body[pos + 1] === 0x2d) {\n break; // End of multipart\n }\n\n // Parse headers\n const headersEnd = findSequence(body, new Uint8Array([0x0d, 0x0a, 0x0d, 0x0a]), pos);\n if (headersEnd === -1) break;\n\n const headersText = decoder.decode(body.slice(pos, headersEnd));\n const headers = parseHeaders(headersText);\n pos = headersEnd + 4;\n\n // Find next boundary\n const nextBoundary = findSequence(body, boundaryBytes, pos);\n if (nextBoundary === -1) break;\n\n // Content is between current position and next boundary (minus CRLF)\n let contentEnd = nextBoundary;\n if (contentEnd > 0 && body[contentEnd - 1] === 0x0a) contentEnd--;\n if (contentEnd > 0 && body[contentEnd - 1] === 0x0d) contentEnd--;\n\n const content = body.slice(pos, contentEnd);\n\n // Parse Content-Disposition header\n const disposition = headers[\"content-disposition\"] || \"\";\n const nameMatch = disposition.match(/name=\"([^\"]+)\"/);\n const filenameMatch = disposition.match(/filename=\"([^\"]+)\"/);\n\n if (nameMatch && nameMatch[1]) {\n const name = nameMatch[1];\n if (filenameMatch && filenameMatch[1]) {\n // File entry\n const filename = filenameMatch[1];\n const type = headers[\"content-type\"] || \"application/octet-stream\";\n entries.push({\n name,\n value: { data: content, filename, type },\n });\n } else {\n // String entry\n entries.push({\n name,\n value: decoder.decode(content),\n });\n }\n }\n\n pos = nextBoundary + boundaryBytes.length;\n }\n\n return { entries };\n}\n\n/**\n * Parse URL-encoded form data\n */\nexport function parseUrlEncodedFormData(body: Uint8Array): FormDataState {\n const text = new TextDecoder().decode(body);\n const entries: FormDataEntry[] = [];\n\n const params = new URLSearchParams(text);\n for (const [name, value] of params) {\n entries.push({ name, value });\n }\n\n return { entries };\n}\n\nfunction findSequence(haystack: Uint8Array, needle: Uint8Array, start: number): number {\n outer: for (let i = start; i <= haystack.length - needle.length; i++) {\n for (let j = 0; j < needle.length; j++) {\n if (haystack[i + j] !== needle[j]) continue outer;\n }\n return i;\n }\n return -1;\n}\n\nfunction parseHeaders(text: string): Record<string, string> {\n const headers: Record<string, string> = {};\n const lines = text.split(/\\r?\\n/);\n for (const line of lines) {\n const colonIndex = line.indexOf(\":\");\n if (colonIndex > 0) {\n const name = line.slice(0, colonIndex).trim().toLowerCase();\n const value = line.slice(colonIndex + 1).trim();\n headers[name] = value;\n }\n }\n return headers;\n}\n\n/**\n * Serialize FormData to multipart/form-data format\n */\nexport function serializeFormData(state: FormDataState): { body: Uint8Array; contentType: string } {\n const boundary = `----FormDataBoundary${Math.random().toString(36).slice(2)}`;\n const encoder = new TextEncoder();\n const parts: Uint8Array[] = [];\n\n for (const entry of state.entries) {\n const headerLines: string[] = [];\n headerLines.push(`--${boundary}`);\n\n if (isFileValue(entry.value)) {\n headerLines.push(\n `Content-Disposition: form-data; name=\"${entry.name}\"; filename=\"${entry.value.filename}\"`\n );\n headerLines.push(`Content-Type: ${entry.value.type}`);\n headerLines.push(\"\");\n\n parts.push(encoder.encode(headerLines.join(\"\\r\\n\") + \"\\r\\n\"));\n parts.push(entry.value.data);\n parts.push(encoder.encode(\"\\r\\n\"));\n } else {\n headerLines.push(`Content-Disposition: form-data; name=\"${entry.name}\"`);\n headerLines.push(\"\");\n headerLines.push(entry.value);\n\n parts.push(encoder.encode(headerLines.join(\"\\r\\n\") + \"\\r\\n\"));\n }\n }\n\n parts.push(encoder.encode(`--${boundary}--\\r\\n`));\n\n // Concatenate all parts\n const totalLength = parts.reduce((sum, part) => sum + part.length, 0);\n const body = new Uint8Array(totalLength);\n let offset = 0;\n for (const part of parts) {\n body.set(part, offset);\n offset += part.length;\n }\n\n return {\n body,\n contentType: `multipart/form-data; boundary=${boundary}`,\n };\n}\n"
|
|
5
|
+
"import type { QuickJSContext, QuickJSHandle } from \"quickjs-emscripten\";\nimport type { StateMap } from \"@ricsam/quickjs-core\";\nimport { defineClass, isInstanceOf, getClassInstanceState } from \"@ricsam/quickjs-core\";\n\n/**\n * Marker to identify FormData file entries that need File reconstruction\n */\nexport const FORMDATA_FILE_MARKER = \"__formDataFile__\";\n\n/**\n * Internal state for Blob/File classes (from packages/core)\n */\ninterface BlobInternalState {\n parts: Uint8Array[];\n type: string;\n size: number;\n}\n\ninterface FileInternalState extends BlobInternalState {\n name: string;\n lastModified: number;\n webkitRelativePath: string;\n}\n\n/**\n * Internal state for FormData entries\n * Each entry can have multiple values (same key appended multiple times)\n */\nexport interface FormDataEntry {\n name: string;\n value: string | FormDataFileValue;\n}\n\nexport interface FormDataFileValue {\n [FORMDATA_FILE_MARKER]: true;\n data: Uint8Array;\n filename: string;\n type: string;\n}\n\nexport interface FormDataState {\n entries: FormDataEntry[];\n}\n\nfunction isFileValue(value: unknown): value is FormDataFileValue {\n return (\n value !== null &&\n typeof value === \"object\" &&\n FORMDATA_FILE_MARKER in value &&\n (value as FormDataFileValue)[FORMDATA_FILE_MARKER] === true\n );\n}\n\n/**\n * Create the FormData class for QuickJS\n */\nexport function createFormDataClass(\n context: QuickJSContext,\n stateMap: StateMap\n): QuickJSHandle {\n return defineClass<FormDataState>(context, stateMap, {\n name: \"FormData\",\n construct: () => {\n return { entries: [] };\n },\n methods: {\n append(this: FormDataState, name: unknown, value: unknown, filename?: unknown) {\n const nameStr = String(name);\n\n // Check for File instance using class identity (preferred method)\n if (isInstanceOf(value, \"File\")) {\n const fileState = getClassInstanceState<FileInternalState>(value);\n if (fileState) {\n const data = concatenateParts(fileState.parts);\n const fileValue: FormDataFileValue = {\n [FORMDATA_FILE_MARKER]: true,\n data,\n filename: filename !== undefined ? String(filename) : fileState.name,\n type: fileState.type || \"application/octet-stream\",\n };\n this.entries.push({ name: nameStr, value: fileValue });\n return;\n }\n }\n\n // Check for Blob instance using class identity\n if (isInstanceOf(value, \"Blob\")) {\n const blobState = getClassInstanceState<BlobInternalState>(value);\n if (blobState) {\n const data = concatenateParts(blobState.parts);\n const fileValue: FormDataFileValue = {\n [FORMDATA_FILE_MARKER]: true,\n data,\n filename: filename !== undefined ? String(filename) : \"blob\",\n type: blobState.type || \"application/octet-stream\",\n };\n this.entries.push({ name: nameStr, value: fileValue });\n return;\n }\n }\n\n // Fallback: duck-typing for Blob-like objects (backwards compatibility)\n if (value && typeof value === \"object\" && \"parts\" in value) {\n const blobLike = value as { parts: Uint8Array[]; type?: string; name?: string };\n const data = concatenateParts(blobLike.parts);\n const fileValue: FormDataFileValue = {\n [FORMDATA_FILE_MARKER]: true,\n data,\n filename: filename !== undefined ? String(filename) : (blobLike.name || \"blob\"),\n type: blobLike.type || \"application/octet-stream\",\n };\n this.entries.push({ name: nameStr, value: fileValue });\n } else if (value && typeof value === \"object\" && \"data\" in value && \"filename\" in value) {\n // Already a FormDataFileValue (or similar structure with data, filename, type)\n const fileVal = value as { data: Uint8Array | number[]; filename: string; type: string };\n // Handle both Uint8Array and number array (for safe cross-context marshalling)\n const data = Array.isArray(fileVal.data)\n ? new Uint8Array(fileVal.data)\n : fileVal.data;\n this.entries.push({\n name: nameStr,\n value: {\n [FORMDATA_FILE_MARKER]: true,\n data,\n filename: filename !== undefined ? String(filename) : fileVal.filename,\n type: fileVal.type,\n },\n });\n } else {\n // String value\n this.entries.push({ name: nameStr, value: String(value) });\n }\n },\n delete(this: FormDataState, name: unknown) {\n const nameStr = String(name);\n this.entries = this.entries.filter((e) => e.name !== nameStr);\n },\n get(this: FormDataState, name: unknown): string | FormDataFileValue | null {\n const nameStr = String(name);\n const entry = this.entries.find((e) => e.name === nameStr);\n return entry ? entry.value : null;\n },\n getAll(this: FormDataState, name: unknown): Array<string | FormDataFileValue> {\n const nameStr = String(name);\n return this.entries\n .filter((e) => e.name === nameStr)\n .map((e) => e.value);\n },\n has(this: FormDataState, name: unknown): boolean {\n const nameStr = String(name);\n return this.entries.some((e) => e.name === nameStr);\n },\n set(this: FormDataState, name: unknown, value: unknown, filename?: unknown) {\n const nameStr = String(name);\n // Remove all existing entries with this name\n this.entries = this.entries.filter((e) => e.name !== nameStr);\n\n // Check for File instance using class identity (preferred method)\n if (isInstanceOf(value, \"File\")) {\n const fileState = getClassInstanceState<FileInternalState>(value);\n if (fileState) {\n const data = concatenateParts(fileState.parts);\n const fileValue: FormDataFileValue = {\n [FORMDATA_FILE_MARKER]: true,\n data,\n filename: filename !== undefined ? String(filename) : fileState.name,\n type: fileState.type || \"application/octet-stream\",\n };\n this.entries.push({ name: nameStr, value: fileValue });\n return;\n }\n }\n\n // Check for Blob instance using class identity\n if (isInstanceOf(value, \"Blob\")) {\n const blobState = getClassInstanceState<BlobInternalState>(value);\n if (blobState) {\n const data = concatenateParts(blobState.parts);\n const fileValue: FormDataFileValue = {\n [FORMDATA_FILE_MARKER]: true,\n data,\n filename: filename !== undefined ? String(filename) : \"blob\",\n type: blobState.type || \"application/octet-stream\",\n };\n this.entries.push({ name: nameStr, value: fileValue });\n return;\n }\n }\n\n // Fallback: duck-typing for Blob-like objects (backwards compatibility)\n if (value && typeof value === \"object\" && \"parts\" in value) {\n const blobLike = value as { parts: Uint8Array[]; type?: string; name?: string };\n const data = concatenateParts(blobLike.parts);\n const fileValue: FormDataFileValue = {\n [FORMDATA_FILE_MARKER]: true,\n data,\n filename: filename !== undefined ? String(filename) : (blobLike.name || \"blob\"),\n type: blobLike.type || \"application/octet-stream\",\n };\n this.entries.push({ name: nameStr, value: fileValue });\n } else if (value && typeof value === \"object\" && \"data\" in value && \"filename\" in value) {\n // Already a FormDataFileValue (or similar structure with data, filename, type)\n const fileVal = value as { data: Uint8Array | number[]; filename: string; type: string };\n // Handle both Uint8Array and number array (for safe cross-context marshalling)\n const data = Array.isArray(fileVal.data)\n ? new Uint8Array(fileVal.data)\n : fileVal.data;\n this.entries.push({\n name: nameStr,\n value: {\n [FORMDATA_FILE_MARKER]: true,\n data,\n filename: filename !== undefined ? String(filename) : fileVal.filename,\n type: fileVal.type,\n },\n });\n } else {\n this.entries.push({ name: nameStr, value: String(value) });\n }\n },\n entries(this: FormDataState): Array<[string, string | FormDataFileValue]> {\n return this.entries.map((e) => [e.name, e.value]);\n },\n keys(this: FormDataState): string[] {\n // Return unique keys in order of first appearance\n const seen = new Set<string>();\n const result: string[] = [];\n for (const entry of this.entries) {\n if (!seen.has(entry.name)) {\n seen.add(entry.name);\n result.push(entry.name);\n }\n }\n return result;\n },\n values(this: FormDataState): Array<string | FormDataFileValue> {\n return this.entries.map((e) => e.value);\n },\n forEach(this: FormDataState, callback: unknown) {\n if (typeof callback !== \"function\") {\n throw new TypeError(\"callback must be a function\");\n }\n for (const entry of this.entries) {\n (callback as (value: string | FormDataFileValue, key: string, parent: FormDataState) => void)(\n entry.value,\n entry.name,\n this\n );\n }\n },\n },\n });\n}\n\n/**\n * Concatenate Uint8Arrays into a single Uint8Array\n */\nfunction concatenateParts(parts: Uint8Array[]): Uint8Array {\n const totalLength = parts.reduce((sum, part) => sum + part.length, 0);\n const result = new Uint8Array(totalLength);\n let offset = 0;\n for (const part of parts) {\n result.set(part, offset);\n offset += part.length;\n }\n return result;\n}\n\n/**\n * Parse multipart/form-data body\n */\nexport function parseMultipartFormData(\n body: Uint8Array,\n contentType: string\n): FormDataState {\n const entries: FormDataEntry[] = [];\n\n // Extract boundary from content-type\n const boundaryMatch = contentType.match(/boundary=([^;]+)/i);\n if (!boundaryMatch || !boundaryMatch[1]) {\n return { entries };\n }\n\n const boundary = boundaryMatch[1].replace(/^[\"']|[\"']$/g, \"\");\n const boundaryBytes = new TextEncoder().encode(`--${boundary}`);\n const endBoundaryBytes = new TextEncoder().encode(`--${boundary}--`);\n\n // Find all parts\n const decoder = new TextDecoder();\n let pos = 0;\n\n // Skip preamble and first boundary\n pos = findSequence(body, boundaryBytes, pos);\n if (pos === -1) return { entries };\n pos += boundaryBytes.length;\n\n while (pos < body.length) {\n // Skip CRLF after boundary\n if (body[pos] === 0x0d && body[pos + 1] === 0x0a) {\n pos += 2;\n } else if (body[pos] === 0x0a) {\n pos += 1;\n }\n\n // Check for end boundary\n if (pos + 2 <= body.length && body[pos] === 0x2d && body[pos + 1] === 0x2d) {\n break; // End of multipart\n }\n\n // Parse headers\n const headersEnd = findSequence(body, new Uint8Array([0x0d, 0x0a, 0x0d, 0x0a]), pos);\n if (headersEnd === -1) break;\n\n const headersText = decoder.decode(body.slice(pos, headersEnd));\n const headers = parseHeaders(headersText);\n pos = headersEnd + 4;\n\n // Find next boundary\n const nextBoundary = findSequence(body, boundaryBytes, pos);\n if (nextBoundary === -1) break;\n\n // Content is between current position and next boundary (minus CRLF)\n let contentEnd = nextBoundary;\n if (contentEnd > 0 && body[contentEnd - 1] === 0x0a) contentEnd--;\n if (contentEnd > 0 && body[contentEnd - 1] === 0x0d) contentEnd--;\n\n const content = body.slice(pos, contentEnd);\n\n // Parse Content-Disposition header\n const disposition = headers[\"content-disposition\"] || \"\";\n const nameMatch = disposition.match(/name=\"([^\"]+)\"/);\n const filenameMatch = disposition.match(/filename=\"([^\"]+)\"/);\n\n if (nameMatch && nameMatch[1]) {\n const name = nameMatch[1];\n if (filenameMatch && filenameMatch[1]) {\n // File entry\n const filename = filenameMatch[1];\n const type = headers[\"content-type\"] || \"application/octet-stream\";\n entries.push({\n name,\n value: { [FORMDATA_FILE_MARKER]: true, data: content, filename, type },\n });\n } else {\n // String entry\n entries.push({\n name,\n value: decoder.decode(content),\n });\n }\n }\n\n pos = nextBoundary + boundaryBytes.length;\n }\n\n return { entries };\n}\n\n/**\n * Parse URL-encoded form data\n */\nexport function parseUrlEncodedFormData(body: Uint8Array): FormDataState {\n const text = new TextDecoder().decode(body);\n const entries: FormDataEntry[] = [];\n\n const params = new URLSearchParams(text);\n for (const [name, value] of params) {\n entries.push({ name, value });\n }\n\n return { entries };\n}\n\nfunction findSequence(haystack: Uint8Array, needle: Uint8Array, start: number): number {\n outer: for (let i = start; i <= haystack.length - needle.length; i++) {\n for (let j = 0; j < needle.length; j++) {\n if (haystack[i + j] !== needle[j]) continue outer;\n }\n return i;\n }\n return -1;\n}\n\nfunction parseHeaders(text: string): Record<string, string> {\n const headers: Record<string, string> = {};\n const lines = text.split(/\\r?\\n/);\n for (const line of lines) {\n const colonIndex = line.indexOf(\":\");\n if (colonIndex > 0) {\n const name = line.slice(0, colonIndex).trim().toLowerCase();\n const value = line.slice(colonIndex + 1).trim();\n headers[name] = value;\n }\n }\n return headers;\n}\n\n/**\n * Serialize FormData to multipart/form-data format\n */\nexport function serializeFormData(state: FormDataState): { body: Uint8Array; contentType: string } {\n const boundary = `----FormDataBoundary${Math.random().toString(36).slice(2)}`;\n const encoder = new TextEncoder();\n const parts: Uint8Array[] = [];\n\n for (const entry of state.entries) {\n const headerLines: string[] = [];\n headerLines.push(`--${boundary}`);\n\n if (isFileValue(entry.value)) {\n headerLines.push(\n `Content-Disposition: form-data; name=\"${entry.name}\"; filename=\"${entry.value.filename}\"`\n );\n headerLines.push(`Content-Type: ${entry.value.type}`);\n headerLines.push(\"\");\n\n parts.push(encoder.encode(headerLines.join(\"\\r\\n\") + \"\\r\\n\"));\n parts.push(entry.value.data);\n parts.push(encoder.encode(\"\\r\\n\"));\n } else {\n headerLines.push(`Content-Disposition: form-data; name=\"${entry.name}\"`);\n headerLines.push(\"\");\n headerLines.push(entry.value);\n\n parts.push(encoder.encode(headerLines.join(\"\\r\\n\") + \"\\r\\n\"));\n }\n }\n\n parts.push(encoder.encode(`--${boundary}--\\r\\n`));\n\n // Concatenate all parts\n const totalLength = parts.reduce((sum, part) => sum + part.length, 0);\n const body = new Uint8Array(totalLength);\n let offset = 0;\n for (const part of parts) {\n body.set(part, offset);\n offset += part.length;\n }\n\n return {\n body,\n contentType: `multipart/form-data; boundary=${boundary}`,\n };\n}\n\n/**\n * Add JavaScript overrides for FormData.get() and FormData.getAll() to reconstruct File instances\n *\n * This is needed because defineClass methods can only return plain objects when marshalling,\n * but we want formData.get(\"file\") to return actual File instances with working methods.\n *\n * @param context The QuickJS context (must have FormData and File classes already defined)\n */\nexport function addFormDataFileMethods(context: QuickJSContext): void {\n const result = context.evalCode(`\n (function() {\n const FILE_MARKER = \"${FORMDATA_FILE_MARKER}\";\n\n function toFile(value) {\n if (!value || typeof value !== \"object\") return value;\n if (value instanceof File) return value;\n if (value[FILE_MARKER] === true) {\n // Reconstruct File from stored data\n const data = Array.isArray(value.data)\n ? new Uint8Array(value.data)\n : value.data;\n return new File([data], value.filename, { type: value.type });\n }\n return value;\n }\n\n const originalGet = FormData.prototype.get;\n FormData.prototype.get = function(name) {\n return toFile(originalGet.call(this, name));\n };\n\n const originalGetAll = FormData.prototype.getAll;\n FormData.prototype.getAll = function(name) {\n return originalGetAll.call(this, name).map(toFile);\n };\n })();\n `);\n if (result.error) {\n const errorMsg = context.dump(result.error);\n result.error.dispose();\n throw new Error(`Failed to add FormData file methods: ${JSON.stringify(errorMsg)}`);\n } else {\n result.value.dispose();\n }\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;AAEA;
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "D50F63F639F0720E64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -237,7 +237,7 @@ function createRequestClass(context, stateMap, createStream) {
|
|
|
237
237
|
}
|
|
238
238
|
return new TextDecoder().decode(this.body);
|
|
239
239
|
},
|
|
240
|
-
async
|
|
240
|
+
async __getFormDataEntries__() {
|
|
241
241
|
if (this.bodyUsed) {
|
|
242
242
|
throw new TypeError("Body has already been consumed");
|
|
243
243
|
}
|
|
@@ -246,16 +246,60 @@ function createRequestClass(context, stateMap, createStream) {
|
|
|
246
246
|
return { entries: [] };
|
|
247
247
|
}
|
|
248
248
|
const contentType = this.headersState.headers.get("content-type")?.[0] || "";
|
|
249
|
+
let formDataState;
|
|
249
250
|
if (contentType.includes("multipart/form-data")) {
|
|
250
|
-
|
|
251
|
+
formDataState = parseMultipartFormData(this.body, contentType);
|
|
251
252
|
} else if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
252
|
-
|
|
253
|
+
formDataState = parseUrlEncodedFormData(this.body);
|
|
254
|
+
} else {
|
|
255
|
+
throw new TypeError("Could not parse content as FormData");
|
|
253
256
|
}
|
|
254
|
-
|
|
257
|
+
return {
|
|
258
|
+
entries: formDataState.entries.map((entry) => {
|
|
259
|
+
if (typeof entry.value === "string") {
|
|
260
|
+
return { name: entry.name, value: entry.value };
|
|
261
|
+
}
|
|
262
|
+
return {
|
|
263
|
+
name: entry.name,
|
|
264
|
+
value: {
|
|
265
|
+
__formDataFile__: true,
|
|
266
|
+
data: Array.from(entry.value.data),
|
|
267
|
+
filename: entry.value.filename,
|
|
268
|
+
type: entry.value.type
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
})
|
|
272
|
+
};
|
|
255
273
|
}
|
|
256
274
|
}
|
|
257
275
|
});
|
|
258
276
|
}
|
|
277
|
+
function addRequestFormDataMethod(context) {
|
|
278
|
+
const result = context.evalCode(`
|
|
279
|
+
Request.prototype.formData = async function() {
|
|
280
|
+
// Get raw entries from private method
|
|
281
|
+
// Note: File data comes as plain number arrays (converted in host side)
|
|
282
|
+
const rawData = await this.__getFormDataEntries__();
|
|
283
|
+
|
|
284
|
+
// Create a proper FormData instance
|
|
285
|
+
const formData = new FormData();
|
|
286
|
+
|
|
287
|
+
// Populate with entries
|
|
288
|
+
// FormData.append handles both string values and file-like objects
|
|
289
|
+
// with number arrays (converted to Uint8Array on the host side)
|
|
290
|
+
for (const entry of rawData.entries) {
|
|
291
|
+
formData.append(entry.name, entry.value);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return formData;
|
|
295
|
+
};
|
|
296
|
+
`);
|
|
297
|
+
if (result.error) {
|
|
298
|
+
result.error.dispose();
|
|
299
|
+
} else {
|
|
300
|
+
result.value.dispose();
|
|
301
|
+
}
|
|
302
|
+
}
|
|
259
303
|
async function createRequestStateFromNative(request) {
|
|
260
304
|
const body = request.body ? new Uint8Array(await request.arrayBuffer()) : null;
|
|
261
305
|
return {
|
|
@@ -278,7 +322,8 @@ async function createRequestStateFromNative(request) {
|
|
|
278
322
|
}
|
|
279
323
|
export {
|
|
280
324
|
createRequestStateFromNative,
|
|
281
|
-
createRequestClass
|
|
325
|
+
createRequestClass,
|
|
326
|
+
addRequestFormDataMethod
|
|
282
327
|
};
|
|
283
328
|
|
|
284
|
-
//# debugId=
|
|
329
|
+
//# debugId=9CDFB08BC3F7DEBF64756E2164756E21
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/globals/request.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import type { QuickJSContext, QuickJSHandle } from \"quickjs-emscripten\";\nimport type { StateMap } from \"@ricsam/quickjs-core\";\nimport { defineClass } from \"@ricsam/quickjs-core\";\nimport type { RequestState, HeadersState, AbortSignalState, FormDataState } from \"../types.mjs\";\nimport { createHeadersStateFromNative, createHeadersLike } from \"./headers.mjs\";\nimport { parseMultipartFormData, parseUrlEncodedFormData } from \"./form-data.mjs\";\n\n/**\n * Type for the stream factory function\n */\ntype StreamFactory = (source: UnderlyingSource) => QuickJSHandle;\n\n/**\n * Create the Request class for QuickJS\n */\nexport function createRequestClass(\n context: QuickJSContext,\n stateMap: StateMap,\n createStream?: StreamFactory\n): QuickJSHandle {\n return defineClass<RequestState>(context, stateMap, {\n name: \"Request\",\n construct: (args) => {\n const input = args[0];\n const init = args[1] as {\n method?: string;\n headers?: object;\n body?: unknown;\n cache?: string;\n credentials?: string;\n integrity?: string;\n keepalive?: boolean;\n mode?: string;\n redirect?: string;\n referrer?: string;\n referrerPolicy?: string;\n signal?: AbortSignalState;\n } | undefined;\n\n let url = \"\";\n let method = \"GET\";\n let headersState: HeadersState = { headers: new Map() };\n let body: Uint8Array | null = null;\n let signal: AbortSignalState | null = null;\n\n // Handle input\n if (typeof input === \"string\") {\n url = input;\n } else if (input && typeof input === \"object\") {\n // Could be URL or Request-like\n if (\"url\" in input) {\n url = String((input as { url: string }).url);\n }\n if (\"method\" in input) {\n method = String((input as { method: string }).method);\n }\n if (\"headersState\" in input) {\n const inputHeaders = (input as RequestState).headersState;\n headersState = {\n headers: new Map(inputHeaders.headers),\n };\n }\n if (\"body\" in input && (input as RequestState).body) {\n body = (input as RequestState).body;\n }\n if (\"signal\" in input) {\n signal = (input as RequestState).signal;\n }\n }\n\n // Apply init options\n if (init) {\n if (init.method) {\n method = init.method.toUpperCase();\n }\n if (init.headers) {\n if (init.headers && typeof init.headers === \"object\") {\n if (\"headers\" in init.headers && init.headers.headers instanceof Map) {\n headersState = {\n headers: new Map((init.headers as HeadersState).headers),\n };\n } else {\n headersState = { headers: new Map() };\n for (const [key, value] of Object.entries(init.headers)) {\n headersState.headers.set(key.toLowerCase(), [String(value)]);\n }\n }\n }\n }\n if (init.body !== undefined && init.body !== null) {\n if (typeof init.body === \"string\") {\n body = new TextEncoder().encode(init.body);\n } else if (init.body instanceof ArrayBuffer) {\n body = new Uint8Array(init.body);\n } else if (init.body instanceof Uint8Array) {\n body = init.body;\n }\n }\n if (init.signal) {\n signal = init.signal;\n }\n }\n\n return {\n method,\n url,\n headersState,\n body,\n bodyUsed: false,\n cache: init?.cache || \"default\",\n credentials: init?.credentials || \"same-origin\",\n destination: \"\",\n integrity: init?.integrity || \"\",\n keepalive: init?.keepalive || false,\n mode: init?.mode || \"cors\",\n redirect: init?.redirect || \"follow\",\n referrer: init?.referrer || \"about:client\",\n referrerPolicy: init?.referrerPolicy || \"\",\n signal,\n };\n },\n properties: {\n method: {\n get(this: RequestState) {\n return this.method;\n },\n },\n url: {\n get(this: RequestState) {\n return this.url;\n },\n },\n headers: {\n get(this: RequestState) {\n return createHeadersLike(this.headersState);\n },\n },\n body: {\n get(this: RequestState) {\n if (!this.body) return null;\n if (!createStream) {\n // Fallback: return raw body if no stream factory\n return this.body;\n }\n // Create a ReadableStream from the body data\n const bodyData = this.body;\n let offset = 0;\n const chunkSize = 65536; // 64KB chunks\n return createStream({\n pull(controller) {\n if (offset >= bodyData.length) {\n controller.close();\n return;\n }\n const chunk = bodyData.slice(offset, Math.min(offset + chunkSize, bodyData.length));\n offset += chunk.length;\n controller.enqueue(chunk);\n },\n });\n },\n },\n bodyUsed: {\n get(this: RequestState) {\n return this.bodyUsed;\n },\n },\n cache: {\n get(this: RequestState) {\n return this.cache;\n },\n },\n credentials: {\n get(this: RequestState) {\n return this.credentials;\n },\n },\n destination: {\n get(this: RequestState) {\n return this.destination;\n },\n },\n integrity: {\n get(this: RequestState) {\n return this.integrity;\n },\n },\n keepalive: {\n get(this: RequestState) {\n return this.keepalive;\n },\n },\n mode: {\n get(this: RequestState) {\n return this.mode;\n },\n },\n redirect: {\n get(this: RequestState) {\n return this.redirect;\n },\n },\n referrer: {\n get(this: RequestState) {\n return this.referrer;\n },\n },\n referrerPolicy: {\n get(this: RequestState) {\n return this.referrerPolicy;\n },\n },\n signal: {\n get(this: RequestState) {\n return this.signal;\n },\n },\n },\n methods: {\n async arrayBuffer(this: RequestState): Promise<ArrayBuffer> {\n if (this.bodyUsed) {\n throw new TypeError(\"Body has already been consumed\");\n }\n this.bodyUsed = true;\n if (!this.body) {\n return new ArrayBuffer(0);\n }\n return this.body.buffer.slice(\n this.body.byteOffset,\n this.body.byteOffset + this.body.byteLength\n ) as ArrayBuffer;\n },\n async blob(this: RequestState): Promise<object> {\n if (this.bodyUsed) {\n throw new TypeError(\"Body has already been consumed\");\n }\n this.bodyUsed = true;\n const contentType = this.headersState.headers.get(\"content-type\")?.[0] || \"\";\n return {\n parts: this.body ? [this.body] : [],\n type: contentType,\n size: this.body?.length || 0,\n };\n },\n clone(this: RequestState): RequestState {\n if (this.bodyUsed) {\n throw new TypeError(\"Body has already been consumed\");\n }\n return {\n ...this,\n headersState: {\n headers: new Map(this.headersState.headers),\n },\n body: this.body ? new Uint8Array(this.body) : null,\n bodyUsed: false,\n };\n },\n async json(this: RequestState): Promise<unknown> {\n if (this.bodyUsed) {\n throw new TypeError(\"Body has already been consumed\");\n }\n this.bodyUsed = true;\n if (!this.body) {\n return JSON.parse(\"\");\n }\n const text = new TextDecoder().decode(this.body);\n return JSON.parse(text);\n },\n async text(this: RequestState): Promise<string> {\n if (this.bodyUsed) {\n throw new TypeError(\"Body has already been consumed\");\n }\n this.bodyUsed = true;\n if (!this.body) {\n return \"\";\n }\n return new TextDecoder().decode(this.body);\n },\n async formData(this: RequestState): Promise<FormDataState> {\n if (this.bodyUsed) {\n throw new TypeError(\"Body has already been consumed\");\n }\n this.bodyUsed = true;\n if (!this.body) {\n return { entries: [] };\n }\n\n const contentType = this.headersState.headers.get(\"content-type\")?.[0] || \"\";\n\n if (contentType.includes(\"multipart/form-data\")) {\n return parseMultipartFormData(this.body, contentType);\n } else if (contentType.includes(\"application/x-www-form-urlencoded\")) {\n return parseUrlEncodedFormData(this.body);\n }\n\n throw new TypeError(\"Could not parse content as FormData\");\n },\n },\n });\n}\n\n/**\n * Create a RequestState from a native Request object\n */\nexport async function createRequestStateFromNative(\n request: Request\n): Promise<RequestState> {\n const body = request.body\n ? new Uint8Array(await request.arrayBuffer())\n : null;\n\n return {\n method: request.method,\n url: request.url,\n headersState: createHeadersStateFromNative(request.headers),\n body,\n bodyUsed: false,\n cache: request.cache,\n credentials: request.credentials,\n destination: request.destination,\n integrity: request.integrity,\n keepalive: request.keepalive,\n mode: request.mode,\n redirect: request.redirect,\n referrer: request.referrer,\n referrerPolicy: request.referrerPolicy,\n signal: null, // Signal handling is complex, simplified here\n };\n}\n"
|
|
5
|
+
"import type { QuickJSContext, QuickJSHandle } from \"quickjs-emscripten\";\nimport type { StateMap } from \"@ricsam/quickjs-core\";\nimport { defineClass } from \"@ricsam/quickjs-core\";\nimport type { RequestState, HeadersState, AbortSignalState, FormDataState } from \"../types.mjs\";\nimport { createHeadersStateFromNative, createHeadersLike } from \"./headers.mjs\";\nimport { parseMultipartFormData, parseUrlEncodedFormData } from \"./form-data.mjs\";\n\n/**\n * Type for the stream factory function\n */\ntype StreamFactory = (source: UnderlyingSource) => QuickJSHandle;\n\n/**\n * Create the Request class for QuickJS\n */\nexport function createRequestClass(\n context: QuickJSContext,\n stateMap: StateMap,\n createStream?: StreamFactory\n): QuickJSHandle {\n return defineClass<RequestState>(context, stateMap, {\n name: \"Request\",\n construct: (args) => {\n const input = args[0];\n const init = args[1] as {\n method?: string;\n headers?: object;\n body?: unknown;\n cache?: string;\n credentials?: string;\n integrity?: string;\n keepalive?: boolean;\n mode?: string;\n redirect?: string;\n referrer?: string;\n referrerPolicy?: string;\n signal?: AbortSignalState;\n } | undefined;\n\n let url = \"\";\n let method = \"GET\";\n let headersState: HeadersState = { headers: new Map() };\n let body: Uint8Array | null = null;\n let signal: AbortSignalState | null = null;\n\n // Handle input\n if (typeof input === \"string\") {\n url = input;\n } else if (input && typeof input === \"object\") {\n // Could be URL or Request-like\n if (\"url\" in input) {\n url = String((input as { url: string }).url);\n }\n if (\"method\" in input) {\n method = String((input as { method: string }).method);\n }\n if (\"headersState\" in input) {\n const inputHeaders = (input as RequestState).headersState;\n headersState = {\n headers: new Map(inputHeaders.headers),\n };\n }\n if (\"body\" in input && (input as RequestState).body) {\n body = (input as RequestState).body;\n }\n if (\"signal\" in input) {\n signal = (input as RequestState).signal;\n }\n }\n\n // Apply init options\n if (init) {\n if (init.method) {\n method = init.method.toUpperCase();\n }\n if (init.headers) {\n if (init.headers && typeof init.headers === \"object\") {\n if (\"headers\" in init.headers && init.headers.headers instanceof Map) {\n headersState = {\n headers: new Map((init.headers as HeadersState).headers),\n };\n } else {\n headersState = { headers: new Map() };\n for (const [key, value] of Object.entries(init.headers)) {\n headersState.headers.set(key.toLowerCase(), [String(value)]);\n }\n }\n }\n }\n if (init.body !== undefined && init.body !== null) {\n if (typeof init.body === \"string\") {\n body = new TextEncoder().encode(init.body);\n } else if (init.body instanceof ArrayBuffer) {\n body = new Uint8Array(init.body);\n } else if (init.body instanceof Uint8Array) {\n body = init.body;\n }\n }\n if (init.signal) {\n signal = init.signal;\n }\n }\n\n return {\n method,\n url,\n headersState,\n body,\n bodyUsed: false,\n cache: init?.cache || \"default\",\n credentials: init?.credentials || \"same-origin\",\n destination: \"\",\n integrity: init?.integrity || \"\",\n keepalive: init?.keepalive || false,\n mode: init?.mode || \"cors\",\n redirect: init?.redirect || \"follow\",\n referrer: init?.referrer || \"about:client\",\n referrerPolicy: init?.referrerPolicy || \"\",\n signal,\n };\n },\n properties: {\n method: {\n get(this: RequestState) {\n return this.method;\n },\n },\n url: {\n get(this: RequestState) {\n return this.url;\n },\n },\n headers: {\n get(this: RequestState) {\n return createHeadersLike(this.headersState);\n },\n },\n body: {\n get(this: RequestState) {\n if (!this.body) return null;\n if (!createStream) {\n // Fallback: return raw body if no stream factory\n return this.body;\n }\n // Create a ReadableStream from the body data\n const bodyData = this.body;\n let offset = 0;\n const chunkSize = 65536; // 64KB chunks\n return createStream({\n pull(controller) {\n if (offset >= bodyData.length) {\n controller.close();\n return;\n }\n const chunk = bodyData.slice(offset, Math.min(offset + chunkSize, bodyData.length));\n offset += chunk.length;\n controller.enqueue(chunk);\n },\n });\n },\n },\n bodyUsed: {\n get(this: RequestState) {\n return this.bodyUsed;\n },\n },\n cache: {\n get(this: RequestState) {\n return this.cache;\n },\n },\n credentials: {\n get(this: RequestState) {\n return this.credentials;\n },\n },\n destination: {\n get(this: RequestState) {\n return this.destination;\n },\n },\n integrity: {\n get(this: RequestState) {\n return this.integrity;\n },\n },\n keepalive: {\n get(this: RequestState) {\n return this.keepalive;\n },\n },\n mode: {\n get(this: RequestState) {\n return this.mode;\n },\n },\n redirect: {\n get(this: RequestState) {\n return this.redirect;\n },\n },\n referrer: {\n get(this: RequestState) {\n return this.referrer;\n },\n },\n referrerPolicy: {\n get(this: RequestState) {\n return this.referrerPolicy;\n },\n },\n signal: {\n get(this: RequestState) {\n return this.signal;\n },\n },\n },\n methods: {\n async arrayBuffer(this: RequestState): Promise<ArrayBuffer> {\n if (this.bodyUsed) {\n throw new TypeError(\"Body has already been consumed\");\n }\n this.bodyUsed = true;\n if (!this.body) {\n return new ArrayBuffer(0);\n }\n return this.body.buffer.slice(\n this.body.byteOffset,\n this.body.byteOffset + this.body.byteLength\n ) as ArrayBuffer;\n },\n async blob(this: RequestState): Promise<object> {\n if (this.bodyUsed) {\n throw new TypeError(\"Body has already been consumed\");\n }\n this.bodyUsed = true;\n const contentType = this.headersState.headers.get(\"content-type\")?.[0] || \"\";\n return {\n parts: this.body ? [this.body] : [],\n type: contentType,\n size: this.body?.length || 0,\n };\n },\n clone(this: RequestState): RequestState {\n if (this.bodyUsed) {\n throw new TypeError(\"Body has already been consumed\");\n }\n return {\n ...this,\n headersState: {\n headers: new Map(this.headersState.headers),\n },\n body: this.body ? new Uint8Array(this.body) : null,\n bodyUsed: false,\n };\n },\n async json(this: RequestState): Promise<unknown> {\n if (this.bodyUsed) {\n throw new TypeError(\"Body has already been consumed\");\n }\n this.bodyUsed = true;\n if (!this.body) {\n return JSON.parse(\"\");\n }\n const text = new TextDecoder().decode(this.body);\n return JSON.parse(text);\n },\n async text(this: RequestState): Promise<string> {\n if (this.bodyUsed) {\n throw new TypeError(\"Body has already been consumed\");\n }\n this.bodyUsed = true;\n if (!this.body) {\n return \"\";\n }\n return new TextDecoder().decode(this.body);\n },\n /**\n * Private method that returns raw FormData entries.\n * Used by the formData() method added via evalCode (see addRequestFormDataMethod).\n *\n * Note: File data is converted to plain number arrays to avoid memory issues\n * when marshalling Uint8Array between host and QuickJS contexts.\n */\n async __getFormDataEntries__(this: RequestState): Promise<{\n entries: Array<{\n name: string;\n value: string | { __formDataFile__: true; data: number[]; filename: string; type: string };\n }>;\n }> {\n if (this.bodyUsed) {\n throw new TypeError(\"Body has already been consumed\");\n }\n this.bodyUsed = true;\n if (!this.body) {\n return { entries: [] };\n }\n\n const contentType = this.headersState.headers.get(\"content-type\")?.[0] || \"\";\n\n let formDataState: FormDataState;\n if (contentType.includes(\"multipart/form-data\")) {\n formDataState = parseMultipartFormData(this.body, contentType);\n } else if (contentType.includes(\"application/x-www-form-urlencoded\")) {\n formDataState = parseUrlEncodedFormData(this.body);\n } else {\n throw new TypeError(\"Could not parse content as FormData\");\n }\n\n // Convert Uint8Array data to plain number arrays for safe marshalling\n // Include marker so FormData.get() can reconstruct File instances\n type SerializedFileValue = { __formDataFile__: true; data: number[]; filename: string; type: string };\n type SerializedEntry = { name: string; value: string | SerializedFileValue };\n return {\n entries: formDataState.entries.map((entry): SerializedEntry => {\n if (typeof entry.value === \"string\") {\n return { name: entry.name, value: entry.value };\n }\n // Convert Uint8Array to number array and include marker\n return {\n name: entry.name,\n value: {\n __formDataFile__: true,\n data: Array.from(entry.value.data),\n filename: entry.value.filename,\n type: entry.value.type,\n },\n };\n }),\n };\n },\n },\n });\n}\n\n/**\n * Add the formData() method to Request.prototype via evalCode.\n *\n * This must be called AFTER both Request and FormData classes are on global.\n * The method creates a proper FormData instance with all entries.\n *\n * @see PATTERNS.md section 2 (Class Methods That Return Instances)\n */\nexport function addRequestFormDataMethod(context: QuickJSContext): void {\n const result = context.evalCode(`\n Request.prototype.formData = async function() {\n // Get raw entries from private method\n // Note: File data comes as plain number arrays (converted in host side)\n const rawData = await this.__getFormDataEntries__();\n\n // Create a proper FormData instance\n const formData = new FormData();\n\n // Populate with entries\n // FormData.append handles both string values and file-like objects\n // with number arrays (converted to Uint8Array on the host side)\n for (const entry of rawData.entries) {\n formData.append(entry.name, entry.value);\n }\n\n return formData;\n };\n `);\n\n if (result.error) {\n result.error.dispose();\n } else {\n result.value.dispose();\n }\n}\n\n/**\n * Create a RequestState from a native Request object\n */\nexport async function createRequestStateFromNative(\n request: Request\n): Promise<RequestState> {\n const body = request.body\n ? new Uint8Array(await request.arrayBuffer())\n : null;\n\n return {\n method: request.method,\n url: request.url,\n headersState: createHeadersStateFromNative(request.headers),\n body,\n bodyUsed: false,\n cache: request.cache,\n credentials: request.credentials,\n destination: request.destination,\n integrity: request.integrity,\n keepalive: request.keepalive,\n mode: request.mode,\n redirect: request.redirect,\n referrer: request.referrer,\n referrerPolicy: request.referrerPolicy,\n signal: null, // Signal handling is complex, simplified here\n };\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "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
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "9CDFB08BC3F7DEBF64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/dist/mjs/package.json
CHANGED
package/dist/mjs/setup.mjs
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
// packages/fetch/src/setup.ts
|
|
3
3
|
import { setupCore, createReadableStream } from "@ricsam/quickjs-core";
|
|
4
4
|
import { createHeadersClass } from "./globals/headers.mjs";
|
|
5
|
-
import { createRequestClass } from "./globals/request.mjs";
|
|
5
|
+
import { createRequestClass, addRequestFormDataMethod } from "./globals/request.mjs";
|
|
6
6
|
import { createResponseClass, addResponseStaticMethods } from "./globals/response.mjs";
|
|
7
7
|
import { setupAbortControllerAndSignal } from "./globals/abort-controller.mjs";
|
|
8
|
-
import { createFormDataClass } from "./globals/form-data.mjs";
|
|
8
|
+
import { createFormDataClass, addFormDataFileMethods } from "./globals/form-data.mjs";
|
|
9
9
|
import { createFetchFunction } from "./globals/fetch.mjs";
|
|
10
10
|
import {
|
|
11
11
|
createServeFunction,
|
|
@@ -65,6 +65,8 @@ function setupFetch(context, options = {}) {
|
|
|
65
65
|
} else {
|
|
66
66
|
formDataIteratorResult.value.dispose();
|
|
67
67
|
}
|
|
68
|
+
addRequestFormDataMethod(context);
|
|
69
|
+
addFormDataFileMethods(context);
|
|
68
70
|
const ServerWebSocketClass = createServerWebSocketClass(context, stateMap, dispatchWsCommand);
|
|
69
71
|
context.setProp(context.global, "__ServerWebSocket__", ServerWebSocketClass);
|
|
70
72
|
ServerWebSocketClass.dispose();
|
|
@@ -89,4 +91,4 @@ export {
|
|
|
89
91
|
setupFetch
|
|
90
92
|
};
|
|
91
93
|
|
|
92
|
-
//# debugId=
|
|
94
|
+
//# debugId=E9661F932BB0303C64756E2164756E21
|
package/dist/mjs/setup.mjs.map
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/setup.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import type { QuickJSContext, QuickJSHandle } from \"quickjs-emscripten\";\nimport { setupCore, createStateMap, createReadableStream } from \"@ricsam/quickjs-core\";\nimport type { SetupFetchOptions, FetchHandle, ServeState } from \"./types.mjs\";\nimport { createHeadersClass } from \"./globals/headers.mjs\";\nimport { createRequestClass } from \"./globals/request.mjs\";\nimport { createResponseClass, addResponseStaticMethods } from \"./globals/response.mjs\";\nimport { setupAbortControllerAndSignal } from \"./globals/abort-controller.mjs\";\nimport { createFormDataClass } from \"./globals/form-data.mjs\";\nimport { createFetchFunction } from \"./globals/fetch.mjs\";\nimport {\n createServeFunction,\n createServerClass,\n createServerWebSocketClass,\n} from \"./globals/serve.mjs\";\nimport { createFetchHandle } from \"./handle.mjs\";\n\n/**\n * Setup Fetch API in a QuickJS context\n *\n * Injects the following globals:\n * - fetch\n * - Request\n * - Response\n * - Headers\n * - AbortController\n * - AbortSignal\n * - serve\n * - FormData\n *\n * Also sets up Core APIs (Streams, Blob, File) if not already present.\n *\n * **Private globals (internal use):**\n * - `__Server__` - Server class for serve() handler, instantiated via evalCode\n * - `__ServerWebSocket__` - WebSocket class for connection handling\n * - `__scheduleTimeout__` - Host function to schedule AbortSignal.timeout()\n * - `__checkTimeout__` - Host function to check if timeout elapsed\n *\n * These private globals follow the `__Name__` convention and are required for\n * JavaScript code in QuickJS to create class instances via evalCode.\n * See PATTERNS.md section 5 for details.\n *\n * @example\n * const handle = setupFetch(context, {\n * onFetch: async (request) => {\n * // Proxy to real fetch\n * return fetch(request);\n * }\n * });\n *\n * context.evalCode(`\n * serve({\n * fetch(request, server) {\n * return new Response(\"Hello!\");\n * }\n * });\n * `);\n *\n * const response = await handle.dispatchRequest(\n * new Request(\"http://localhost/\")\n * );\n */\nexport function setupFetch(\n context: QuickJSContext,\n options: SetupFetchOptions = {}\n): FetchHandle {\n // Setup core if not already done\n const coreHandle =\n options.coreHandle ??\n setupCore(context, {\n stateMap: options.stateMap,\n });\n\n const stateMap = options.stateMap ?? coreHandle.stateMap;\n\n // Create serve state\n const serveState: ServeState = {\n fetchHandler: null,\n websocketHandlers: {},\n pendingUpgrade: null,\n activeConnections: new Map(),\n };\n\n // WebSocket command dispatcher\n const wsCommandCallbacks = new Set<\n (cmd: import(\"./types.mjs\").WebSocketCommand) => void\n >();\n const dispatchWsCommand = (cmd: import(\"./types.mjs\").WebSocketCommand) => {\n for (const cb of wsCommandCallbacks) {\n cb(cmd);\n }\n };\n\n // Create stream factory for Request/Response body\n const streamFactory = (source: UnderlyingSource) =>\n createReadableStream(context, stateMap, source);\n\n // Create Headers class\n const HeadersClass = createHeadersClass(context, stateMap);\n context.setProp(context.global, \"Headers\", HeadersClass);\n HeadersClass.dispose();\n\n // Add Symbol.iterator support for Headers (for...of, Array.from, spread)\n const iteratorResult = context.evalCode(`\n Headers.prototype[Symbol.iterator] = function() {\n return this.entries()[Symbol.iterator]();\n };\n `);\n if (iteratorResult.error) {\n iteratorResult.error.dispose();\n } else {\n iteratorResult.value.dispose();\n }\n\n // Create Request class\n const RequestClass = createRequestClass(context, stateMap, streamFactory);\n context.setProp(context.global, \"Request\", RequestClass);\n RequestClass.dispose();\n\n // Create Response class\n const ResponseClass = createResponseClass(context, stateMap, streamFactory);\n context.setProp(context.global, \"Response\", ResponseClass);\n ResponseClass.dispose();\n\n // Add Response static methods (must be after Response and Headers are on global)\n addResponseStaticMethods(context);\n\n // Create AbortSignal and AbortController classes (pure QuickJS implementation)\n setupAbortControllerAndSignal(context);\n\n // Create FormData class\n const FormDataClass = createFormDataClass(context, stateMap);\n context.setProp(context.global, \"FormData\", FormDataClass);\n FormDataClass.dispose();\n\n // Add Symbol.iterator support for FormData (for...of, Array.from, spread)\n const formDataIteratorResult = context.evalCode(`\n FormData.prototype[Symbol.iterator] = function() {\n return this.entries()[Symbol.iterator]();\n };\n `);\n if (formDataIteratorResult.error) {\n formDataIteratorResult.error.dispose();\n } else {\n formDataIteratorResult.value.dispose();\n }\n\n // Create ServerWebSocket class (internal, for WebSocket handling)\n const ServerWebSocketClass = createServerWebSocketClass(\n context,\n stateMap,\n dispatchWsCommand\n );\n // Set on global with internal name so we can use evalCode to instantiate\n context.setProp(context.global, \"__ServerWebSocket__\", ServerWebSocketClass);\n ServerWebSocketClass.dispose();\n // Note: ServerWebSocketClass handle is now owned by global\n\n // Create Server class (internal, passed to fetch handler)\n const ServerClass = createServerClass(context, stateMap, serveState);\n // Set on global with internal name so we can use evalCode to instantiate\n context.setProp(context.global, \"__Server__\", ServerClass);\n ServerClass.dispose();\n // Note: ServerClass handle is now owned by global\n\n // Create fetch function\n const fetchFn = createFetchFunction(context, options.onFetch);\n context.setProp(context.global, \"fetch\", fetchFn);\n fetchFn.dispose();\n\n // Create serve function\n const serveFn = createServeFunction(context, stateMap, serveState);\n context.setProp(context.global, \"serve\", serveFn);\n serveFn.dispose();\n\n // Create and return the handle\n const fetchHandle = createFetchHandle(\n context,\n stateMap,\n serveState\n );\n\n // Wire up WebSocket command callbacks\n const originalOnWebSocketCommand = fetchHandle.onWebSocketCommand;\n fetchHandle.onWebSocketCommand = (callback) => {\n wsCommandCallbacks.add(callback);\n return () => wsCommandCallbacks.delete(callback);\n };\n\n return fetchHandle;\n}\n"
|
|
5
|
+
"import type { QuickJSContext, QuickJSHandle } from \"quickjs-emscripten\";\nimport { setupCore, createStateMap, createReadableStream } from \"@ricsam/quickjs-core\";\nimport type { SetupFetchOptions, FetchHandle, ServeState } from \"./types.mjs\";\nimport { createHeadersClass } from \"./globals/headers.mjs\";\nimport { createRequestClass, addRequestFormDataMethod } from \"./globals/request.mjs\";\nimport { createResponseClass, addResponseStaticMethods } from \"./globals/response.mjs\";\nimport { setupAbortControllerAndSignal } from \"./globals/abort-controller.mjs\";\nimport { createFormDataClass, addFormDataFileMethods } from \"./globals/form-data.mjs\";\nimport { createFetchFunction } from \"./globals/fetch.mjs\";\nimport {\n createServeFunction,\n createServerClass,\n createServerWebSocketClass,\n} from \"./globals/serve.mjs\";\nimport { createFetchHandle } from \"./handle.mjs\";\n\n/**\n * Setup Fetch API in a QuickJS context\n *\n * Injects the following globals:\n * - fetch\n * - Request\n * - Response\n * - Headers\n * - AbortController\n * - AbortSignal\n * - serve\n * - FormData\n *\n * Also sets up Core APIs (Streams, Blob, File) if not already present.\n *\n * **Private globals (internal use):**\n * - `__Server__` - Server class for serve() handler, instantiated via evalCode\n * - `__ServerWebSocket__` - WebSocket class for connection handling\n * - `__scheduleTimeout__` - Host function to schedule AbortSignal.timeout()\n * - `__checkTimeout__` - Host function to check if timeout elapsed\n *\n * These private globals follow the `__Name__` convention and are required for\n * JavaScript code in QuickJS to create class instances via evalCode.\n * See PATTERNS.md section 5 for details.\n *\n * @example\n * const handle = setupFetch(context, {\n * onFetch: async (request) => {\n * // Proxy to real fetch\n * return fetch(request);\n * }\n * });\n *\n * context.evalCode(`\n * serve({\n * fetch(request, server) {\n * return new Response(\"Hello!\");\n * }\n * });\n * `);\n *\n * const response = await handle.dispatchRequest(\n * new Request(\"http://localhost/\")\n * );\n */\nexport function setupFetch(\n context: QuickJSContext,\n options: SetupFetchOptions = {}\n): FetchHandle {\n // Setup core if not already done\n const coreHandle =\n options.coreHandle ??\n setupCore(context, {\n stateMap: options.stateMap,\n });\n\n const stateMap = options.stateMap ?? coreHandle.stateMap;\n\n // Create serve state\n const serveState: ServeState = {\n fetchHandler: null,\n websocketHandlers: {},\n pendingUpgrade: null,\n activeConnections: new Map(),\n };\n\n // WebSocket command dispatcher\n const wsCommandCallbacks = new Set<\n (cmd: import(\"./types.mjs\").WebSocketCommand) => void\n >();\n const dispatchWsCommand = (cmd: import(\"./types.mjs\").WebSocketCommand) => {\n for (const cb of wsCommandCallbacks) {\n cb(cmd);\n }\n };\n\n // Create stream factory for Request/Response body\n const streamFactory = (source: UnderlyingSource) =>\n createReadableStream(context, stateMap, source);\n\n // Create Headers class\n const HeadersClass = createHeadersClass(context, stateMap);\n context.setProp(context.global, \"Headers\", HeadersClass);\n HeadersClass.dispose();\n\n // Add Symbol.iterator support for Headers (for...of, Array.from, spread)\n const iteratorResult = context.evalCode(`\n Headers.prototype[Symbol.iterator] = function() {\n return this.entries()[Symbol.iterator]();\n };\n `);\n if (iteratorResult.error) {\n iteratorResult.error.dispose();\n } else {\n iteratorResult.value.dispose();\n }\n\n // Create Request class\n const RequestClass = createRequestClass(context, stateMap, streamFactory);\n context.setProp(context.global, \"Request\", RequestClass);\n RequestClass.dispose();\n\n // Create Response class\n const ResponseClass = createResponseClass(context, stateMap, streamFactory);\n context.setProp(context.global, \"Response\", ResponseClass);\n ResponseClass.dispose();\n\n // Add Response static methods (must be after Response and Headers are on global)\n addResponseStaticMethods(context);\n\n // Create AbortSignal and AbortController classes (pure QuickJS implementation)\n setupAbortControllerAndSignal(context);\n\n // Create FormData class\n const FormDataClass = createFormDataClass(context, stateMap);\n context.setProp(context.global, \"FormData\", FormDataClass);\n FormDataClass.dispose();\n\n // Add Symbol.iterator support for FormData (for...of, Array.from, spread)\n const formDataIteratorResult = context.evalCode(`\n FormData.prototype[Symbol.iterator] = function() {\n return this.entries()[Symbol.iterator]();\n };\n `);\n if (formDataIteratorResult.error) {\n formDataIteratorResult.error.dispose();\n } else {\n formDataIteratorResult.value.dispose();\n }\n\n // Add Request.formData() method that returns a proper FormData instance\n // Must be after both Request and FormData are on global\n addRequestFormDataMethod(context);\n\n // Add FormData.get()/getAll() overrides to reconstruct File instances\n // Must be after both FormData and File are on global\n addFormDataFileMethods(context);\n\n // Create ServerWebSocket class (internal, for WebSocket handling)\n const ServerWebSocketClass = createServerWebSocketClass(\n context,\n stateMap,\n dispatchWsCommand\n );\n // Set on global with internal name so we can use evalCode to instantiate\n context.setProp(context.global, \"__ServerWebSocket__\", ServerWebSocketClass);\n ServerWebSocketClass.dispose();\n // Note: ServerWebSocketClass handle is now owned by global\n\n // Create Server class (internal, passed to fetch handler)\n const ServerClass = createServerClass(context, stateMap, serveState);\n // Set on global with internal name so we can use evalCode to instantiate\n context.setProp(context.global, \"__Server__\", ServerClass);\n ServerClass.dispose();\n // Note: ServerClass handle is now owned by global\n\n // Create fetch function\n const fetchFn = createFetchFunction(context, options.onFetch);\n context.setProp(context.global, \"fetch\", fetchFn);\n fetchFn.dispose();\n\n // Create serve function\n const serveFn = createServeFunction(context, stateMap, serveState);\n context.setProp(context.global, \"serve\", serveFn);\n serveFn.dispose();\n\n // Create and return the handle\n const fetchHandle = createFetchHandle(\n context,\n stateMap,\n serveState\n );\n\n // Wire up WebSocket command callbacks\n const originalOnWebSocketCommand = fetchHandle.onWebSocketCommand;\n fetchHandle.onWebSocketCommand = (callback) => {\n wsCommandCallbacks.add(callback);\n return () => wsCommandCallbacks.delete(callback);\n };\n\n return fetchHandle;\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AAAA;AAKA;AA+CO,SAAS,UAAU,CACxB,SACA,UAA6B,CAAC,GACjB;AAAA,EAEb,MAAM,aACJ,QAAQ,cACR,UAAU,SAAS;AAAA,IACjB,UAAU,QAAQ;AAAA,EACpB,CAAC;AAAA,EAEH,MAAM,WAAW,QAAQ,YAAY,WAAW;AAAA,EAGhD,MAAM,aAAyB;AAAA,IAC7B,cAAc;AAAA,IACd,mBAAmB,CAAC;AAAA,IACpB,gBAAgB;AAAA,IAChB,mBAAmB,IAAI;AAAA,EACzB;AAAA,EAGA,MAAM,qBAAqB,IAAI;AAAA,EAG/B,MAAM,oBAAoB,CAAC,QAAgD;AAAA,IACzE,WAAW,MAAM,oBAAoB;AAAA,MACnC,GAAG,GAAG;AAAA,IACR;AAAA;AAAA,EAIF,MAAM,gBAAgB,CAAC,WACrB,qBAAqB,SAAS,UAAU,MAAM;AAAA,EAGhD,MAAM,eAAe,mBAAmB,SAAS,QAAQ;AAAA,EACzD,QAAQ,QAAQ,QAAQ,QAAQ,WAAW,YAAY;AAAA,EACvD,aAAa,QAAQ;AAAA,EAGrB,MAAM,iBAAiB,QAAQ,SAAS;AAAA;AAAA;AAAA;AAAA,GAIvC;AAAA,EACD,IAAI,eAAe,OAAO;AAAA,IACxB,eAAe,MAAM,QAAQ;AAAA,EAC/B,EAAO;AAAA,IACL,eAAe,MAAM,QAAQ;AAAA;AAAA,EAI/B,MAAM,eAAe,mBAAmB,SAAS,UAAU,aAAa;AAAA,EACxE,QAAQ,QAAQ,QAAQ,QAAQ,WAAW,YAAY;AAAA,EACvD,aAAa,QAAQ;AAAA,EAGrB,MAAM,gBAAgB,oBAAoB,SAAS,UAAU,aAAa;AAAA,EAC1E,QAAQ,QAAQ,QAAQ,QAAQ,YAAY,aAAa;AAAA,EACzD,cAAc,QAAQ;AAAA,EAGtB,yBAAyB,OAAO;AAAA,EAGhC,8BAA8B,OAAO;AAAA,EAGrC,MAAM,gBAAgB,oBAAoB,SAAS,QAAQ;AAAA,EAC3D,QAAQ,QAAQ,QAAQ,QAAQ,YAAY,aAAa;AAAA,EACzD,cAAc,QAAQ;AAAA,EAGtB,MAAM,yBAAyB,QAAQ,SAAS;AAAA;AAAA;AAAA;AAAA,GAI/C;AAAA,EACD,IAAI,uBAAuB,OAAO;AAAA,IAChC,uBAAuB,MAAM,QAAQ;AAAA,EACvC,EAAO;AAAA,IACL,uBAAuB,MAAM,QAAQ;AAAA;AAAA,
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": ";;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AAAA;AAKA;AA+CO,SAAS,UAAU,CACxB,SACA,UAA6B,CAAC,GACjB;AAAA,EAEb,MAAM,aACJ,QAAQ,cACR,UAAU,SAAS;AAAA,IACjB,UAAU,QAAQ;AAAA,EACpB,CAAC;AAAA,EAEH,MAAM,WAAW,QAAQ,YAAY,WAAW;AAAA,EAGhD,MAAM,aAAyB;AAAA,IAC7B,cAAc;AAAA,IACd,mBAAmB,CAAC;AAAA,IACpB,gBAAgB;AAAA,IAChB,mBAAmB,IAAI;AAAA,EACzB;AAAA,EAGA,MAAM,qBAAqB,IAAI;AAAA,EAG/B,MAAM,oBAAoB,CAAC,QAAgD;AAAA,IACzE,WAAW,MAAM,oBAAoB;AAAA,MACnC,GAAG,GAAG;AAAA,IACR;AAAA;AAAA,EAIF,MAAM,gBAAgB,CAAC,WACrB,qBAAqB,SAAS,UAAU,MAAM;AAAA,EAGhD,MAAM,eAAe,mBAAmB,SAAS,QAAQ;AAAA,EACzD,QAAQ,QAAQ,QAAQ,QAAQ,WAAW,YAAY;AAAA,EACvD,aAAa,QAAQ;AAAA,EAGrB,MAAM,iBAAiB,QAAQ,SAAS;AAAA;AAAA;AAAA;AAAA,GAIvC;AAAA,EACD,IAAI,eAAe,OAAO;AAAA,IACxB,eAAe,MAAM,QAAQ;AAAA,EAC/B,EAAO;AAAA,IACL,eAAe,MAAM,QAAQ;AAAA;AAAA,EAI/B,MAAM,eAAe,mBAAmB,SAAS,UAAU,aAAa;AAAA,EACxE,QAAQ,QAAQ,QAAQ,QAAQ,WAAW,YAAY;AAAA,EACvD,aAAa,QAAQ;AAAA,EAGrB,MAAM,gBAAgB,oBAAoB,SAAS,UAAU,aAAa;AAAA,EAC1E,QAAQ,QAAQ,QAAQ,QAAQ,YAAY,aAAa;AAAA,EACzD,cAAc,QAAQ;AAAA,EAGtB,yBAAyB,OAAO;AAAA,EAGhC,8BAA8B,OAAO;AAAA,EAGrC,MAAM,gBAAgB,oBAAoB,SAAS,QAAQ;AAAA,EAC3D,QAAQ,QAAQ,QAAQ,QAAQ,YAAY,aAAa;AAAA,EACzD,cAAc,QAAQ;AAAA,EAGtB,MAAM,yBAAyB,QAAQ,SAAS;AAAA;AAAA;AAAA;AAAA,GAI/C;AAAA,EACD,IAAI,uBAAuB,OAAO;AAAA,IAChC,uBAAuB,MAAM,QAAQ;AAAA,EACvC,EAAO;AAAA,IACL,uBAAuB,MAAM,QAAQ;AAAA;AAAA,EAKvC,yBAAyB,OAAO;AAAA,EAIhC,uBAAuB,OAAO;AAAA,EAG9B,MAAM,uBAAuB,2BAC3B,SACA,UACA,iBACF;AAAA,EAEA,QAAQ,QAAQ,QAAQ,QAAQ,uBAAuB,oBAAoB;AAAA,EAC3E,qBAAqB,QAAQ;AAAA,EAI7B,MAAM,cAAc,kBAAkB,SAAS,UAAU,UAAU;AAAA,EAEnE,QAAQ,QAAQ,QAAQ,QAAQ,cAAc,WAAW;AAAA,EACzD,YAAY,QAAQ;AAAA,EAIpB,MAAM,UAAU,oBAAoB,SAAS,QAAQ,OAAO;AAAA,EAC5D,QAAQ,QAAQ,QAAQ,QAAQ,SAAS,OAAO;AAAA,EAChD,QAAQ,QAAQ;AAAA,EAGhB,MAAM,UAAU,oBAAoB,SAAS,UAAU,UAAU;AAAA,EACjE,QAAQ,QAAQ,QAAQ,QAAQ,SAAS,OAAO;AAAA,EAChD,QAAQ,QAAQ;AAAA,EAGhB,MAAM,cAAc,kBAClB,SACA,UACA,UACF;AAAA,EAGA,MAAM,6BAA6B,YAAY;AAAA,EAC/C,YAAY,qBAAqB,CAAC,aAAa;AAAA,IAC7C,mBAAmB,IAAI,QAAQ;AAAA,IAC/B,OAAO,MAAM,mBAAmB,OAAO,QAAQ;AAAA;AAAA,EAGjD,OAAO;AAAA;",
|
|
8
|
+
"debugId": "E9661F932BB0303C64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { QuickJSContext, QuickJSHandle } from "quickjs-emscripten";
|
|
2
2
|
import type { StateMap } from "@ricsam/quickjs-core";
|
|
3
|
+
/**
|
|
4
|
+
* Marker to identify FormData file entries that need File reconstruction
|
|
5
|
+
*/
|
|
6
|
+
export declare const FORMDATA_FILE_MARKER = "__formDataFile__";
|
|
3
7
|
/**
|
|
4
8
|
* Internal state for FormData entries
|
|
5
9
|
* Each entry can have multiple values (same key appended multiple times)
|
|
@@ -9,6 +13,7 @@ export interface FormDataEntry {
|
|
|
9
13
|
value: string | FormDataFileValue;
|
|
10
14
|
}
|
|
11
15
|
export interface FormDataFileValue {
|
|
16
|
+
[FORMDATA_FILE_MARKER]: true;
|
|
12
17
|
data: Uint8Array;
|
|
13
18
|
filename: string;
|
|
14
19
|
type: string;
|
|
@@ -35,3 +40,12 @@ export declare function serializeFormData(state: FormDataState): {
|
|
|
35
40
|
body: Uint8Array;
|
|
36
41
|
contentType: string;
|
|
37
42
|
};
|
|
43
|
+
/**
|
|
44
|
+
* Add JavaScript overrides for FormData.get() and FormData.getAll() to reconstruct File instances
|
|
45
|
+
*
|
|
46
|
+
* This is needed because defineClass methods can only return plain objects when marshalling,
|
|
47
|
+
* but we want formData.get("file") to return actual File instances with working methods.
|
|
48
|
+
*
|
|
49
|
+
* @param context The QuickJS context (must have FormData and File classes already defined)
|
|
50
|
+
*/
|
|
51
|
+
export declare function addFormDataFileMethods(context: QuickJSContext): void;
|
|
@@ -9,6 +9,15 @@ type StreamFactory = (source: UnderlyingSource) => QuickJSHandle;
|
|
|
9
9
|
* Create the Request class for QuickJS
|
|
10
10
|
*/
|
|
11
11
|
export declare function createRequestClass(context: QuickJSContext, stateMap: StateMap, createStream?: StreamFactory): QuickJSHandle;
|
|
12
|
+
/**
|
|
13
|
+
* Add the formData() method to Request.prototype via evalCode.
|
|
14
|
+
*
|
|
15
|
+
* This must be called AFTER both Request and FormData classes are on global.
|
|
16
|
+
* The method creates a proper FormData instance with all entries.
|
|
17
|
+
*
|
|
18
|
+
* @see PATTERNS.md section 2 (Class Methods That Return Instances)
|
|
19
|
+
*/
|
|
20
|
+
export declare function addRequestFormDataMethod(context: QuickJSContext): void;
|
|
12
21
|
/**
|
|
13
22
|
* Create a RequestState from a native Request object
|
|
14
23
|
*/
|
package/dist/types/types.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ricsam/quickjs-fetch",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"main": "./dist/cjs/index.cjs",
|
|
5
5
|
"types": "./dist/types/index.d.ts",
|
|
6
6
|
"exports": {
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"typecheck": "tsc --noEmit"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@ricsam/quickjs-core": "^0.2.
|
|
19
|
+
"@ricsam/quickjs-core": "^0.2.5",
|
|
20
20
|
"quickjs-emscripten": "^0.31.0"
|
|
21
21
|
},
|
|
22
22
|
"peerDependencies": {
|