diff --git a/packages/server/src/builder-variants.test-d.ts b/packages/server/src/builder-variants.test-d.ts index b549ae6c3..3109973e4 100644 --- a/packages/server/src/builder-variants.test-d.ts +++ b/packages/server/src/builder-variants.test-d.ts @@ -5,7 +5,7 @@ import type { BuilderWithMiddlewares, ProcedureBuilder, ProcedureBuilderWithInpu import type { Context } from './context' import type { ORPCErrorConstructorMap } from './error' import type { Lazy } from './lazy' -import type { MiddlewareOutputFn } from './middleware' +import type { Middleware, MiddlewareOutputFn } from './middleware' import type { Procedure } from './procedure' import type { DecoratedProcedure } from './procedure-decorated' import type { EnhancedRouter } from './router-utils' @@ -57,10 +57,10 @@ describe('BuilderWithMiddlewares', () => { BuilderWithMiddlewares< InitialContext, CurrentContext, - typeof inputSchema, - typeof outputSchema, - MergedErrorMap, - BaseMeta + typeof inputSchema, + typeof outputSchema, + MergedErrorMap, + BaseMeta > >() @@ -68,44 +68,59 @@ describe('BuilderWithMiddlewares', () => { builder.errors({ TOO_MANY_REQUESTS: { data: {} } }) }) - it('.use', () => { - const applied = builder.use(({ context, next, path, procedure, errors, signal }, input, output) => { - expectTypeOf(input).toEqualTypeOf() - expectTypeOf(context).toEqualTypeOf() - expectTypeOf(path).toEqualTypeOf() - expectTypeOf(procedure).toEqualTypeOf< - Procedure - >() - expectTypeOf(output).toEqualTypeOf>() - expectTypeOf(errors).toEqualTypeOf>() - expectTypeOf(signal).toEqualTypeOf>() + describe('.use', () => { + it('without map input', () => { + const applied = builder.use(({ context, next, path, procedure, errors, signal }, input, output) => { + expectTypeOf(input).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + expectTypeOf(path).toEqualTypeOf() + expectTypeOf(procedure).toEqualTypeOf< + Procedure + >() + expectTypeOf(output).toEqualTypeOf>() + expectTypeOf(errors).toEqualTypeOf>() + expectTypeOf(signal).toEqualTypeOf>() - return next({ - context: { - extra: true, - }, + return next({ + context: { + extra: true, + }, + }) }) + + expectTypeOf(applied).toEqualTypeOf< + BuilderWithMiddlewares< + InitialContext & Record, + Omit & { extra: boolean }, + typeof inputSchema, + typeof outputSchema, + typeof baseErrorMap, + BaseMeta + > + >() + + // invalid TInContext + expectTypeOf(builder.use({} as Middleware<{ auth: 'invalid' }, any, any, any, any, any>)).toEqualTypeOf() + // @ts-expect-error --- input is not match + builder.use(({ next }, input: 'invalid') => next({})) + // @ts-expect-error --- output is not match + builder.use(({ next }, input, output: MiddlewareOutputFn<'invalid'>) => next({})) }) - expectTypeOf(applied).toEqualTypeOf< - BuilderWithMiddlewares< - InitialContext, - CurrentContext & { extra: boolean }, - typeof inputSchema, - typeof outputSchema, - typeof baseErrorMap, - BaseMeta - > - >() + it('with TInContext', () => { + const mid = {} as Middleware<{ cacheable?: boolean }, Record, unknown, unknown, ORPCErrorConstructorMap, BaseMeta> - // @ts-expect-error --- conflict context - builder.use(({ next }) => next({ context: { db: 123 } })) - // conflict but not detected - expectTypeOf(builder.use(({ next }) => next({ context: { db: undefined } }))).toMatchTypeOf() - // @ts-expect-error --- input is not match - builder.use(({ next }, input: 'invalid') => next({})) - // @ts-expect-error --- output is not match - builder.use(({ next }, input, output: MiddlewareOutputFn<'invalid'>) => next({})) + expectTypeOf(builder.use(mid)).toEqualTypeOf< + BuilderWithMiddlewares< + InitialContext & { cacheable?: boolean }, + Omit & Record, + typeof inputSchema, + typeof outputSchema, + typeof baseErrorMap, + BaseMeta + > + >() + }) }) it('.meta', () => { @@ -113,10 +128,10 @@ describe('BuilderWithMiddlewares', () => { BuilderWithMiddlewares< InitialContext, CurrentContext, - typeof inputSchema, - typeof outputSchema, - typeof baseErrorMap, - BaseMeta + typeof inputSchema, + typeof outputSchema, + typeof baseErrorMap, + BaseMeta > >() @@ -216,7 +231,7 @@ describe('BuilderWithMiddlewares', () => { it('.router', () => { expectTypeOf(builder.router(router)).toEqualTypeOf< - EnhancedRouter + EnhancedRouter >() builder.router({ @@ -240,7 +255,7 @@ describe('BuilderWithMiddlewares', () => { it('.lazy', () => { expectTypeOf(builder.lazy(() => Promise.resolve({ default: router }))).toEqualTypeOf< - EnhancedRouter, InitialContext, typeof baseErrorMap> + EnhancedRouter, InitialContext, CurrentContext, typeof baseErrorMap> >() // @ts-expect-error - initial context is not match @@ -313,44 +328,59 @@ describe('ProcedureBuilder', () => { builder.errors({ TOO_MANY_REQUESTS: { data: {} } }) }) - it('.use', () => { - const applied = builder.use(({ context, next, path, procedure, errors, signal }, input, output) => { - expectTypeOf(input).toEqualTypeOf() - expectTypeOf(context).toEqualTypeOf() - expectTypeOf(path).toEqualTypeOf() - expectTypeOf(procedure).toEqualTypeOf< - Procedure - >() - expectTypeOf(output).toEqualTypeOf>() - expectTypeOf(errors).toEqualTypeOf>() - expectTypeOf(signal).toEqualTypeOf>() + describe('.use', () => { + it('without map input', () => { + const applied = builder.use(({ context, next, path, procedure, errors, signal }, input, output) => { + expectTypeOf(input).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + expectTypeOf(path).toEqualTypeOf() + expectTypeOf(procedure).toEqualTypeOf< + Procedure + >() + expectTypeOf(output).toEqualTypeOf>() + expectTypeOf(errors).toEqualTypeOf>() + expectTypeOf(signal).toEqualTypeOf>() - return next({ - context: { - extra: true, - }, + return next({ + context: { + extra: true, + }, + }) }) + + expectTypeOf(applied).toEqualTypeOf< + ProcedureBuilder< + InitialContext & Record, + Omit & { extra: boolean }, + typeof inputSchema, + typeof outputSchema, + typeof baseErrorMap, + BaseMeta + > + >() + + // invalid TInContext + expectTypeOf(builder.use({} as Middleware<{ auth: 'invalid' }, any, any, any, any, any>)).toEqualTypeOf() + // @ts-expect-error --- input is not match + builder.use(({ next }, input: 'invalid') => next({})) + // @ts-expect-error --- output is not match + builder.use(({ next }, input, output: MiddlewareOutputFn<'invalid'>) => next({})) }) - expectTypeOf(applied).toEqualTypeOf< - ProcedureBuilder< - InitialContext, - CurrentContext & { extra: boolean }, + it('with TInContext', () => { + const mid = {} as Middleware<{ cacheable?: boolean }, Record, unknown, unknown, ORPCErrorConstructorMap, BaseMeta> + + expectTypeOf(builder.use(mid)).toEqualTypeOf< + ProcedureBuilder< + InitialContext & { cacheable?: boolean }, + Omit & Record, typeof inputSchema, typeof outputSchema, typeof baseErrorMap, BaseMeta - > - >() - - // @ts-expect-error --- conflict context - builder.use(({ next }) => next({ context: { db: 123 } })) - // conflict but not detected - expectTypeOf(builder.use(({ next }) => next({ context: { db: undefined } }))).toMatchTypeOf() - // @ts-expect-error --- input is not match - builder.use(({ next }, input: 'invalid') => next({})) - // @ts-expect-error --- output is not match - builder.use(({ next }, input, output: MiddlewareOutputFn<'invalid'>) => next({})) + > + >() + }) }) it('.meta', () => { @@ -514,8 +544,8 @@ describe('ProcedureBuilderWithInput', () => { expectTypeOf(applied).toEqualTypeOf< ProcedureBuilderWithInput< - InitialContext, - CurrentContext & { extra: boolean }, + InitialContext & Record, + Omit & { extra: boolean }, typeof inputSchema, typeof outputSchema, typeof baseErrorMap, @@ -523,10 +553,8 @@ describe('ProcedureBuilderWithInput', () => { > >() - // @ts-expect-error --- conflict context - builder.use(({ next }) => next({ context: { db: 123 } })) - // conflict but not detected - expectTypeOf(builder.use(({ next }) => next({ context: { db: undefined } }))).toMatchTypeOf() + // invalid TInContext + expectTypeOf(builder.use({} as Middleware<{ auth: 'invalid' }, any, any, any, any, any>)).toEqualTypeOf() // @ts-expect-error --- input is not match builder.use(({ next }, input: 'invalid') => next({})) // @ts-expect-error --- output is not match @@ -558,8 +586,8 @@ describe('ProcedureBuilderWithInput', () => { expectTypeOf(applied).toEqualTypeOf< ProcedureBuilderWithInput< - InitialContext, - CurrentContext & { extra: boolean }, + InitialContext & Record, + Omit & { extra: boolean }, typeof inputSchema, typeof outputSchema, typeof baseErrorMap, @@ -573,15 +601,39 @@ describe('ProcedureBuilderWithInput', () => { input => ({ invalid: true }), ) - // @ts-expect-error --- conflict context - builder.use(({ next }) => next({ context: { db: 123 } }), input => ({ mapped: true })) - // conflict but not detected - expectTypeOf(builder.use(({ next }) => next({ context: { db: undefined } }), input => ({ mapped: true }))).toMatchTypeOf() + // invalid TInContext + expectTypeOf(builder.use({} as Middleware<{ auth: 'invalid' }, any, any, any, any, any>, () => { })).toEqualTypeOf() // @ts-expect-error --- input is not match builder.use(({ next }, input: 'invalid') => next({}), input => ({ mapped: true })) // @ts-expect-error --- output is not match builder.use(({ next }, input, output: MiddlewareOutputFn<'invalid'>) => next({}), input => ({ mapped: true })) }) + + it('with TInContext', () => { + const mid = {} as Middleware<{ cacheable?: boolean }, Record, unknown, unknown, ORPCErrorConstructorMap, BaseMeta> + + expectTypeOf(builder.use(mid)).toEqualTypeOf< + ProcedureBuilderWithInput< + InitialContext & { cacheable?: boolean }, + Omit & Record, + typeof inputSchema, + typeof outputSchema, + typeof baseErrorMap, + BaseMeta + > + >() + + expectTypeOf(builder.use(mid, () => { })).toEqualTypeOf< + ProcedureBuilderWithInput< + InitialContext & { cacheable?: boolean }, + Omit & Record, + typeof inputSchema, + typeof outputSchema, + typeof baseErrorMap, + BaseMeta + > + >() + }) }) it('.meta', () => { @@ -707,44 +759,59 @@ describe('ProcedureBuilderWithOutput', () => { builder.errors({ TOO_MANY_REQUESTS: { data: {} } }) }) - it('.use', () => { - const applied = builder.use(({ context, next, path, procedure, errors, signal }, input, output) => { - expectTypeOf(input).toEqualTypeOf() - expectTypeOf(context).toEqualTypeOf() - expectTypeOf(path).toEqualTypeOf() - expectTypeOf(procedure).toEqualTypeOf< - Procedure - >() - expectTypeOf(output).toEqualTypeOf>() - expectTypeOf(errors).toEqualTypeOf>() - expectTypeOf(signal).toEqualTypeOf>() + describe('.use', () => { + it('without map input', () => { + const applied = builder.use(({ context, next, path, procedure, errors, signal }, input, output) => { + expectTypeOf(input).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + expectTypeOf(path).toEqualTypeOf() + expectTypeOf(procedure).toEqualTypeOf< + Procedure + >() + expectTypeOf(output).toEqualTypeOf>() + expectTypeOf(errors).toEqualTypeOf>() + expectTypeOf(signal).toEqualTypeOf>() - return next({ - context: { - extra: true, - }, + return next({ + context: { + extra: true, + }, + }) }) - }) - expectTypeOf(applied).toEqualTypeOf< - ProcedureBuilderWithOutput< - InitialContext, - CurrentContext & { extra: boolean }, - typeof inputSchema, - typeof outputSchema, - typeof baseErrorMap, - BaseMeta - > - >() + expectTypeOf(applied).toEqualTypeOf< + ProcedureBuilderWithOutput< + InitialContext & Record, + Omit & { extra: boolean }, + typeof inputSchema, + typeof outputSchema, + typeof baseErrorMap, + BaseMeta + > + >() + + // invalid TInContext + expectTypeOf(builder.use({} as Middleware<{ auth: 'invalid' }, any, any, any, any, any>)).toEqualTypeOf() + // @ts-expect-error --- input is not match + builder.use(({ next }, input: 'invalid') => next({})) + // @ts-expect-error --- output is not match + builder.use(({ next }, input, output: MiddlewareOutputFn<'invalid'>) => next({})) + }) - // @ts-expect-error --- conflict context - builder.use(({ next }) => next({ context: { db: 123 } })) - // conflict but not detected - expectTypeOf(builder.use(({ next }) => next({ context: { db: undefined } }))).toMatchTypeOf() - // @ts-expect-error --- input is not match - builder.use(({ next }, input: 'invalid') => next({})) - // @ts-expect-error --- output is not match - builder.use(({ next }, input, output: MiddlewareOutputFn<'invalid'>) => next({})) + it('with TInContext', () => { + const mid = {} as Middleware<{ cacheable?: boolean }, Record, unknown, any, ORPCErrorConstructorMap, BaseMeta> + + expectTypeOf(builder.use(mid)).toEqualTypeOf< + ProcedureBuilderWithOutput< + InitialContext & { cacheable?: boolean }, + Omit & Record, + typeof inputSchema, + typeof outputSchema, + typeof baseErrorMap, + BaseMeta + > + >() + }) }) it('.meta', () => { @@ -895,8 +962,8 @@ describe('ProcedureBuilderWithInputOutput', () => { expectTypeOf(applied).toEqualTypeOf< ProcedureBuilderWithInputOutput< - InitialContext, - CurrentContext & { extra: boolean }, + InitialContext & Record, + Omit & { extra: boolean }, typeof inputSchema, typeof outputSchema, typeof baseErrorMap, @@ -904,10 +971,8 @@ describe('ProcedureBuilderWithInputOutput', () => { > >() - // @ts-expect-error --- conflict context - builder.use(({ next }) => next({ context: { db: 123 } })) - // conflict but not detected - expectTypeOf(builder.use(({ next }) => next({ context: { db: undefined } }))).toMatchTypeOf() + // invalid TInContext + expectTypeOf(builder.use({} as Middleware<{ auth: 'invalid' }, any, any, any, any, any>)).toEqualTypeOf() // @ts-expect-error --- input is not match builder.use(({ next }, input: 'invalid') => next({})) // @ts-expect-error --- output is not match @@ -939,8 +1004,8 @@ describe('ProcedureBuilderWithInputOutput', () => { expectTypeOf(applied).toEqualTypeOf< ProcedureBuilderWithInputOutput< - InitialContext, - CurrentContext & { extra: boolean }, + InitialContext & Record, + Omit & { extra: boolean }, typeof inputSchema, typeof outputSchema, typeof baseErrorMap, @@ -954,15 +1019,39 @@ describe('ProcedureBuilderWithInputOutput', () => { input => ({ invalid: true }), ) - // @ts-expect-error --- conflict context - builder.use(({ next }) => next({ context: { db: 123 } }), input => ({ mapped: true })) - // conflict but not detected - expectTypeOf(builder.use(({ next }) => next({ context: { db: undefined } }), input => ({ mapped: true }))).toMatchTypeOf() + // invalid TInContext + expectTypeOf(builder.use({} as Middleware<{ auth: 'invalid' }, any, any, any, any, any>, () => { })).toEqualTypeOf() // @ts-expect-error --- input is not match builder.use(({ next }, input: 'invalid') => next({}), input => ({ mapped: true })) // @ts-expect-error --- output is not match builder.use(({ next }, input, output: MiddlewareOutputFn<'invalid'>) => next({}), input => ({ mapped: true })) }) + + it('with TInContext', () => { + const mid = {} as Middleware<{ cacheable?: boolean }, Record, unknown, any, ORPCErrorConstructorMap, BaseMeta> + + expectTypeOf(builder.use(mid)).toEqualTypeOf< + ProcedureBuilderWithInputOutput< + InitialContext & { cacheable?: boolean }, + Omit & Record, + typeof inputSchema, + typeof outputSchema, + typeof baseErrorMap, + BaseMeta + > + >() + + expectTypeOf(builder.use(mid, () => { })).toEqualTypeOf< + ProcedureBuilderWithInputOutput< + InitialContext & { cacheable?: boolean }, + Omit & Record, + typeof inputSchema, + typeof outputSchema, + typeof baseErrorMap, + BaseMeta + > + >() + }) }) it('.meta', () => { @@ -1046,42 +1135,55 @@ describe('RouterBuilder', () => { expectTypeOf().toEqualTypeOf() }) - it('.use', () => { - const applied = builder.use(({ context, next, path, procedure, errors, signal }, input, output) => { - expectTypeOf(input).toEqualTypeOf() - expectTypeOf(context).toEqualTypeOf() - expectTypeOf(path).toEqualTypeOf() - expectTypeOf(procedure).toEqualTypeOf< - Procedure - >() - expectTypeOf(output).toEqualTypeOf>() - expectTypeOf(errors).toEqualTypeOf>() - expectTypeOf(signal).toEqualTypeOf>() + describe('.use', () => { + it('without map input', () => { + const applied = builder.use(({ context, next, path, procedure, errors, signal }, input, output) => { + expectTypeOf(input).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + expectTypeOf(path).toEqualTypeOf() + expectTypeOf(procedure).toEqualTypeOf< + Procedure + >() + expectTypeOf(output).toEqualTypeOf>() + expectTypeOf(errors).toEqualTypeOf>() + expectTypeOf(signal).toEqualTypeOf>() - return next({ - context: { - extra: true, - }, + return next({ + context: { + extra: true, + }, + }) }) + + expectTypeOf(applied).toEqualTypeOf< + RouterBuilder< + InitialContext & Record, + Omit & { extra: boolean }, + typeof baseErrorMap, + BaseMeta + > + >() + + // invalid TInContext + expectTypeOf(builder.use({} as Middleware<{ auth: 'invalid' }, any, any, any, any, any>)).toEqualTypeOf() + // @ts-expect-error --- input is not match + builder.use(({ next }, input: 'invalid') => next({})) + // @ts-expect-error --- output is not match + builder.use(({ next }, input, output: MiddlewareOutputFn<'invalid'>) => next({})) }) - expectTypeOf(applied).toEqualTypeOf< - RouterBuilder< - InitialContext, - CurrentContext & { extra: boolean }, - typeof baseErrorMap, - BaseMeta - > - >() + it('with TInContext', () => { + const mid = {} as Middleware<{ cacheable?: boolean }, Record, unknown, unknown, ORPCErrorConstructorMap, BaseMeta> - // @ts-expect-error --- conflict context - builder.use(({ next }) => next({ context: { db: 123 } })) - // conflict but not detected - expectTypeOf(builder.use(({ next }) => next({ context: { db: undefined } }))).toMatchTypeOf() - // @ts-expect-error --- input is not match - builder.use(({ next }, input: 'invalid') => next({})) - // @ts-expect-error --- output is not match - builder.use(({ next }, input, output: MiddlewareOutputFn<'invalid'>) => next({})) + expectTypeOf(builder.use(mid)).toEqualTypeOf< + RouterBuilder< + InitialContext & { cacheable?: boolean }, + Omit & Record, + typeof baseErrorMap, + BaseMeta + > + >() + }) }) it('.prefix', () => { @@ -1101,7 +1203,7 @@ describe('RouterBuilder', () => { it('.router', () => { expectTypeOf(builder.router(router)).toEqualTypeOf< - EnhancedRouter + EnhancedRouter >() builder.router({ @@ -1125,7 +1227,7 @@ describe('RouterBuilder', () => { it('.lazy', () => { expectTypeOf(builder.lazy(() => Promise.resolve({ default: router }))).toEqualTypeOf< - EnhancedRouter, InitialContext, typeof baseErrorMap> + EnhancedRouter, InitialContext, CurrentContext, typeof baseErrorMap> >() // @ts-expect-error - initial context is not match diff --git a/packages/server/src/builder-variants.ts b/packages/server/src/builder-variants.ts index 9b1e72e9c..2ff4f45a2 100644 --- a/packages/server/src/builder-variants.ts +++ b/packages/server/src/builder-variants.ts @@ -1,6 +1,6 @@ import type { AnySchema, ContractRouter, ErrorMap, HTTPPath, InferSchemaInput, InferSchemaOutput, MergedErrorMap, Meta, Route, Schema } from '@orpc/contract' import type { BuilderDef } from './builder' -import type { ConflictContextGuard, Context, MergedContext } from './context' +import type { Context, ContextExtendsGuard, MergedCurrentContext, MergedInitialContext } from './context' import type { ORPCErrorConstructorMap } from './error' import type { Lazy } from './lazy' import type { MapInputMiddleware, Middleware } from './middleware' @@ -30,19 +30,19 @@ export interface BuilderWithMiddlewares< TMeta > - 'use'( + 'use'( middleware: Middleware< - TCurrentContext, + UInContext, UOutContext, unknown, unknown, ORPCErrorConstructorMap, TMeta >, - ): ConflictContextGuard> & - BuilderWithMiddlewares< - TInitialContext, - MergedContext, + ): ContextExtendsGuard + & BuilderWithMiddlewares< + MergedInitialContext, + MergedCurrentContext, TInputSchema, TOutputSchema, TErrorMap, @@ -75,11 +75,11 @@ export interface BuilderWithMiddlewares< 'router', TCurrentContext>>( router: U - ): EnhancedRouter + ): EnhancedRouter 'lazy', TCurrentContext>>( loader: () => Promise<{ default: U }>, - ): EnhancedRouter, TInitialContext, TErrorMap> + ): EnhancedRouter, TInitialContext, TCurrentContext, TErrorMap> } export interface ProcedureBuilder< @@ -103,19 +103,19 @@ export interface ProcedureBuilder< TMeta > - 'use'( + 'use'( middleware: Middleware< - TCurrentContext, + UInContext, UOutContext, unknown, unknown, ORPCErrorConstructorMap, TMeta >, - ): ConflictContextGuard> & - ProcedureBuilder< - TInitialContext, - MergedContext, + ): ContextExtendsGuard + & ProcedureBuilder< + MergedInitialContext, + MergedCurrentContext, TInputSchema, TOutputSchema, TErrorMap, @@ -157,28 +157,28 @@ export interface ProcedureBuilderWithInput< errors: U, ): ProcedureBuilderWithInput, TMeta> - 'use'( + 'use'( middleware: Middleware< - TCurrentContext, + UInContext, UOutContext, InferSchemaOutput, unknown, ORPCErrorConstructorMap, TMeta >, - ): ConflictContextGuard> & - ProcedureBuilderWithInput< - TInitialContext, - MergedContext, + ): ContextExtendsGuard + & ProcedureBuilderWithInput< + MergedInitialContext, + MergedCurrentContext, TInputSchema, TOutputSchema, TErrorMap, TMeta > - 'use'( + 'use'( middleware: Middleware< - TCurrentContext, + UInContext, UOutContext, UInput, unknown, @@ -186,10 +186,10 @@ export interface ProcedureBuilderWithInput< TMeta >, mapInput: MapInputMiddleware, UInput>, - ): ConflictContextGuard> & - ProcedureBuilderWithInput< - TInitialContext, - MergedContext, + ): ContextExtendsGuard + & ProcedureBuilderWithInput< + MergedInitialContext, + MergedCurrentContext, TInputSchema, TOutputSchema, TErrorMap, @@ -234,19 +234,19 @@ export interface ProcedureBuilderWithOutput< TMeta > - 'use'( + 'use'( middleware: Middleware< - TCurrentContext, + UInContext, UOutContext, unknown, InferSchemaInput, ORPCErrorConstructorMap, TMeta >, - ): ConflictContextGuard> & - ProcedureBuilderWithOutput< - TInitialContext, - MergedContext, + ): ContextExtendsGuard + & ProcedureBuilderWithOutput< + MergedInitialContext, + MergedCurrentContext, TInputSchema, TOutputSchema, TErrorMap, @@ -284,28 +284,28 @@ export interface ProcedureBuilderWithInputOutput< errors: U, ): ProcedureBuilderWithInputOutput, TMeta> - 'use'( + 'use'( middleware: Middleware< - TCurrentContext, + UInContext, UOutContext, InferSchemaOutput, InferSchemaInput, ORPCErrorConstructorMap, TMeta >, - ): ConflictContextGuard> & - ProcedureBuilderWithInputOutput< - TInitialContext, - MergedContext, + ): ContextExtendsGuard + & ProcedureBuilderWithInputOutput< + MergedInitialContext, + MergedCurrentContext, TInputSchema, TOutputSchema, TErrorMap, TMeta > - 'use'( + 'use'( middleware: Middleware< - TCurrentContext, + UInContext, UOutContext, UInput, InferSchemaInput, @@ -313,10 +313,10 @@ export interface ProcedureBuilderWithInputOutput< TMeta >, mapInput: MapInputMiddleware, UInput>, - ): ConflictContextGuard> & - ProcedureBuilderWithInputOutput< - TInitialContext, - MergedContext, + ): ContextExtendsGuard + & ProcedureBuilderWithInputOutput< + MergedInitialContext, + MergedCurrentContext, TInputSchema, TOutputSchema, TErrorMap, @@ -348,17 +348,22 @@ export interface RouterBuilder< errors: U, ): RouterBuilder, TMeta> - 'use'( + 'use'( middleware: Middleware< - TCurrentContext, + UInContext, UOutContext, unknown, unknown, ORPCErrorConstructorMap, TMeta >, - ): ConflictContextGuard> & - RouterBuilder, TErrorMap, TMeta> + ): ContextExtendsGuard + & RouterBuilder< + MergedInitialContext, + MergedCurrentContext, + TErrorMap, + TMeta + > 'prefix'(prefix: HTTPPath): RouterBuilder @@ -366,9 +371,9 @@ export interface RouterBuilder< 'router', TCurrentContext>>( router: U - ): EnhancedRouter + ): EnhancedRouter 'lazy', TCurrentContext>>( loader: () => Promise<{ default: U }>, - ): EnhancedRouter, TInitialContext, TErrorMap> + ): EnhancedRouter, TInitialContext, TCurrentContext, TErrorMap> } diff --git a/packages/server/src/builder.test-d.ts b/packages/server/src/builder.test-d.ts index 70728a104..042f22b34 100644 --- a/packages/server/src/builder.test-d.ts +++ b/packages/server/src/builder.test-d.ts @@ -6,7 +6,7 @@ import type { BuilderWithMiddlewares, ProcedureBuilder, ProcedureBuilderWithInpu import type { Context } from './context' import type { ORPCErrorConstructorMap } from './error' import type { Lazy } from './lazy' -import type { MiddlewareOutputFn } from './middleware' +import type { Middleware, MiddlewareOutputFn } from './middleware' import type { DecoratedMiddleware } from './middleware-decorated' import type { Procedure } from './procedure' import type { DecoratedProcedure } from './procedure-decorated' @@ -232,8 +232,8 @@ describe('Builder', () => { expectTypeOf(applied).toEqualTypeOf< BuilderWithMiddlewares< - InitialContext, - CurrentContext & { extra: boolean }, + InitialContext & Record, + Omit & { extra: boolean }, typeof inputSchema, typeof outputSchema, typeof baseErrorMap, @@ -241,10 +241,8 @@ describe('Builder', () => { > >() - // @ts-expect-error --- conflict context - builder.use(({ next }) => next({ context: { db: 123 } })) - // conflict but not detected - expectTypeOf(builder.use(({ next }) => next({ context: { db: undefined } }))).toMatchTypeOf() + // invalid TInContext + expectTypeOf(builder.use({} as Middleware<{ auth: 'invalid' }, any, any, any, any, any>)).toEqualTypeOf() // @ts-expect-error --- input is not match builder.use(({ next }, input: 'invalid') => next({})) // @ts-expect-error --- output is not match @@ -276,8 +274,8 @@ describe('Builder', () => { expectTypeOf(applied).toEqualTypeOf< BuilderWithMiddlewares< - InitialContext, - CurrentContext & { extra: boolean }, + InitialContext & Record, + Omit & { extra: boolean }, typeof inputSchema, typeof outputSchema, typeof baseErrorMap, @@ -291,15 +289,39 @@ describe('Builder', () => { input => ({ invalid: true }), ) - // @ts-expect-error --- conflict context - builder.use(({ next }) => next({ context: { db: 123 } }), input => ({ mapped: true })) - // conflict but not detected - expectTypeOf(builder.use(({ next }) => next({ context: { db: undefined } }), input => ({ mapped: true }))).toMatchTypeOf() + // invalid TInContext + expectTypeOf(builder.use({} as Middleware<{ auth: 'invalid' }, any, any, any, any, any>, () => { })).toEqualTypeOf() // @ts-expect-error --- input is not match builder.use(({ next }, input: 'invalid') => next({}), input => ({ mapped: true })) // @ts-expect-error --- output is not match builder.use(({ next }, input, output: MiddlewareOutputFn<'invalid'>) => next({}), input => ({ mapped: true })) }) + + it('with TInContext', () => { + const mid = {} as Middleware<{ cacheable?: boolean }, Record, unknown, unknown, ORPCErrorConstructorMap, BaseMeta> + + expectTypeOf(builder.use(mid)).toEqualTypeOf< + BuilderWithMiddlewares< + InitialContext & { cacheable?: boolean }, + Omit & Record, + typeof inputSchema, + typeof outputSchema, + typeof baseErrorMap, + BaseMeta + > + >() + + expectTypeOf(builder.use(mid, () => { })).toEqualTypeOf< + BuilderWithMiddlewares< + InitialContext & { cacheable?: boolean }, + Omit & Record, + typeof inputSchema, + typeof outputSchema, + typeof baseErrorMap, + BaseMeta + > + >() + }) }) it('.meta', () => { @@ -410,7 +432,7 @@ describe('Builder', () => { it('.router', () => { expectTypeOf(builder.router(router)).toEqualTypeOf< - EnhancedRouter + EnhancedRouter >() builder.router({ @@ -434,7 +456,7 @@ describe('Builder', () => { it('.lazy', () => { expectTypeOf(builder.lazy(() => Promise.resolve({ default: router }))).toEqualTypeOf< - EnhancedRouter, InitialContext, typeof baseErrorMap> + EnhancedRouter, InitialContext, CurrentContext, typeof baseErrorMap> >() // @ts-expect-error - initial context is not match diff --git a/packages/server/src/builder.ts b/packages/server/src/builder.ts index 8851804a9..57403d438 100644 --- a/packages/server/src/builder.ts +++ b/packages/server/src/builder.ts @@ -1,6 +1,6 @@ import type { AnySchema, ContractProcedureDef, ContractRouter, ErrorMap, HTTPPath, MergedErrorMap, Meta, Route, Schema } from '@orpc/contract' import type { BuilderWithMiddlewares, ProcedureBuilder, ProcedureBuilderWithInput, ProcedureBuilderWithOutput, RouterBuilder } from './builder-variants' -import type { ConflictContextGuard, Context, MergedContext } from './context' +import type { Context, ContextExtendsGuard, MergedCurrentContext, MergedInitialContext } from './context' import type { ORPCErrorConstructorMap } from './error' import type { Lazy } from './lazy' import type { AnyMiddleware, MapInputMiddleware, Middleware } from './middleware' @@ -122,28 +122,28 @@ export class Builder< }) } - use( + use( middleware: Middleware< - TCurrentContext, + UInContext, UOutContext, unknown, unknown, ORPCErrorConstructorMap, TMeta >, - ): ConflictContextGuard> & - BuilderWithMiddlewares< - TInitialContext, - MergedContext, + ): ContextExtendsGuard + & BuilderWithMiddlewares< + MergedInitialContext, + MergedCurrentContext, TInputSchema, TOutputSchema, TErrorMap, TMeta > - use( + use( middleware: Middleware< - TCurrentContext, + UInContext, UOutContext, UInput, unknown, @@ -151,10 +151,10 @@ export class Builder< TMeta >, mapInput: MapInputMiddleware, - ): ConflictContextGuard> & - BuilderWithMiddlewares< - TInitialContext, - MergedContext, + ): ContextExtendsGuard + & BuilderWithMiddlewares< + MergedInitialContext, + MergedCurrentContext, TInputSchema, TOutputSchema, TErrorMap, @@ -240,14 +240,14 @@ export class Builder< router, TCurrentContext>>( router: U, - ): EnhancedRouter { + ): EnhancedRouter { return enhanceRouter(router, this['~orpc']) } lazy, TCurrentContext>>( loader: () => Promise<{ default: U }>, - ): EnhancedRouter, TInitialContext, TErrorMap> { - return enhanceRouter(lazy(loader), this['~orpc']) + ): EnhancedRouter, TInitialContext, TCurrentContext, TErrorMap> { + return enhanceRouter(lazy(loader), this['~orpc']) as any } } diff --git a/packages/server/src/context.test-d.ts b/packages/server/src/context.test-d.ts index d5ed70d5c..24284d3ff 100644 --- a/packages/server/src/context.test-d.ts +++ b/packages/server/src/context.test-d.ts @@ -1,12 +1,31 @@ -import type { ConflictContextGuard, MergedContext } from './context' +import type { ContextExtendsGuard, MergedCurrentContext, MergedInitialContext } from './context' -it('MergedContext', () => { - expectTypeOf>().toMatchTypeOf<{ a: string, b: number }>() - expectTypeOf>().toMatchTypeOf<{ a: never }>() +it('MergedInitialContext', () => { + expectTypeOf>().toMatchTypeOf<{ a: string, b: number }>() + expectTypeOf>().toMatchTypeOf<{ a: never }>() + expectTypeOf>().toMatchTypeOf<{ a: string }>() }) -it('ConflictContextGuard', () => { - expectTypeOf>>().toEqualTypeOf() - expectTypeOf>>().toEqualTypeOf() - expectTypeOf>().toEqualTypeOf() +it('MergedCurrentContext', () => { + expectTypeOf>().toMatchTypeOf<{ a: string, b: number }>() + expectTypeOf>().toMatchTypeOf<{ a: number }>() +}) + +interface Empty { + +} + +it('ContextExtendsGuard', () => { + expectTypeOf < ContextExtendsGuard< { a: string, b: string }, { a: string }>>().toEqualTypeOf() + expectTypeOf>().toEqualTypeOf() + expectTypeOf < ContextExtendsGuard< { a: string, b: string }, Empty>>().toEqualTypeOf() + expectTypeOf < ContextExtendsGuard< { a: string, b: string }, Record>>().toEqualTypeOf() + expectTypeOf < ContextExtendsGuard< { a: string, b: string }, { g?: string }>>().toEqualTypeOf() + expectTypeOf>().toEqualTypeOf() + expectTypeOf>().toEqualTypeOf() + expectTypeOf>().toEqualTypeOf() + + expectTypeOf < ContextExtendsGuard < { a: string }, { a: string, b: string }>>().toEqualTypeOf() + expectTypeOf < ContextExtendsGuard < { a: number }, { a: string }>>().toEqualTypeOf() + expectTypeOf>().toEqualTypeOf() }) diff --git a/packages/server/src/context.test.ts b/packages/server/src/context.test.ts index 87699ec46..fc4e5e241 100644 --- a/packages/server/src/context.test.ts +++ b/packages/server/src/context.test.ts @@ -1,6 +1,6 @@ -import { mergeContext } from './context' +import { mergeCurrentContext } from './context' -it('mergeContext', () => { - expect(mergeContext({ a: 1 }, { b: 2 })).toEqual({ a: 1, b: 2 }) - expect(mergeContext({ a: 1 }, { a: 2 })).toEqual({ a: 2 }) +it('mergeCurrentContext', () => { + expect(mergeCurrentContext({ a: 1 }, { b: 2 })).toEqual({ a: 1, b: 2 }) + expect(mergeCurrentContext({ a: 1 }, { a: 2 })).toEqual({ a: 2 }) }) diff --git a/packages/server/src/context.ts b/packages/server/src/context.ts index 7bdb83a79..3f444e4e1 100644 --- a/packages/server/src/context.ts +++ b/packages/server/src/context.ts @@ -1,17 +1,18 @@ -import type { IsNever } from '@orpc/shared' - export type Context = Record -export type MergedContext = T & U +export type MergedInitialContext< + TInitial extends Context, + TAdditional extends Context, + TCurrent extends Context, +> = TInitial & Omit + +export type MergedCurrentContext = Omit & U -export function mergeContext( +export function mergeCurrentContext( context: T, other: U, -): MergedContext { +): MergedCurrentContext { return { ...context, ...other } } -export type ConflictContextGuard = - true extends IsNever | { [K in keyof T]: IsNever }[keyof T] - ? never // 'Conflict context detected: Please ensure your middlewares do not return conflicting context' - : unknown +export type ContextExtendsGuard = T extends T & U ? unknown : never diff --git a/packages/server/src/implementer-procedure.test-d.ts b/packages/server/src/implementer-procedure.test-d.ts index 4eb841e2d..ec7d3b7dd 100644 --- a/packages/server/src/implementer-procedure.test-d.ts +++ b/packages/server/src/implementer-procedure.test-d.ts @@ -7,7 +7,7 @@ import type { Builder } from './builder' import type { Context } from './context' import type { ORPCErrorConstructorMap } from './error' import type { ImplementedProcedure, ProcedureImplementer } from './implementer-procedure' -import type { MiddlewareOutputFn } from './middleware' +import type { Middleware, MiddlewareOutputFn } from './middleware' import type { Procedure } from './procedure' import type { DecoratedProcedure } from './procedure-decorated' @@ -80,23 +80,23 @@ describe('ImplementedProcedure', () => { }) expectTypeOf(applied).toEqualTypeOf< - DecoratedProcedure< - InitialContext, - CurrentContext & { extra: boolean }, - typeof inputSchema, - typeof outputSchema, - typeof baseErrorMap, - BaseMeta + ImplementedProcedure< + InitialContext & Omit, + Omit & { extra: boolean }, + typeof inputSchema, + typeof outputSchema, + typeof baseErrorMap, + BaseMeta > >() - // @ts-expect-error --- conflict context - implemented.use(({ next }) => next({ context: { db: 123 } })) + // invalid TInContext + expectTypeOf(implemented.use({} as Middleware<{ auth: 'invalid' }, any, any, any, any, any>)).toEqualTypeOf() // @ts-expect-error --- input is not match implemented.use(({ next }, input: 'invalid') => next({})) // @ts-expect-error --- output is not match implemented.use(({ next }, input, output: MiddlewareOutputFn<'invalid'>) => next({})) - // conflict context but not detected + // conflict context expectTypeOf(implemented.use(({ next }) => next({ context: { db: undefined } }))).toEqualTypeOf() }) @@ -119,18 +119,18 @@ describe('ImplementedProcedure', () => { }) expectTypeOf(applied).toEqualTypeOf< - DecoratedProcedure< - InitialContext, - CurrentContext & { extra: boolean }, - typeof inputSchema, - typeof outputSchema, - typeof baseErrorMap, - BaseMeta + ImplementedProcedure< + InitialContext & Omit, + Omit & { extra: boolean }, + typeof inputSchema, + typeof outputSchema, + typeof baseErrorMap, + BaseMeta > >() - // @ts-expect-error --- conflict context - implemented.use(({ next }) => ({ context: { db: 123 } }), () => {}) + // invalid TInContext + expectTypeOf(implemented.use({} as Middleware<{ auth: 'invalid' }, any, any, any, any, any>, () => {})).toEqualTypeOf() // @ts-expect-error --- input is not match implemented.use(({ next }, input: 'invalid') => next({}), () => {}) // @ts-expect-error --- output is not match @@ -138,6 +138,32 @@ describe('ImplementedProcedure', () => { // conflict context but not detected expectTypeOf(implemented.use(({ next }) => next({ context: { db: undefined } }), () => {})).toEqualTypeOf() }) + + it('with TInContext', () => { + const mid = {} as Middleware<{ cacheable?: boolean }, Record, unknown, any, ORPCErrorConstructorMap, BaseMeta> + + expectTypeOf(implemented.use(mid)).toEqualTypeOf< + ImplementedProcedure< + InitialContext & { cacheable?: boolean }, + Omit & Record, + typeof inputSchema, + typeof outputSchema, + typeof baseErrorMap, + BaseMeta + > + >() + + expectTypeOf(implemented.use(mid, () => { })).toEqualTypeOf< + ImplementedProcedure< + InitialContext & { cacheable?: boolean }, + Omit & Record, + typeof inputSchema, + typeof outputSchema, + typeof baseErrorMap, + BaseMeta + > + >() + }) }) it('.callable', () => { @@ -230,8 +256,8 @@ describe('ProcedureImplementer', () => { expectTypeOf(applied).toEqualTypeOf< ProcedureImplementer< - InitialContext, - CurrentContext & { extra: boolean }, + InitialContext & Record, + Omit & { extra: boolean }, typeof inputSchema, typeof outputSchema, typeof baseErrorMap, @@ -239,10 +265,8 @@ describe('ProcedureImplementer', () => { > >() - // @ts-expect-error --- conflict context - builder.use(({ next }) => next({ context: { db: 123 } })) - // conflict but not detected - expectTypeOf(builder.use(({ next }) => next({ context: { db: undefined } }))).toMatchTypeOf() + // invalid TInContext + expectTypeOf(builder.use({} as Middleware<{ auth: 'invalid' }, any, any, any, any, any>)).toEqualTypeOf() // @ts-expect-error --- input is not match builder.use(({ next }, input: 'invalid') => next({})) // @ts-expect-error --- output is not match @@ -274,8 +298,8 @@ describe('ProcedureImplementer', () => { expectTypeOf(applied).toEqualTypeOf< ProcedureImplementer< - InitialContext, - CurrentContext & { extra: boolean }, + InitialContext & Record, + Omit & { extra: boolean }, typeof inputSchema, typeof outputSchema, typeof baseErrorMap, @@ -289,15 +313,39 @@ describe('ProcedureImplementer', () => { input => ({ invalid: true }), ) - // @ts-expect-error --- conflict context - builder.use(({ next }) => next({ context: { db: 123 } }), input => ({ mapped: true })) - // conflict but not detected - expectTypeOf(builder.use(({ next }) => next({ context: { db: undefined } }), input => ({ mapped: true }))).toMatchTypeOf() + // invalid TInContext + expectTypeOf(builder.use({} as Middleware<{ auth: 'invalid' }, any, any, any, any, any>, () => {})).toEqualTypeOf() // @ts-expect-error --- input is not match builder.use(({ next }, input: 'invalid') => next({}), input => ({ mapped: true })) // @ts-expect-error --- output is not match builder.use(({ next }, input, output: MiddlewareOutputFn<'invalid'>) => next({}), input => ({ mapped: true })) }) + + it('with TInContext', () => { + const mid = {} as Middleware<{ cacheable?: boolean }, Record, unknown, any, ORPCErrorConstructorMap, BaseMeta> + + expectTypeOf(builder.use(mid)).toEqualTypeOf< + ProcedureImplementer< + InitialContext & { cacheable?: boolean }, + Omit & Record, + typeof inputSchema, + typeof outputSchema, + typeof baseErrorMap, + BaseMeta + > + >() + + expectTypeOf(builder.use(mid, () => { })).toEqualTypeOf< + ProcedureImplementer< + InitialContext & { cacheable?: boolean }, + Omit & Record, + typeof inputSchema, + typeof outputSchema, + typeof baseErrorMap, + BaseMeta + > + >() + }) }) it('.handler', () => { diff --git a/packages/server/src/implementer-procedure.ts b/packages/server/src/implementer-procedure.ts index 7cb13821f..7344d1d53 100644 --- a/packages/server/src/implementer-procedure.ts +++ b/packages/server/src/implementer-procedure.ts @@ -2,12 +2,11 @@ import type { ClientContext, ClientRest } from '@orpc/client' import type { AnySchema, ErrorMap, InferSchemaInput, InferSchemaOutput, Meta } from '@orpc/contract' import type { MaybeOptionalOptions } from '@orpc/shared' import type { BuilderDef } from './builder' -import type { ConflictContextGuard, Context, MergedContext } from './context' +import type { Context, ContextExtendsGuard, MergedCurrentContext, MergedInitialContext } from './context' import type { ORPCErrorConstructorMap } from './error' import type { MapInputMiddleware, Middleware } from './middleware' import type { Procedure, ProcedureHandler } from './procedure' import type { CreateProcedureClientOptions, ProcedureClient } from './procedure-client' -import type { DecoratedProcedure } from './procedure-decorated' /** * Like `DecoratedProcedure`, but removed all method that can change the contract. @@ -20,28 +19,29 @@ export interface ImplementedProcedure< TErrorMap extends ErrorMap, TMeta extends Meta, > extends Procedure { - use( + use( middleware: Middleware< - TCurrentContext, - U, + UInContext, + UOutContext, InferSchemaOutput, InferSchemaInput, ORPCErrorConstructorMap, TMeta >, - ): ConflictContextGuard> - & DecoratedProcedure< - TInitialContext, - MergedContext, + ): ContextExtendsGuard + & ContextExtendsGuard, TCurrentContext> + & ImplementedProcedure< + MergedInitialContext, + MergedCurrentContext, TInputSchema, TOutputSchema, TErrorMap, TMeta > - use( + use( middleware: Middleware< - TCurrentContext, + UInContext, UOutContext, UInput, InferSchemaInput, @@ -49,10 +49,11 @@ export interface ImplementedProcedure< TMeta >, mapInput: MapInputMiddleware, UInput>, - ): ConflictContextGuard> - & DecoratedProcedure< - TInitialContext, - MergedContext, + ): ContextExtendsGuard + & ContextExtendsGuard, TCurrentContext> + & ImplementedProcedure< + MergedInitialContext, + MergedCurrentContext, TInputSchema, TOutputSchema, TErrorMap, @@ -107,28 +108,28 @@ export interface ProcedureImplementer< > { '~orpc': BuilderDef - 'use'( + 'use'( middleware: Middleware< - TCurrentContext, - U, + UInContext, + UOutContext, InferSchemaOutput, InferSchemaInput, ORPCErrorConstructorMap, TMeta >, - ): ConflictContextGuard> + ): ContextExtendsGuard & ProcedureImplementer< - TInitialContext, - MergedContext, + MergedInitialContext, + MergedCurrentContext, TInputSchema, TOutputSchema, TErrorMap, TMeta > - 'use'( + 'use'( middleware: Middleware< - TCurrentContext, + UInContext, UOutContext, UInput, InferSchemaInput, @@ -136,10 +137,10 @@ export interface ProcedureImplementer< TMeta >, mapInput: MapInputMiddleware, UInput>, - ): ConflictContextGuard> + ): ContextExtendsGuard & ProcedureImplementer< - TInitialContext, - MergedContext, + MergedInitialContext, + MergedCurrentContext, TInputSchema, TOutputSchema, TErrorMap, diff --git a/packages/server/src/implementer-variants.test-d.ts b/packages/server/src/implementer-variants.test-d.ts index 1599cbfb6..9e317fbd9 100644 --- a/packages/server/src/implementer-variants.test-d.ts +++ b/packages/server/src/implementer-variants.test-d.ts @@ -1,13 +1,13 @@ import type { AnySchema, ErrorMap, Meta, Schema } from '@orpc/contract' import type { baseErrorMap, BaseMeta, inputSchema, outputSchema, router } from '../../contract/tests/shared' import type { CurrentContext, InitialContext } from '../tests/shared' -import type { Context, MergedContext } from './context' +import type { Context, MergedCurrentContext } from './context' import type { ORPCErrorConstructorMap } from './error' import type { ImplementerInternal } from './implementer' import type { ProcedureImplementer } from './implementer-procedure' import type { ImplementerInternalWithMiddlewares } from './implementer-variants' import type { Lazy } from './lazy' -import type { MiddlewareOutputFn } from './middleware' +import type { Middleware, MiddlewareOutputFn } from './middleware' import type { Procedure } from './procedure' import type { EnhancedRouter } from './router-utils' import { router as implRouter } from '../tests/shared' @@ -19,46 +19,58 @@ describe('ImplementerWithMiddlewares', () => { }) describe('router level', () => { - it('.use', () => { - const applied = implementer.nested.use(({ context, next, path, procedure, errors, signal }, input, output) => { - expectTypeOf(input).toEqualTypeOf() - expectTypeOf(context).toEqualTypeOf() - expectTypeOf(path).toEqualTypeOf() - expectTypeOf(procedure).toEqualTypeOf< - Procedure - >() - expectTypeOf(output).toEqualTypeOf>() - expectTypeOf(errors).toEqualTypeOf>>() - expectTypeOf(signal).toEqualTypeOf>() - - return next({ - context: { - extra: true, - }, + describe('.use', () => { + it('without map input', () => { + const applied = implementer.nested.use(({ context, next, path, procedure, errors, signal }, input, output) => { + expectTypeOf(input).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + expectTypeOf(path).toEqualTypeOf() + expectTypeOf(procedure).toEqualTypeOf< + Procedure + >() + expectTypeOf(output).toEqualTypeOf>() + expectTypeOf(errors).toEqualTypeOf>>() + expectTypeOf(signal).toEqualTypeOf>() + + return next({ + context: { + extra: true, + }, + }) }) - }) - expectTypeOf(applied).toMatchTypeOf< - ImplementerInternalWithMiddlewares< + expectTypeOf(applied).toEqualTypeOf< + ImplementerInternalWithMiddlewares< typeof router['nested'], - InitialContext, - MergedContext - > - >() + InitialContext & Record, + MergedCurrentContext + > + >() - // @ts-expect-error --- conflict context - implementer.use(({ next }) => next({ context: { db: 123 } })) - // conflict but not detected - expectTypeOf(implementer.use(({ next }) => next({ context: { db: undefined } }))).toMatchTypeOf() - // @ts-expect-error --- input is not match - implementer.use(({ next }, input: 'invalid') => next({})) - // @ts-expect-error --- output is not match - implementer.use(({ next }, input, output: MiddlewareOutputFn<'invalid'>) => next({})) + // invalid TInContext + expectTypeOf(implementer.nested.use({} as Middleware<{ auth: 'invalid' }, any, any, any, any, any>)).toEqualTypeOf() + // @ts-expect-error --- input is not match + implementer.use(({ next }, input: 'invalid') => next({})) + // @ts-expect-error --- output is not match + implementer.use(({ next }, input, output: MiddlewareOutputFn<'invalid'>) => next({})) + }) + + it('with TInContext', () => { + const mid = {} as Middleware<{ cacheable?: boolean }, Record, unknown, unknown, ORPCErrorConstructorMap, BaseMeta> + + expectTypeOf(implementer.use(mid)).toEqualTypeOf< + ImplementerInternalWithMiddlewares< + typeof router, + InitialContext & { cacheable?: boolean }, + Omit & Record + > + >() + }) }) it('.router', () => { expectTypeOf(implementer.router(implRouter)).toEqualTypeOf< - EnhancedRouter> + EnhancedRouter < typeof implRouter, InitialContext, CurrentContext, Record> >() implementer.router({ @@ -93,7 +105,7 @@ describe('ImplementerWithMiddlewares', () => { it('.lazy', () => { expectTypeOf(implementer.lazy(() => Promise.resolve({ default: implRouter }))).toEqualTypeOf< - EnhancedRouter, InitialContext, Record> + EnhancedRouter, InitialContext, CurrentContext, Record> >() // @ts-expect-error - initial context is not match diff --git a/packages/server/src/implementer-variants.ts b/packages/server/src/implementer-variants.ts index ce0719d50..201ed26bb 100644 --- a/packages/server/src/implementer-variants.ts +++ b/packages/server/src/implementer-variants.ts @@ -1,5 +1,5 @@ import type { AnyContractRouter, ContractProcedure, InferContractRouterErrorMap, InferContractRouterMeta } from '@orpc/contract' -import type { ConflictContextGuard, Context, MergedContext } from './context' +import type { Context, ContextExtendsGuard, MergedCurrentContext, MergedInitialContext } from './context' import type { ORPCErrorConstructorMap } from './error' import type { ProcedureImplementer } from './implementer-procedure' import type { Lazy } from './lazy' @@ -12,24 +12,28 @@ export interface RouterImplementerWithMiddlewares< TInitialContext extends Context, TCurrentContext extends Context, > { - use( + use( middleware: Middleware< - TCurrentContext, - U, + UInContext, + UOutContext, unknown, unknown, ORPCErrorConstructorMap>, InferContractRouterMeta >, - ): ConflictContextGuard> - & ImplementerInternalWithMiddlewares> + ): ContextExtendsGuard + & ImplementerInternalWithMiddlewares< + T, + MergedInitialContext, + MergedCurrentContext + > router>( - router: U): EnhancedRouter> + router: U): EnhancedRouter> lazy>( loader: () => Promise<{ default: U }> - ): EnhancedRouter, TInitialContext, Record> + ): EnhancedRouter, TInitialContext, TCurrentContext, Record> } export type ImplementerInternalWithMiddlewares< diff --git a/packages/server/src/implementer.test-d.ts b/packages/server/src/implementer.test-d.ts index 6704cad3d..8c2c547ad 100644 --- a/packages/server/src/implementer.test-d.ts +++ b/packages/server/src/implementer.test-d.ts @@ -1,13 +1,13 @@ import type { AnySchema, ErrorMap, Meta, Schema } from '@orpc/contract' import type { baseErrorMap, BaseMeta, inputSchema, outputSchema, router } from '../../contract/tests/shared' import type { CurrentContext, InitialContext } from '../tests/shared' -import type { Context, MergedContext } from './context' +import type { Context, MergedCurrentContext } from './context' import type { ORPCErrorConstructorMap } from './error' import type { Implementer } from './implementer' import type { ProcedureImplementer } from './implementer-procedure' import type { ImplementerInternalWithMiddlewares } from './implementer-variants' import type { Lazy } from './lazy' -import type { MiddlewareOutputFn } from './middleware' +import type { Middleware, MiddlewareOutputFn } from './middleware' import type { DecoratedMiddleware } from './middleware-decorated' import type { Procedure } from './procedure' import type { EnhancedRouter } from './router-utils' @@ -88,42 +88,58 @@ describe('Implementer', () => { }) }) - it('.use', () => { - const applied = implementer.nested.use(({ context, next, path, procedure, errors, signal }, input, output) => { - expectTypeOf(input).toEqualTypeOf() - expectTypeOf(context).toEqualTypeOf() - expectTypeOf(path).toEqualTypeOf() - expectTypeOf(procedure).toEqualTypeOf< - Procedure - >() - expectTypeOf(output).toEqualTypeOf>() - expectTypeOf(errors).toEqualTypeOf>>() - expectTypeOf(signal).toEqualTypeOf>() - - return next({ - context: { - extra: true, - }, + describe('.use', () => { + it('without map input', () => { + const applied = implementer.nested.use(({ context, next, path, procedure, errors, signal }, input, output) => { + expectTypeOf(input).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf() + expectTypeOf(path).toEqualTypeOf() + expectTypeOf(procedure).toEqualTypeOf< + Procedure + >() + expectTypeOf(output).toEqualTypeOf>() + expectTypeOf(errors).toEqualTypeOf>>() + expectTypeOf(signal).toEqualTypeOf>() + + return next({ + context: { + extra: true, + }, + }) }) + + expectTypeOf(applied).toEqualTypeOf< + ImplementerInternalWithMiddlewares< + typeof router['nested'], + InitialContext & Record, + MergedCurrentContext + > + >() + + // invalid TInContext + expectTypeOf(implementer.nested.use({} as Middleware<{ auth: 'invalid' }, any, any, any, any, any>)).toEqualTypeOf() + // @ts-expect-error --- input is not match + implementer.use(({ next }, input: 'invalid') => next({})) + // @ts-expect-error --- output is not match + implementer.use(({ next }, input, output: MiddlewareOutputFn<'invalid'>) => next({})) }) - expectTypeOf(applied).toMatchTypeOf< - ImplementerInternalWithMiddlewares> - >() + it('with TInContext', () => { + const mid = {} as Middleware<{ cacheable?: boolean }, Record, unknown, unknown, ORPCErrorConstructorMap, BaseMeta> - // @ts-expect-error --- conflict context - implementer.use(({ next }) => next({ context: { db: 123 } })) - // conflict but not detected - expectTypeOf(implementer.use(({ next }) => next({ context: { db: undefined } }))).toMatchTypeOf() - // @ts-expect-error --- input is not match - implementer.use(({ next }, input: 'invalid') => next({})) - // @ts-expect-error --- output is not match - implementer.use(({ next }, input, output: MiddlewareOutputFn<'invalid'>) => next({})) + expectTypeOf(implementer.use(mid)).toEqualTypeOf< + ImplementerInternalWithMiddlewares< + typeof router, + InitialContext & { cacheable?: boolean }, + Omit & Record + > + >() + }) }) it('.router', () => { expectTypeOf(implementer.router(implRouter)).toEqualTypeOf< - EnhancedRouter> + EnhancedRouter < typeof implRouter, InitialContext, CurrentContext, Record> >() implementer.router({ @@ -158,7 +174,7 @@ describe('Implementer', () => { it('.lazy', () => { expectTypeOf(implementer.lazy(() => Promise.resolve({ default: implRouter }))).toEqualTypeOf< - EnhancedRouter, InitialContext, Record> + EnhancedRouter, InitialContext, CurrentContext, Record> >() // @ts-expect-error - initial context is not match diff --git a/packages/server/src/implementer.ts b/packages/server/src/implementer.ts index 9df5e63d4..3c6a3c956 100644 --- a/packages/server/src/implementer.ts +++ b/packages/server/src/implementer.ts @@ -1,6 +1,6 @@ import type { AnyContractRouter, ContractProcedure, InferContractRouterErrorMap, InferContractRouterMeta } from '@orpc/contract' import type { AnyFunction } from '@orpc/shared' -import type { ConflictContextGuard, Context, MergedContext } from './context' +import type { Context, ContextExtendsGuard, MergedCurrentContext, MergedInitialContext } from './context' import type { ORPCErrorConstructorMap } from './error' import type { ProcedureImplementer } from './implementer-procedure' import type { ImplementerInternalWithMiddlewares } from './implementer-variants' @@ -32,24 +32,28 @@ export interface RouterImplementer< >, ): DecoratedMiddleware, InferContractRouterMeta> // ORPCErrorConstructorMap ensures middleware can used in any procedure - use( + use( middleware: Middleware< - TCurrentContext, - U, + UInContext, + UOutContext, unknown, unknown, ORPCErrorConstructorMap>, InferContractRouterMeta >, - ): ConflictContextGuard> - & ImplementerInternalWithMiddlewares> + ): ContextExtendsGuard + & ImplementerInternalWithMiddlewares< + T, + MergedInitialContext, + MergedCurrentContext + > router>( - router: U): EnhancedRouter> + router: U): EnhancedRouter> lazy>( loader: () => Promise<{ default: U }> - ): EnhancedRouter, TInitialContext, Record> + ): EnhancedRouter, TInitialContext, TCurrentContext, Record> } export type ImplementerInternal< diff --git a/packages/server/src/middleware-decorated.test-d.ts b/packages/server/src/middleware-decorated.test-d.ts index 1bcc32a4e..bea5ad86f 100644 --- a/packages/server/src/middleware-decorated.test-d.ts +++ b/packages/server/src/middleware-decorated.test-d.ts @@ -1,7 +1,7 @@ import type { AnySchema, ErrorMap } from '@orpc/contract' import type { baseErrorMap, BaseMeta } from '../../contract/tests/shared' import type { CurrentContext } from '../tests/shared' -import type { Context, MergedContext } from './context' +import type { Context } from './context' import type { ORPCErrorConstructorMap } from './error' import type { Middleware, MiddlewareOutputFn } from './middleware' import type { DecoratedMiddleware } from './middleware-decorated' @@ -52,7 +52,7 @@ describe('DecoratedMiddleware', () => { it('without map input', () => { const applied = decorated.concat(({ context, next, path, procedure, errors, signal }, input, output) => { expectTypeOf(input).toEqualTypeOf<{ input: string }>() - expectTypeOf(context).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf & { extra: boolean }>() expectTypeOf(path).toEqualTypeOf() expectTypeOf(procedure).toEqualTypeOf< Procedure @@ -70,8 +70,8 @@ describe('DecoratedMiddleware', () => { expectTypeOf(applied).toEqualTypeOf< DecoratedMiddleware< - CurrentContext, - MergedContext<{ extra: boolean }, { extra2: boolean }>, + CurrentContext & Record, + Omit<{ extra: boolean }, never> & { extra2: boolean }, { input: string }, { output: number }, ORPCErrorConstructorMap, @@ -79,8 +79,8 @@ describe('DecoratedMiddleware', () => { > >() - // @ts-expect-error --- conflict context - decorated.concat(({ next }) => next({ context: { extra: 'invalid' } })) + // invalid TInContext + expectTypeOf(decorated.concat({} as Middleware<{ auth: 'invalid' }, any, any, any, any, any>)).toEqualTypeOf() // @ts-expect-error --- output is not match decorated.concat(({ next }, input, output: MiddlewareOutputFn<'invalid'>) => next({})) }) @@ -88,7 +88,7 @@ describe('DecoratedMiddleware', () => { it('with map input', () => { const applied = decorated.concat(({ context, next, path, procedure, errors, signal }, input: { mapped: boolean }, output) => { expectTypeOf(input).toEqualTypeOf<{ mapped: boolean }>() - expectTypeOf(context).toEqualTypeOf() + expectTypeOf(context).toEqualTypeOf & { extra: boolean }>() expectTypeOf(path).toEqualTypeOf() expectTypeOf(procedure).toEqualTypeOf< Procedure @@ -110,8 +110,8 @@ describe('DecoratedMiddleware', () => { expectTypeOf(applied).toEqualTypeOf< DecoratedMiddleware< - CurrentContext, - MergedContext<{ extra: boolean }, { extra2: boolean }>, + CurrentContext & Record, + Omit<{ extra: boolean }, never> & { extra2: boolean }, { input: string }, { output: number }, ORPCErrorConstructorMap, @@ -125,10 +125,36 @@ describe('DecoratedMiddleware', () => { input => ({ invalid: true }), ) - // @ts-expect-error --- conflict context - decorated.concat(({ next }) => next({ context: { extra: 'invalid' } }), input => ({ mapped: true })) + // invalid TInContext + expectTypeOf(decorated.concat({} as Middleware<{ auth: 'invalid' }, any, any, any, any, any>, () => { })).toEqualTypeOf() // @ts-expect-error --- output is not match decorated.concat(({ next }, input, output: MiddlewareOutputFn<'invalid'>) => next({}), input => ({ mapped: true })) }) + + it('with TInContext', () => { + const mid = {} as Middleware<{ cacheable?: boolean }, Record, unknown, any, ORPCErrorConstructorMap, BaseMeta> + + expectTypeOf(decorated.concat(mid)).toEqualTypeOf< + DecoratedMiddleware< + CurrentContext & Omit<{ cacheable?: boolean }, 'db' | 'auth' | 'extra'>, + Omit<{ extra: boolean }, never> & Record, + { input: string }, + { output: number }, + ORPCErrorConstructorMap, + BaseMeta + > + >() + + expectTypeOf(decorated.concat(mid, () => { })).toEqualTypeOf< + DecoratedMiddleware< + CurrentContext & Omit<{ cacheable?: boolean }, 'db' | 'auth' | 'extra'>, + Omit<{ extra: boolean }, never> & Record, + { input: string }, + { output: number }, + ORPCErrorConstructorMap, + BaseMeta + > + >() + }) }) }) diff --git a/packages/server/src/middleware-decorated.ts b/packages/server/src/middleware-decorated.ts index 3a68a5e5c..3e8c612dc 100644 --- a/packages/server/src/middleware-decorated.ts +++ b/packages/server/src/middleware-decorated.ts @@ -1,5 +1,5 @@ import type { Meta } from '@orpc/contract' -import type { Context, MergedContext } from './context' +import type { Context, ContextExtendsGuard, MergedCurrentContext, MergedInitialContext } from './context' import type { ORPCErrorConstructorMap } from './error' import type { AnyMiddleware, MapInputMiddleware, Middleware } from './middleware' @@ -15,30 +15,32 @@ export interface DecoratedMiddleware< map: MapInputMiddleware, ): DecoratedMiddleware - concat( + concat>( middleware: Middleware< - TInContext & TOutContext, + UInContext, UOutContext, TInput, TOutput, TErrorConstructorMap, TMeta >, - ): DecoratedMiddleware< - TInContext, - MergedContext, - TInput, - TOutput, - TErrorConstructorMap, - TMeta - > + ): ContextExtendsGuard, UInContext> + & DecoratedMiddleware< + MergedInitialContext>, + MergedCurrentContext, + TInput, + TOutput, + TErrorConstructorMap, + TMeta + > concat< UOutContext extends Context, UMappedInput, + UInContext extends Context = MergedCurrentContext, >( middleware: Middleware< - TInContext & TOutContext, + UInContext, UOutContext, UMappedInput, TOutput, @@ -46,14 +48,15 @@ export interface DecoratedMiddleware< TMeta >, mapInput: MapInputMiddleware, - ): DecoratedMiddleware< - TInContext, - MergedContext, - TInput, - TOutput, - TErrorConstructorMap, - TMeta - > + ): ContextExtendsGuard, UInContext> + & DecoratedMiddleware< + MergedInitialContext>, + MergedCurrentContext, + TInput, + TOutput, + TErrorConstructorMap, + TMeta + > } export function decorateMiddleware< diff --git a/packages/server/src/middleware.test-d.ts b/packages/server/src/middleware.test-d.ts index c50983173..17af89b2a 100644 --- a/packages/server/src/middleware.test-d.ts +++ b/packages/server/src/middleware.test-d.ts @@ -23,7 +23,7 @@ describe('middleware', () => { >() expectTypeOf(signal).toEqualTypeOf>() expectTypeOf(output).toEqualTypeOf>() - expectTypeOf(next).toEqualTypeOf>() + expectTypeOf(next).toEqualTypeOf>() expectTypeOf(errors).toEqualTypeOf>() return next() diff --git a/packages/server/src/middleware.ts b/packages/server/src/middleware.ts index 8320b3075..d52295df4 100644 --- a/packages/server/src/middleware.ts +++ b/packages/server/src/middleware.ts @@ -13,8 +13,8 @@ export type MiddlewareNextFnOptions = Record { - = Record>( +export interface MiddlewareNextFn { + >( ...rest: MaybeOptionalOptions> ): MiddlewareResult } @@ -34,7 +34,7 @@ export interface MiddlewareOptions< procedure: Procedure signal?: AbortSignal lastEventId: string | undefined - next: MiddlewareNextFn + next: MiddlewareNextFn errors: TErrorConstructorMap } diff --git a/packages/server/src/procedure-client.ts b/packages/server/src/procedure-client.ts index 00c53a6ae..0b2177f17 100644 --- a/packages/server/src/procedure-client.ts +++ b/packages/server/src/procedure-client.ts @@ -169,7 +169,7 @@ async function executeProcedureInternal(procedure: AnyProcedure, options: Proced let currentContext = options.context let currentInput = options.input - const next: MiddlewareNextFn = async (...[nextOptions]) => { + const next: MiddlewareNextFn = async (...[nextOptions]) => { const index = currentIndex currentIndex += 1 currentContext = { ...currentContext, ...nextOptions?.context } diff --git a/packages/server/src/procedure-decorated.test-d.ts b/packages/server/src/procedure-decorated.test-d.ts index 567dfadfa..0e6cd15d3 100644 --- a/packages/server/src/procedure-decorated.test-d.ts +++ b/packages/server/src/procedure-decorated.test-d.ts @@ -4,7 +4,7 @@ import type { baseErrorMap, BaseMeta, inputSchema, outputSchema } from '../../co import type { CurrentContext, InitialContext } from '../tests/shared' import type { Context } from './context' import type { ORPCErrorConstructorMap } from './error' -import type { MiddlewareOutputFn } from './middleware' +import type { Middleware, MiddlewareOutputFn } from './middleware' import type { Procedure } from './procedure' import type { DecoratedProcedure } from './procedure-decorated' @@ -109,8 +109,8 @@ describe('DecoratedProcedure', () => { expectTypeOf(applied).toEqualTypeOf< DecoratedProcedure< - InitialContext, - CurrentContext & { extra: boolean }, + InitialContext & Record, + Omit & { extra: boolean }, typeof inputSchema, typeof outputSchema, typeof baseErrorMap, @@ -118,8 +118,8 @@ describe('DecoratedProcedure', () => { > >() - // @ts-expect-error --- conflict context - builder.use(({ next }) => next({ context: { db: 123 } })) + // invalid TInContext + expectTypeOf(builder.use({} as Middleware<{ auth: 'invalid' }, any, any, any, any, any>)).toEqualTypeOf() // @ts-expect-error --- input is not match builder.use(({ next }, input: 'invalid') => next({})) // @ts-expect-error --- output is not match @@ -148,8 +148,8 @@ describe('DecoratedProcedure', () => { expectTypeOf(applied).toEqualTypeOf< DecoratedProcedure< - InitialContext, - CurrentContext & { extra: boolean }, + InitialContext & Record, + Omit & { extra: boolean }, typeof inputSchema, typeof outputSchema, typeof baseErrorMap, @@ -157,8 +157,8 @@ describe('DecoratedProcedure', () => { > >() - // @ts-expect-error --- conflict context - builder.use(({ next }) => ({ context: { db: 123 } }), () => {}) + // invalid TInContext + expectTypeOf(builder.use({} as Middleware<{ auth: 'invalid' }, any, any, any, any, any>, () => { })).toEqualTypeOf() // @ts-expect-error --- input is not match builder.use(({ next }, input: 'invalid') => next({}), () => {}) // @ts-expect-error --- output is not match @@ -166,6 +166,32 @@ describe('DecoratedProcedure', () => { // conflict context but not detected expectTypeOf(builder.use(({ next }) => next({ context: { db: undefined } }), () => {})).toEqualTypeOf() }) + + it('with TInContext', () => { + const mid = {} as Middleware<{ cacheable?: boolean }, Record, unknown, any, ORPCErrorConstructorMap, BaseMeta> + + expectTypeOf(builder.use(mid)).toEqualTypeOf< + DecoratedProcedure< + InitialContext & { cacheable?: boolean }, + Omit & Record, + typeof inputSchema, + typeof outputSchema, + typeof baseErrorMap, + BaseMeta + > + >() + + expectTypeOf(builder.use(mid, () => { })).toEqualTypeOf< + DecoratedProcedure< + InitialContext & { cacheable?: boolean }, + Omit & Record, + typeof inputSchema, + typeof outputSchema, + typeof baseErrorMap, + BaseMeta + > + >() + }) }) it('.callable', () => { diff --git a/packages/server/src/procedure-decorated.ts b/packages/server/src/procedure-decorated.ts index a0cf177f5..43f96e24d 100644 --- a/packages/server/src/procedure-decorated.ts +++ b/packages/server/src/procedure-decorated.ts @@ -1,7 +1,7 @@ import type { ClientContext, ClientRest } from '@orpc/client' import type { AnySchema, ErrorMap, InferSchemaInput, InferSchemaOutput, MergedErrorMap, Meta, Route } from '@orpc/contract' import type { MaybeOptionalOptions } from '@orpc/shared' -import type { ConflictContextGuard, Context, MergedContext } from './context' +import type { Context, ContextExtendsGuard, MergedCurrentContext, MergedInitialContext } from './context' import type { ORPCErrorConstructorMap } from './error' import type { AnyMiddleware, MapInputMiddleware, Middleware } from './middleware' import type { CreateProcedureClientOptions, ProcedureClient } from './procedure-client' @@ -53,28 +53,29 @@ export class DecoratedProcedure< }) } - use( + use( middleware: Middleware< - TCurrentContext, - U, + UInContext, + UOutContext, InferSchemaOutput, InferSchemaInput, ORPCErrorConstructorMap, TMeta >, - ): ConflictContextGuard> + ): ContextExtendsGuard + & ContextExtendsGuard, TCurrentContext> & DecoratedProcedure< - TInitialContext, - MergedContext, + MergedInitialContext, + MergedCurrentContext, TInputSchema, TOutputSchema, TErrorMap, TMeta > - use( + use( middleware: Middleware< - TCurrentContext, + UInContext, UOutContext, UInput, InferSchemaInput, @@ -82,10 +83,11 @@ export class DecoratedProcedure< TMeta >, mapInput: MapInputMiddleware, UInput>, - ): ConflictContextGuard> + ): ContextExtendsGuard + & ContextExtendsGuard, TCurrentContext> & DecoratedProcedure< - TInitialContext, - MergedContext, + MergedInitialContext, + MergedCurrentContext, TInputSchema, TOutputSchema, TErrorMap, diff --git a/packages/server/src/router-utils.test-d.ts b/packages/server/src/router-utils.test-d.ts index e947f7338..48ac7a7fb 100644 --- a/packages/server/src/router-utils.test-d.ts +++ b/packages/server/src/router-utils.test-d.ts @@ -1,7 +1,7 @@ import type { MergedErrorMap, Meta, Schema } from '@orpc/contract' import type { baseErrorMap, BaseMeta, inputSchema, outputSchema } from '../../contract/tests/shared' import type { CurrentContext, InitialContext, router } from '../tests/shared' -import type { Context } from './context' +import type { Context, MergedInitialContext } from './context' import type { Lazy } from './lazy' import type { Procedure } from './procedure' import type { AccessibleLazyRouter, EnhancedRouter, UnlaziedRouter } from './router-utils' @@ -18,58 +18,60 @@ it('AccessibleLazyRouter', () => { }) it('EnhancedRouter', () => { - type TErrorMap = { INVALID: { message: string }, OVERRIDE: { message: string } } - type Enhanced = EnhancedRouter + type TErrorMap = { INVALID: { message: string }, OVERRIDE: { message: string } } + type InitialContext2 = { auth?: boolean, user?: string } + type CurrentContext2 = { auth?: boolean, user?: string, db?: string, extra?: string } + type Enhanced = EnhancedRouter - expectTypeOf().toEqualTypeOf< - Lazy< - Procedure< - InitialContext, - CurrentContext, - typeof inputSchema, - typeof outputSchema, - MergedErrorMap, - BaseMeta - > + expectTypeOf().toEqualTypeOf< + Lazy< + Procedure< + MergedInitialContext, + CurrentContext, + typeof inputSchema, + typeof outputSchema, + MergedErrorMap, + BaseMeta > - >() + > + >() - expectTypeOf().toEqualTypeOf< - Lazy< - Procedure< - InitialContext, - CurrentContext, - typeof inputSchema, - typeof outputSchema, - MergedErrorMap, - BaseMeta - > + expectTypeOf().toEqualTypeOf< + Lazy< + Procedure< + MergedInitialContext, + CurrentContext, + typeof inputSchema, + typeof outputSchema, + MergedErrorMap, + BaseMeta > - >() + > + >() + + expectTypeOf().toEqualTypeOf< + Procedure< + MergedInitialContext, + Context, + Schema, + Schema, + MergedErrorMap>, + Meta + > + >() - expectTypeOf().toEqualTypeOf< + expectTypeOf().toEqualTypeOf< + Lazy< Procedure< - InitialContext, + MergedInitialContext, Context, Schema, Schema, MergedErrorMap>, Meta > - >() - - expectTypeOf().toEqualTypeOf< - Lazy< - Procedure< - InitialContext, - Context, - Schema, - Schema, - MergedErrorMap>, - Meta - > - > - >() + > + >() }) it('UnlaziedRouter', () => { diff --git a/packages/server/src/router-utils.ts b/packages/server/src/router-utils.ts index 1a9922db9..574ec3beb 100644 --- a/packages/server/src/router-utils.ts +++ b/packages/server/src/router-utils.ts @@ -1,5 +1,5 @@ import type { AnyContractProcedure, AnyContractRouter, EnhanceRouteOptions, ErrorMap, MergedErrorMap } from '@orpc/contract' -import type { Context } from './context' +import type { Context, MergedInitialContext } from './context' import type { AnyMiddleware } from './middleware' import type { AnyRouter } from './router' import { enhanceRoute, isContractProcedure, mergeErrorMap, mergePrefix } from '@orpc/contract' @@ -71,11 +71,16 @@ export function createAccessibleLazyRouter return recursive as any } -export type EnhancedRouter, TInitialContext extends Context, TErrorMap extends ErrorMap> = +export type EnhancedRouter< + T extends Lazyable, + TInitialContext extends Context, + TCurrentContext extends Context, + TErrorMap extends ErrorMap, +> = T extends Lazy - ? AccessibleLazyRouter> + ? AccessibleLazyRouter> : T extends Procedure< - any, + infer UInitialContext, infer UCurrentContext, infer UInputSchema, infer UOutputSchema, @@ -83,7 +88,7 @@ export type EnhancedRouter, TInitialContext extend infer UMeta > ? Procedure< - TInitialContext, + MergedInitialContext, UCurrentContext, UInputSchema, UOutputSchema, @@ -91,7 +96,7 @@ export type EnhancedRouter, TInitialContext extend UMeta > : { - [K in keyof T]: T[K] extends Lazyable ? EnhancedRouter : never + [K in keyof T]: T[K] extends Lazyable ? EnhancedRouter : never } export interface EnhanceRouterOptions extends EnhanceRouteOptions { @@ -99,10 +104,15 @@ export interface EnhanceRouterOptions extends Enhanc errorMap: TErrorMap } -export function enhanceRouter, TInitialContext extends Context, TErrorMap extends ErrorMap>( +export function enhanceRouter< + T extends Lazyable, + TInitialContext extends Context, + TCurrentContext extends Context, + TErrorMap extends ErrorMap, +>( router: T, options: EnhanceRouterOptions, -): EnhancedRouter { +): EnhancedRouter { if (isLazy(router)) { const laziedMeta = getLazyMeta(router) const enhancedPrefix = laziedMeta?.prefix ? mergePrefix(options.prefix, laziedMeta?.prefix) : options.prefix