Skip to content

Commit

Permalink
type tests
Browse files Browse the repository at this point in the history
  • Loading branch information
unnoq committed Jan 30, 2025
1 parent 4e20085 commit 9f99892
Show file tree
Hide file tree
Showing 6 changed files with 467 additions and 18 deletions.
20 changes: 11 additions & 9 deletions packages/server/src/builder-variants.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1031,15 +1031,17 @@ describe('RouterBuilder', () => {

// @ts-expect-error - meta def is not match
builder.lazy(() => Promise.resolve({
ping: {} as Procedure<
Context,
Context,
undefined,
undefined,
unknown,
Record<never, never>,
{ invalid: true }
>,
default: {
ping: {} as Procedure<
Context,
Context,
undefined,
undefined,
unknown,
Record<never, never>,
{ invalid: true }
>,
},
}))
})
})
20 changes: 11 additions & 9 deletions packages/server/src/builder.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -409,15 +409,17 @@ describe('Builder', () => {

// @ts-expect-error - meta def is not match
builder.lazy(() => Promise.resolve({
ping: {} as Procedure<
Context,
Context,
undefined,
undefined,
unknown,
Record<never, never>,
{ invalid: true }
>,
default: {
ping: {} as Procedure<
Context,
Context,
undefined,
undefined,
unknown,
Record<never, never>,
{ invalid: true }
>,
},
}))
})
})
11 changes: 11 additions & 0 deletions packages/server/src/implementer-variants.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { router } from '../../contract/tests/helpers'
import type { InitialContext } from '../tests/shared'
import type { ImplementerInternal } from './implementer'
import type { ImplementerInternalWithMiddlewares } from './implementer-variants'

describe('ImplementerWithMiddlewares', () => {
it('backwards compatibility with Implementer', () => {
const implementer: ImplementerInternalWithMiddlewares<typeof router, InitialContext, InitialContext>
= {} as ImplementerInternal<typeof router, InitialContext>
})
})
39 changes: 39 additions & 0 deletions packages/server/src/implementer-variants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { AnyContractRouter, ContractProcedure, ContractRouter, ContractRouterToErrorMap, ORPCErrorConstructorMap } from '@orpc/contract'
import type { ConflictContextGuard, Context, MergedContext } from './context'
import type { ProcedureImplementer } from './implementer-procedure'
import type { FlattenLazy } from './lazy-utils'
import type { Middleware } from './middleware'
import type { AdaptedRouter, Router } from './router'

