UNPKG

@orpc/server

Version:

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

181 lines (174 loc) 7.04 kB
import { ORPCError } from '@orpc/client'; import { toArray, intercept, resolveMaybeOptionalOptions } from '@orpc/shared'; import { toStandardLazyRequest, toFetchResponse } from '@orpc/standard-server-fetch'; import { r as resolveFriendlyStandardHandleOptions } from '../../shared/server.DZ5BIITo.mjs'; import '@orpc/standard-server'; import '@orpc/contract'; import { C as CompositeStandardHandlerPlugin, b as StandardRPCHandler } from '../../shared/server.Bxx6tqNe.mjs'; import '@orpc/client/standard'; import '@orpc/standard-server/batch'; import { S as StrictGetMethodPlugin } from '../../shared/server.TEVCLCFC.mjs'; import '../../shared/server.Ds4HPpvH.mjs'; class BodyLimitPlugin { maxBodySize; constructor(options) { this.maxBodySize = options.maxBodySize; } initRuntimeAdapter(options) { options.adapterInterceptors ??= []; options.adapterInterceptors.push(async (options2) => { if (!options2.request.body) { return options2.next(); } let currentBodySize = 0; const rawReader = options2.request.body.getReader(); const reader = new ReadableStream({ start: async (controller) => { try { if (Number(options2.request.headers.get("content-length")) > this.maxBodySize) { controller.error(new ORPCError("PAYLOAD_TOO_LARGE")); return; } while (true) { const { done, value } = await rawReader.read(); if (done) { break; } currentBodySize += value.length; if (currentBodySize > this.maxBodySize) { controller.error(new ORPCError("PAYLOAD_TOO_LARGE")); break; } controller.enqueue(value); } } finally { controller.close(); } } }); const requestInit = { body: reader, duplex: "half" }; return options2.next({ ...options2, request: new Request(options2.request, requestInit) }); }); } } const ORDERED_SUPPORTED_ENCODINGS = ["gzip", "deflate"]; class CompressionPlugin { encodings; threshold; filter; constructor(options = {}) { this.encodings = options.encodings ?? ORDERED_SUPPORTED_ENCODINGS; this.threshold = options.threshold ?? 1024; this.filter = (request, response) => { const hasContentDisposition = response.headers.has("content-disposition"); const contentType = response.headers.get("content-type"); if (!hasContentDisposition && contentType?.startsWith("text/event-stream")) { return false; } return options.filter ? options.filter(request, response) : isCompressibleContentType(contentType); }; } initRuntimeAdapter(options) { options.adapterInterceptors ??= []; options.adapterInterceptors.unshift(async (options2) => { const result = await options2.next(); if (!result.matched) { return result; } const response = result.response; if (response.headers.has("content-encoding") || response.headers.has("transfer-encoding") || isNoTransformCacheControl(response.headers.get("cache-control"))) { return result; } const contentLength = response.headers.get("content-length"); if (contentLength && Number(contentLength) < this.threshold) { return result; } const acceptEncoding = options2.request.headers.get("accept-encoding")?.split(",").map((enc) => enc.trim().split(";")[0]); const encoding = this.encodings.find((enc) => acceptEncoding?.includes(enc)); if (!response.body || encoding === void 0) { return result; } if (!this.filter(options2.request, response)) { return result; } const compressedBody = response.body.pipeThrough(new CompressionStream(encoding)); const compressedHeaders = new Headers(response.headers); compressedHeaders.delete("content-length"); compressedHeaders.set("content-encoding", encoding); return { ...result, response: new Response(compressedBody, { status: response.status, statusText: response.statusText, headers: compressedHeaders }) }; }); } } const COMPRESSIBLE_CONTENT_TYPE_REGEX = /^\s*(?:text\/(?!event-stream(?:[;\s]|$))[^;\s]+|application\/(?:javascript|json|xml|xml-dtd|ecmascript|dart|postscript|rtf|tar|toml|vnd\.dart|vnd\.ms-fontobject|vnd\.ms-opentype|wasm|x-httpd-php|x-javascript|x-ns-proxy-autoconfig|x-sh|x-tar|x-virtualbox-hdd|x-virtualbox-ova|x-virtualbox-ovf|x-virtualbox-vbox|x-virtualbox-vdi|x-virtualbox-vhd|x-virtualbox-vmdk|x-www-form-urlencoded)|font\/(?:otf|ttf)|image\/(?:bmp|vnd\.adobe\.photoshop|vnd\.microsoft\.icon|vnd\.ms-dds|x-icon|x-ms-bmp)|message\/rfc822|model\/gltf-binary|x-shader\/x-fragment|x-shader\/x-vertex|[^;\s]+?\+(?:json|text|xml|yaml))(?:[;\s]|$)/i; function isCompressibleContentType(contentType) { if (contentType === null) { return false; } return COMPRESSIBLE_CONTENT_TYPE_REGEX.test(contentType); } const CACHE_CONTROL_NO_TRANSFORM_REGEX = /(?:^|,)\s*no-transform\s*(?:,|$)/i; function isNoTransformCacheControl(cacheControl) { if (cacheControl === null) { return false; } return CACHE_CONTROL_NO_TRANSFORM_REGEX.test(cacheControl); } class CompositeFetchHandlerPlugin extends CompositeStandardHandlerPlugin { initRuntimeAdapter(options) { for (const plugin of this.plugins) { plugin.initRuntimeAdapter?.(options); } } } class FetchHandler { constructor(standardHandler, options = {}) { this.standardHandler = standardHandler; const plugin = new CompositeFetchHandlerPlugin(options.plugins); plugin.initRuntimeAdapter(options); this.adapterInterceptors = toArray(options.adapterInterceptors); this.toFetchResponseOptions = options; } toFetchResponseOptions; adapterInterceptors; async handle(request, ...rest) { return intercept( this.adapterInterceptors, { ...resolveFriendlyStandardHandleOptions(resolveMaybeOptionalOptions(rest)), request, toFetchResponseOptions: this.toFetchResponseOptions }, async ({ request: request2, toFetchResponseOptions, ...options }) => { const standardRequest = toStandardLazyRequest(request2); const result = await this.standardHandler.handle(standardRequest, options); if (!result.matched) { return result; } return { matched: true, response: toFetchResponse(result.response, toFetchResponseOptions) }; } ); } } class RPCHandler extends FetchHandler { constructor(router, options = {}) { if (options.strictGetMethodPluginEnabled ?? true) { options.plugins ??= []; options.plugins.push(new StrictGetMethodPlugin()); } super(new StandardRPCHandler(router, options), options); } } export { BodyLimitPlugin, CompositeFetchHandlerPlugin, CompressionPlugin, FetchHandler, RPCHandler };