diff --git a/apps/content/docs/openapi/input-output-structure.md b/apps/content/docs/openapi/input-output-structure.md index 659c87aef..1e41412f7 100644 --- a/apps/content/docs/openapi/input-output-structure.md +++ b/apps/content/docs/openapi/input-output-structure.md @@ -96,9 +96,9 @@ export type DetailedOutput = { Make sure your handler’s return value matches this structure when using detailed mode. -## Default Configuration +## Initial Configuration -Customize the default oRPC input/output structure settings using `.$route`: +Customize the initial oRPC input/output structure settings using `.$route`: ```ts const base = os.$route({ inputStructure: 'detailed' }) diff --git a/apps/content/docs/openapi/routing.md b/apps/content/docs/openapi/routing.md index 382efdc06..81bc8754f 100644 --- a/apps/content/docs/openapi/routing.md +++ b/apps/content/docs/openapi/routing.md @@ -66,9 +66,9 @@ const router = { } ``` -## Default Configuration +## Initial Configuration -Customize the default oRPC routing settings using `.$route`: +Customize the initial oRPC routing settings using `.$route`: ```ts const base = os.$route({ method: 'GET' }) diff --git a/apps/content/docs/procedure.md b/apps/content/docs/procedure.md index 9662653af..0207d9552 100644 --- a/apps/content/docs/procedure.md +++ b/apps/content/docs/procedure.md @@ -69,6 +69,17 @@ const example = os To learn more, see the [Middleware](/docs/middleware) documentation. +## Initial Configuration + +Customize the initial input schema using `.$input`: + +```ts +const base = os.$input(z.void()) +const base = os.$input>() +``` + +Unlike `.input`, the `.$input` method lets you redefine the input schema after its initial configuration. This is useful when you need to enforce a `void` input when no `.input` is specified. + ## Reusability Each modification to a builder creates a completely new instance, avoiding reference issues. This makes it easy to reuse and extend procedures efficiently. diff --git a/packages/server/src/builder-variants.test-d.ts b/packages/server/src/builder-variants.test-d.ts index 1cccf339d..86d075a14 100644 --- a/packages/server/src/builder-variants.test-d.ts +++ b/packages/server/src/builder-variants.test-d.ts @@ -34,7 +34,7 @@ describe('BuilderWithMiddlewares', () => { it('backward compatibility', () => { const expected = {} as OmitChainMethodDeep< typeof generalBuilder, - '$config' | '$context' | '$meta' | '$route' | 'middleware' + '$config' | '$context' | '$meta' | '$route' | '$input' | 'middleware' > // expectTypeOf(builder).toMatchTypeOf(expected) @@ -279,7 +279,7 @@ describe('ProcedureBuilder', () => { it('backward compatibility', () => { const expected = {} as OmitChainMethodDeep< typeof generalBuilder, - '$config' | '$context' | '$meta' | '$route' | 'middleware' | 'prefix' | 'tag' | 'router' | 'lazy' + '$config' | '$context' | '$meta' | '$route' | '$input' | 'middleware' | 'prefix' | 'tag' | 'router' | 'lazy' > expectTypeOf(builder).toMatchTypeOf(expected) @@ -458,7 +458,7 @@ describe('ProcedureBuilderWithInput', () => { it('backward compatibility', () => { const expected = {} as OmitChainMethodDeep< typeof generalBuilder, - '$config' | '$context' | '$meta' | '$route' | 'middleware' | 'prefix' | 'tag' | 'router' | 'lazy' | 'input' + '$config' | '$context' | '$meta' | '$route' | '$input' | 'middleware' | 'prefix' | 'tag' | 'router' | 'lazy' | 'input' > expectTypeOf(builder).toMatchTypeOf(expected) @@ -673,7 +673,7 @@ describe('ProcedureBuilderWithOutput', () => { it('backward compatibility', () => { const expected = {} as OmitChainMethodDeep< typeof generalBuilder, - '$config' | '$context' | '$meta' | '$route' | 'middleware' | 'prefix' | 'tag' | 'router' | 'lazy' | 'output' + '$config' | '$context' | '$meta' | '$route' | '$input' | 'middleware' | 'prefix' | 'tag' | 'router' | 'lazy' | 'output' > // expectTypeOf(builder).toMatchTypeOf(expected) @@ -839,7 +839,7 @@ describe('ProcedureBuilderWithInputOutput', () => { it('backward compatibility', () => { const expected = {} as OmitChainMethodDeep< typeof generalBuilder, - '$config' | '$context' | '$meta' | '$route' | 'middleware' | 'prefix' | 'tag' | 'router' | 'lazy' | 'input' | 'output' + '$config' | '$context' | '$meta' | '$route' | '$input' | 'middleware' | 'prefix' | 'tag' | 'router' | 'lazy' | 'input' | 'output' > // expectTypeOf(builder).toMatchTypeOf(expected) @@ -1039,7 +1039,7 @@ describe('RouterBuilder', () => { it('backward compatibility', () => { const expected = {} as OmitChainMethodDeep< typeof generalBuilder, - '$config' | '$context' | '$meta' | '$route' | 'middleware' | 'meta' | 'route' | 'input' | 'output' | 'handler' + '$config' | '$context' | '$meta' | '$route' | '$input' | 'middleware' | 'meta' | 'route' | 'input' | 'output' | 'handler' > // expectTypeOf(builder).toMatchTypeOf(expected) diff --git a/packages/server/src/builder.test-d.ts b/packages/server/src/builder.test-d.ts index 75ca9a7d8..70728a104 100644 --- a/packages/server/src/builder.test-d.ts +++ b/packages/server/src/builder.test-d.ts @@ -11,6 +11,7 @@ import type { DecoratedMiddleware } from './middleware-decorated' import type { Procedure } from './procedure' import type { DecoratedProcedure } from './procedure-decorated' import type { EnhancedRouter } from './router-utils' +import { z } from 'zod' import { generalSchema } from '../../contract/tests/shared' import { router } from '../tests/shared' @@ -115,6 +116,42 @@ describe('Builder', () => { builder.$route({ method: 'INVALID' }) }) + describe('.$input', () => { + it('with actual schema', () => { + const schema = z.void() + + expectTypeOf(builder.$input(schema)).toEqualTypeOf< + Builder< + InitialContext, + CurrentContext, + typeof schema, + typeof outputSchema, + typeof baseErrorMap, + BaseMeta + > + >() + + // @ts-expect-error --- invalid schema + builder.$input({}) + }) + + it('with types only', () => { + expectTypeOf(builder.$input>()).toEqualTypeOf< + Builder< + InitialContext, + CurrentContext, + Schema, + typeof outputSchema, + typeof baseErrorMap, + BaseMeta + > + >() + + // @ts-expect-error --- invalid schema + builder.$input<'invalid'>() + }) + }) + describe('.middleware', () => { it('works', () => { expectTypeOf( diff --git a/packages/server/src/builder.test.ts b/packages/server/src/builder.test.ts index 64e0a6db6..9173ba04a 100644 --- a/packages/server/src/builder.test.ts +++ b/packages/server/src/builder.test.ts @@ -1,4 +1,6 @@ +import type { Schema } from '@orpc/contract' import { isContractProcedure } from '@orpc/contract' +import { z } from 'zod' import { baseErrorMap, baseMeta, baseRoute, generalSchema, inputSchema, outputSchema } from '../../contract/tests/shared' import { router } from '../tests/shared' import { Builder } from './builder' @@ -94,6 +96,31 @@ describe('builder', () => { }) }) + describe('.$input', () => { + it('with actual schema', () => { + const schema = z.void() + const applied = builder.$input(schema) + + expect(applied).instanceOf(Builder) + expect(applied).not.toBe(builder) + expect(applied['~orpc']).toEqual({ + ...def, + inputSchema: schema, + }) + }) + + it('with type only', () => { + const applied = builder.$input>() + + expect(applied).instanceOf(Builder) + expect(applied).not.toBe(builder) + expect(applied['~orpc']).toEqual({ + ...def, + inputSchema: undefined, + }) + }) + }) + it('.middleware', () => { const mid = vi.fn() const applied = builder.middleware(mid) diff --git a/packages/server/src/builder.ts b/packages/server/src/builder.ts index cba55e09a..8851804a9 100644 --- a/packages/server/src/builder.ts +++ b/packages/server/src/builder.ts @@ -98,6 +98,15 @@ export class Builder< }) } + $input( + initialInputSchema?: U, + ): Builder { + return new Builder({ + ...this['~orpc'], + inputSchema: initialInputSchema, + }) + } + middleware( // = any here is important to make middleware can be used in any output by default middleware: Middleware, TMeta>, ): DecoratedMiddleware, TMeta> { // ORPCErrorConstructorMap ensures middleware can used in any procedure diff --git a/packages/server/src/implementer-procedure.test-d.ts b/packages/server/src/implementer-procedure.test-d.ts index bcf234ae8..05fd6a633 100644 --- a/packages/server/src/implementer-procedure.test-d.ts +++ b/packages/server/src/implementer-procedure.test-d.ts @@ -190,7 +190,7 @@ describe('ProcedureImplementer', () => { it('backward compatibility', () => { const expected = {} as OmitChainMethodDeep< typeof generalBuilder, - '$config' | '$context' | '$meta' | '$route' | 'middleware' | 'prefix' | 'tag' | 'router' | 'lazy' | 'input' | 'output' | 'meta' | 'route' | 'errors' + '$config' | '$context' | '$meta' | '$route' | '$input' | 'middleware' | 'prefix' | 'tag' | 'router' | 'lazy' | 'input' | 'output' | 'meta' | 'route' | 'errors' > // expectTypeOf(builder).toMatchTypeOf(expected)