@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
JavaScript
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 };