From 63fbe9ee6d45afc7c51963bb54c09732d8893a69 Mon Sep 17 00:00:00 2001 From: unnoq Date: Thu, 13 Mar 2025 16:08:20 +0700 Subject: [PATCH 1/5] wip --- packages/server/src/builder-variants.ts | 85 +++++++++++--------- packages/server/src/builder.ts | 24 +++--- packages/server/src/context.ts | 20 ++--- packages/server/src/implementer-procedure.ts | 48 +++++------ packages/server/src/implementer-variants.ts | 16 ++-- packages/server/src/implementer.ts | 16 ++-- packages/server/src/middleware-decorated.ts | 43 +++++----- packages/server/src/middleware.ts | 6 +- packages/server/src/procedure-decorated.ts | 26 +++--- 9 files changed, 153 insertions(+), 131 deletions(-) diff --git a/packages/server/src/builder-variants.ts b/packages/server/src/builder-variants.ts index 9b1e72e9c..563a33918 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, @@ -103,7 +103,7 @@ export interface ProcedureBuilder< TMeta > - 'use'( + 'use'( middleware: Middleware< TCurrentContext, UOutContext, @@ -112,10 +112,10 @@ export interface ProcedureBuilder< ORPCErrorConstructorMap, TMeta >, - ): ConflictContextGuard> & - ProcedureBuilder< - TInitialContext, - MergedContext, + ): ContextExtendsGuard + & ProcedureBuilder< + MergedInitialContext, + MergedCurrentContext, TInputSchema, TOutputSchema, TErrorMap, @@ -157,7 +157,7 @@ export interface ProcedureBuilderWithInput< errors: U, ): ProcedureBuilderWithInput, TMeta> - 'use'( + 'use'( middleware: Middleware< TCurrentContext, UOutContext, @@ -166,17 +166,17 @@ export interface ProcedureBuilderWithInput< ORPCErrorConstructorMap, TMeta >, - ): ConflictContextGuard> & - ProcedureBuilderWithInput< - TInitialContext, - MergedContext, + ): ContextExtendsGuard + & ProcedureBuilderWithInput< + MergedInitialContext, + MergedCurrentContext, TInputSchema, TOutputSchema, TErrorMap, TMeta > - 'use'( + 'use'( middleware: Middleware< TCurrentContext, UOutContext, @@ -186,10 +186,10 @@ export interface ProcedureBuilderWithInput< TMeta >, mapInput: MapInputMiddleware, UInput>, - ): ConflictContextGuard> & - ProcedureBuilderWithInput< - TInitialContext, - MergedContext, + ): ContextExtendsGuard + & ProcedureBuilderWithInput< + MergedInitialContext, + MergedCurrentContext, TInputSchema, TOutputSchema, TErrorMap, @@ -234,7 +234,7 @@ export interface ProcedureBuilderWithOutput< TMeta > - 'use'( + 'use'( middleware: Middleware< TCurrentContext, UOutContext, @@ -243,10 +243,10 @@ export interface ProcedureBuilderWithOutput< ORPCErrorConstructorMap, TMeta >, - ): ConflictContextGuard> & - ProcedureBuilderWithOutput< - TInitialContext, - MergedContext, + ): ContextExtendsGuard + & ProcedureBuilderWithOutput< + MergedInitialContext, + MergedCurrentContext, TInputSchema, TOutputSchema, TErrorMap, @@ -284,7 +284,7 @@ export interface ProcedureBuilderWithInputOutput< errors: U, ): ProcedureBuilderWithInputOutput, TMeta> - 'use'( + 'use'( middleware: Middleware< TCurrentContext, UOutContext, @@ -293,17 +293,17 @@ export interface ProcedureBuilderWithInputOutput< ORPCErrorConstructorMap, TMeta >, - ): ConflictContextGuard> & - ProcedureBuilderWithInputOutput< - TInitialContext, - MergedContext, + ): ContextExtendsGuard + & ProcedureBuilderWithInputOutput< + MergedInitialContext, + MergedCurrentContext, TInputSchema, TOutputSchema, TErrorMap, TMeta > - 'use'( + 'use'( middleware: Middleware< TCurrentContext, UOutContext, @@ -313,10 +313,10 @@ export interface ProcedureBuilderWithInputOutput< TMeta >, mapInput: MapInputMiddleware, UInput>, - ): ConflictContextGuard> & - ProcedureBuilderWithInputOutput< - TInitialContext, - MergedContext, + ): ContextExtendsGuard + & ProcedureBuilderWithInputOutput< + MergedInitialContext, + MergedCurrentContext, TInputSchema, TOutputSchema, TErrorMap, @@ -348,7 +348,7 @@ export interface RouterBuilder< errors: U, ): RouterBuilder, TMeta> - 'use'( + 'use'( middleware: Middleware< TCurrentContext, UOutContext, @@ -357,8 +357,13 @@ export interface RouterBuilder< ORPCErrorConstructorMap, TMeta >, - ): ConflictContextGuard> & - RouterBuilder, TErrorMap, TMeta> + ): ContextExtendsGuard + & RouterBuilder< + MergedInitialContext, + MergedCurrentContext, + TErrorMap, + TMeta + > 'prefix'(prefix: HTTPPath): RouterBuilder diff --git a/packages/server/src/builder.ts b/packages/server/src/builder.ts index 8851804a9..2bd26e8f1 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,26 +122,26 @@ 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, UOutContext, @@ -151,10 +151,10 @@ export class Builder< TMeta >, mapInput: MapInputMiddleware, - ): ConflictContextGuard> & - BuilderWithMiddlewares< - TInitialContext, - MergedContext, + ): ContextExtendsGuard + & BuilderWithMiddlewares< + MergedInitialContext, + MergedCurrentContext, TInputSchema, TOutputSchema, TErrorMap, diff --git a/packages/server/src/context.ts b/packages/server/src/context.ts index 7bdb83a79..117908919 100644 --- a/packages/server/src/context.ts +++ b/packages/server/src/context.ts @@ -1,17 +1,19 @@ -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 = + ((c: T & U) => any) extends ((c: U) => any) ? unknown : never diff --git a/packages/server/src/implementer-procedure.ts b/packages/server/src/implementer-procedure.ts index 7cb13821f..792c99970 100644 --- a/packages/server/src/implementer-procedure.ts +++ b/packages/server/src/implementer-procedure.ts @@ -2,7 +2,7 @@ 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' @@ -20,28 +20,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> + ): ContextExtendsGuard + & ContextExtendsGuard> & DecoratedProcedure< - TInitialContext, - MergedContext, + MergedInitialContext, + MergedCurrentContext, TInputSchema, TOutputSchema, TErrorMap, TMeta > - use( + use( middleware: Middleware< - TCurrentContext, + UInContext, UOutContext, UInput, InferSchemaInput, @@ -49,10 +50,11 @@ export interface ImplementedProcedure< TMeta >, mapInput: MapInputMiddleware, UInput>, - ): ConflictContextGuard> + ): ContextExtendsGuard + & ContextExtendsGuard> & DecoratedProcedure< - TInitialContext, - MergedContext, + MergedInitialContext, + MergedCurrentContext, TInputSchema, TOutputSchema, TErrorMap, @@ -107,28 +109,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 +138,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.ts b/packages/server/src/implementer-variants.ts index ce0719d50..f62621dd8 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,17 +12,21 @@ 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> diff --git a/packages/server/src/implementer.ts b/packages/server/src/implementer.ts index 9df5e63d4..6d3cfc1ca 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,17 +32,21 @@ 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> diff --git a/packages/server/src/middleware-decorated.ts b/packages/server/src/middleware-decorated.ts index 3a68a5e5c..233c11f87 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> + & 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> + & DecoratedMiddleware< + MergedInitialContext, + MergedCurrentContext, + TInput, + TOutput, + TErrorConstructorMap, + TMeta + > } export function decorateMiddleware< 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-decorated.ts b/packages/server/src/procedure-decorated.ts index a0cf177f5..9cd9e4f1b 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> & 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> & DecoratedProcedure< - TInitialContext, - MergedContext, + MergedInitialContext, + MergedCurrentContext, TInputSchema, TOutputSchema, TErrorMap, From 4766efba4b157b8ca2666e2883d0088d01c635fe Mon Sep 17 00:00:00 2001 From: unnoq Date: Thu, 13 Mar 2025 20:50:42 +0700 Subject: [PATCH 2/5] wip --- .../server/src/builder-variants.test-d.ts | 424 +++++++++++------- packages/server/src/builder-variants.ts | 14 +- packages/server/src/builder.test-d.ts | 48 +- packages/server/src/builder.ts | 2 +- packages/server/src/context.test-d.ts | 32 +- packages/server/src/context.test.ts | 8 +- .../src/implementer-procedure.test-d.ts | 112 +++-- packages/server/src/implementer-procedure.ts | 5 +- .../server/src/implementer-variants.test-d.ts | 78 ++-- packages/server/src/implementer.test-d.ts | 74 +-- .../server/src/middleware-decorated.test-d.ts | 48 +- packages/server/src/middleware-decorated.ts | 2 +- packages/server/src/middleware.test-d.ts | 2 +- .../server/src/procedure-decorated.test-d.ts | 44 +- 14 files changed, 580 insertions(+), 313 deletions(-) diff --git a/packages/server/src/builder-variants.test-d.ts b/packages/server/src/builder-variants.test-d.ts index b549ae6c3..24e33b6cd 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 > >() @@ -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', () => { diff --git a/packages/server/src/builder-variants.ts b/packages/server/src/builder-variants.ts index 563a33918..3029a01e5 100644 --- a/packages/server/src/builder-variants.ts +++ b/packages/server/src/builder-variants.ts @@ -105,7 +105,7 @@ export interface ProcedureBuilder< 'use'( middleware: Middleware< - TCurrentContext, + UInContext, UOutContext, unknown, unknown, @@ -159,7 +159,7 @@ export interface ProcedureBuilderWithInput< 'use'( middleware: Middleware< - TCurrentContext, + UInContext, UOutContext, InferSchemaOutput, unknown, @@ -178,7 +178,7 @@ export interface ProcedureBuilderWithInput< 'use'( middleware: Middleware< - TCurrentContext, + UInContext, UOutContext, UInput, unknown, @@ -236,7 +236,7 @@ export interface ProcedureBuilderWithOutput< 'use'( middleware: Middleware< - TCurrentContext, + UInContext, UOutContext, unknown, InferSchemaInput, @@ -286,7 +286,7 @@ export interface ProcedureBuilderWithInputOutput< 'use'( middleware: Middleware< - TCurrentContext, + UInContext, UOutContext, InferSchemaOutput, InferSchemaInput, @@ -305,7 +305,7 @@ export interface ProcedureBuilderWithInputOutput< 'use'( middleware: Middleware< - TCurrentContext, + UInContext, UOutContext, UInput, InferSchemaInput, @@ -350,7 +350,7 @@ export interface RouterBuilder< 'use'( middleware: Middleware< - TCurrentContext, + UInContext, UOutContext, unknown, unknown, diff --git a/packages/server/src/builder.test-d.ts b/packages/server/src/builder.test-d.ts index 70728a104..c973f80b7 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', () => { diff --git a/packages/server/src/builder.ts b/packages/server/src/builder.ts index 2bd26e8f1..c299f84a6 100644 --- a/packages/server/src/builder.ts +++ b/packages/server/src/builder.ts @@ -143,7 +143,7 @@ export class Builder< use( middleware: Middleware< - TCurrentContext, + UInContext, UOutContext, UInput, unknown, diff --git a/packages/server/src/context.test-d.ts b/packages/server/src/context.test-d.ts index d5ed70d5c..c6985f6ce 100644 --- a/packages/server/src/context.test-d.ts +++ b/packages/server/src/context.test-d.ts @@ -1,12 +1,28 @@ -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>().toEqualTypeOf() + expectTypeOf>().toEqualTypeOf() + expectTypeOf>().toEqualTypeOf() + expectTypeOf, { a: string, b: string }>>().toEqualTypeOf() + expectTypeOf>().toEqualTypeOf() + + expectTypeOf>().toEqualTypeOf() + expectTypeOf>().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/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 792c99970..f3e6ac421 100644 --- a/packages/server/src/implementer-procedure.ts +++ b/packages/server/src/implementer-procedure.ts @@ -7,7 +7,6 @@ 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. @@ -31,7 +30,7 @@ export interface ImplementedProcedure< >, ): ContextExtendsGuard & ContextExtendsGuard> - & DecoratedProcedure< + & ImplementedProcedure< MergedInitialContext, MergedCurrentContext, TInputSchema, @@ -52,7 +51,7 @@ export interface ImplementedProcedure< mapInput: MapInputMiddleware, UInput>, ): ContextExtendsGuard & ContextExtendsGuard> - & DecoratedProcedure< + & ImplementedProcedure< MergedInitialContext, MergedCurrentContext, TInputSchema, diff --git a/packages/server/src/implementer-variants.test-d.ts b/packages/server/src/implementer-variants.test-d.ts index 1599cbfb6..f2f3c4aab 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,41 +19,53 @@ 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', () => { diff --git a/packages/server/src/implementer.test-d.ts b/packages/server/src/implementer.test-d.ts index 6704cad3d..9ba251ec2 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,37 +88,53 @@ 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', () => { 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 233c11f87..f398bc281 100644 --- a/packages/server/src/middleware-decorated.ts +++ b/packages/server/src/middleware-decorated.ts @@ -50,7 +50,7 @@ export interface DecoratedMiddleware< mapInput: MapInputMiddleware, ): ContextExtendsGuard> & DecoratedMiddleware< - MergedInitialContext, + MergedInitialContext>, MergedCurrentContext, TInput, TOutput, 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/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', () => { From 5457950b269a733e6ce6b23ddde2d1864b09da81 Mon Sep 17 00:00:00 2001 From: unnoq Date: Thu, 13 Mar 2025 21:03:52 +0700 Subject: [PATCH 3/5] wip --- .../server/src/builder-variants.test-d.ts | 8 +- packages/server/src/builder-variants.ts | 8 +- packages/server/src/builder.test-d.ts | 4 +- packages/server/src/builder.ts | 6 +- packages/server/src/router-utils.test-d.ts | 84 ++++++++++--------- packages/server/src/router-utils.ts | 26 ++++-- 6 files changed, 74 insertions(+), 62 deletions(-) diff --git a/packages/server/src/builder-variants.test-d.ts b/packages/server/src/builder-variants.test-d.ts index 24e33b6cd..3109973e4 100644 --- a/packages/server/src/builder-variants.test-d.ts +++ b/packages/server/src/builder-variants.test-d.ts @@ -231,7 +231,7 @@ describe('BuilderWithMiddlewares', () => { it('.router', () => { expectTypeOf(builder.router(router)).toEqualTypeOf< - EnhancedRouter + EnhancedRouter >() builder.router({ @@ -255,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 @@ -1203,7 +1203,7 @@ describe('RouterBuilder', () => { it('.router', () => { expectTypeOf(builder.router(router)).toEqualTypeOf< - EnhancedRouter + EnhancedRouter >() builder.router({ @@ -1227,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 3029a01e5..eb8615357 100644 --- a/packages/server/src/builder-variants.ts +++ b/packages/server/src/builder-variants.ts @@ -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< @@ -371,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 c973f80b7..042f22b34 100644 --- a/packages/server/src/builder.test-d.ts +++ b/packages/server/src/builder.test-d.ts @@ -432,7 +432,7 @@ describe('Builder', () => { it('.router', () => { expectTypeOf(builder.router(router)).toEqualTypeOf< - EnhancedRouter + EnhancedRouter >() builder.router({ @@ -456,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 c299f84a6..c24d2cbae 100644 --- a/packages/server/src/builder.ts +++ b/packages/server/src/builder.ts @@ -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/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 From dab7d8f478cf842611beeb2e4b6ff07a78312c74 Mon Sep 17 00:00:00 2001 From: unnoq Date: Thu, 13 Mar 2025 21:32:09 +0700 Subject: [PATCH 4/5] fix types --- packages/server/src/implementer-variants.test-d.ts | 4 ++-- packages/server/src/implementer-variants.ts | 4 ++-- packages/server/src/implementer.test-d.ts | 4 ++-- packages/server/src/implementer.ts | 4 ++-- packages/server/src/procedure-client.ts | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/server/src/implementer-variants.test-d.ts b/packages/server/src/implementer-variants.test-d.ts index f2f3c4aab..9e317fbd9 100644 --- a/packages/server/src/implementer-variants.test-d.ts +++ b/packages/server/src/implementer-variants.test-d.ts @@ -70,7 +70,7 @@ describe('ImplementerWithMiddlewares', () => { it('.router', () => { expectTypeOf(implementer.router(implRouter)).toEqualTypeOf< - EnhancedRouter> + EnhancedRouter < typeof implRouter, InitialContext, CurrentContext, Record> >() implementer.router({ @@ -105,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 f62621dd8..7a9ba0376 100644 --- a/packages/server/src/implementer-variants.ts +++ b/packages/server/src/implementer-variants.ts @@ -29,11 +29,11 @@ export interface RouterImplementerWithMiddlewares< > 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 9ba251ec2..8c2c547ad 100644 --- a/packages/server/src/implementer.test-d.ts +++ b/packages/server/src/implementer.test-d.ts @@ -139,7 +139,7 @@ describe('Implementer', () => { it('.router', () => { expectTypeOf(implementer.router(implRouter)).toEqualTypeOf< - EnhancedRouter> + EnhancedRouter < typeof implRouter, InitialContext, CurrentContext, Record> >() implementer.router({ @@ -174,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 6d3cfc1ca..a0abdaa7f 100644 --- a/packages/server/src/implementer.ts +++ b/packages/server/src/implementer.ts @@ -49,11 +49,11 @@ export interface RouterImplementer< > 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/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 } From 6cfcfd76b69cd1406085f8e7a5a178d47e220221 Mon Sep 17 00:00:00 2001 From: unnoq Date: Thu, 13 Mar 2025 21:58:19 +0700 Subject: [PATCH 5/5] improve --- packages/server/src/builder-variants.ts | 16 ++++++++-------- packages/server/src/builder.ts | 4 ++-- packages/server/src/context.test-d.ts | 17 ++++++++++------- packages/server/src/context.ts | 3 +-- packages/server/src/implementer-procedure.ts | 12 ++++++------ packages/server/src/implementer-variants.ts | 2 +- packages/server/src/implementer.ts | 2 +- packages/server/src/middleware-decorated.ts | 4 ++-- packages/server/src/procedure-decorated.ts | 8 ++++---- 9 files changed, 35 insertions(+), 33 deletions(-) diff --git a/packages/server/src/builder-variants.ts b/packages/server/src/builder-variants.ts index eb8615357..2ff4f45a2 100644 --- a/packages/server/src/builder-variants.ts +++ b/packages/server/src/builder-variants.ts @@ -39,7 +39,7 @@ export interface BuilderWithMiddlewares< ORPCErrorConstructorMap, TMeta >, - ): ContextExtendsGuard + ): ContextExtendsGuard & BuilderWithMiddlewares< MergedInitialContext, MergedCurrentContext, @@ -112,7 +112,7 @@ export interface ProcedureBuilder< ORPCErrorConstructorMap, TMeta >, - ): ContextExtendsGuard + ): ContextExtendsGuard & ProcedureBuilder< MergedInitialContext, MergedCurrentContext, @@ -166,7 +166,7 @@ export interface ProcedureBuilderWithInput< ORPCErrorConstructorMap, TMeta >, - ): ContextExtendsGuard + ): ContextExtendsGuard & ProcedureBuilderWithInput< MergedInitialContext, MergedCurrentContext, @@ -186,7 +186,7 @@ export interface ProcedureBuilderWithInput< TMeta >, mapInput: MapInputMiddleware, UInput>, - ): ContextExtendsGuard + ): ContextExtendsGuard & ProcedureBuilderWithInput< MergedInitialContext, MergedCurrentContext, @@ -243,7 +243,7 @@ export interface ProcedureBuilderWithOutput< ORPCErrorConstructorMap, TMeta >, - ): ContextExtendsGuard + ): ContextExtendsGuard & ProcedureBuilderWithOutput< MergedInitialContext, MergedCurrentContext, @@ -293,7 +293,7 @@ export interface ProcedureBuilderWithInputOutput< ORPCErrorConstructorMap, TMeta >, - ): ContextExtendsGuard + ): ContextExtendsGuard & ProcedureBuilderWithInputOutput< MergedInitialContext, MergedCurrentContext, @@ -313,7 +313,7 @@ export interface ProcedureBuilderWithInputOutput< TMeta >, mapInput: MapInputMiddleware, UInput>, - ): ContextExtendsGuard + ): ContextExtendsGuard & ProcedureBuilderWithInputOutput< MergedInitialContext, MergedCurrentContext, @@ -357,7 +357,7 @@ export interface RouterBuilder< ORPCErrorConstructorMap, TMeta >, - ): ContextExtendsGuard + ): ContextExtendsGuard & RouterBuilder< MergedInitialContext, MergedCurrentContext, diff --git a/packages/server/src/builder.ts b/packages/server/src/builder.ts index c24d2cbae..57403d438 100644 --- a/packages/server/src/builder.ts +++ b/packages/server/src/builder.ts @@ -131,7 +131,7 @@ export class Builder< ORPCErrorConstructorMap, TMeta >, - ): ContextExtendsGuard + ): ContextExtendsGuard & BuilderWithMiddlewares< MergedInitialContext, MergedCurrentContext, @@ -151,7 +151,7 @@ export class Builder< TMeta >, mapInput: MapInputMiddleware, - ): ContextExtendsGuard + ): ContextExtendsGuard & BuilderWithMiddlewares< MergedInitialContext, MergedCurrentContext, diff --git a/packages/server/src/context.test-d.ts b/packages/server/src/context.test-d.ts index c6985f6ce..24284d3ff 100644 --- a/packages/server/src/context.test-d.ts +++ b/packages/server/src/context.test-d.ts @@ -16,13 +16,16 @@ interface Empty { } it('ContextExtendsGuard', () => { - expectTypeOf>().toEqualTypeOf() + expectTypeOf < ContextExtendsGuard< { a: string, b: string }, { a: string }>>().toEqualTypeOf() expectTypeOf>().toEqualTypeOf() - expectTypeOf>().toEqualTypeOf() - expectTypeOf, { a: string, b: 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>().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.ts b/packages/server/src/context.ts index 117908919..3f444e4e1 100644 --- a/packages/server/src/context.ts +++ b/packages/server/src/context.ts @@ -15,5 +15,4 @@ export function mergeCurrentContext( return { ...context, ...other } } -export type ContextExtendsGuard = - ((c: T & U) => any) extends ((c: U) => any) ? unknown : never +export type ContextExtendsGuard = T extends T & U ? unknown : never diff --git a/packages/server/src/implementer-procedure.ts b/packages/server/src/implementer-procedure.ts index f3e6ac421..7344d1d53 100644 --- a/packages/server/src/implementer-procedure.ts +++ b/packages/server/src/implementer-procedure.ts @@ -28,8 +28,8 @@ export interface ImplementedProcedure< ORPCErrorConstructorMap, TMeta >, - ): ContextExtendsGuard - & ContextExtendsGuard> + ): ContextExtendsGuard + & ContextExtendsGuard, TCurrentContext> & ImplementedProcedure< MergedInitialContext, MergedCurrentContext, @@ -49,8 +49,8 @@ export interface ImplementedProcedure< TMeta >, mapInput: MapInputMiddleware, UInput>, - ): ContextExtendsGuard - & ContextExtendsGuard> + ): ContextExtendsGuard + & ContextExtendsGuard, TCurrentContext> & ImplementedProcedure< MergedInitialContext, MergedCurrentContext, @@ -117,7 +117,7 @@ export interface ProcedureImplementer< ORPCErrorConstructorMap, TMeta >, - ): ContextExtendsGuard + ): ContextExtendsGuard & ProcedureImplementer< MergedInitialContext, MergedCurrentContext, @@ -137,7 +137,7 @@ export interface ProcedureImplementer< TMeta >, mapInput: MapInputMiddleware, UInput>, - ): ContextExtendsGuard + ): ContextExtendsGuard & ProcedureImplementer< MergedInitialContext, MergedCurrentContext, diff --git a/packages/server/src/implementer-variants.ts b/packages/server/src/implementer-variants.ts index 7a9ba0376..201ed26bb 100644 --- a/packages/server/src/implementer-variants.ts +++ b/packages/server/src/implementer-variants.ts @@ -21,7 +21,7 @@ export interface RouterImplementerWithMiddlewares< ORPCErrorConstructorMap>, InferContractRouterMeta >, - ): ContextExtendsGuard + ): ContextExtendsGuard & ImplementerInternalWithMiddlewares< T, MergedInitialContext, diff --git a/packages/server/src/implementer.ts b/packages/server/src/implementer.ts index a0abdaa7f..3c6a3c956 100644 --- a/packages/server/src/implementer.ts +++ b/packages/server/src/implementer.ts @@ -41,7 +41,7 @@ export interface RouterImplementer< ORPCErrorConstructorMap>, InferContractRouterMeta >, - ): ContextExtendsGuard + ): ContextExtendsGuard & ImplementerInternalWithMiddlewares< T, MergedInitialContext, diff --git a/packages/server/src/middleware-decorated.ts b/packages/server/src/middleware-decorated.ts index f398bc281..3e8c612dc 100644 --- a/packages/server/src/middleware-decorated.ts +++ b/packages/server/src/middleware-decorated.ts @@ -24,7 +24,7 @@ export interface DecoratedMiddleware< TErrorConstructorMap, TMeta >, - ): ContextExtendsGuard> + ): ContextExtendsGuard, UInContext> & DecoratedMiddleware< MergedInitialContext>, MergedCurrentContext, @@ -48,7 +48,7 @@ export interface DecoratedMiddleware< TMeta >, mapInput: MapInputMiddleware, - ): ContextExtendsGuard> + ): ContextExtendsGuard, UInContext> & DecoratedMiddleware< MergedInitialContext>, MergedCurrentContext, diff --git a/packages/server/src/procedure-decorated.ts b/packages/server/src/procedure-decorated.ts index 9cd9e4f1b..43f96e24d 100644 --- a/packages/server/src/procedure-decorated.ts +++ b/packages/server/src/procedure-decorated.ts @@ -62,8 +62,8 @@ export class DecoratedProcedure< ORPCErrorConstructorMap, TMeta >, - ): ContextExtendsGuard - & ContextExtendsGuard> + ): ContextExtendsGuard + & ContextExtendsGuard, TCurrentContext> & DecoratedProcedure< MergedInitialContext, MergedCurrentContext, @@ -83,8 +83,8 @@ export class DecoratedProcedure< TMeta >, mapInput: MapInputMiddleware, UInput>, - ): ContextExtendsGuard - & ContextExtendsGuard> + ): ContextExtendsGuard + & ContextExtendsGuard, TCurrentContext> & DecoratedProcedure< MergedInitialContext, MergedCurrentContext,