export type ImplementerInternalWithMiddlewares<
TContract extends AnyContractRouter,
TInitialContext extends Context,
TCurrentContext extends Context,
> =
&(
TContract extends ContractProcedure<infer UInputSchema, infer UOutputSchema, infer UErrorMap, infer UMeta>
? ProcedureImplementer<TInitialContext, TCurrentContext, UInputSchema, UOutputSchema, UErrorMap, UMeta>
: TContract extends ContractRouter<infer UMeta> ? {
use<U extends Context>(
middleware: Middleware<
TInitialContext,
U,
unknown,
unknown,
ORPCErrorConstructorMap<ContractRouterToErrorMap<TContract>>,
UMeta
>,
): ConflictContextGuard<MergedContext<TCurrentContext, U>>
& ImplementerInternalWithMiddlewares<TContract, TInitialContext, MergedContext<TCurrentContext, U>>

router<U extends Router<TCurrentContext, TContract>>(router: U): AdaptedRouter<U, TInitialContext, Record<never, never>>

lazy<U extends Router<TInitialContext, TContract>>(
loader: () => Promise<{ default: U }>
): AdaptedRouter<FlattenLazy<U>, TInitialContext, Record<never, never>>
} & {
[K in keyof TContract]: TContract[K] extends AnyContractRouter
? ImplementerInternalWithMiddlewares<TContract[K], TInitialContext, TCurrentContext>
: never
} : never
)
212 changes: 212 additions & 0 deletions packages/server/src/implementer.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
import type { ErrorMap, Meta, ORPCErrorConstructorMap, 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 { 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 { DecoratedMiddleware } from './middleware-decorated'
import type { Procedure } from './procedure'
import type { AdaptedRouter } from './router'
import { router as implRouter } from '../tests/shared'

describe('Implementer', () => {
const implementer = {} as Implementer<typeof router, InitialContext, CurrentContext>

describe('root level', () => {
it('.$context', () => {
const applied = implementer.$context<{ anything: string }>()

expectTypeOf(applied).toMatchTypeOf<
Implementer<typeof router, { anything: string }, { anything: string }>
>()
})

it('.$config', () => {
const applied = implementer.$config({
initialInputValidationIndex: Number.NEGATIVE_INFINITY,
})

expectTypeOf(applied).toMatchTypeOf<
Implementer<typeof router, InitialContext, CurrentContext>
>()
})
})

it('each procedure is a ProcedureImplementer', () => {
type ExpectedPing = ProcedureImplementer<
InitialContext,
CurrentContext,
typeof inputSchema,
typeof outputSchema,
typeof baseErrorMap,
BaseMeta
>

type ExpectedPong = ProcedureImplementer<
InitialContext,
CurrentContext,
undefined,
undefined,
Record<never, never>,
Meta
>

expectTypeOf(implementer.ping).toEqualTypeOf<ExpectedPing>()
expectTypeOf(implementer.nested.ping).toEqualTypeOf<ExpectedPing>()
expectTypeOf(implementer.pong).toEqualTypeOf<ExpectedPong>()
expectTypeOf(implementer.nested.pong).toEqualTypeOf<ExpectedPong>()
})

describe('router level', () => {
it('.middleware', () => {
it('works', () => {
const mid = implementer.nested.middleware(({ context, next, path, procedure, errors, signal }, input, output) => {
expectTypeOf(input).toEqualTypeOf<unknown>()
expectTypeOf(context).toEqualTypeOf<CurrentContext>()
expectTypeOf(path).toEqualTypeOf<string[]>()
expectTypeOf(procedure).toEqualTypeOf<
Procedure<Context, Context, Schema, Schema, unknown, ErrorMap, BaseMeta | Meta>
>()
expectTypeOf(output).toEqualTypeOf<MiddlewareOutputFn<any>>()
expectTypeOf(errors).toEqualTypeOf<ORPCErrorConstructorMap<typeof baseErrorMap | Record<never, never>>>()
expectTypeOf(signal).toEqualTypeOf<undefined | InstanceType<typeof AbortSignal>>()

return next({
context: {
extra: true,
},
})
})

expectTypeOf(mid).toMatchTypeOf<
DecoratedMiddleware<
CurrentContext,
{ extra: boolean },
unknown,
any,
ORPCErrorConstructorMap<any>,
Meta | BaseMeta
>
>()

// @ts-expect-error --- conflict context
implementer.middleware(({ next }) => next({ db: 123 }))
})

it('can type input and output', () => {
expectTypeOf(
implementer.middleware(({ next }, input: 'input', output: MiddlewareOutputFn<'output'>) => next()),
).toEqualTypeOf<
DecoratedMiddleware<
CurrentContext,
Record<never, never>,
'input',
'output',
ORPCErrorConstructorMap<any>,
Meta | BaseMeta
>
>()
})
})

it('.use', () => {
const applied = implementer.nested.use(({ context, next, path, procedure, errors, signal }, input, output) => {
expectTypeOf(input).toEqualTypeOf<unknown>()
expectTypeOf(context).toEqualTypeOf<CurrentContext>()
expectTypeOf(path).toEqualTypeOf<string[]>()
expectTypeOf(procedure).toEqualTypeOf<
Procedure<Context, Context, Schema, Schema, unknown, ErrorMap, BaseMeta | Meta>
>()
expectTypeOf(output).toEqualTypeOf<MiddlewareOutputFn<unknown>>()
expectTypeOf(errors).toEqualTypeOf<ORPCErrorConstructorMap<typeof baseErrorMap | Record<never, never>>>()
expectTypeOf(signal).toEqualTypeOf<undefined | InstanceType<typeof AbortSignal>>()

return next({
context: {
extra: true,
},
})
})

expectTypeOf(applied).toMatchTypeOf<
ImplementerInternalWithMiddlewares<typeof router['nested'], InitialContext, MergedContext<CurrentContext, { extra: boolean }>>
>()

// @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<never>()
// @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('.router', () => {
expectTypeOf(implementer.router(implRouter)).toEqualTypeOf<
AdaptedRouter<typeof implRouter, InitialContext, Record<never, never>>
>()

implementer.router({
// @ts-expect-error - initial context is not match
ping: {} as Procedure<{ invalid: true }, Context, undefined, undefined, unknown, Record<never, never>, BaseMeta>,
})

implementer.router({
// @ts-expect-error - meta def is not match
ping: {} as Procedure<
Context,
Context,
undefined,
undefined,
unknown,
Record<never, never>,
{ invalid: true }
>,
})

// @ts-expect-error - missing implementation
implementer.router({
ping: implRouter.ping,
})
})

it('.lazy', () => {
expectTypeOf(implementer.lazy(() => Promise.resolve({ default: implRouter }))).toEqualTypeOf<
AdaptedRouter<Lazy<typeof implRouter>, InitialContext, Record<never, never>>
>()

// @ts-expect-error - initial context is not match
implementer.lazy(() => Promise.resolve({
default: {
ping: {} as Procedure<{ invalid: true }, Context, undefined, undefined, unknown, Record<never, never>, BaseMeta>,
},
}))

// @ts-expect-error - meta def is not match
implementer.lazy(() => Promise.resolve({
default: {
ping: {} as Procedure<
Context,
Context,
undefined,
undefined,
unknown,
Record<never, never>,
{ invalid: true }
>,
},
}))

// @ts-expect-error - missing implementation
implementer.lazy(() => Promise.resolve({
default: {
ping: implRouter.ping,
},
}))
})
})
})
Loading

0 comments on commit 9f99892

Please sign in to comment.