@ricsam/quickjs-fetch 0.2.3 → 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/handle.cjs +12 -2
- package/dist/cjs/handle.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/handle.mjs +12 -2
- package/dist/mjs/handle.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": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEiE,IAAjE;AAKO,IAAM,uBAAuB;AAqCpC,SAAS,WAAW,CAAC,OAA4C;AAAA,EAC/D,OACE,UAAU,QACV,OAAO,UAAU,YACjB,wBAAwB,SACvB,MAA4B,0BAA0B;AAAA;AAOpD,SAAS,mBAAmB,CACjC,SACA,UACe;AAAA,EACf,OAAO,gCAA2B,SAAS,UAAU;AAAA,IACnD,MAAM;AAAA,IACN,WAAW,MAAM;AAAA,MACf,OAAO,EAAE,SAAS,CAAC,EAAE;AAAA;AAAA,IAEvB,SAAS;AAAA,MACP,MAAM,CAAsB,MAAe,OAAgB,UAAoB;AAAA,QAC7E,MAAM,UAAU,OAAO,IAAI;AAAA,QAG3B,IAAI,iCAAa,OAAO,MAAM,GAAG;AAAA,UAC/B,MAAM,YAAY,0CAAyC,KAAK;AAAA,UAChE,IAAI,WAAW;AAAA,YACb,MAAM,OAAO,iBAAiB,UAAU,KAAK;AAAA,YAC7C,MAAM,YAA+B;AAAA,eAClC,uBAAuB;AAAA,cACxB;AAAA,cACA,UAAU,aAAa,YAAY,OAAO,QAAQ,IAAI,UAAU;AAAA,cAChE,MAAM,UAAU,QAAQ;AAAA,YAC1B;AAAA,YACA,KAAK,QAAQ,KAAK,EAAE,MAAM,SAAS,OAAO,UAAU,CAAC;AAAA,YACrD;AAAA,UACF;AAAA,QACF;AAAA,QAGA,IAAI,iCAAa,OAAO,MAAM,GAAG;AAAA,UAC/B,MAAM,YAAY,0CAAyC,KAAK;AAAA,UAChE,IAAI,WAAW;AAAA,YACb,MAAM,OAAO,iBAAiB,UAAU,KAAK;AAAA,YAC7C,MAAM,YAA+B;AAAA,eAClC,uBAAuB;AAAA,cACxB;AAAA,cACA,UAAU,aAAa,YAAY,OAAO,QAAQ,IAAI;AAAA,cACtD,MAAM,UAAU,QAAQ;AAAA,YAC1B;AAAA,YACA,KAAK,QAAQ,KAAK,EAAE,MAAM,SAAS,OAAO,UAAU,CAAC;AAAA,YACrD;AAAA,UACF;AAAA,QACF;AAAA,QAGA,IAAI,SAAS,OAAO,UAAU,YAAY,WAAW,OAAO;AAAA,UAC1D,MAAM,WAAW;AAAA,UACjB,MAAM,OAAO,iBAAiB,SAAS,KAAK;AAAA,UAC5C,MAAM,YAA+B;AAAA,aAClC,uBAAuB;AAAA,YACxB;AAAA,YACA,UAAU,aAAa,YAAY,OAAO,QAAQ,IAAK,SAAS,QAAQ;AAAA,YACxE,MAAM,SAAS,QAAQ;AAAA,UACzB;AAAA,UACA,KAAK,QAAQ,KAAK,EAAE,MAAM,SAAS,OAAO,UAAU,CAAC;AAAA,QACvD,EAAO,SAAI,SAAS,OAAO,UAAU,YAAY,UAAU,SAAS,cAAc,OAAO;AAAA,UAEvF,MAAM,UAAU;AAAA,UAEhB,MAAM,OAAO,MAAM,QAAQ,QAAQ,IAAI,IACnC,IAAI,WAAW,QAAQ,IAAI,IAC3B,QAAQ;AAAA,UACZ,KAAK,QAAQ,KAAK;AAAA,YAChB,MAAM;AAAA,YACN,OAAO;AAAA,eACJ,uBAAuB;AAAA,cACxB;AAAA,cACA,UAAU,aAAa,YAAY,OAAO,QAAQ,IAAI,QAAQ;AAAA,cAC9D,MAAM,QAAQ;AAAA,YAChB;AAAA,UACF,CAAC;AAAA,QACH,EAAO;AAAA,UAEL,KAAK,QAAQ,KAAK,EAAE,MAAM,SAAS,OAAO,OAAO,KAAK,EAAE,CAAC;AAAA;AAAA;AAAA,MAG7D,MAAM,CAAsB,MAAe;AAAA,QACzC,MAAM,UAAU,OAAO,IAAI;AAAA,QAC3B,KAAK,UAAU,KAAK,QAAQ,OAAO,CAAC,MAAM,EAAE,SAAS,OAAO;AAAA;AAAA,MAE9D,GAAG,CAAsB,MAAkD;AAAA,QACzE,MAAM,UAAU,OAAO,IAAI;AAAA,QAC3B,MAAM,QAAQ,KAAK,QAAQ,KAAK,CAAC,MAAM,EAAE,SAAS,OAAO;AAAA,QACzD,OAAO,QAAQ,MAAM,QAAQ;AAAA;AAAA,MAE/B,MAAM,CAAsB,MAAkD;AAAA,QAC5E,MAAM,UAAU,OAAO,IAAI;AAAA,QAC3B,OAAO,KAAK,QACT,OAAO,CAAC,MAAM,EAAE,SAAS,OAAO,EAChC,IAAI,CAAC,MAAM,EAAE,KAAK;AAAA;AAAA,MAEvB,GAAG,CAAsB,MAAwB;AAAA,QAC/C,MAAM,UAAU,OAAO,IAAI;AAAA,QAC3B,OAAO,KAAK,QAAQ,KAAK,CAAC,MAAM,EAAE,SAAS,OAAO;AAAA;AAAA,MAEpD,GAAG,CAAsB,MAAe,OAAgB,UAAoB;AAAA,QAC1E,MAAM,UAAU,OAAO,IAAI;AAAA,QAE3B,KAAK,UAAU,KAAK,QAAQ,OAAO,CAAC,MAAM,EAAE,SAAS,OAAO;AAAA,QAG5D,IAAI,iCAAa,OAAO,MAAM,GAAG;AAAA,UAC/B,MAAM,YAAY,0CAAyC,KAAK;AAAA,UAChE,IAAI,WAAW;AAAA,YACb,MAAM,OAAO,iBAAiB,UAAU,KAAK;AAAA,YAC7C,MAAM,YAA+B;AAAA,eAClC,uBAAuB;AAAA,cACxB;AAAA,cACA,UAAU,aAAa,YAAY,OAAO,QAAQ,IAAI,UAAU;AAAA,cAChE,MAAM,UAAU,QAAQ;AAAA,YAC1B;AAAA,YACA,KAAK,QAAQ,KAAK,EAAE,MAAM,SAAS,OAAO,UAAU,CAAC;AAAA,YACrD;AAAA,UACF;AAAA,QACF;AAAA,QAGA,IAAI,iCAAa,OAAO,MAAM,GAAG;AAAA,UAC/B,MAAM,YAAY,0CAAyC,KAAK;AAAA,UAChE,IAAI,WAAW;AAAA,YACb,MAAM,OAAO,iBAAiB,UAAU,KAAK;AAAA,YAC7C,MAAM,YAA+B;AAAA,eAClC,uBAAuB;AAAA,cACxB;AAAA,cACA,UAAU,aAAa,YAAY,OAAO,QAAQ,IAAI;AAAA,cACtD,MAAM,UAAU,QAAQ;AAAA,YAC1B;AAAA,YACA,KAAK,QAAQ,KAAK,EAAE,MAAM,SAAS,OAAO,UAAU,CAAC;AAAA,YACrD;AAAA,UACF;AAAA,QACF;AAAA,QAGA,IAAI,SAAS,OAAO,UAAU,YAAY,WAAW,OAAO;AAAA,UAC1D,MAAM,WAAW;AAAA,UACjB,MAAM,OAAO,iBAAiB,SAAS,KAAK;AAAA,UAC5C,MAAM,YAA+B;AAAA,aAClC,uBAAuB;AAAA,YACxB;AAAA,YACA,UAAU,aAAa,YAAY,OAAO,QAAQ,IAAK,SAAS,QAAQ;AAAA,YACxE,MAAM,SAAS,QAAQ;AAAA,UACzB;AAAA,UACA,KAAK,QAAQ,KAAK,EAAE,MAAM,SAAS,OAAO,UAAU,CAAC;AAAA,QACvD,EAAO,SAAI,SAAS,OAAO,UAAU,YAAY,UAAU,SAAS,cAAc,OAAO;AAAA,UAEvF,MAAM,UAAU;AAAA,UAEhB,MAAM,OAAO,MAAM,QAAQ,QAAQ,IAAI,IACnC,IAAI,WAAW,QAAQ,IAAI,IAC3B,QAAQ;AAAA,UACZ,KAAK,QAAQ,KAAK;AAAA,YAChB,MAAM;AAAA,YACN,OAAO;AAAA,eACJ,uBAAuB;AAAA,cACxB;AAAA,cACA,UAAU,aAAa,YAAY,OAAO,QAAQ,IAAI,QAAQ;AAAA,cAC9D,MAAM,QAAQ;AAAA,YAChB;AAAA,UACF,CAAC;AAAA,QACH,EAAO;AAAA,UACL,KAAK,QAAQ,KAAK,EAAE,MAAM,SAAS,OAAO,OAAO,KAAK,EAAE,CAAC;AAAA;AAAA;AAAA,MAG7D,OAAO,GAAmE;AAAA,QACxE,OAAO,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC;AAAA;AAAA,MAElD,IAAI,GAAgC;AAAA,QAElC,MAAM,OAAO,IAAI;AAAA,QACjB,MAAM,SAAmB,CAAC;AAAA,QAC1B,WAAW,SAAS,KAAK,SAAS;AAAA,UAChC,IAAI,CAAC,KAAK,IAAI,MAAM,IAAI,GAAG;AAAA,YACzB,KAAK,IAAI,MAAM,IAAI;AAAA,YACnB,OAAO,KAAK,MAAM,IAAI;AAAA,UACxB;AAAA,QACF;AAAA,QACA,OAAO;AAAA;AAAA,MAET,MAAM,GAAyD;AAAA,QAC7D,OAAO,KAAK,QAAQ,IAAI,CAAC,MAAM,EAAE,KAAK;AAAA;AAAA,MAExC,OAAO,CAAsB,UAAmB;AAAA,QAC9C,IAAI,OAAO,aAAa,YAAY;AAAA,UAClC,MAAM,IAAI,UAAU,6BAA6B;AAAA,QACnD;AAAA,QACA,WAAW,SAAS,KAAK,SAAS;AAAA,UAC/B,SACC,MAAM,OACN,MAAM,MACN,IACF;AAAA,QACF;AAAA;AAAA,IAEJ;AAAA,EACF,CAAC;AAAA;AAMH,SAAS,gBAAgB,CAAC,OAAiC;AAAA,EACzD,MAAM,cAAc,MAAM,OAAO,CAAC,KAAK,SAAS,MAAM,KAAK,QAAQ,CAAC;AAAA,EACpE,MAAM,SAAS,IAAI,WAAW,WAAW;AAAA,EACzC,IAAI,SAAS;AAAA,EACb,WAAW,QAAQ,OAAO;AAAA,IACxB,OAAO,IAAI,MAAM,MAAM;AAAA,IACvB,UAAU,KAAK;AAAA,EACjB;AAAA,EACA,OAAO;AAAA;AAMF,SAAS,sBAAsB,CACpC,MACA,aACe;AAAA,EACf,MAAM,UAA2B,CAAC;AAAA,EAGlC,MAAM,gBAAgB,YAAY,MAAM,mBAAmB;AAAA,EAC3D,IAAI,CAAC,iBAAiB,CAAC,cAAc,IAAI;AAAA,IACvC,OAAO,EAAE,QAAQ;AAAA,EACnB;AAAA,EAEA,MAAM,WAAW,cAAc,GAAG,QAAQ,gBAAgB,EAAE;AAAA,EAC5D,MAAM,gBAAgB,IAAI,YAAY,EAAE,OAAO,KAAK,UAAU;AAAA,EAC9D,MAAM,mBAAmB,IAAI,YAAY,EAAE,OAAO,KAAK,YAAY;AAAA,EAGnE,MAAM,UAAU,IAAI;AAAA,EACpB,IAAI,MAAM;AAAA,EAGV,MAAM,aAAa,MAAM,eAAe,GAAG;AAAA,EAC3C,IAAI,QAAQ;AAAA,IAAI,OAAO,EAAE,QAAQ;AAAA,EACjC,OAAO,cAAc;AAAA,EAErB,OAAO,MAAM,KAAK,QAAQ;AAAA,IAExB,IAAI,KAAK,SAAS,MAAQ,KAAK,MAAM,OAAO,IAAM;AAAA,MAChD,OAAO;AAAA,IACT,EAAO,SAAI,KAAK,SAAS,IAAM;AAAA,MAC7B,OAAO;AAAA,IACT;AAAA,IAGA,IAAI,MAAM,KAAK,KAAK,UAAU,KAAK,SAAS,MAAQ,KAAK,MAAM,OAAO,IAAM;AAAA,MAC1E;AAAA,IACF;AAAA,IAGA,MAAM,aAAa,aAAa,MAAM,IAAI,WAAW,CAAC,IAAM,IAAM,IAAM,EAAI,CAAC,GAAG,GAAG;AAAA,IACnF,IAAI,eAAe;AAAA,MAAI;AAAA,IAEvB,MAAM,cAAc,QAAQ,OAAO,KAAK,MAAM,KAAK,UAAU,CAAC;AAAA,IAC9D,MAAM,UAAU,aAAa,WAAW;AAAA,IACxC,MAAM,aAAa;AAAA,IAGnB,MAAM,eAAe,aAAa,MAAM,eAAe,GAAG;AAAA,IAC1D,IAAI,iBAAiB;AAAA,MAAI;AAAA,IAGzB,IAAI,aAAa;AAAA,IACjB,IAAI,aAAa,KAAK,KAAK,aAAa,OAAO;AAAA,MAAM;AAAA,IACrD,IAAI,aAAa,KAAK,KAAK,aAAa,OAAO;AAAA,MAAM;AAAA,IAErD,MAAM,UAAU,KAAK,MAAM,KAAK,UAAU;AAAA,IAG1C,MAAM,cAAc,QAAQ,0BAA0B;AAAA,IACtD,MAAM,YAAY,YAAY,MAAM,gBAAgB;AAAA,IACpD,MAAM,gBAAgB,YAAY,MAAM,oBAAoB;AAAA,IAE5D,IAAI,aAAa,UAAU,IAAI;AAAA,MAC7B,MAAM,OAAO,UAAU;AAAA,MACvB,IAAI,iBAAiB,cAAc,IAAI;AAAA,QAErC,MAAM,WAAW,cAAc;AAAA,QAC/B,MAAM,OAAO,QAAQ,mBAAmB;AAAA,QACxC,QAAQ,KAAK;AAAA,UACX;AAAA,UACA,OAAO,GAAG,uBAAuB,MAAM,MAAM,SAAS,UAAU,KAAK;AAAA,QACvE,CAAC;AAAA,MACH,EAAO;AAAA,QAEL,QAAQ,KAAK;AAAA,UACX;AAAA,UACA,OAAO,QAAQ,OAAO,OAAO;AAAA,QAC/B,CAAC;AAAA;AAAA,IAEL;AAAA,IAEA,MAAM,eAAe,cAAc;AAAA,EACrC;AAAA,EAEA,OAAO,EAAE,QAAQ;AAAA;AAMZ,SAAS,uBAAuB,CAAC,MAAiC;AAAA,EACvE,MAAM,OAAO,IAAI,YAAY,EAAE,OAAO,IAAI;AAAA,EAC1C,MAAM,UAA2B,CAAC;AAAA,EAElC,MAAM,SAAS,IAAI,gBAAgB,IAAI;AAAA,EACvC,YAAY,MAAM,UAAU,QAAQ;AAAA,IAClC,QAAQ,KAAK,EAAE,MAAM,MAAM,CAAC;AAAA,EAC9B;AAAA,EAEA,OAAO,EAAE,QAAQ;AAAA;AAGnB,SAAS,YAAY,CAAC,UAAsB,QAAoB,OAAuB;AAAA,EACrF;AAAA,IAAO,SAAS,IAAI,MAAO,KAAK,SAAS,SAAS,OAAO,QAAQ,KAAK;AAAA,MACpE,SAAS,IAAI,EAAG,IAAI,OAAO,QAAQ,KAAK;AAAA,QACtC,IAAI,SAAS,IAAI,OAAO,OAAO;AAAA,UAAI;AAAA,MACrC;AAAA,MACA,OAAO;AAAA,IACT;AAAA,EACA,OAAO;AAAA;AAGT,SAAS,YAAY,CAAC,MAAsC;AAAA,EAC1D,MAAM,UAAkC,CAAC;AAAA,EACzC,MAAM,QAAQ,KAAK,MAAM,OAAO;AAAA,EAChC,WAAW,QAAQ,OAAO;AAAA,IACxB,MAAM,aAAa,KAAK,QAAQ,GAAG;AAAA,IACnC,IAAI,aAAa,GAAG;AAAA,MAClB,MAAM,OAAO,KAAK,MAAM,GAAG,UAAU,EAAE,KAAK,EAAE,YAAY;AAAA,MAC1D,MAAM,QAAQ,KAAK,MAAM,aAAa,CAAC,EAAE,KAAK;AAAA,MAC9C,QAAQ,QAAQ;AAAA,IAClB;AAAA,EACF;AAAA,EACA,OAAO;AAAA;AAMF,SAAS,iBAAiB,CAAC,OAAiE;AAAA,EACjG,MAAM,WAAW,uBAAuB,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC;AAAA,EAC1E,MAAM,UAAU,IAAI;AAAA,EACpB,MAAM,QAAsB,CAAC;AAAA,EAE7B,WAAW,SAAS,MAAM,SAAS;AAAA,IACjC,MAAM,cAAwB,CAAC;AAAA,IAC/B,YAAY,KAAK,KAAK,UAAU;AAAA,IAEhC,IAAI,YAAY,MAAM,KAAK,GAAG;AAAA,MAC5B,YAAY,KACV,yCAAyC,MAAM,oBAAoB,MAAM,MAAM,WACjF;AAAA,MACA,YAAY,KAAK,iBAAiB,MAAM,MAAM,MAAM;AAAA,MACpD,YAAY,KAAK,EAAE;AAAA,MAEnB,MAAM,KAAK,QAAQ,OAAO,YAAY,KAAK;AAAA,CAAM,IAAI;AAAA,CAAM,CAAC;AAAA,MAC5D,MAAM,KAAK,MAAM,MAAM,IAAI;AAAA,MAC3B,MAAM,KAAK,QAAQ,OAAO;AAAA,CAAM,CAAC;AAAA,IACnC,EAAO;AAAA,MACL,YAAY,KAAK,yCAAyC,MAAM,OAAO;AAAA,MACvE,YAAY,KAAK,EAAE;AAAA,MACnB,YAAY,KAAK,MAAM,KAAK;AAAA,MAE5B,MAAM,KAAK,QAAQ,OAAO,YAAY,KAAK;AAAA,CAAM,IAAI;AAAA,CAAM,CAAC;AAAA;AAAA,EAEhE;AAAA,EAEA,MAAM,KAAK,QAAQ,OAAO,KAAK;AAAA,CAAgB,CAAC;AAAA,EAGhD,MAAM,cAAc,MAAM,OAAO,CAAC,KAAK,SAAS,MAAM,KAAK,QAAQ,CAAC;AAAA,EACpE,MAAM,OAAO,IAAI,WAAW,WAAW;AAAA,EACvC,IAAI,SAAS;AAAA,EACb,WAAW,QAAQ,OAAO;AAAA,IACxB,KAAK,IAAI,MAAM,MAAM;AAAA,IACrB,UAAU,KAAK;AAAA,EACjB;AAAA,EAEA,OAAO;AAAA,IACL;AAAA,IACA,aAAa,iCAAiC;AAAA,EAChD;AAAA;AAWK,SAAS,sBAAsB,CAAC,SAA+B;AAAA,EACpE,MAAM,SAAS,QAAQ,SAAS;AAAA;AAAA,6BAEL;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAyB1B;AAAA,EACD,IAAI,OAAO,OAAO;AAAA,IAChB,MAAM,WAAW,QAAQ,KAAK,OAAO,KAAK;AAAA,IAC1C,OAAO,MAAM,QAAQ;AAAA,IACrB,MAAM,IAAI,MAAM,wCAAwC,KAAK,UAAU,QAAQ,GAAG;AAAA,EACpF,EAAO;AAAA,IACL,OAAO,MAAM,QAAQ;AAAA;AAAA;",
|
|
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/handle.cjs
CHANGED
|
@@ -84,7 +84,17 @@ function createFetchHandle(context, stateMap, serveState) {
|
|
|
84
84
|
const hasThen = context.typeof(thenHandle) === "function";
|
|
85
85
|
thenHandle.dispose();
|
|
86
86
|
if (hasThen) {
|
|
87
|
-
|
|
87
|
+
context.runtime.executePendingJobs();
|
|
88
|
+
const resolved = await Promise.race([
|
|
89
|
+
context.resolvePromise(responseHandle),
|
|
90
|
+
(async () => {
|
|
91
|
+
for (let i = 0;i < 1000; i++) {
|
|
92
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
93
|
+
context.runtime.executePendingJobs();
|
|
94
|
+
}
|
|
95
|
+
throw new Error("Promise resolution timeout");
|
|
96
|
+
})()
|
|
97
|
+
]);
|
|
88
98
|
responseHandle.dispose();
|
|
89
99
|
context.runtime.executePendingJobs();
|
|
90
100
|
if (resolved.error) {
|
|
@@ -245,4 +255,4 @@ function createFetchHandle(context, stateMap, serveState) {
|
|
|
245
255
|
}
|
|
246
256
|
})
|
|
247
257
|
|
|
248
|
-
//# debugId=
|
|
258
|
+
//# debugId=CF7F460488EA084964756E2164756E21
|
package/dist/cjs/handle.cjs.map
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/handle.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import type { QuickJSContext, QuickJSHandle } from \"quickjs-emscripten\";\nimport { marshal, getInstanceState, setInstanceState } from \"@ricsam/quickjs-core\";\nimport type {\n StateMap,\n FetchHandle,\n ServeState,\n UpgradeRequest,\n WebSocketCommand,\n ServerWebSocketState,\n RequestState,\n ResponseState,\n} from \"./types.cjs\";\nimport { createRequestStateFromNative } from \"./globals/request.cjs\";\nimport { responseStateToNative } from \"./globals/response.cjs\";\n\n/**\n * Create a proper Request instance in QuickJS with the given state\n */\nfunction createRequestInstance(\n context: QuickJSContext,\n stateMap: StateMap,\n requestState: RequestState\n): QuickJSHandle {\n // Create the Request instance using evalCode (classes must be called with 'new')\n // Pass the URL as the constructor argument - minimal construction\n const urlHandle = context.newString(requestState.url);\n context.setProp(context.global, \"__requestUrl__\", urlHandle);\n urlHandle.dispose();\n\n const requestResult = context.evalCode(`new Request(__requestUrl__)`);\n\n // Clean up temporary global\n context.setProp(context.global, \"__requestUrl__\", context.undefined);\n\n if (requestResult.error) {\n const error = context.dump(requestResult.error);\n requestResult.error.dispose();\n throw new Error(`Failed to create Request: ${JSON.stringify(error)}`);\n }\n\n const requestHandle = requestResult.value;\n\n // Overwrite the internal state with our full RequestState\n setInstanceState(context, requestHandle, requestState);\n\n return requestHandle;\n}\n\n/**\n * Create the FetchHandle implementation\n */\nexport function createFetchHandle(\n context: QuickJSContext,\n stateMap: StateMap,\n serveState: ServeState\n): FetchHandle {\n const wsCommandCallbacks = new Set<(cmd: WebSocketCommand) => void>();\n\n return {\n stateMap,\n\n async dispatchRequest(request: Request): Promise<Response> {\n if (!serveState.fetchHandler) {\n throw new Error(\"No serve() handler registered\");\n }\n\n // Clear any previous upgrade request\n serveState.pendingUpgrade = null;\n\n // Convert native Request to RequestState\n const requestState = await createRequestStateFromNative(request);\n\n // Create proper QuickJS Request instance with internal state\n const requestHandle = createRequestInstance(context, stateMap, requestState);\n\n // Create Server instance using evalCode (classes must be called with 'new')\n const serverResult = context.evalCode(`new __Server__()`);\n if (serverResult.error) {\n requestHandle.dispose();\n const error = context.dump(serverResult.error);\n serverResult.error.dispose();\n throw new Error(`Failed to create Server: ${error}`);\n }\n const serverHandle = serverResult.value;\n\n try {\n // Call the fetch handler\n const result = context.callFunction(\n serveState.fetchHandler,\n context.undefined,\n requestHandle,\n serverHandle\n );\n\n if (result.error) {\n const error = context.dump(result.error);\n result.error.dispose();\n throw new Error(`Fetch handler error: ${JSON.stringify(error)}`);\n }\n\n let responseHandle = result.value;\n\n // Check if result is a Promise\n const typeofResult = context.typeof(responseHandle);\n if (typeofResult === \"object\") {\n // Check for .then method (Promise-like)\n const thenHandle = context.getProp(responseHandle, \"then\");\n const hasThen = context.typeof(thenHandle) === \"function\";\n thenHandle.dispose();\n\n if (hasThen) {\n // Resolve the promise\n const resolved = await context.resolvePromise(responseHandle);\n responseHandle.dispose();\n context.runtime.executePendingJobs();\n\n if (resolved.error) {\n const error = context.dump(resolved.error);\n resolved.error.dispose();\n throw new Error(`Fetch handler promise rejected: ${JSON.stringify(error)}`);\n }\n responseHandle = resolved.value;\n }\n }\n\n // Get the ResponseState directly from the instance\n const responseState = getInstanceState<ResponseState>(context, responseHandle);\n responseHandle.dispose();\n\n if (!responseState) {\n throw new Error(\"Failed to get Response state\");\n }\n\n // Convert to native Response\n return responseStateToNative(responseState);\n } finally {\n requestHandle.dispose();\n serverHandle.dispose();\n }\n },\n\n getUpgradeRequest(): UpgradeRequest | null {\n return serveState.pendingUpgrade;\n },\n\n /**\n * Dispatch WebSocket open event to the QuickJS handler.\n *\n * **IMPORTANT:** An `open` handler MUST be defined in the serve() websocket\n * options for the connection to be tracked. Without an open handler, the\n * connection is not stored and subsequent message/close/error events will\n * be silently ignored.\n *\n * @param connectionId - Unique identifier for this WebSocket connection (host-assigned)\n * @param data - Optional user data from server.upgrade() to associate with the connection\n */\n dispatchWebSocketOpen(connectionId: string, data?: unknown): void {\n if (!serveState.websocketHandlers.open) {\n return;\n }\n\n // Create ServerWebSocket instance using evalCode (classes must be called with 'new')\n // Pass arguments through temporary globals\n const connectionIdHandle = context.newString(connectionId);\n const dataHandle = marshal(context, data);\n context.setProp(context.global, \"__wsConnectionId__\", connectionIdHandle);\n context.setProp(context.global, \"__wsData__\", dataHandle);\n connectionIdHandle.dispose();\n dataHandle.dispose();\n\n const wsResult = context.evalCode(`new __ServerWebSocket__(__wsConnectionId__, __wsData__)`);\n\n // Clean up temporary globals\n context.setProp(context.global, \"__wsConnectionId__\", context.undefined);\n context.setProp(context.global, \"__wsData__\", context.undefined);\n\n if (wsResult.error) {\n wsResult.error.dispose();\n return;\n }\n\n const wsHandle = wsResult.value;\n\n // Store the connection\n serveState.activeConnections.set(connectionId, {\n data,\n readyState: 1,\n connectionId,\n wsHandle,\n });\n\n // Call the open handler\n const result = context.callFunction(\n serveState.websocketHandlers.open,\n context.undefined,\n wsHandle\n );\n\n if (result.error) {\n result.error.dispose();\n } else {\n result.value.dispose();\n }\n\n context.runtime.executePendingJobs();\n },\n\n /**\n * Dispatch WebSocket message event to the QuickJS handler.\n *\n * Requires the connection to have been tracked via dispatchWebSocketOpen().\n * If no message handler is defined, or the connection is not tracked, this is a no-op.\n *\n * @param connectionId - The connection ID from dispatchWebSocketOpen()\n * @param message - The message content (string or binary data)\n */\n dispatchWebSocketMessage(\n connectionId: string,\n message: string | ArrayBuffer\n ): void {\n if (!serveState.websocketHandlers.message) {\n return;\n }\n\n const connection = serveState.activeConnections.get(connectionId);\n if (!connection || !connection.wsHandle) {\n return;\n }\n\n const messageHandle =\n typeof message === \"string\"\n ? context.newString(message)\n : context.newArrayBuffer(message);\n\n const result = context.callFunction(\n serveState.websocketHandlers.message,\n context.undefined,\n connection.wsHandle,\n messageHandle\n );\n\n messageHandle.dispose();\n\n if (result.error) {\n result.error.dispose();\n } else {\n result.value.dispose();\n }\n\n context.runtime.executePendingJobs();\n },\n\n /**\n * Dispatch WebSocket close event to the QuickJS handler and clean up the connection.\n *\n * Updates the connection ready state to CLOSED (3), calls the close handler if defined,\n * disposes the WebSocket handle, and removes the connection from tracking.\n *\n * @param connectionId - The connection ID from dispatchWebSocketOpen()\n * @param code - The WebSocket close code (e.g., 1000 for normal closure)\n * @param reason - The close reason string\n */\n dispatchWebSocketClose(\n connectionId: string,\n code: number,\n reason: string\n ): void {\n const connection = serveState.activeConnections.get(connectionId);\n if (!connection || !connection.wsHandle) {\n serveState.activeConnections.delete(connectionId);\n return;\n }\n\n if (!serveState.websocketHandlers.close) {\n // No close handler, but still need to cleanup the connection\n connection.wsHandle.dispose();\n serveState.activeConnections.delete(connectionId);\n return;\n }\n\n // Update ready state\n connection.readyState = 3; // CLOSED\n\n const codeHandle = context.newNumber(code);\n const reasonHandle = context.newString(reason);\n\n const result = context.callFunction(\n serveState.websocketHandlers.close,\n context.undefined,\n connection.wsHandle,\n codeHandle,\n reasonHandle\n );\n\n codeHandle.dispose();\n reasonHandle.dispose();\n\n if (result.error) {\n result.error.dispose();\n } else {\n result.value.dispose();\n }\n\n // Cleanup\n connection.wsHandle.dispose();\n serveState.activeConnections.delete(connectionId);\n\n context.runtime.executePendingJobs();\n },\n\n /**\n * Dispatch WebSocket error event to the QuickJS handler.\n *\n * Requires the connection to have been tracked via dispatchWebSocketOpen().\n * If no error handler is defined, or the connection is not tracked, this is a no-op.\n *\n * @param connectionId - The connection ID from dispatchWebSocketOpen()\n * @param error - The error that occurred\n */\n dispatchWebSocketError(connectionId: string, error: Error): void {\n if (!serveState.websocketHandlers.error) {\n return;\n }\n\n const connection = serveState.activeConnections.get(connectionId);\n if (!connection || !connection.wsHandle) {\n return;\n }\n\n const errorHandle = marshal(context, {\n name: error.name,\n message: error.message,\n });\n\n const result = context.callFunction(\n serveState.websocketHandlers.error,\n context.undefined,\n connection.wsHandle,\n errorHandle\n );\n\n errorHandle.dispose();\n\n if (result.error) {\n result.error.dispose();\n } else {\n result.value.dispose();\n }\n\n context.runtime.executePendingJobs();\n },\n\n /**\n * Register a callback for outgoing WebSocket commands from QuickJS.\n *\n * Called when QuickJS code calls ws.send() or ws.close() on a ServerWebSocket.\n * The callback receives command objects that should be forwarded to the actual WebSocket.\n *\n * @param callback - Function to handle outgoing WebSocket commands\n * @returns Unsubscribe function to remove the callback\n */\n onWebSocketCommand(\n callback: (command: WebSocketCommand) => void\n ): () => void {\n wsCommandCallbacks.add(callback);\n return () => wsCommandCallbacks.delete(callback);\n },\n\n hasServeHandler(): boolean {\n return serveState.fetchHandler !== null;\n },\n\n dispose(): void {\n // Dispose WebSocket handler handles\n if (serveState.websocketHandlers.open) {\n serveState.websocketHandlers.open.dispose();\n serveState.websocketHandlers.open = undefined;\n }\n if (serveState.websocketHandlers.message) {\n serveState.websocketHandlers.message.dispose();\n serveState.websocketHandlers.message = undefined;\n }\n if (serveState.websocketHandlers.close) {\n serveState.websocketHandlers.close.dispose();\n serveState.websocketHandlers.close = undefined;\n }\n if (serveState.websocketHandlers.error) {\n serveState.websocketHandlers.error.dispose();\n serveState.websocketHandlers.error = undefined;\n }\n\n // Dispose fetch handler handle\n if (serveState.fetchHandler) {\n serveState.fetchHandler.dispose();\n serveState.fetchHandler = null;\n }\n\n // Dispose active WebSocket connection handles\n for (const connection of serveState.activeConnections.values()) {\n if (connection.wsHandle) {\n connection.wsHandle.dispose();\n }\n }\n serveState.activeConnections.clear();\n\n // Note: __Server__ and __ServerWebSocket__ are on global and will be cleaned up by context.dispose()\n },\n };\n}\n"
|
|
5
|
+
"import type { QuickJSContext, QuickJSHandle } from \"quickjs-emscripten\";\nimport { marshal, getInstanceState, setInstanceState } from \"@ricsam/quickjs-core\";\nimport type {\n StateMap,\n FetchHandle,\n ServeState,\n UpgradeRequest,\n WebSocketCommand,\n ServerWebSocketState,\n RequestState,\n ResponseState,\n} from \"./types.cjs\";\nimport { createRequestStateFromNative } from \"./globals/request.cjs\";\nimport { responseStateToNative } from \"./globals/response.cjs\";\n\n/**\n * Create a proper Request instance in QuickJS with the given state\n */\nfunction createRequestInstance(\n context: QuickJSContext,\n stateMap: StateMap,\n requestState: RequestState\n): QuickJSHandle {\n // Create the Request instance using evalCode (classes must be called with 'new')\n // Pass the URL as the constructor argument - minimal construction\n const urlHandle = context.newString(requestState.url);\n context.setProp(context.global, \"__requestUrl__\", urlHandle);\n urlHandle.dispose();\n\n const requestResult = context.evalCode(`new Request(__requestUrl__)`);\n\n // Clean up temporary global\n context.setProp(context.global, \"__requestUrl__\", context.undefined);\n\n if (requestResult.error) {\n const error = context.dump(requestResult.error);\n requestResult.error.dispose();\n throw new Error(`Failed to create Request: ${JSON.stringify(error)}`);\n }\n\n const requestHandle = requestResult.value;\n\n // Overwrite the internal state with our full RequestState\n setInstanceState(context, requestHandle, requestState);\n\n return requestHandle;\n}\n\n/**\n * Create the FetchHandle implementation\n */\nexport function createFetchHandle(\n context: QuickJSContext,\n stateMap: StateMap,\n serveState: ServeState\n): FetchHandle {\n const wsCommandCallbacks = new Set<(cmd: WebSocketCommand) => void>();\n\n return {\n stateMap,\n\n async dispatchRequest(request: Request): Promise<Response> {\n if (!serveState.fetchHandler) {\n throw new Error(\"No serve() handler registered\");\n }\n\n // Clear any previous upgrade request\n serveState.pendingUpgrade = null;\n\n // Convert native Request to RequestState\n const requestState = await createRequestStateFromNative(request);\n\n // Create proper QuickJS Request instance with internal state\n const requestHandle = createRequestInstance(context, stateMap, requestState);\n\n // Create Server instance using evalCode (classes must be called with 'new')\n const serverResult = context.evalCode(`new __Server__()`);\n if (serverResult.error) {\n requestHandle.dispose();\n const error = context.dump(serverResult.error);\n serverResult.error.dispose();\n throw new Error(`Failed to create Server: ${error}`);\n }\n const serverHandle = serverResult.value;\n\n try {\n // Call the fetch handler\n const result = context.callFunction(\n serveState.fetchHandler,\n context.undefined,\n requestHandle,\n serverHandle\n );\n\n if (result.error) {\n const error = context.dump(result.error);\n result.error.dispose();\n throw new Error(`Fetch handler error: ${JSON.stringify(error)}`);\n }\n\n let responseHandle = result.value;\n\n // Check if result is a Promise\n const typeofResult = context.typeof(responseHandle);\n if (typeofResult === \"object\") {\n // Check for .then method (Promise-like)\n const thenHandle = context.getProp(responseHandle, \"then\");\n const hasThen = context.typeof(thenHandle) === \"function\";\n thenHandle.dispose();\n\n if (hasThen) {\n // Execute pending jobs BEFORE resolvePromise to allow\n // JavaScript-native async functions to schedule their resolution.\n // This is critical for async functions that don't use host-side awaits.\n context.runtime.executePendingJobs();\n\n // For pure JS async functions (without host-side awaits), we need to\n // wrap resolvePromise in a race with repeated executePendingJobs calls.\n // The sync variant of QuickJS needs this to process microtasks.\n const resolved = await Promise.race([\n context.resolvePromise(responseHandle),\n (async () => {\n // Keep executing pending jobs until the promise resolves\n // This handles pure JS async functions that don't trigger host callbacks\n for (let i = 0; i < 1000; i++) {\n await new Promise((r) => setTimeout(r, 0));\n context.runtime.executePendingJobs();\n }\n // If we get here, something is very wrong\n throw new Error(\"Promise resolution timeout\");\n })(),\n ]);\n responseHandle.dispose();\n context.runtime.executePendingJobs();\n\n if (resolved.error) {\n const error = context.dump(resolved.error);\n resolved.error.dispose();\n throw new Error(`Fetch handler promise rejected: ${JSON.stringify(error)}`);\n }\n responseHandle = resolved.value;\n }\n }\n\n // Get the ResponseState directly from the instance\n const responseState = getInstanceState<ResponseState>(context, responseHandle);\n responseHandle.dispose();\n\n if (!responseState) {\n throw new Error(\"Failed to get Response state\");\n }\n\n // Convert to native Response\n return responseStateToNative(responseState);\n } finally {\n requestHandle.dispose();\n serverHandle.dispose();\n }\n },\n\n getUpgradeRequest(): UpgradeRequest | null {\n return serveState.pendingUpgrade;\n },\n\n /**\n * Dispatch WebSocket open event to the QuickJS handler.\n *\n * **IMPORTANT:** An `open` handler MUST be defined in the serve() websocket\n * options for the connection to be tracked. Without an open handler, the\n * connection is not stored and subsequent message/close/error events will\n * be silently ignored.\n *\n * @param connectionId - Unique identifier for this WebSocket connection (host-assigned)\n * @param data - Optional user data from server.upgrade() to associate with the connection\n */\n dispatchWebSocketOpen(connectionId: string, data?: unknown): void {\n if (!serveState.websocketHandlers.open) {\n return;\n }\n\n // Create ServerWebSocket instance using evalCode (classes must be called with 'new')\n // Pass arguments through temporary globals\n const connectionIdHandle = context.newString(connectionId);\n const dataHandle = marshal(context, data);\n context.setProp(context.global, \"__wsConnectionId__\", connectionIdHandle);\n context.setProp(context.global, \"__wsData__\", dataHandle);\n connectionIdHandle.dispose();\n dataHandle.dispose();\n\n const wsResult = context.evalCode(`new __ServerWebSocket__(__wsConnectionId__, __wsData__)`);\n\n // Clean up temporary globals\n context.setProp(context.global, \"__wsConnectionId__\", context.undefined);\n context.setProp(context.global, \"__wsData__\", context.undefined);\n\n if (wsResult.error) {\n wsResult.error.dispose();\n return;\n }\n\n const wsHandle = wsResult.value;\n\n // Store the connection\n serveState.activeConnections.set(connectionId, {\n data,\n readyState: 1,\n connectionId,\n wsHandle,\n });\n\n // Call the open handler\n const result = context.callFunction(\n serveState.websocketHandlers.open,\n context.undefined,\n wsHandle\n );\n\n if (result.error) {\n result.error.dispose();\n } else {\n result.value.dispose();\n }\n\n context.runtime.executePendingJobs();\n },\n\n /**\n * Dispatch WebSocket message event to the QuickJS handler.\n *\n * Requires the connection to have been tracked via dispatchWebSocketOpen().\n * If no message handler is defined, or the connection is not tracked, this is a no-op.\n *\n * @param connectionId - The connection ID from dispatchWebSocketOpen()\n * @param message - The message content (string or binary data)\n */\n dispatchWebSocketMessage(\n connectionId: string,\n message: string | ArrayBuffer\n ): void {\n if (!serveState.websocketHandlers.message) {\n return;\n }\n\n const connection = serveState.activeConnections.get(connectionId);\n if (!connection || !connection.wsHandle) {\n return;\n }\n\n const messageHandle =\n typeof message === \"string\"\n ? context.newString(message)\n : context.newArrayBuffer(message);\n\n const result = context.callFunction(\n serveState.websocketHandlers.message,\n context.undefined,\n connection.wsHandle,\n messageHandle\n );\n\n messageHandle.dispose();\n\n if (result.error) {\n result.error.dispose();\n } else {\n result.value.dispose();\n }\n\n context.runtime.executePendingJobs();\n },\n\n /**\n * Dispatch WebSocket close event to the QuickJS handler and clean up the connection.\n *\n * Updates the connection ready state to CLOSED (3), calls the close handler if defined,\n * disposes the WebSocket handle, and removes the connection from tracking.\n *\n * @param connectionId - The connection ID from dispatchWebSocketOpen()\n * @param code - The WebSocket close code (e.g., 1000 for normal closure)\n * @param reason - The close reason string\n */\n dispatchWebSocketClose(\n connectionId: string,\n code: number,\n reason: string\n ): void {\n const connection = serveState.activeConnections.get(connectionId);\n if (!connection || !connection.wsHandle) {\n serveState.activeConnections.delete(connectionId);\n return;\n }\n\n if (!serveState.websocketHandlers.close) {\n // No close handler, but still need to cleanup the connection\n connection.wsHandle.dispose();\n serveState.activeConnections.delete(connectionId);\n return;\n }\n\n // Update ready state\n connection.readyState = 3; // CLOSED\n\n const codeHandle = context.newNumber(code);\n const reasonHandle = context.newString(reason);\n\n const result = context.callFunction(\n serveState.websocketHandlers.close,\n context.undefined,\n connection.wsHandle,\n codeHandle,\n reasonHandle\n );\n\n codeHandle.dispose();\n reasonHandle.dispose();\n\n if (result.error) {\n result.error.dispose();\n } else {\n result.value.dispose();\n }\n\n // Cleanup\n connection.wsHandle.dispose();\n serveState.activeConnections.delete(connectionId);\n\n context.runtime.executePendingJobs();\n },\n\n /**\n * Dispatch WebSocket error event to the QuickJS handler.\n *\n * Requires the connection to have been tracked via dispatchWebSocketOpen().\n * If no error handler is defined, or the connection is not tracked, this is a no-op.\n *\n * @param connectionId - The connection ID from dispatchWebSocketOpen()\n * @param error - The error that occurred\n */\n dispatchWebSocketError(connectionId: string, error: Error): void {\n if (!serveState.websocketHandlers.error) {\n return;\n }\n\n const connection = serveState.activeConnections.get(connectionId);\n if (!connection || !connection.wsHandle) {\n return;\n }\n\n const errorHandle = marshal(context, {\n name: error.name,\n message: error.message,\n });\n\n const result = context.callFunction(\n serveState.websocketHandlers.error,\n context.undefined,\n connection.wsHandle,\n errorHandle\n );\n\n errorHandle.dispose();\n\n if (result.error) {\n result.error.dispose();\n } else {\n result.value.dispose();\n }\n\n context.runtime.executePendingJobs();\n },\n\n /**\n * Register a callback for outgoing WebSocket commands from QuickJS.\n *\n * Called when QuickJS code calls ws.send() or ws.close() on a ServerWebSocket.\n * The callback receives command objects that should be forwarded to the actual WebSocket.\n *\n * @param callback - Function to handle outgoing WebSocket commands\n * @returns Unsubscribe function to remove the callback\n */\n onWebSocketCommand(\n callback: (command: WebSocketCommand) => void\n ): () => void {\n wsCommandCallbacks.add(callback);\n return () => wsCommandCallbacks.delete(callback);\n },\n\n hasServeHandler(): boolean {\n return serveState.fetchHandler !== null;\n },\n\n dispose(): void {\n // Dispose WebSocket handler handles\n if (serveState.websocketHandlers.open) {\n serveState.websocketHandlers.open.dispose();\n serveState.websocketHandlers.open = undefined;\n }\n if (serveState.websocketHandlers.message) {\n serveState.websocketHandlers.message.dispose();\n serveState.websocketHandlers.message = undefined;\n }\n if (serveState.websocketHandlers.close) {\n serveState.websocketHandlers.close.dispose();\n serveState.websocketHandlers.close = undefined;\n }\n if (serveState.websocketHandlers.error) {\n serveState.websocketHandlers.error.dispose();\n serveState.websocketHandlers.error = undefined;\n }\n\n // Dispose fetch handler handle\n if (serveState.fetchHandler) {\n serveState.fetchHandler.dispose();\n serveState.fetchHandler = null;\n }\n\n // Dispose active WebSocket connection handles\n for (const connection of serveState.activeConnections.values()) {\n if (connection.wsHandle) {\n connection.wsHandle.dispose();\n }\n }\n serveState.activeConnections.clear();\n\n // Note: __Server__ and __ServerWebSocket__ are on global and will be cleaned up by context.dispose()\n },\n };\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAC4D,IAA5D;AAW6C,IAA7C;AACsC,IAAtC;AAKA,SAAS,qBAAqB,CAC5B,SACA,UACA,cACe;AAAA,EAGf,MAAM,YAAY,QAAQ,UAAU,aAAa,GAAG;AAAA,EACpD,QAAQ,QAAQ,QAAQ,QAAQ,kBAAkB,SAAS;AAAA,EAC3D,UAAU,QAAQ;AAAA,EAElB,MAAM,gBAAgB,QAAQ,SAAS,6BAA6B;AAAA,EAGpE,QAAQ,QAAQ,QAAQ,QAAQ,kBAAkB,QAAQ,SAAS;AAAA,EAEnE,IAAI,cAAc,OAAO;AAAA,IACvB,MAAM,QAAQ,QAAQ,KAAK,cAAc,KAAK;AAAA,IAC9C,cAAc,MAAM,QAAQ;AAAA,IAC5B,MAAM,IAAI,MAAM,6BAA6B,KAAK,UAAU,KAAK,GAAG;AAAA,EACtE;AAAA,EAEA,MAAM,gBAAgB,cAAc;AAAA,EAGpC,qCAAiB,SAAS,eAAe,YAAY;AAAA,EAErD,OAAO;AAAA;AAMF,SAAS,iBAAiB,CAC/B,SACA,UACA,YACa;AAAA,EACb,MAAM,qBAAqB,IAAI;AAAA,EAE/B,OAAO;AAAA,IACL;AAAA,SAEM,gBAAe,CAAC,SAAqC;AAAA,MACzD,IAAI,CAAC,WAAW,cAAc;AAAA,QAC5B,MAAM,IAAI,MAAM,+BAA+B;AAAA,MACjD;AAAA,MAGA,WAAW,iBAAiB;AAAA,MAG5B,MAAM,eAAe,MAAM,4CAA6B,OAAO;AAAA,MAG/D,MAAM,gBAAgB,sBAAsB,SAAS,UAAU,YAAY;AAAA,MAG3E,MAAM,eAAe,QAAQ,SAAS,kBAAkB;AAAA,MACxD,IAAI,aAAa,OAAO;AAAA,QACtB,cAAc,QAAQ;AAAA,QACtB,MAAM,QAAQ,QAAQ,KAAK,aAAa,KAAK;AAAA,QAC7C,aAAa,MAAM,QAAQ;AAAA,QAC3B,MAAM,IAAI,MAAM,4BAA4B,OAAO;AAAA,MACrD;AAAA,MACA,MAAM,eAAe,aAAa;AAAA,MAElC,IAAI;AAAA,QAEF,MAAM,SAAS,QAAQ,aACrB,WAAW,cACX,QAAQ,WACR,eACA,YACF;AAAA,QAEA,IAAI,OAAO,OAAO;AAAA,UAChB,MAAM,QAAQ,QAAQ,KAAK,OAAO,KAAK;AAAA,UACvC,OAAO,MAAM,QAAQ;AAAA,UACrB,MAAM,IAAI,MAAM,wBAAwB,KAAK,UAAU,KAAK,GAAG;AAAA,QACjE;AAAA,QAEA,IAAI,iBAAiB,OAAO;AAAA,QAG5B,MAAM,eAAe,QAAQ,OAAO,cAAc;AAAA,QAClD,IAAI,iBAAiB,UAAU;AAAA,UAE7B,MAAM,aAAa,QAAQ,QAAQ,gBAAgB,MAAM;AAAA,UACzD,MAAM,UAAU,QAAQ,OAAO,UAAU,MAAM;AAAA,UAC/C,WAAW,QAAQ;AAAA,UAEnB,IAAI,SAAS;AAAA,
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "CF7F460488EA084964756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/dist/cjs/package.json
CHANGED