@lowerdeck/rpc-server 1.0.0

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.umd.js","sources":["../src/controller.ts","../src/rpcMux.ts","../src/server.ts","../src/extractIp.ts"],"sourcesContent":["import { ServiceError, validationError } from '@lowerdeck/error';\nimport { ValidationType } from '@lowerdeck/validation';\nimport * as Cookie from 'cookie';\n\nexport type Method = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';\n\nexport interface ServiceRequest {\n query: URLSearchParams;\n headers: Headers;\n url: string;\n ip?: string;\n body: any;\n rawBody: any;\n requestId: string;\n\n getCookies: () => Record<string, string | undefined>;\n getCookie: (name: string) => string | undefined;\n setCookie: (name: string, value: string, opts?: Cookie.SerializeOptions) => void;\n\n sharedMiddlewareMemo: Map<string, Promise<any>>;\n beforeSend: (handler: () => Promise<any>) => void;\n appendHeaders: (headers: Record<string, string | string[]>) => void;\n}\n\nexport type Simplify<T> = { [KeyType in keyof T]: T[KeyType] } & {};\nexport type ExtendContext<C extends object, E> = E extends object ? Simplify<C & E> : C;\n\nexport class Group<Context extends { [key: string]: any } = {}> {\n constructor(\n private _middleware: Array<(ctx: Context & ServiceRequest) => Promise<any>> = []\n ) {}\n\n use<T extends { [key: string]: any } | undefined | void>(\n handler: (ctx: Context & ServiceRequest) => Promise<T>,\n opts?: {\n getSharedMemoKey?: (ctx: Context & ServiceRequest) => string;\n }\n ) {\n let middleware = async (ctx: Parameters<typeof handler>[0]): Promise<T> => {\n let key = opts?.getSharedMemoKey?.(ctx);\n if (key && ctx.sharedMiddlewareMemo.has(key)) {\n return await ctx.sharedMiddlewareMemo.get(key)!;\n }\n\n let res = handler(ctx);\n if (key) ctx.sharedMiddlewareMemo.set(key, res);\n\n return await res;\n };\n\n return new Group<Simplify<Context & T>>([...this._middleware, middleware]);\n }\n\n createMiddleware<T extends { [key: string]: any }, P = void>(\n handler: (ctx: Context & ServiceRequest, input: P) => Promise<T>,\n opts?: {\n getSharedMemoKey?: (ctx: Context & ServiceRequest, input: P) => string;\n }\n ) {\n return (input: P) =>\n async (ctx: Context & ServiceRequest): Promise<T> => {\n let key = opts?.getSharedMemoKey?.(ctx, input);\n if (key && ctx.sharedMiddlewareMemo.has(key)) {\n return await ctx.sharedMiddlewareMemo.get(key)!;\n }\n\n let res = handler(ctx, input);\n if (key) ctx.sharedMiddlewareMemo.set(key, res);\n\n return await res;\n };\n }\n\n handler() {\n return new Handler([...this._middleware]);\n }\n\n controller<\n HandlersAndSubControllers extends {\n [key: string]: Handler<any, any, any> | Controller<any>;\n }\n >(handlers: HandlersAndSubControllers): Controller<HandlersAndSubControllers> {\n return handlers;\n }\n}\n\nexport type Controller<\n HandlersAndSubControllers extends { [key: string]: Handler<any, any, any> | Controller<any> }\n> = HandlersAndSubControllers;\n\nexport type InferControllerType<T> = T extends Controller<infer U> ? U : never;\n\nexport type InferClient<\n HandlersAndSubControllers extends { [key: string]: Handler<any, any, any> | Controller<any> }\n> = {\n [K in keyof HandlersAndSubControllers]: HandlersAndSubControllers[K] extends Handler<\n infer I,\n infer O,\n infer C\n >\n ? ((\n input: I,\n opts?: { headers?: Record<string, string>; query?: Record<string, string> }\n ) => Promise<O>) & {\n getFull: (\n input: I,\n opts?: { headers?: Record<string, string>; query?: Record<string, string> }\n ) => Promise<{\n data: O;\n status: number;\n headers: Record<string, string>;\n }>;\n }\n : HandlersAndSubControllers[K] extends Controller<infer U>\n ? InferClient<U>\n : never;\n};\n\nexport class Handler<Input, Output, Context extends { [key: string]: any } = {}> {\n private _handler!: (\n ctx: Context & Omit<ServiceRequest, 'body'> & { input: Input }\n ) => Promise<Output>;\n private _validation: ValidationType<Input> | undefined;\n\n constructor(\n private _middleware: Array<(ctx: Context & ServiceRequest) => Promise<any>> = []\n ) {}\n\n do<HandlerOutput>(\n handler: (\n ctx: Context & Omit<ServiceRequest, 'body'> & { input: Input }\n ) => Promise<HandlerOutput>\n ) {\n if (this._handler != undefined) throw new Error('Handler already defined');\n\n // @ts-ignore\n this._handler = handler;\n\n return this as any as Handler<Input, HandlerOutput, Context>;\n }\n\n use<T extends { [key: string]: any } = {}>(\n handler: (ctx: Context & ServiceRequest) => Promise<T | undefined | void>\n ) {\n this._middleware.push(handler);\n return this as any as Handler<Input, Output, ExtendContext<Context, T>>;\n }\n\n input<HandlerInput>(validation: ValidationType<HandlerInput>) {\n if (this._validation != undefined) throw new Error('Input validation already defined');\n\n // @ts-ignore\n this._validation = validation;\n\n return this as any as Handler<HandlerInput, Output, Context>;\n }\n\n async run(\n req: ServiceRequest,\n initialContext: any\n ): Promise<{\n response: Output;\n }> {\n if (!this._handler) throw new Error('Handler not defined');\n\n let input = req.body as Input;\n\n if (this._validation) {\n let valRes = this._validation.validate(req.body);\n\n if (!valRes.success) {\n throw new ServiceError(\n validationError({ errors: valRes.errors, entity: 'call_data' })\n );\n }\n\n input = valRes.value;\n }\n\n let ctx = {\n ...initialContext,\n ...req,\n\n // Always use the sanitized input\n body: input\n };\n\n for (let mw of this._middleware) {\n let res = await mw(ctx);\n if (res) ctx = { ...ctx, ...res };\n }\n\n let res = await this._handler({\n ...ctx,\n input,\n body: undefined\n });\n\n return {\n response: res\n };\n }\n}\n","import {\n internalServerError,\n notAcceptableError,\n notFoundError,\n validationError\n} from '@lowerdeck/error';\nimport { createExecutionContext, provideExecutionContext } from '@lowerdeck/execution-context';\nimport { generateCustomId } from '@lowerdeck/id';\nimport { memo } from '@lowerdeck/memo';\nimport { getSentry } from '@lowerdeck/sentry';\nimport { serialize } from '@lowerdeck/serialize';\nimport { v } from '@lowerdeck/validation';\nimport * as Cookie from 'cookie';\nimport { ServiceRequest } from './controller';\nimport { parseForwardedFor } from './extractIp';\n\nlet Sentry = getSentry();\n\nlet validation = v.object({\n calls: v.array(\n v.object({\n id: v.string(),\n name: v.string(),\n payload: v.any()\n })\n )\n});\n\nexport let rpcMux = (\n opts: {\n path: string;\n cors?: {\n headers?: string[];\n } & ({ domains: string[] } | { check: (origin: string) => boolean });\n },\n rpcs: {\n handlerNames: string[];\n runMany: (\n req: ServiceRequest,\n body: {\n requestId: string;\n calls: {\n id: string;\n name: string;\n payload: any;\n }[];\n }\n ) => Promise<{\n status: number;\n body: {\n calls: any[];\n };\n }>;\n }[]\n) => {\n let handlerNameToRpcMap = new Map<string, number>(\n rpcs.flatMap((rpc, i) => rpc.handlerNames.map(name => [name, i]))\n );\n\n return {\n path: opts.path,\n\n fetch: async (req: any): Promise<any> => {\n let origin = req.headers.get('origin') ?? '';\n let corsOk = false;\n\n if (opts.cors && 'domains' in opts.cors) {\n try {\n let url = new URL(origin);\n let rootDomain = url.hostname.split('.').slice(-2).join('.');\n corsOk = opts.cors.domains.includes(rootDomain);\n } catch (e) {\n // Ignore -> no cors\n }\n } else if (opts.cors && 'check' in opts.cors) {\n corsOk = opts.cors.check(origin);\n }\n\n let url = new URL(req.url);\n\n let additionalCorsHeaders = opts.cors?.headers?.join(', ');\n if (additionalCorsHeaders) additionalCorsHeaders = `, ${additionalCorsHeaders}`.trim();\n\n let corsHeaders: Record<string, string> = corsOk\n ? {\n 'access-control-allow-origin': origin,\n 'access-control-allow-methods': 'POST, OPTIONS',\n 'access-control-allow-headers': `Content-Type, Authorization, Baggage, Sentry-Trace${\n additionalCorsHeaders ?? ''\n }`,\n 'access-control-max-age': '604800',\n 'access-control-allow-credentials': 'true'\n }\n : {};\n\n if (req.method == 'OPTIONS') {\n if (corsOk) {\n return new Response(null, {\n status: 204,\n headers: corsHeaders\n });\n }\n\n return new Response(null, { status: 403 });\n }\n\n let body: any = null;\n\n try {\n body = serialize.decode(await req.text());\n } catch (e) {\n return new Response(\n JSON.stringify(notAcceptableError({ message: 'Invalid JSON' }).toResponse()),\n { status: 406 }\n );\n }\n\n let sentryTraceHeaders = req.headers.get('sentry-trace');\n let sentryTrace =\n (Array.isArray(sentryTraceHeaders)\n ? sentryTraceHeaders.join(',')\n : sentryTraceHeaders) ?? undefined;\n let baggage = req.headers.get('baggage');\n\n let ip = parseForwardedFor(\n req.headers.get('lowerdeck-connecting-ip') ??\n req.headers.get('cf-connecting-ip') ??\n req.headers.get('x-forwarded-for') ??\n req.headers.get('x-real-ip')\n );\n\n let headers = new Headers();\n\n if (corsOk) {\n for (let [key, value] of Object.entries(corsHeaders)) {\n headers.append(key, value);\n }\n }\n\n return await Sentry.withIsolationScope(\n async () =>\n await Sentry.continueTrace(\n { sentryTrace, baggage },\n async () =>\n await Sentry.startSpan(\n {\n name: 'rpc request',\n op: 'rpc.server',\n attributes: {\n ip,\n transport: 'http',\n ua: req.headers.get('user-agent') ?? '',\n origin: req.headers.get('origin') ?? ''\n }\n },\n async () => {\n try {\n let beforeSends: Array<() => Promise<any>> = [];\n let id = generateCustomId('req_');\n\n let parseCookies = memo(() =>\n Cookie.parse(req.headers.get('cookie') ?? '')\n );\n\n let request: ServiceRequest = {\n url: req.url,\n headers: req.headers,\n query: url.searchParams,\n body,\n rawBody: body,\n ip,\n requestId: id,\n\n getCookies: () => parseCookies(),\n getCookie: (name: string) => parseCookies()[name],\n setCookie: (name: string, value: string, opts?: any) => {\n let cookie = Cookie.serialize(name, value, opts);\n // @ts-ignore\n headers.append('Set-Cookie', cookie);\n },\n\n beforeSend: (handler: () => Promise<any>) => {\n beforeSends.push(handler);\n },\n\n sharedMiddlewareMemo: new Map<string, Promise<any>>(),\n\n appendHeaders: (newHeaders: Record<string, string | string[]>) => {\n for (let [key, value] of Object.entries(newHeaders)) {\n if (Array.isArray(value)) {\n for (let v of value) headers.append(key, v);\n } else {\n headers.append(key, value);\n }\n }\n }\n };\n\n Sentry.getCurrentScope().setContext('rpc.request', {\n url: req.url,\n query: Object.fromEntries(url.searchParams.entries())\n });\n\n Sentry.getCurrentScope().addAttachment({\n filename: 'rpc.request.body.json',\n data: body,\n contentType: 'application/json'\n });\n\n let valRes = validation.validate(body);\n if (!valRes.success) {\n return new Response(\n JSON.stringify(\n validationError({\n errors: valRes.errors,\n entity: 'request_data'\n }).toResponse()\n ),\n { status: 406, headers }\n );\n }\n\n return provideExecutionContext(\n createExecutionContext({\n type: 'request',\n contextId: id,\n ip: ip ?? '0.0.0.0',\n userAgent: req.headers.get('user-agent') ?? ''\n }),\n async () => {\n let callsByRpc = new Map<\n number,\n { id: string; name: string; payload: any }[]\n >();\n\n for (let call of body.calls) {\n let rpcIndex = handlerNameToRpcMap.get(call.name);\n if (rpcIndex == undefined) {\n return new Response(\n JSON.stringify(\n notFoundError({ entity: 'handler' }).toResponse()\n ),\n { status: 404, headers }\n );\n }\n\n let calls = callsByRpc.get(rpcIndex) ?? [];\n calls.push(call);\n callsByRpc.set(rpcIndex, calls);\n }\n\n // let res = await runMany(request);\n\n let resRef = {\n body: {\n __typename: 'rpc.response',\n calls: [] as any[]\n },\n status: 200\n };\n\n await Promise.all(\n Array.from(callsByRpc.entries()).map(async ([rpcIndex, calls]) => {\n let rpc = rpcs[rpcIndex];\n let res = await rpc.runMany(request, {\n requestId: id,\n calls\n });\n\n resRef.status = Math.max(resRef.status, res.status);\n resRef.body.calls.push(...res.body.calls);\n })\n );\n\n headers.append('x-req-id', id);\n headers.append('content-type', 'application/rpc+json');\n headers.append('x-powered-by', 'lowerdeck RPC');\n\n await Promise.all(beforeSends.map(s => s()));\n\n return new Response(serialize.encode(resRef.body), {\n status: resRef.status,\n headers\n });\n }\n );\n } catch (e) {\n console.error(e);\n\n Sentry.captureException(e);\n\n return new Response(JSON.stringify(internalServerError().toResponse()), {\n status: 500,\n headers\n });\n }\n }\n )\n )\n );\n }\n };\n};\n","import { internalServerError, isServiceError, notFoundError } from '@lowerdeck/error';\nimport { getSentry } from '@lowerdeck/sentry';\nimport { Controller, Handler, ServiceRequest } from './controller';\n\nlet Sentry = getSentry();\n\nexport let createServer =\n (opts: {\n onError?: (opts: {\n request: ServiceRequest;\n error: any;\n reqId: string;\n callId: string;\n callName: string;\n }) => void;\n onRequest?: (opts: {\n reqId: string;\n callId: string;\n callName: string;\n request: ServiceRequest;\n response: { status: number; body: any };\n }) => void;\n }) =>\n (controller: Controller<any>) => {\n let findHandler = (name: string): Handler<any, any, any> | null => {\n let parts = name.split(':');\n let current = controller;\n\n while (current && parts.length > 0) {\n current = current[parts.shift()!];\n if (!current) return null;\n }\n\n if (current && current instanceof Handler) return current;\n\n return null;\n };\n\n let getSupportedHandlerNames = (controller: Controller<any>): string[] =>\n Object.entries(controller).flatMap(([key, value]) => {\n if (value instanceof Handler) return [key];\n if (typeof value == 'object')\n return getSupportedHandlerNames(value).map(name => `${key}:${name}`);\n return [];\n });\n\n let handlerNames = getSupportedHandlerNames(controller);\n\n let run = async (\n req: ServiceRequest,\n call: { id: string; name: string; payload: any },\n reqId: string\n ): Promise<{\n response: any;\n status: number;\n request: ServiceRequest;\n }> => {\n let request = { ...req, body: call.payload };\n\n try {\n let handler = findHandler(call.name);\n\n if (!handler) {\n return {\n request,\n status: 404,\n response: notFoundError({ entity: 'handler' }).toResponse()\n };\n }\n\n let response = await handler.run(request, {});\n\n return {\n status: 200,\n request: req,\n response: response.response\n };\n } catch (e) {\n console.error(e);\n\n if (isServiceError(e)) {\n if (e.data.status >= 500) {\n Sentry.captureException(e, {\n tags: { reqId }\n });\n }\n\n return {\n request,\n status: e.data.status,\n response: e.toResponse()\n };\n }\n\n Sentry.captureException(e, {\n tags: { reqId }\n });\n\n opts.onError?.({\n callName: call.name,\n callId: call.id,\n request: req,\n error: e,\n reqId\n });\n\n return {\n request,\n status: 500,\n response: internalServerError().toResponse()\n };\n }\n };\n\n let runMany = async (\n req: ServiceRequest,\n body: {\n calls: {\n id: string;\n name: string;\n payload: any;\n }[];\n requestId: string;\n }\n ): Promise<{\n status: number;\n body: any;\n }> => {\n let callRes = await Promise.all(\n body.calls.map(async (call, i) => {\n let res = await run(req, call as any, body.requestId);\n\n try {\n opts.onRequest?.({\n reqId: body.requestId,\n callId: call.id,\n callName: call.name,\n request: res.request,\n response: { status: res.status, body: res.response }\n });\n } catch (e) {\n Sentry.captureException(e);\n console.error(e);\n }\n\n return {\n __typename: 'rpc.response.call',\n id: call.id,\n name: call.name,\n status: res.status,\n result: res.response\n };\n })\n );\n\n return {\n status: Math.max(...callRes.map(c => c.status)),\n body: {\n __typename: 'rpc.response',\n calls: callRes\n }\n };\n };\n\n return {\n handlerNames,\n runMany\n\n // fetch,\n\n // http: async (\n // req: IncomingMessage & {\n // body: any;\n // },\n // res: ServerResponse & { send: (body: any) => void }\n // ) => {\n // let headers = new Headers(\n // Object.fromEntries(\n // Object.entries(req.headers).map(([key, value]) => [\n // key,\n // value === undefined ? '' : String(value)\n // ])\n // )\n // );\n // let url = new URL(req.url ?? '', `http://${req.headers.host}`);\n\n // let request = new Request(url.toString(), {\n // method: req.method,\n // headers,\n // body: JSON.stringify(req.body)\n // });\n\n // let response = await fetch(request);\n\n // res.statusCode = response.status;\n\n // for (let [key, value] of response.headers.entries()) {\n // res.setHeader(key, value);\n // }\n\n // res.send(response.body);\n // }\n };\n };\n","export let parseForwardedFor = (xForwardedForHeader?: string | null | undefined) => {\n if (typeof xForwardedForHeader != 'string') return undefined;\n\n let ips = xForwardedForHeader\n .split(',')\n .map(ip => ip.trim())\n .filter(Boolean);\n return ips.length > 0 ? ips[0] : undefined;\n};\n"],"names":["_settle","pact","state","value","s","_Pact","o","bind","v","then","observer","prototype","onFulfilled","onRejected","result","this","callback","e","_this","_isSettledPact","thenable","Group","_middleware","_proto","use","handler","opts","concat","ctx","_exit","_temp2","_result","res","key","sharedMiddlewareMemo","set","Promise","resolve","getSharedMemoKey","_temp","has","get","_await$ctx$sharedMidd","reject","createMiddleware","input","_exit2","_temp4","_result2","_temp3","_await$ctx$sharedMidd2","Handler","controller","handlers","_handler","_validation","_proto2","undefined","Error","push","validation","run","req","initialContext","_temp6","_extends","body","response","valRes","validate","success","ServiceError","validationError","errors","entity","_temp5","_forOf","mw","Sentry","getSentry","object","calls","array","id","string","name","payload","any","getSupportedHandlerNames","Object","entries","flatMap","_ref","map","handlerNames","runMany","all","call","i","reqId","request","parts","split","current","length","shift","findHandler","status","notFoundError","toResponse","_catch","console","error","isServiceError","data","captureException","tags","onError","callName","callId","internalServerError","requestId","onRequest","__typename","callRes","Math","max","apply","c","rpcs","handlerNameToRpcMap","Map","rpc","path","fetch","_req$headers$get6","_opts$cors","_ref2","_ref3","_req$headers$get","sentryTraceHeaders","headers","sentryTrace","Array","isArray","join","baggage","ip","xForwardedForHeader","ips","trim","filter","Boolean","parseForwardedFor","Headers","corsOk","_i","_Object$entries","corsHeaders","_Object$entries$_i","append","withIsolationScope","continueTrace","_req$headers$get2","_req$headers$get3","startSpan","op","attributes","transport","ua","origin","_req$headers$get5","beforeSends","generateCustomId","parseCookies","memo","_req$headers$get4","Cookie","parse","url","query","searchParams","rawBody","getCookies","getCookie","setCookie","cookie","serialize","beforeSend","appendHeaders","newHeaders","_i2","_Object$entries2","_Object$entries2$_i","_iterator","_step","_createForOfIteratorHelperLoose","done","getCurrentScope","setContext","fromEntries","addAttachment","filename","contentType","provideExecutionContext","createExecutionContext","type","contextId","userAgent","_step2","callsByRpc","_iterator2","_callsByRpc$get","rpcIndex","Response","JSON","stringify","resRef","from","_ref4","_resRef$body$calls","encode","cors","rootDomain","URL","hostname","slice","domains","includes","check","additionalCorsHeaders","method","_decode","decode","text","_req$text","_Response","notAcceptableError","message"],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import { ServiceRequest } from './controller';
2
+ export declare let rpcMux: (opts: {
3
+ path: string;
4
+ cors?: {
5
+ headers?: string[];
6
+ } & ({
7
+ domains: string[];
8
+ } | {
9
+ check: (origin: string) => boolean;
10
+ });
11
+ }, rpcs: {
12
+ handlerNames: string[];
13
+ runMany: (req: ServiceRequest, body: {
14
+ requestId: string;
15
+ calls: {
16
+ id: string;
17
+ name: string;
18
+ payload: any;
19
+ }[];
20
+ }) => Promise<{
21
+ status: number;
22
+ body: {
23
+ calls: any[];
24
+ };
25
+ }>;
26
+ }[]) => {
27
+ path: string;
28
+ fetch: (req: any) => Promise<any>;
29
+ };
30
+ //# sourceMappingURL=rpcMux.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpcMux.d.ts","sourceRoot":"","sources":["../src/rpcMux.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAe9C,eAAO,IAAI,MAAM,GACf,MAAM;IACJ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE;QACL,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;KACpB,GAAG,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG;QAAE,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAA;KAAE,CAAC,CAAC;CACtE,EACD,MAAM;IACJ,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,OAAO,EAAE,CACP,GAAG,EAAE,cAAc,EACnB,IAAI,EAAE;QACJ,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,EAAE;YACL,EAAE,EAAE,MAAM,CAAC;YACX,IAAI,EAAE,MAAM,CAAC;YACb,OAAO,EAAE,GAAG,CAAC;SACd,EAAE,CAAC;KACL,KACE,OAAO,CAAC;QACX,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE;YACJ,KAAK,EAAE,GAAG,EAAE,CAAC;SACd,CAAC;KACH,CAAC,CAAC;CACJ,EAAE;;iBASkB,GAAG,KAAG,OAAO,CAAC,GAAG,CAAC;CAgPxC,CAAC"}
@@ -0,0 +1,34 @@
1
+ import { Controller, ServiceRequest } from './controller';
2
+ export declare let createServer: (opts: {
3
+ onError?: (opts: {
4
+ request: ServiceRequest;
5
+ error: any;
6
+ reqId: string;
7
+ callId: string;
8
+ callName: string;
9
+ }) => void;
10
+ onRequest?: (opts: {
11
+ reqId: string;
12
+ callId: string;
13
+ callName: string;
14
+ request: ServiceRequest;
15
+ response: {
16
+ status: number;
17
+ body: any;
18
+ };
19
+ }) => void;
20
+ }) => (controller: Controller<any>) => {
21
+ handlerNames: string[];
22
+ runMany: (req: ServiceRequest, body: {
23
+ calls: {
24
+ id: string;
25
+ name: string;
26
+ payload: any;
27
+ }[];
28
+ requestId: string;
29
+ }) => Promise<{
30
+ status: number;
31
+ body: any;
32
+ }>;
33
+ };
34
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAW,cAAc,EAAE,MAAM,cAAc,CAAC;AAInE,eAAO,IAAI,YAAY,GACpB,MAAM;IACL,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE;QACf,OAAO,EAAE,cAAc,CAAC;QACxB,KAAK,EAAE,GAAG,CAAC;QACX,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;KAClB,KAAK,IAAI,CAAC;IACX,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,cAAc,CAAC;QACxB,QAAQ,EAAE;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,GAAG,CAAA;SAAE,CAAC;KACzC,KAAK,IAAI,CAAC;CACZ,MACA,YAAY,UAAU,CAAC,GAAG,CAAC;;mBA4FnB,cAAc,QACb;QACJ,KAAK,EAAE;YACL,EAAE,EAAE,MAAM,CAAC;YACX,IAAI,EAAE,MAAM,CAAC;YACb,OAAO,EAAE,GAAG,CAAC;SACd,EAAE,CAAC;QACJ,SAAS,EAAE,MAAM,CAAC;KACnB,KACA,OAAO,CAAC;QACT,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,GAAG,CAAC;KACX,CAAC;CA4EH,CAAC"}
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@lowerdeck/rpc-server",
3
+ "version": "1.0.0",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "author": "Tobias Herber",
8
+ "license": "Apache 2",
9
+ "type": "module",
10
+ "source": "src/index.ts",
11
+ "exports": {
12
+ "types": "./dist/index.d.ts",
13
+ "require": "./dist/index.cjs",
14
+ "import": "./dist/index.module.js",
15
+ "default": "./dist/index.module.js"
16
+ },
17
+ "main": "./dist/index.cjs",
18
+ "module": "./dist/index.module.js",
19
+ "types": "dist/index.d.ts",
20
+ "unpkg": "./dist/index.umd.js",
21
+ "scripts": {
22
+ "test": "vitest run --passWithNoTests",
23
+ "lint": "prettier src/**/*.ts --check",
24
+ "build": "microbundle"
25
+ },
26
+ "devDependencies": {
27
+ "microbundle": "^0.15.1",
28
+ "@lowerdeck/tsconfig": "^1.0.0",
29
+ "typescript": "^5.9.3",
30
+ "vitest": "^3.2.4"
31
+ },
32
+ "dependencies": {
33
+ "@lowerdeck/error": "^1.0.5",
34
+ "@lowerdeck/execution-context": "^1.0.0",
35
+ "@lowerdeck/id": "^1.0.2",
36
+ "@lowerdeck/memo": "^1.0.1",
37
+ "@lowerdeck/sentry": "^1.0.0",
38
+ "@lowerdeck/serialize": "^1.0.1",
39
+ "@lowerdeck/validation": "^1.0.1",
40
+ "cookie": "^1.1.1"
41
+ }
42
+ }
@@ -0,0 +1,251 @@
1
+ import { v } from '@lowerdeck/validation';
2
+ import { describe, expect, test } from 'vitest';
3
+ import { Group } from './controller';
4
+
5
+ describe('controller', () => {
6
+ test('should create group', () => {
7
+ let group = new Group();
8
+ expect(group).toBeDefined();
9
+ });
10
+
11
+ test('should register middleware', async () => {
12
+ let group = new Group();
13
+
14
+ let group1 = group.use(async ctx => {
15
+ expect(typeof ctx).toBe('object');
16
+ expect(ctx.body).toBe('body');
17
+
18
+ return {
19
+ test: 'test' as const
20
+ };
21
+ });
22
+
23
+ let group2 = group1.use(async ctx => {
24
+ expect(ctx.test).toBe('test');
25
+
26
+ return {
27
+ hello: 'world' as const
28
+ };
29
+ });
30
+
31
+ let handler = group2.handler().do(async ctx => {
32
+ expect(ctx.hello).toBe('world');
33
+ expect(ctx.test).toBe('test');
34
+
35
+ return ctx;
36
+ });
37
+
38
+ expect(
39
+ await handler.run(
40
+ {
41
+ body: 'body',
42
+ query: new URLSearchParams(),
43
+ headers: new Headers(),
44
+ rawBody: '',
45
+ sharedMiddlewareMemo: new Map(),
46
+ beforeSend: () => {},
47
+ appendHeaders: () => {},
48
+ url: ''
49
+ } as any,
50
+ {}
51
+ )
52
+ ).toEqual({
53
+ response: {
54
+ hello: 'world',
55
+ test: 'test',
56
+ input: 'body',
57
+ query: new URLSearchParams(),
58
+ headers: new Headers(),
59
+ rawBody: '',
60
+ sharedMiddlewareMemo: new Map(),
61
+ appendHeaders: expect.any(Function),
62
+ beforeSend: expect.any(Function),
63
+ body: undefined,
64
+ url: ''
65
+ }
66
+ });
67
+ });
68
+
69
+ test('should register handler middleware', async () => {
70
+ let group = new Group().use(async ctx => {
71
+ expect(typeof ctx).toBe('object');
72
+ expect(ctx.body).toBe('body');
73
+
74
+ return {
75
+ test: 'test' as const
76
+ };
77
+ });
78
+
79
+ let handler = group
80
+ .handler()
81
+ .use(async ctx => {
82
+ expect(ctx.test).toBe('test');
83
+
84
+ return {
85
+ hello: 'world' as const
86
+ };
87
+ })
88
+ .do(async ctx => {
89
+ expect(ctx.hello).toBe('world');
90
+ expect(ctx.test).toBe('test');
91
+
92
+ return ctx;
93
+ });
94
+
95
+ expect(
96
+ await handler.run(
97
+ {
98
+ body: 'body',
99
+ query: new URLSearchParams(),
100
+ headers: new Headers(),
101
+ rawBody: '',
102
+ sharedMiddlewareMemo: new Map(),
103
+ beforeSend: () => {},
104
+ appendHeaders: () => {},
105
+ url: ''
106
+ } as any,
107
+ {}
108
+ )
109
+ ).toMatchObject({
110
+ response: {
111
+ hello: 'world',
112
+ test: 'test',
113
+ input: 'body',
114
+ query: new URLSearchParams(),
115
+ headers: new Headers(),
116
+ rawBody: '',
117
+ sharedMiddlewareMemo: new Map(),
118
+ appendHeaders: expect.any(Function),
119
+ beforeSend: expect.any(Function),
120
+ body: undefined,
121
+ url: ''
122
+ }
123
+ });
124
+ });
125
+
126
+ test('should run validation', async () => {
127
+ let group = new Group().use(async ctx => {
128
+ expect(typeof ctx).toBe('object');
129
+ expect(ctx.body).toEqual({ hello: 'world' });
130
+
131
+ return {
132
+ test: 'test' as const
133
+ };
134
+ });
135
+
136
+ let handler = group
137
+ .handler()
138
+ .input(
139
+ v.object({
140
+ hello: v.string()
141
+ })
142
+ )
143
+ .do(async ctx => {
144
+ expect(ctx.test).toBe('test');
145
+ expect(ctx.input).toEqual({ hello: 'world' });
146
+
147
+ return ctx;
148
+ });
149
+
150
+ expect(
151
+ await handler.run(
152
+ {
153
+ query: new URLSearchParams(),
154
+ headers: new Headers(),
155
+ rawBody: '',
156
+ sharedMiddlewareMemo: new Map(),
157
+ beforeSend: () => {},
158
+ appendHeaders: () => {},
159
+ url: '',
160
+ body: { hello: 'world', other: false }
161
+ } as any,
162
+ {}
163
+ )
164
+ ).toEqual({
165
+ response: {
166
+ test: 'test',
167
+ input: { hello: 'world' },
168
+ query: new URLSearchParams(),
169
+ headers: new Headers(),
170
+ rawBody: '',
171
+ sharedMiddlewareMemo: new Map(),
172
+ appendHeaders: expect.any(Function),
173
+ beforeSend: expect.any(Function),
174
+ body: undefined,
175
+ url: ''
176
+ }
177
+ });
178
+
179
+ try {
180
+ await handler.run(
181
+ {
182
+ query: new URLSearchParams(),
183
+ headers: new Headers(),
184
+ rawBody: '',
185
+ sharedMiddlewareMemo: new Map(),
186
+ beforeSend: () => {},
187
+ appendHeaders: () => {},
188
+ url: '',
189
+ body: { hello: 42 }
190
+ } as any,
191
+ {}
192
+ );
193
+ } catch (error) {
194
+ expect(error).toBeDefined();
195
+ }
196
+ });
197
+
198
+ test('should group handlers to controller', async () => {
199
+ let group = new Group();
200
+
201
+ let controller = group.controller({
202
+ first: group
203
+ .handler()
204
+ .input(
205
+ v.object({
206
+ hello: v.string()
207
+ })
208
+ )
209
+ .do(async ctx => {
210
+ return ctx;
211
+ }),
212
+
213
+ second: group.handler().do(async ctx => {
214
+ return { cool: true };
215
+ }),
216
+
217
+ sub: group.controller({
218
+ third: group
219
+ .handler()
220
+ .input(
221
+ v.object({
222
+ hello: v.string()
223
+ })
224
+ )
225
+ .do(async ctx => {
226
+ return { sub: true };
227
+ })
228
+ })
229
+ });
230
+
231
+ expect(
232
+ await controller.sub.third.run(
233
+ {
234
+ query: new URLSearchParams(),
235
+ headers: new Headers(),
236
+ rawBody: '',
237
+ sharedMiddlewareMemo: new Map(),
238
+ beforeSend: () => {},
239
+ appendHeaders: () => {},
240
+ url: '',
241
+ body: { hello: 'world' }
242
+ } as any,
243
+ {}
244
+ )
245
+ ).toEqual({
246
+ response: {
247
+ sub: true
248
+ }
249
+ });
250
+ });
251
+ });
@@ -0,0 +1,203 @@
1
+ import { ServiceError, validationError } from '@lowerdeck/error';
2
+ import { ValidationType } from '@lowerdeck/validation';
3
+ import * as Cookie from 'cookie';
4
+
5
+ export type Method = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
6
+
7
+ export interface ServiceRequest {
8
+ query: URLSearchParams;
9
+ headers: Headers;
10
+ url: string;
11
+ ip?: string;
12
+ body: any;
13
+ rawBody: any;
14
+ requestId: string;
15
+
16
+ getCookies: () => Record<string, string | undefined>;
17
+ getCookie: (name: string) => string | undefined;
18
+ setCookie: (name: string, value: string, opts?: Cookie.SerializeOptions) => void;
19
+
20
+ sharedMiddlewareMemo: Map<string, Promise<any>>;
21
+ beforeSend: (handler: () => Promise<any>) => void;
22
+ appendHeaders: (headers: Record<string, string | string[]>) => void;
23
+ }
24
+
25
+ export type Simplify<T> = { [KeyType in keyof T]: T[KeyType] } & {};
26
+ export type ExtendContext<C extends object, E> = E extends object ? Simplify<C & E> : C;
27
+
28
+ export class Group<Context extends { [key: string]: any } = {}> {
29
+ constructor(
30
+ private _middleware: Array<(ctx: Context & ServiceRequest) => Promise<any>> = []
31
+ ) {}
32
+
33
+ use<T extends { [key: string]: any } | undefined | void>(
34
+ handler: (ctx: Context & ServiceRequest) => Promise<T>,
35
+ opts?: {
36
+ getSharedMemoKey?: (ctx: Context & ServiceRequest) => string;
37
+ }
38
+ ) {
39
+ let middleware = async (ctx: Parameters<typeof handler>[0]): Promise<T> => {
40
+ let key = opts?.getSharedMemoKey?.(ctx);
41
+ if (key && ctx.sharedMiddlewareMemo.has(key)) {
42
+ return await ctx.sharedMiddlewareMemo.get(key)!;
43
+ }
44
+
45
+ let res = handler(ctx);
46
+ if (key) ctx.sharedMiddlewareMemo.set(key, res);
47
+
48
+ return await res;
49
+ };
50
+
51
+ return new Group<Simplify<Context & T>>([...this._middleware, middleware]);
52
+ }
53
+
54
+ createMiddleware<T extends { [key: string]: any }, P = void>(
55
+ handler: (ctx: Context & ServiceRequest, input: P) => Promise<T>,
56
+ opts?: {
57
+ getSharedMemoKey?: (ctx: Context & ServiceRequest, input: P) => string;
58
+ }
59
+ ) {
60
+ return (input: P) =>
61
+ async (ctx: Context & ServiceRequest): Promise<T> => {
62
+ let key = opts?.getSharedMemoKey?.(ctx, input);
63
+ if (key && ctx.sharedMiddlewareMemo.has(key)) {
64
+ return await ctx.sharedMiddlewareMemo.get(key)!;
65
+ }
66
+
67
+ let res = handler(ctx, input);
68
+ if (key) ctx.sharedMiddlewareMemo.set(key, res);
69
+
70
+ return await res;
71
+ };
72
+ }
73
+
74
+ handler() {
75
+ return new Handler([...this._middleware]);
76
+ }
77
+
78
+ controller<
79
+ HandlersAndSubControllers extends {
80
+ [key: string]: Handler<any, any, any> | Controller<any>;
81
+ }
82
+ >(handlers: HandlersAndSubControllers): Controller<HandlersAndSubControllers> {
83
+ return handlers;
84
+ }
85
+ }
86
+
87
+ export type Controller<
88
+ HandlersAndSubControllers extends { [key: string]: Handler<any, any, any> | Controller<any> }
89
+ > = HandlersAndSubControllers;
90
+
91
+ export type InferControllerType<T> = T extends Controller<infer U> ? U : never;
92
+
93
+ export type InferClient<
94
+ HandlersAndSubControllers extends { [key: string]: Handler<any, any, any> | Controller<any> }
95
+ > = {
96
+ [K in keyof HandlersAndSubControllers]: HandlersAndSubControllers[K] extends Handler<
97
+ infer I,
98
+ infer O,
99
+ infer C
100
+ >
101
+ ? ((
102
+ input: I,
103
+ opts?: { headers?: Record<string, string>; query?: Record<string, string> }
104
+ ) => Promise<O>) & {
105
+ getFull: (
106
+ input: I,
107
+ opts?: { headers?: Record<string, string>; query?: Record<string, string> }
108
+ ) => Promise<{
109
+ data: O;
110
+ status: number;
111
+ headers: Record<string, string>;
112
+ }>;
113
+ }
114
+ : HandlersAndSubControllers[K] extends Controller<infer U>
115
+ ? InferClient<U>
116
+ : never;
117
+ };
118
+
119
+ export class Handler<Input, Output, Context extends { [key: string]: any } = {}> {
120
+ private _handler!: (
121
+ ctx: Context & Omit<ServiceRequest, 'body'> & { input: Input }
122
+ ) => Promise<Output>;
123
+ private _validation: ValidationType<Input> | undefined;
124
+
125
+ constructor(
126
+ private _middleware: Array<(ctx: Context & ServiceRequest) => Promise<any>> = []
127
+ ) {}
128
+
129
+ do<HandlerOutput>(
130
+ handler: (
131
+ ctx: Context & Omit<ServiceRequest, 'body'> & { input: Input }
132
+ ) => Promise<HandlerOutput>
133
+ ) {
134
+ if (this._handler != undefined) throw new Error('Handler already defined');
135
+
136
+ // @ts-ignore
137
+ this._handler = handler;
138
+
139
+ return this as any as Handler<Input, HandlerOutput, Context>;
140
+ }
141
+
142
+ use<T extends { [key: string]: any } = {}>(
143
+ handler: (ctx: Context & ServiceRequest) => Promise<T | undefined | void>
144
+ ) {
145
+ this._middleware.push(handler);
146
+ return this as any as Handler<Input, Output, ExtendContext<Context, T>>;
147
+ }
148
+
149
+ input<HandlerInput>(validation: ValidationType<HandlerInput>) {
150
+ if (this._validation != undefined) throw new Error('Input validation already defined');
151
+
152
+ // @ts-ignore
153
+ this._validation = validation;
154
+
155
+ return this as any as Handler<HandlerInput, Output, Context>;
156
+ }
157
+
158
+ async run(
159
+ req: ServiceRequest,
160
+ initialContext: any
161
+ ): Promise<{
162
+ response: Output;
163
+ }> {
164
+ if (!this._handler) throw new Error('Handler not defined');
165
+
166
+ let input = req.body as Input;
167
+
168
+ if (this._validation) {
169
+ let valRes = this._validation.validate(req.body);
170
+
171
+ if (!valRes.success) {
172
+ throw new ServiceError(
173
+ validationError({ errors: valRes.errors, entity: 'call_data' })
174
+ );
175
+ }
176
+
177
+ input = valRes.value;
178
+ }
179
+
180
+ let ctx = {
181
+ ...initialContext,
182
+ ...req,
183
+
184
+ // Always use the sanitized input
185
+ body: input
186
+ };
187
+
188
+ for (let mw of this._middleware) {
189
+ let res = await mw(ctx);
190
+ if (res) ctx = { ...ctx, ...res };
191
+ }
192
+
193
+ let res = await this._handler({
194
+ ...ctx,
195
+ input,
196
+ body: undefined
197
+ });
198
+
199
+ return {
200
+ response: res
201
+ };
202
+ }
203
+ }
@@ -0,0 +1,9 @@
1
+ export let parseForwardedFor = (xForwardedForHeader?: string | null | undefined) => {
2
+ if (typeof xForwardedForHeader != 'string') return undefined;
3
+
4
+ let ips = xForwardedForHeader
5
+ .split(',')
6
+ .map(ip => ip.trim())
7
+ .filter(Boolean);
8
+ return ips.length > 0 ? ips[0] : undefined;
9
+ };
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from './controller';
2
+ export * from './rpcMux';
3
+ export * from './server';