UNPKG

@orpc/standard-server-fetch

Version:

<div align="center"> <image align="center" src="https://orpc.unnoq.com/logo.webp" width=280 alt="oRPC logo" /> </div>

234 lines (227 loc) • 7.45 kB
import { stringifyJSON, parseEmptyableJSON, isTypescriptObject, isAsyncIteratorObject, once } from '@orpc/shared'; import { EventDecoderStream, encodeEventMessage, getEventMeta, ErrorEvent, withEventMeta, getFilenameFromContentDisposition, generateContentDisposition } from '@orpc/standard-server'; function toEventIterator(stream) { const eventStream = stream.pipeThrough(new TextDecoderStream()).pipeThrough(new EventDecoderStream()); const reader = eventStream.getReader(); async function* gen() { try { while (true) { const { done, value } = await reader.read(); if (done) { return; } switch (value.event) { case "message": { let message = parseEmptyableJSON(value.data); if (isTypescriptObject(message)) { message = withEventMeta(message, value); } yield message; break; } case "error": { let error = new ErrorEvent({ data: parseEmptyableJSON(value.data) }); error = withEventMeta(error, value); throw error; } case "done": { let done2 = parseEmptyableJSON(value.data); if (isTypescriptObject(done2)) { done2 = withEventMeta(done2, value); } return done2; } } } } finally { await reader.cancel(); } } return gen(); } function toEventStream(iterator, options = {}) { const keepAliveEnabled = options.eventIteratorKeepAliveEnabled ?? true; const keepAliveInterval = options.eventIteratorKeepAliveInterval ?? 5e3; const keepAliveComment = options.eventIteratorKeepAliveComment ?? ""; let timeout; const stream = new ReadableStream({ async pull(controller) { try { if (keepAliveEnabled) { timeout = setInterval(() => { controller.enqueue(encodeEventMessage({ comments: [keepAliveComment] })); }, keepAliveInterval); } const value = await iterator.next(); clearInterval(timeout); const meta = getEventMeta(value.value); if (!value.done || value.value !== void 0 || meta !== void 0) { controller.enqueue(encodeEventMessage({ ...meta, event: value.done ? "done" : "message", data: stringifyJSON(value.value) })); } if (value.done) { controller.close(); } } catch (err) { clearInterval(timeout); controller.enqueue(encodeEventMessage({ ...getEventMeta(err), event: "error", data: err instanceof ErrorEvent ? stringifyJSON(err.data) : void 0 })); controller.close(); } }, async cancel(reason) { if (reason) { await iterator.throw?.(reason); } else { await iterator.return?.(); } } }).pipeThrough(new TextEncoderStream()); return stream; } async function toStandardBody(re) { if (!re.body) { return void 0; } const contentDisposition = re.headers.get("content-disposition"); if (typeof contentDisposition === "string") { const fileName = getFilenameFromContentDisposition(contentDisposition) ?? "blob"; const blob2 = await re.blob(); return new File([blob2], fileName, { type: blob2.type }); } const contentType = re.headers.get("content-type"); if (!contentType || contentType.startsWith("application/json")) { const text = await re.text(); return parseEmptyableJSON(text); } if (contentType.startsWith("multipart/form-data")) { return await re.formData(); } if (contentType.startsWith("application/x-www-form-urlencoded")) { const text = await re.text(); return new URLSearchParams(text); } if (contentType.startsWith("text/event-stream")) { return toEventIterator(re.body); } if (contentType.startsWith("text/plain")) { return await re.text(); } const blob = await re.blob(); return new File([blob], "blob", { type: blob.type }); } function toFetchBody(body, headers, options = {}) { const currentContentDisposition = headers.get("content-disposition"); headers.delete("content-type"); headers.delete("content-disposition"); if (body === void 0) { return void 0; } if (body instanceof Blob) { headers.set("content-type", body.type); headers.set("content-length", body.size.toString()); headers.set( "content-disposition", currentContentDisposition ?? generateContentDisposition(body instanceof File ? body.name : "blob") ); return body; } if (body instanceof FormData) { return body; } if (body instanceof URLSearchParams) { return body; } if (isAsyncIteratorObject(body)) { headers.set("content-type", "text/event-stream"); headers.set("cache-control", "no-cache"); headers.set("connection", "keep-alive"); return toEventStream(body, options); } headers.set("content-type", "application/json"); return stringifyJSON(body); } function toStandardHeaders(headers, standardHeaders = {}) { for (const [key, value] of headers) { if (Array.isArray(standardHeaders[key])) { standardHeaders[key].push(value); } else if (standardHeaders[key] !== void 0) { standardHeaders[key] = [standardHeaders[key], value]; } else { standardHeaders[key] = value; } } return standardHeaders; } function toFetchHeaders(headers, fetchHeaders = new Headers()) { for (const [key, value] of Object.entries(headers)) { if (Array.isArray(value)) { for (const v of value) { fetchHeaders.append(key, v); } } else if (value !== void 0) { fetchHeaders.append(key, value); } } return fetchHeaders; } function toStandardLazyRequest(request) { return { url: new URL(request.url), signal: request.signal, method: request.method, body: once(() => toStandardBody(request)), get headers() { const headers = toStandardHeaders(request.headers); Object.defineProperty(this, "headers", { value: headers, writable: true }); return headers; }, set headers(value) { Object.defineProperty(this, "headers", { value, writable: true }); } }; } function toFetchRequest(request, options = {}) { const headers = toFetchHeaders(request.headers); const body = toFetchBody(request.body, headers, options); return new Request(request.url, { signal: request.signal, method: request.method, headers, body }); } function toFetchResponse(response, options = {}) { const headers = toFetchHeaders(response.headers); const body = toFetchBody(response.body, headers, options); return new Response(body, { headers, status: response.status }); } function toStandardLazyResponse(response) { return { body: once(() => toStandardBody(response)), status: response.status, get headers() { const headers = toStandardHeaders(response.headers); Object.defineProperty(this, "headers", { value: headers, writable: true }); return headers; }, set headers(value) { Object.defineProperty(this, "headers", { value, writable: true }); } }; } export { toEventIterator, toEventStream, toFetchBody, toFetchHeaders, toFetchRequest, toFetchResponse, toStandardBody, toStandardHeaders, toStandardLazyRequest, toStandardLazyResponse };