UNPKG

@orpc/server

Version:

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

490 lines (480 loc) • 17.5 kB
import { mergeErrorMap, mergeMeta, mergeRoute, mergePrefix, mergeTags, isContractProcedure, getContractRouter, fallbackContractConfig } from '@orpc/contract'; export { ValidationError, eventIterator, type } from '@orpc/contract'; import { P as Procedure, b as addMiddleware, c as createProcedureClient, e as enhanceRouter, l as lazy, s as setHiddenRouterContract, u as unlazy, g as getRouter, i as isProcedure, d as isLazy, f as createAssertedLazyProcedure } from './shared/server.B_fj3X5m.mjs'; export { L as LAZY_SYMBOL, p as call, r as createAccessibleLazyRouter, a as createContractedProcedure, h as createORPCErrorConstructorMap, q as getHiddenRouterContract, j as getLazyMeta, n as isStartWithMiddlewares, m as mergeCurrentContext, o as mergeMiddlewares, k as middlewareOutputFn, w as resolveContractProcedures, t as traverseContractProcedures, x as unlazyRouter, v as validateORPCError } from './shared/server.B_fj3X5m.mjs'; import { toORPCError } from '@orpc/client'; export { ORPCError, isDefinedError, safe } from '@orpc/client'; import { resolveMaybeOptionalOptions } from '@orpc/shared'; export { AsyncIteratorClass, EventPublisher, asyncIteratorToStream as eventIteratorToStream, onError, onFinish, onStart, onSuccess, streamToAsyncIteratorClass as streamToEventIterator } from '@orpc/shared'; export { getEventMeta, withEventMeta } from '@orpc/standard-server'; const DEFAULT_CONFIG = { initialInputValidationIndex: 0, initialOutputValidationIndex: 0, dedupeLeadingMiddlewares: true }; function fallbackConfig(key, value) { if (value === void 0) { return DEFAULT_CONFIG[key]; } return value; } function decorateMiddleware(middleware) { const decorated = (...args) => middleware(...args); decorated.mapInput = (mapInput) => { const mapped = decorateMiddleware( (options, input, ...rest) => middleware(options, mapInput(input), ...rest) ); return mapped; }; decorated.concat = (concatMiddleware, mapInput) => { const mapped = mapInput ? decorateMiddleware(concatMiddleware).mapInput(mapInput) : concatMiddleware; const concatted = decorateMiddleware((options, input, output, ...rest) => { const merged = middleware({ ...options, next: (...[nextOptions1]) => mapped({ ...options, context: { ...options.context, ...nextOptions1?.context }, next: (...[nextOptions2]) => options.next({ context: { ...nextOptions1?.context, ...nextOptions2?.context } }) }, input, output, ...rest) }, input, output, ...rest); return merged; }); return concatted; }; return decorated; } function createActionableClient(client) { const action = async (input) => { try { return [null, await client(input)]; } catch (error) { if (error instanceof Error && "digest" in error && typeof error.digest === "string" && error.digest.startsWith("NEXT_")) { throw error; } return [toORPCError(error).toJSON(), void 0]; } }; return action; } class DecoratedProcedure extends Procedure { /** * Adds type-safe custom errors. * The provided errors are spared-merged with any existing errors. * * @see {@link https://orpc.unnoq.com/docs/error-handling#type%E2%80%90safe-error-handling Type-Safe Error Handling Docs} */ errors(errors) { return new DecoratedProcedure({ ...this["~orpc"], errorMap: mergeErrorMap(this["~orpc"].errorMap, errors) }); } /** * Sets or updates the metadata. * The provided metadata is spared-merged with any existing metadata. * * @see {@link https://orpc.unnoq.com/docs/metadata Metadata Docs} */ meta(meta) { return new DecoratedProcedure({ ...this["~orpc"], meta: mergeMeta(this["~orpc"].meta, meta) }); } /** * Sets or updates the route definition. * The provided route is spared-merged with any existing route. * This option is typically relevant when integrating with OpenAPI. * * @see {@link https://orpc.unnoq.com/docs/openapi/routing OpenAPI Routing Docs} * @see {@link https://orpc.unnoq.com/docs/openapi/input-output-structure OpenAPI Input/Output Structure Docs} */ route(route) { return new DecoratedProcedure({ ...this["~orpc"], route: mergeRoute(this["~orpc"].route, route) }); } use(middleware, mapInput) { const mapped = mapInput ? decorateMiddleware(middleware).mapInput(mapInput) : middleware; return new DecoratedProcedure({ ...this["~orpc"], middlewares: addMiddleware(this["~orpc"].middlewares, mapped) }); } /** * Make this procedure callable (works like a function while still being a procedure). * * @see {@link https://orpc.unnoq.com/docs/client/server-side Server-side Client Docs} */ callable(...rest) { const client = createProcedureClient(this, ...rest); return new Proxy(client, { get: (target, key) => { return Reflect.has(this, key) ? Reflect.get(this, key) : Reflect.get(target, key); }, has: (target, key) => { return Reflect.has(this, key) || Reflect.has(target, key); } }); } /** * Make this procedure compatible with server action. * * @see {@link https://orpc.unnoq.com/docs/server-action Server Action Docs} */ actionable(...rest) { const action = createActionableClient(createProcedureClient(this, ...rest)); return new Proxy(action, { get: (target, key) => { return Reflect.has(this, key) ? Reflect.get(this, key) : Reflect.get(target, key); }, has: (target, key) => { return Reflect.has(this, key) || Reflect.has(target, key); } }); } } class Builder { /** * This property holds the defined options. */ "~orpc"; constructor(def) { this["~orpc"] = def; } /** * Sets or overrides the config. * * @see {@link https://orpc.unnoq.com/docs/client/server-side#middlewares-order Middlewares Order Docs} * @see {@link https://orpc.unnoq.com/docs/best-practices/dedupe-middleware#configuration Dedupe Middleware Docs} */ $config(config) { const inputValidationCount = this["~orpc"].inputValidationIndex - fallbackConfig("initialInputValidationIndex", this["~orpc"].config.initialInputValidationIndex); const outputValidationCount = this["~orpc"].outputValidationIndex - fallbackConfig("initialOutputValidationIndex", this["~orpc"].config.initialOutputValidationIndex); return new Builder({ ...this["~orpc"], config, dedupeLeadingMiddlewares: fallbackConfig("dedupeLeadingMiddlewares", config.dedupeLeadingMiddlewares), inputValidationIndex: fallbackConfig("initialInputValidationIndex", config.initialInputValidationIndex) + inputValidationCount, outputValidationIndex: fallbackConfig("initialOutputValidationIndex", config.initialOutputValidationIndex) + outputValidationCount }); } /** * Set or override the initial context. * * @see {@link https://orpc.unnoq.com/docs/context Context Docs} */ $context() { return new Builder({ ...this["~orpc"], middlewares: [], inputValidationIndex: fallbackConfig("initialInputValidationIndex", this["~orpc"].config.initialInputValidationIndex), outputValidationIndex: fallbackConfig("initialOutputValidationIndex", this["~orpc"].config.initialOutputValidationIndex) }); } /** * Sets or overrides the initial meta. * * @see {@link https://orpc.unnoq.com/docs/metadata Metadata Docs} */ $meta(initialMeta) { return new Builder({ ...this["~orpc"], meta: initialMeta }); } /** * Sets or overrides the initial route. * This option is typically relevant when integrating with OpenAPI. * * @see {@link https://orpc.unnoq.com/docs/openapi/routing OpenAPI Routing Docs} * @see {@link https://orpc.unnoq.com/docs/openapi/input-output-structure OpenAPI Input/Output Structure Docs} */ $route(initialRoute) { return new Builder({ ...this["~orpc"], route: initialRoute }); } /** * Sets or overrides the initial input schema. * * @see {@link https://orpc.unnoq.com/docs/procedure#initial-configuration Initial Procedure Configuration Docs} */ $input(initialInputSchema) { return new Builder({ ...this["~orpc"], inputSchema: initialInputSchema }); } /** * Creates a middleware. * * @see {@link https://orpc.unnoq.com/docs/middleware Middleware Docs} */ middleware(middleware) { return decorateMiddleware(middleware); } /** * Adds type-safe custom errors. * The provided errors are spared-merged with any existing errors. * * @see {@link https://orpc.unnoq.com/docs/error-handling#type%E2%80%90safe-error-handling Type-Safe Error Handling Docs} */ errors(errors) { return new Builder({ ...this["~orpc"], errorMap: mergeErrorMap(this["~orpc"].errorMap, errors) }); } use(middleware, mapInput) { const mapped = mapInput ? decorateMiddleware(middleware).mapInput(mapInput) : middleware; return new Builder({ ...this["~orpc"], middlewares: addMiddleware(this["~orpc"].middlewares, mapped) }); } /** * Sets or updates the metadata. * The provided metadata is spared-merged with any existing metadata. * * @see {@link https://orpc.unnoq.com/docs/metadata Metadata Docs} */ meta(meta) { return new Builder({ ...this["~orpc"], meta: mergeMeta(this["~orpc"].meta, meta) }); } /** * Sets or updates the route definition. * The provided route is spared-merged with any existing route. * This option is typically relevant when integrating with OpenAPI. * * @see {@link https://orpc.unnoq.com/docs/openapi/routing OpenAPI Routing Docs} * @see {@link https://orpc.unnoq.com/docs/openapi/input-output-structure OpenAPI Input/Output Structure Docs} */ route(route) { return new Builder({ ...this["~orpc"], route: mergeRoute(this["~orpc"].route, route) }); } /** * Defines the input validation schema. * * @see {@link https://orpc.unnoq.com/docs/procedure#input-output-validation Input Validation Docs} */ input(schema) { return new Builder({ ...this["~orpc"], inputSchema: schema, inputValidationIndex: fallbackConfig("initialInputValidationIndex", this["~orpc"].config.initialInputValidationIndex) + this["~orpc"].middlewares.length }); } /** * Defines the output validation schema. * * @see {@link https://orpc.unnoq.com/docs/procedure#input-output-validation Output Validation Docs} */ output(schema) { return new Builder({ ...this["~orpc"], outputSchema: schema, outputValidationIndex: fallbackConfig("initialOutputValidationIndex", this["~orpc"].config.initialOutputValidationIndex) + this["~orpc"].middlewares.length }); } /** * Defines the handler of the procedure. * * @see {@link https://orpc.unnoq.com/docs/procedure Procedure Docs} */ handler(handler) { return new DecoratedProcedure({ ...this["~orpc"], handler }); } /** * Prefixes all procedures in the router. * The provided prefix is post-appended to any existing router prefix. * * @note This option does not affect procedures that do not define a path in their route definition. * * @see {@link https://orpc.unnoq.com/docs/openapi/routing#route-prefixes OpenAPI Route Prefixes Docs} */ prefix(prefix) { return new Builder({ ...this["~orpc"], prefix: mergePrefix(this["~orpc"].prefix, prefix) }); } /** * Adds tags to all procedures in the router. * This helpful when you want to group procedures together in the OpenAPI specification. * * @see {@link https://orpc.unnoq.com/docs/openapi/openapi-specification#operation-metadata OpenAPI Operation Metadata Docs} */ tag(...tags) { return new Builder({ ...this["~orpc"], tags: mergeTags(this["~orpc"].tags, tags) }); } /** * Applies all of the previously defined options to the specified router. * * @see {@link https://orpc.unnoq.com/docs/router#extending-router Extending Router Docs} */ router(router) { return enhanceRouter(router, this["~orpc"]); } /** * Create a lazy router * And applies all of the previously defined options to the specified router. * * @see {@link https://orpc.unnoq.com/docs/router#extending-router Extending Router Docs} */ lazy(loader) { return enhanceRouter(lazy(loader), this["~orpc"]); } } const os = new Builder({ config: {}, route: {}, meta: {}, errorMap: {}, inputValidationIndex: fallbackConfig("initialInputValidationIndex"), outputValidationIndex: fallbackConfig("initialOutputValidationIndex"), middlewares: [], dedupeLeadingMiddlewares: true }); function implementerInternal(contract, config, middlewares) { if (isContractProcedure(contract)) { const impl2 = new Builder({ ...contract["~orpc"], config, middlewares, inputValidationIndex: fallbackConfig("initialInputValidationIndex", config?.initialInputValidationIndex) + middlewares.length, outputValidationIndex: fallbackConfig("initialOutputValidationIndex", config?.initialOutputValidationIndex) + middlewares.length, dedupeLeadingMiddlewares: fallbackConfig("dedupeLeadingMiddlewares", config.dedupeLeadingMiddlewares) }); return impl2; } const impl = new Proxy(contract, { get: (target, key) => { if (typeof key !== "string") { return Reflect.get(target, key); } let method; if (key === "middleware") { method = (mid) => decorateMiddleware(mid); } else if (key === "use") { method = (mid) => { return implementerInternal( contract, config, addMiddleware(middlewares, mid) ); }; } else if (key === "router") { method = (router) => { const adapted = enhanceRouter(router, { middlewares, errorMap: {}, prefix: void 0, tags: void 0, dedupeLeadingMiddlewares: fallbackConfig("dedupeLeadingMiddlewares", config.dedupeLeadingMiddlewares) }); return setHiddenRouterContract(adapted, contract); }; } else if (key === "lazy") { method = (loader) => { const adapted = enhanceRouter(lazy(loader), { middlewares, errorMap: {}, prefix: void 0, tags: void 0, dedupeLeadingMiddlewares: fallbackConfig("dedupeLeadingMiddlewares", config.dedupeLeadingMiddlewares) }); return setHiddenRouterContract(adapted, contract); }; } const next = getContractRouter(target, [key]); if (!next) { return method ?? next; } const nextImpl = implementerInternal(next, config, middlewares); if (method) { return new Proxy(method, { get(_, key2) { return Reflect.get(nextImpl, key2); } }); } return nextImpl; } }); return impl; } function implement(contract, config = {}) { const implInternal = implementerInternal(contract, config, []); const impl = new Proxy(implInternal, { get: (target, key) => { let method; if (key === "$context") { method = () => impl; } else if (key === "$config") { method = (config2) => implement(contract, config2); } const next = Reflect.get(target, key); if (!method || !next || typeof next !== "function" && typeof next !== "object") { return method || next; } return new Proxy(method, { get(_, key2) { return Reflect.get(next, key2); } }); } }); return impl; } function inferRPCMethodFromRouter(router) { return async (_, path) => { const { default: procedure } = await unlazy(getRouter(router, path)); if (!isProcedure(procedure)) { throw new Error( `[inferRPCMethodFromRouter] No valid procedure found at path "${path.join(".")}". This may happen when the router is not properly configured.` ); } const method = fallbackContractConfig("defaultMethod", procedure["~orpc"].route.method); return method === "HEAD" ? "GET" : method; }; } function createRouterClient(router, ...rest) { const options = resolveMaybeOptionalOptions(rest); if (isProcedure(router)) { const caller = createProcedureClient(router, options); return caller; } const procedureCaller = isLazy(router) ? createProcedureClient(createAssertedLazyProcedure(router), options) : {}; const recursive = new Proxy(procedureCaller, { get(target, key) { if (typeof key !== "string") { return Reflect.get(target, key); } const next = getRouter(router, [key]); if (!next) { return Reflect.get(target, key); } return createRouterClient(next, { ...rest[0], path: [...rest[0]?.path ?? [], key] }); } }); return recursive; } export { Builder, DecoratedProcedure, Procedure, addMiddleware, createActionableClient, createAssertedLazyProcedure, createProcedureClient, createRouterClient, decorateMiddleware, enhanceRouter, fallbackConfig, getRouter, implement, implementerInternal, inferRPCMethodFromRouter, isLazy, isProcedure, lazy, os, setHiddenRouterContract, unlazy };