@ricsam/quickjs-fetch 0.2.8 → 0.2.10
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/README.md +4 -3
- package/dist/cjs/globals/request.cjs +83 -12
- package/dist/cjs/globals/request.cjs.map +3 -3
- package/dist/cjs/globals/serve.cjs +4 -20
- package/dist/cjs/globals/serve.cjs.map +3 -3
- package/dist/cjs/handle.cjs +35 -9
- package/dist/cjs/handle.cjs.map +3 -3
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/setup.cjs +82 -2
- package/dist/cjs/setup.cjs.map +3 -3
- package/dist/cjs/upload-stream-queue.cjs +171 -0
- package/dist/cjs/upload-stream-queue.cjs.map +10 -0
- package/dist/mjs/globals/request.mjs +83 -12
- package/dist/mjs/globals/request.mjs.map +3 -3
- package/dist/mjs/globals/serve.mjs +4 -20
- package/dist/mjs/globals/serve.mjs.map +3 -3
- package/dist/mjs/handle.mjs +35 -9
- package/dist/mjs/handle.mjs.map +3 -3
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/setup.mjs +82 -2
- package/dist/mjs/setup.mjs.map +3 -3
- package/dist/mjs/upload-stream-queue.mjs +145 -0
- package/dist/mjs/upload-stream-queue.mjs.map +10 -0
- package/dist/types/globals/request.d.ts +23 -3
- package/dist/types/globals/serve.d.ts +1 -1
- package/dist/types/types.d.ts +26 -6
- package/dist/types/upload-stream-queue.d.ts +69 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -94,9 +94,10 @@ const response = await handle.dispatchRequest(
|
|
|
94
94
|
// Check for WebSocket upgrade
|
|
95
95
|
const upgrade = handle.getUpgradeRequest();
|
|
96
96
|
if (upgrade?.requested) {
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
// connectionId is generated by QuickJS in server.upgrade()
|
|
98
|
+
// User data stays within QuickJS (no marshalling of complex objects)
|
|
99
|
+
const { connectionId } = upgrade;
|
|
100
|
+
handle.dispatchWebSocketOpen(connectionId);
|
|
100
101
|
|
|
101
102
|
// Listen for outgoing messages
|
|
102
103
|
handle.onWebSocketCommand((command) => {
|
|
@@ -38,7 +38,27 @@ module.exports = __toCommonJS(exports_request);
|
|
|
38
38
|
var import_quickjs_core = require("@ricsam/quickjs-core");
|
|
39
39
|
var import_headers = require("./headers.cjs");
|
|
40
40
|
var import_form_data = require("./form-data.cjs");
|
|
41
|
-
|
|
41
|
+
var import_upload_stream_queue = require("../upload-stream-queue.cjs");
|
|
42
|
+
async function consumeNativeStream(stream) {
|
|
43
|
+
const chunks = [];
|
|
44
|
+
const reader = stream.getReader();
|
|
45
|
+
while (true) {
|
|
46
|
+
const { done, value } = await reader.read();
|
|
47
|
+
if (done)
|
|
48
|
+
break;
|
|
49
|
+
chunks.push(value);
|
|
50
|
+
}
|
|
51
|
+
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
52
|
+
const result = new Uint8Array(totalLength);
|
|
53
|
+
let offset = 0;
|
|
54
|
+
for (const chunk of chunks) {
|
|
55
|
+
result.set(chunk, offset);
|
|
56
|
+
offset += chunk.length;
|
|
57
|
+
}
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
function createRequestClass(context, stateMap, streamHelpers) {
|
|
61
|
+
const createStream = streamHelpers?.createStream;
|
|
42
62
|
return import_quickjs_core.defineClass(context, stateMap, {
|
|
43
63
|
name: "Request",
|
|
44
64
|
construct: (args) => {
|
|
@@ -146,15 +166,34 @@ function createRequestClass(context, stateMap, createStream) {
|
|
|
146
166
|
},
|
|
147
167
|
body: {
|
|
148
168
|
get() {
|
|
149
|
-
if (!this.body)
|
|
150
|
-
return null;
|
|
151
169
|
if (!createStream) {
|
|
152
170
|
return this.body;
|
|
153
171
|
}
|
|
172
|
+
if (this._uploadStreamGlobalKey) {
|
|
173
|
+
return streamHelpers?.getStreamByKey(this._uploadStreamGlobalKey) || null;
|
|
174
|
+
}
|
|
175
|
+
if (this._cachedBodyStream !== undefined) {
|
|
176
|
+
return this._cachedBodyStream;
|
|
177
|
+
}
|
|
178
|
+
if (this.nativeBodyStream) {
|
|
179
|
+
if (!streamHelpers?.createEmptyStream || !streamHelpers?.getStreamByKey) {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
const { instanceId: streamInstanceId, globalKey } = streamHelpers.createEmptyStream();
|
|
183
|
+
const cleanup = import_upload_stream_queue.startNativeStreamReader(this.nativeBodyStream, streamInstanceId, streamHelpers.pumpEventLoop);
|
|
184
|
+
this._uploadStreamCleanup = cleanup;
|
|
185
|
+
this._uploadStreamGlobalKey = globalKey;
|
|
186
|
+
this.nativeBodyStream = undefined;
|
|
187
|
+
return streamHelpers.getStreamByKey(globalKey);
|
|
188
|
+
}
|
|
189
|
+
if (!this.body) {
|
|
190
|
+
this._cachedBodyStream = null;
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
154
193
|
const bodyData = this.body;
|
|
155
194
|
let offset = 0;
|
|
156
195
|
const chunkSize = 65536;
|
|
157
|
-
|
|
196
|
+
const stream = createStream({
|
|
158
197
|
pull(controller) {
|
|
159
198
|
if (offset >= bodyData.length) {
|
|
160
199
|
controller.close();
|
|
@@ -165,6 +204,8 @@ function createRequestClass(context, stateMap, createStream) {
|
|
|
165
204
|
controller.enqueue(chunk);
|
|
166
205
|
}
|
|
167
206
|
});
|
|
207
|
+
this._cachedBodyStream = stream;
|
|
208
|
+
return stream;
|
|
168
209
|
}
|
|
169
210
|
},
|
|
170
211
|
bodyUsed: {
|
|
@@ -229,6 +270,10 @@ function createRequestClass(context, stateMap, createStream) {
|
|
|
229
270
|
throw new TypeError("Body has already been consumed");
|
|
230
271
|
}
|
|
231
272
|
this.bodyUsed = true;
|
|
273
|
+
if (this.nativeBodyStream) {
|
|
274
|
+
const buffer = await consumeNativeStream(this.nativeBodyStream);
|
|
275
|
+
return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
|
276
|
+
}
|
|
232
277
|
if (!this.body) {
|
|
233
278
|
return new ArrayBuffer(0);
|
|
234
279
|
}
|
|
@@ -240,6 +285,14 @@ function createRequestClass(context, stateMap, createStream) {
|
|
|
240
285
|
}
|
|
241
286
|
this.bodyUsed = true;
|
|
242
287
|
const contentType = this.headersState.headers.get("content-type")?.[0] || "";
|
|
288
|
+
if (this.nativeBodyStream) {
|
|
289
|
+
const buffer = await consumeNativeStream(this.nativeBodyStream);
|
|
290
|
+
return {
|
|
291
|
+
parts: [buffer],
|
|
292
|
+
type: contentType,
|
|
293
|
+
size: buffer.length
|
|
294
|
+
};
|
|
295
|
+
}
|
|
243
296
|
return {
|
|
244
297
|
parts: this.body ? [this.body] : [],
|
|
245
298
|
type: contentType,
|
|
@@ -250,6 +303,9 @@ function createRequestClass(context, stateMap, createStream) {
|
|
|
250
303
|
if (this.bodyUsed) {
|
|
251
304
|
throw new TypeError("Body has already been consumed");
|
|
252
305
|
}
|
|
306
|
+
if (this.nativeBodyStream) {
|
|
307
|
+
throw new TypeError("Cannot clone Request with streaming body");
|
|
308
|
+
}
|
|
253
309
|
return {
|
|
254
310
|
...this,
|
|
255
311
|
headersState: {
|
|
@@ -264,6 +320,11 @@ function createRequestClass(context, stateMap, createStream) {
|
|
|
264
320
|
throw new TypeError("Body has already been consumed");
|
|
265
321
|
}
|
|
266
322
|
this.bodyUsed = true;
|
|
323
|
+
if (this.nativeBodyStream) {
|
|
324
|
+
const buffer = await consumeNativeStream(this.nativeBodyStream);
|
|
325
|
+
const text2 = new TextDecoder().decode(buffer);
|
|
326
|
+
return JSON.parse(text2);
|
|
327
|
+
}
|
|
267
328
|
if (!this.body) {
|
|
268
329
|
return JSON.parse("");
|
|
269
330
|
}
|
|
@@ -275,6 +336,10 @@ function createRequestClass(context, stateMap, createStream) {
|
|
|
275
336
|
throw new TypeError("Body has already been consumed");
|
|
276
337
|
}
|
|
277
338
|
this.bodyUsed = true;
|
|
339
|
+
if (this.nativeBodyStream) {
|
|
340
|
+
const buffer = await consumeNativeStream(this.nativeBodyStream);
|
|
341
|
+
return new TextDecoder().decode(buffer);
|
|
342
|
+
}
|
|
278
343
|
if (!this.body) {
|
|
279
344
|
return "";
|
|
280
345
|
}
|
|
@@ -285,15 +350,21 @@ function createRequestClass(context, stateMap, createStream) {
|
|
|
285
350
|
throw new TypeError("Body has already been consumed");
|
|
286
351
|
}
|
|
287
352
|
this.bodyUsed = true;
|
|
288
|
-
|
|
353
|
+
let bodyData = null;
|
|
354
|
+
if (this.nativeBodyStream) {
|
|
355
|
+
bodyData = await consumeNativeStream(this.nativeBodyStream);
|
|
356
|
+
} else {
|
|
357
|
+
bodyData = this.body;
|
|
358
|
+
}
|
|
359
|
+
if (!bodyData) {
|
|
289
360
|
return { entries: [] };
|
|
290
361
|
}
|
|
291
362
|
const contentType = this.headersState.headers.get("content-type")?.[0] || "";
|
|
292
363
|
let formDataState;
|
|
293
364
|
if (contentType.includes("multipart/form-data")) {
|
|
294
|
-
formDataState = import_form_data.parseMultipartFormData(
|
|
365
|
+
formDataState = import_form_data.parseMultipartFormData(bodyData, contentType);
|
|
295
366
|
} else if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
296
|
-
formDataState = import_form_data.parseUrlEncodedFormData(
|
|
367
|
+
formDataState = import_form_data.parseUrlEncodedFormData(bodyData);
|
|
297
368
|
} else {
|
|
298
369
|
throw new TypeError("Could not parse content as FormData");
|
|
299
370
|
}
|
|
@@ -343,13 +414,12 @@ function addRequestFormDataMethod(context) {
|
|
|
343
414
|
result.value.dispose();
|
|
344
415
|
}
|
|
345
416
|
}
|
|
346
|
-
|
|
347
|
-
const body = request.body ? new Uint8Array(await request.arrayBuffer()) : null;
|
|
417
|
+
function createRequestStateFromNative(request) {
|
|
348
418
|
return {
|
|
349
419
|
method: request.method,
|
|
350
420
|
url: request.url,
|
|
351
421
|
headersState: import_headers.createHeadersStateFromNative(request.headers),
|
|
352
|
-
body,
|
|
422
|
+
body: null,
|
|
353
423
|
bodyUsed: false,
|
|
354
424
|
cache: request.cache,
|
|
355
425
|
credentials: request.credentials,
|
|
@@ -360,9 +430,10 @@ async function createRequestStateFromNative(request) {
|
|
|
360
430
|
redirect: request.redirect,
|
|
361
431
|
referrer: request.referrer,
|
|
362
432
|
referrerPolicy: request.referrerPolicy,
|
|
363
|
-
signal: null
|
|
433
|
+
signal: null,
|
|
434
|
+
nativeBodyStream: request.body ?? undefined
|
|
364
435
|
};
|
|
365
436
|
}
|
|
366
437
|
})
|
|
367
438
|
|
|
368
|
-
//# debugId=
|
|
439
|
+
//# debugId=B2E1A14BC8FBB5F164756E2164756E21
|
|
@@ -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, getInstanceStateById } 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 if (\n \"__isDefineClassInstance__\" in init.headers &&\n (init.headers as { __isDefineClassInstance__?: boolean }).__isDefineClassInstance__ === true &&\n \"__instanceId__\" in init.headers\n ) {\n // Unmarshalled Headers instance from defineClass\n const instanceId = (init.headers as { __instanceId__: number }).__instanceId__;\n const state = getInstanceStateById<HeadersState>(instanceId);\n if (state && state.headers instanceof Map) {\n headersState = {\n headers: new Map(state.headers),\n };\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"
|
|
5
|
+
"import type { QuickJSContext, QuickJSHandle } from \"quickjs-emscripten\";\nimport type { StateMap } from \"@ricsam/quickjs-core\";\nimport { defineClass, getInstanceStateById } 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\";\nimport { startNativeStreamReader } from \"../upload-stream-queue.cjs\";\n\n/**\n * Type for the stream factory function\n */\ntype StreamFactory = (source: UnderlyingSource) => QuickJSHandle;\n\n/**\n * Stream helpers passed to the body getter for upload streaming\n */\ninterface StreamHelpers {\n createStream: StreamFactory;\n /** Creates an empty ReadableStream, stores on global, returns instanceId and globalKey */\n createEmptyStream: () => { instanceId: number; globalKey: string };\n /** Gets a fresh handle to the stream stored at globalKey */\n getStreamByKey: (globalKey: string) => QuickJSHandle | null;\n pumpEventLoop: () => void;\n}\n\n/**\n * Helper to consume a native ReadableStream into a Uint8Array.\n * Used by text(), json(), arrayBuffer(), blob() methods.\n */\nasync function consumeNativeStream(\n stream: ReadableStream<Uint8Array>\n): Promise<Uint8Array> {\n const chunks: Uint8Array[] = [];\n const reader = stream.getReader();\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n chunks.push(value);\n }\n\n // Concatenate chunks into single Uint8Array\n const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);\n const result = new Uint8Array(totalLength);\n let offset = 0;\n for (const chunk of chunks) {\n result.set(chunk, offset);\n offset += chunk.length;\n }\n\n return result;\n}\n\n/**\n * Create the Request class for QuickJS\n */\nexport function createRequestClass(\n context: QuickJSContext,\n stateMap: StateMap,\n streamHelpers?: StreamHelpers\n): QuickJSHandle {\n const createStream = streamHelpers?.createStream;\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 if (\n \"__isDefineClassInstance__\" in init.headers &&\n (init.headers as { __isDefineClassInstance__?: boolean }).__isDefineClassInstance__ === true &&\n \"__instanceId__\" in init.headers\n ) {\n // Unmarshalled Headers instance from defineClass\n const instanceId = (init.headers as { __instanceId__: number }).__instanceId__;\n const state = getInstanceStateById<HeadersState>(instanceId);\n if (state && state.headers instanceof Map) {\n headersState = {\n headers: new Map(state.headers),\n };\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 & { _uploadStreamGlobalKey?: string; _uploadStreamCleanup?: () => void; _cachedBodyStream?: object | null }) {\n if (!createStream) {\n // Fallback: return raw body if no stream factory\n return this.body;\n }\n\n // If we have an upload stream, get a fresh handle each time\n // (Can't cache handles because __hostCall__ disposes them after each call)\n if (this._uploadStreamGlobalKey) {\n return streamHelpers?.getStreamByKey(this._uploadStreamGlobalKey) || null;\n }\n\n // Return cached buffered body stream if already created\n if (this._cachedBodyStream !== undefined) {\n return this._cachedBodyStream;\n }\n\n // Streaming native body - create a QuickJS ReadableStream that bridges to the native stream\n // Uses the \"Inverted Direct State Access\" pattern: host pushes to QuickJS stream's queue\n if (this.nativeBodyStream) {\n if (!streamHelpers?.createEmptyStream || !streamHelpers?.getStreamByKey) {\n // No helpers available - fall back to null (legacy behavior)\n return null;\n }\n\n // Create a QuickJS ReadableStream with no source callbacks.\n // The stream is stored on a global because __hostCall__ disposes returned\n // handles after each call. We cache the globalKey and get a fresh handle\n // on each access via getStreamByKey().\n const { instanceId: streamInstanceId, globalKey } = streamHelpers.createEmptyStream();\n\n // Start background reader that pushes to the QuickJS stream\n // The onChunkPushed callback pumps the event loop to process pending reads\n const cleanup = startNativeStreamReader(\n this.nativeBodyStream,\n streamInstanceId,\n streamHelpers.pumpEventLoop\n );\n\n // Store cleanup function for potential cancellation\n this._uploadStreamCleanup = cleanup;\n // Cache the global key so subsequent calls can get the same stream\n this._uploadStreamGlobalKey = globalKey;\n\n // Clear nativeBodyStream to prevent double-consumption\n // This ensures text()/json()/arrayBuffer() don't try to read from it\n this.nativeBodyStream = undefined;\n\n // Get fresh handle to return\n return streamHelpers.getStreamByKey(globalKey);\n }\n\n // Fallback: buffered body (backwards compatibility)\n if (!this.body) {\n this._cachedBodyStream = null;\n return null;\n }\n const bodyData = this.body;\n let offset = 0;\n const chunkSize = 65536; // 64KB chunks\n const stream = 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 this._cachedBodyStream = stream;\n return stream;\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\n // Consume native stream if present\n if (this.nativeBodyStream) {\n const buffer = await consumeNativeStream(this.nativeBodyStream);\n return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength) as ArrayBuffer;\n }\n\n // Fallback: buffered body\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\n // Consume native stream if present\n if (this.nativeBodyStream) {\n const buffer = await consumeNativeStream(this.nativeBodyStream);\n return {\n parts: [buffer],\n type: contentType,\n size: buffer.length,\n };\n }\n\n // Fallback: buffered body\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 // Note: Cannot clone streaming body - would need to tee the stream\n if (this.nativeBodyStream) {\n throw new TypeError(\"Cannot clone Request with streaming body\");\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\n // Consume native stream if present\n if (this.nativeBodyStream) {\n const buffer = await consumeNativeStream(this.nativeBodyStream);\n const text = new TextDecoder().decode(buffer);\n return JSON.parse(text);\n }\n\n // Fallback: buffered body\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\n // Consume native stream if present\n if (this.nativeBodyStream) {\n const buffer = await consumeNativeStream(this.nativeBodyStream);\n return new TextDecoder().decode(buffer);\n }\n\n // Fallback: buffered body\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\n // Get body data from native stream or buffered body\n let bodyData: Uint8Array | null = null;\n if (this.nativeBodyStream) {\n bodyData = await consumeNativeStream(this.nativeBodyStream);\n } else {\n bodyData = this.body;\n }\n\n if (!bodyData) {\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(bodyData, contentType);\n } else if (contentType.includes(\"application/x-www-form-urlencoded\")) {\n formDataState = parseUrlEncodedFormData(bodyData);\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 *\n * This is now synchronous - we preserve the native ReadableStream instead of\n * buffering it. The stream is consumed lazily when QuickJS code accesses\n * request.body or calls request.text()/json()/arrayBuffer().\n *\n * This enables streaming large uploads (1GB+) without buffering.\n */\nexport function createRequestStateFromNative(\n request: Request\n): RequestState {\n return {\n method: request.method,\n url: request.url,\n headersState: createHeadersStateFromNative(request.headers),\n body: null, // Not buffered upfront - use nativeBodyStream instead\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 nativeBodyStream: request.body ?? undefined,\n };\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEkD,IAAlD;AAEgE,IAAhE;AACgE,IAAhE;
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "B2E1A14BC8FBB5F164756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -41,20 +41,13 @@ function createServerWebSocketClass(context, stateMap, onWebSocketCommand) {
|
|
|
41
41
|
name: "ServerWebSocket",
|
|
42
42
|
construct: (args) => {
|
|
43
43
|
const connectionId = String(args[0]);
|
|
44
|
-
const data = args[1];
|
|
45
44
|
return {
|
|
46
|
-
data,
|
|
47
45
|
readyState: 1,
|
|
48
46
|
connectionId,
|
|
49
47
|
wsHandle: null
|
|
50
48
|
};
|
|
51
49
|
},
|
|
52
50
|
properties: {
|
|
53
|
-
data: {
|
|
54
|
-
get() {
|
|
55
|
-
return this.data;
|
|
56
|
-
}
|
|
57
|
-
},
|
|
58
51
|
readyState: {
|
|
59
52
|
get() {
|
|
60
53
|
return this.readyState;
|
|
@@ -97,22 +90,13 @@ function createServerWebSocketClass(context, stateMap, onWebSocketCommand) {
|
|
|
97
90
|
}
|
|
98
91
|
});
|
|
99
92
|
}
|
|
100
|
-
function createServerClass(context, stateMap,
|
|
93
|
+
function createServerClass(context, stateMap, _serveState) {
|
|
101
94
|
return import_quickjs_core.defineClass(context, stateMap, {
|
|
102
95
|
name: "Server",
|
|
103
96
|
construct: () => {
|
|
104
|
-
return {
|
|
97
|
+
return {};
|
|
105
98
|
},
|
|
106
|
-
methods: {
|
|
107
|
-
upgrade(_request, options) {
|
|
108
|
-
const data = options && typeof options === "object" && "data" in options ? options.data : undefined;
|
|
109
|
-
this.serveState.pendingUpgrade = {
|
|
110
|
-
requested: true,
|
|
111
|
-
data
|
|
112
|
-
};
|
|
113
|
-
return true;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
99
|
+
methods: {}
|
|
116
100
|
});
|
|
117
101
|
}
|
|
118
102
|
function createServeFunction(context, stateMap, serveState) {
|
|
@@ -179,4 +163,4 @@ function createServeFunction(context, stateMap, serveState) {
|
|
|
179
163
|
}
|
|
180
164
|
})
|
|
181
165
|
|
|
182
|
-
//# debugId=
|
|
166
|
+
//# debugId=D110C1B8E74ED4FB64756E2164756E21
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/globals/serve.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 { ServeState, ServerWebSocketState, WebSocketCommand } from \"../types.cjs\";\n\
|
|
5
|
+
"import type { QuickJSContext, QuickJSHandle } from \"quickjs-emscripten\";\nimport type { StateMap } from \"@ricsam/quickjs-core\";\nimport { defineClass } from \"@ricsam/quickjs-core\";\nimport type { ServeState, ServerWebSocketState, WebSocketCommand } from \"../types.cjs\";\n\n// Server state is empty - upgrade method is pure JavaScript and stores data in a global registry\ntype ServerState = Record<string, never>;\n\n/**\n * Create the ServerWebSocket class for QuickJS\n */\nexport function createServerWebSocketClass(\n context: QuickJSContext,\n stateMap: StateMap,\n onWebSocketCommand: (cmd: WebSocketCommand) => void\n): QuickJSHandle {\n return defineClass<ServerWebSocketState>(context, stateMap, {\n name: \"ServerWebSocket\",\n construct: (args) => {\n const connectionId = String(args[0]);\n // Note: 'data' is accessed via a pure-JS getter that reads from __upgradeRegistry__\n // This avoids unmarshalling complex objects (like Zod schemas) that would exceed depth limit\n return {\n readyState: 1, // OPEN\n connectionId,\n wsHandle: null,\n };\n },\n properties: {\n // Note: 'data' is NOT defined here - it's a pure-JS getter added in setup.ts\n // that reads from __upgradeRegistry__ to avoid marshalling complex objects\n readyState: {\n get(this: ServerWebSocketState) {\n return this.readyState;\n },\n },\n },\n methods: {\n send(this: ServerWebSocketState, message: unknown) {\n if (this.readyState !== 1) {\n throw new Error(\"WebSocket is not open\");\n }\n\n let data: string | ArrayBuffer;\n if (typeof message === \"string\") {\n data = message;\n } else if (message instanceof ArrayBuffer) {\n data = message;\n } else if (message instanceof Uint8Array) {\n data = message.buffer.slice(\n message.byteOffset,\n message.byteOffset + message.byteLength\n ) as ArrayBuffer;\n } else {\n data = String(message);\n }\n\n onWebSocketCommand({\n type: \"message\",\n connectionId: this.connectionId,\n data,\n });\n },\n close(this: ServerWebSocketState, code?: unknown, reason?: unknown) {\n if (this.readyState === 3) {\n return; // Already closed\n }\n\n this.readyState = 2; // CLOSING\n\n onWebSocketCommand({\n type: \"close\",\n connectionId: this.connectionId,\n code: typeof code === \"number\" ? code : undefined,\n reason: typeof reason === \"string\" ? reason : undefined,\n });\n },\n },\n });\n}\n\n/**\n * Create the Server class for QuickJS\n */\nexport function createServerClass(\n context: QuickJSContext,\n stateMap: StateMap,\n _serveState: ServeState\n): QuickJSHandle {\n // Note: The upgrade method is added as pure JavaScript in setup.ts\n // to avoid marshalling data across the QuickJS/host boundary.\n // This keeps complex objects (like Zod schemas) within QuickJS.\n return defineClass<ServerState>(context, stateMap, {\n name: \"Server\",\n construct: () => {\n return {};\n },\n methods: {},\n });\n}\n\n/**\n * Create the serve function for QuickJS\n * Uses context.newFunction instead of defineFunction to properly manage handle lifetimes\n */\nexport function createServeFunction(\n context: QuickJSContext,\n stateMap: StateMap,\n serveState: ServeState\n): QuickJSHandle {\n return context.newFunction(\"serve\", (optionsHandle) => {\n if (context.typeof(optionsHandle) !== \"object\") {\n throw context.newError(\"serve requires an options object\");\n }\n\n // Dispose previous handlers if any\n if (serveState.fetchHandler) {\n serveState.fetchHandler.dispose();\n serveState.fetchHandler = null;\n }\n\n // Get and store fetch handler - don't dispose, keep alive for later calls\n const fetchHandle = context.getProp(optionsHandle, \"fetch\");\n if (context.typeof(fetchHandle) === \"function\") {\n serveState.fetchHandler = fetchHandle;\n } else {\n fetchHandle.dispose();\n }\n\n // Get and store websocket handlers\n const websocketHandle = context.getProp(optionsHandle, \"websocket\");\n if (context.typeof(websocketHandle) === \"object\") {\n // Dispose previous handlers\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 const openHandle = context.getProp(websocketHandle, \"open\");\n if (context.typeof(openHandle) === \"function\") {\n serveState.websocketHandlers.open = openHandle;\n } else {\n openHandle.dispose();\n }\n\n const messageHandle = context.getProp(websocketHandle, \"message\");\n if (context.typeof(messageHandle) === \"function\") {\n serveState.websocketHandlers.message = messageHandle;\n } else {\n messageHandle.dispose();\n }\n\n const closeHandle = context.getProp(websocketHandle, \"close\");\n if (context.typeof(closeHandle) === \"function\") {\n serveState.websocketHandlers.close = closeHandle;\n } else {\n closeHandle.dispose();\n }\n\n const errorHandle = context.getProp(websocketHandle, \"error\");\n if (context.typeof(errorHandle) === \"function\") {\n serveState.websocketHandlers.error = errorHandle;\n } else {\n errorHandle.dispose();\n }\n }\n websocketHandle.dispose();\n\n return context.undefined;\n });\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAE4B,IAA5B;
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "D110C1B8E74ED4FB64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/dist/cjs/handle.cjs
CHANGED
|
@@ -108,8 +108,19 @@ function createFetchHandle(context, stateMap, serveState) {
|
|
|
108
108
|
if (!serveState.fetchHandler) {
|
|
109
109
|
throw new Error("No serve() handler registered");
|
|
110
110
|
}
|
|
111
|
+
if (serveState.pendingUpgrade?.connectionId) {
|
|
112
|
+
const connId = serveState.pendingUpgrade.connectionId;
|
|
113
|
+
if (!serveState.activeConnections.has(connId)) {
|
|
114
|
+
const deleteResult = context.evalCode(`__upgradeRegistry__.delete("${connId}")`);
|
|
115
|
+
if (deleteResult.error) {
|
|
116
|
+
deleteResult.error.dispose();
|
|
117
|
+
} else {
|
|
118
|
+
deleteResult.value.dispose();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
111
122
|
serveState.pendingUpgrade = null;
|
|
112
|
-
const requestState =
|
|
123
|
+
const requestState = import_request.createRequestStateFromNative(request);
|
|
113
124
|
const requestHandle = createRequestInstance(context, stateMap, requestState);
|
|
114
125
|
const serverResult = context.evalCode(`new __Server__()`);
|
|
115
126
|
if (serverResult.error) {
|
|
@@ -176,26 +187,24 @@ function createFetchHandle(context, stateMap, serveState) {
|
|
|
176
187
|
getUpgradeRequest() {
|
|
177
188
|
return serveState.pendingUpgrade;
|
|
178
189
|
},
|
|
179
|
-
dispatchWebSocketOpen(connectionId
|
|
190
|
+
dispatchWebSocketOpen(connectionId) {
|
|
180
191
|
if (!serveState.websocketHandlers.open) {
|
|
181
192
|
return;
|
|
182
193
|
}
|
|
183
194
|
const connectionIdHandle = context.newString(connectionId);
|
|
184
|
-
const dataHandle = import_quickjs_core.marshal(context, data);
|
|
185
195
|
context.setProp(context.global, "__wsConnectionId__", connectionIdHandle);
|
|
186
|
-
context.setProp(context.global, "__wsData__", dataHandle);
|
|
187
196
|
connectionIdHandle.dispose();
|
|
188
|
-
|
|
189
|
-
const wsResult = context.evalCode(`new __ServerWebSocket__(__wsConnectionId__, __wsData__)`);
|
|
197
|
+
const wsResult = context.evalCode(`new __ServerWebSocket__(__wsConnectionId__)`);
|
|
190
198
|
context.setProp(context.global, "__wsConnectionId__", context.undefined);
|
|
191
|
-
context.setProp(context.global, "__wsData__", context.undefined);
|
|
192
199
|
if (wsResult.error) {
|
|
193
200
|
wsResult.error.dispose();
|
|
194
201
|
return;
|
|
195
202
|
}
|
|
196
203
|
const wsHandle = wsResult.value;
|
|
204
|
+
const connIdForGetter = context.newString(connectionId);
|
|
205
|
+
context.setProp(wsHandle, "__connectionId__", connIdForGetter);
|
|
206
|
+
connIdForGetter.dispose();
|
|
197
207
|
serveState.activeConnections.set(connectionId, {
|
|
198
|
-
data,
|
|
199
208
|
readyState: 1,
|
|
200
209
|
connectionId,
|
|
201
210
|
wsHandle
|
|
@@ -227,14 +236,24 @@ function createFetchHandle(context, stateMap, serveState) {
|
|
|
227
236
|
context.runtime.executePendingJobs();
|
|
228
237
|
},
|
|
229
238
|
dispatchWebSocketClose(connectionId, code, reason) {
|
|
239
|
+
const cleanupRegistry = () => {
|
|
240
|
+
const deleteResult = context.evalCode(`__upgradeRegistry__.delete("${connectionId}")`);
|
|
241
|
+
if (deleteResult.error) {
|
|
242
|
+
deleteResult.error.dispose();
|
|
243
|
+
} else {
|
|
244
|
+
deleteResult.value.dispose();
|
|
245
|
+
}
|
|
246
|
+
};
|
|
230
247
|
const connection = serveState.activeConnections.get(connectionId);
|
|
231
248
|
if (!connection || !connection.wsHandle) {
|
|
232
249
|
serveState.activeConnections.delete(connectionId);
|
|
250
|
+
cleanupRegistry();
|
|
233
251
|
return;
|
|
234
252
|
}
|
|
235
253
|
if (!serveState.websocketHandlers.close) {
|
|
236
254
|
connection.wsHandle.dispose();
|
|
237
255
|
serveState.activeConnections.delete(connectionId);
|
|
256
|
+
cleanupRegistry();
|
|
238
257
|
return;
|
|
239
258
|
}
|
|
240
259
|
connection.readyState = 3;
|
|
@@ -250,6 +269,7 @@ function createFetchHandle(context, stateMap, serveState) {
|
|
|
250
269
|
}
|
|
251
270
|
connection.wsHandle.dispose();
|
|
252
271
|
serveState.activeConnections.delete(connectionId);
|
|
272
|
+
cleanupRegistry();
|
|
253
273
|
context.runtime.executePendingJobs();
|
|
254
274
|
},
|
|
255
275
|
dispatchWebSocketError(connectionId, error) {
|
|
@@ -281,6 +301,12 @@ function createFetchHandle(context, stateMap, serveState) {
|
|
|
281
301
|
return serveState.fetchHandler !== null;
|
|
282
302
|
},
|
|
283
303
|
dispose() {
|
|
304
|
+
const clearResult = context.evalCode(`__upgradeRegistry__.clear()`);
|
|
305
|
+
if (clearResult.error) {
|
|
306
|
+
clearResult.error.dispose();
|
|
307
|
+
} else {
|
|
308
|
+
clearResult.value.dispose();
|
|
309
|
+
}
|
|
284
310
|
if (serveState.websocketHandlers.open) {
|
|
285
311
|
serveState.websocketHandlers.open.dispose();
|
|
286
312
|
serveState.websocketHandlers.open = undefined;
|
|
@@ -312,4 +338,4 @@ function createFetchHandle(context, stateMap, serveState) {
|
|
|
312
338
|
}
|
|
313
339
|
})
|
|
314
340
|
|
|
315
|
-
//# debugId=
|
|
341
|
+
//# debugId=CDC4947C4614CBB964756E2164756E21
|