Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions packages/effect/src/context.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,17 @@ describe('InferEffectServices', () => {
expectTypeOf<InferEffectServices<{ auth: boolean }>>().toEqualTypeOf<never>()
})

it('does not distribute over union contexts', () => {
expectTypeOf<InferEffectServices<WithEffectContext<Service1> | WithEffectContext<Service2>>>().toEqualTypeOf<never>()
expectTypeOf<InferEffectServices<WithEffectContext<Service1> | { auth: boolean }>>().toEqualTypeOf<never>()
expectTypeOf<InferEffectServices<WithEffectContext<Service1 | Service2> | WithEffectContext<Service1>>>().toEqualTypeOf<Service1>()
})

it('infers services from intersection and any contexts', () => {
expectTypeOf<InferEffectServices<WithEffectContext<Service1> & { auth: boolean }>>().toEqualTypeOf<Service1>()
expectTypeOf<InferEffectServices<any>>().toEqualTypeOf<unknown>()
})

it('accepts a context with more services than required', () => {
expectTypeOf<WithEffectContext<Service1 | Service2>>().toExtend<WithEffectContext<Service1>>()
})
Expand Down
3 changes: 2 additions & 1 deletion packages/effect/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@ export interface WithEffectContext<Services> {

/**
* Infers the Effect services available in an oRPC context that extends `WithEffectContext`.
* For a union context, only the services every member provides are inferred.
*
* @see {@link https://orpc.dev/docs/integrations/effect#effect-services | Effect Integration - Effect Services}
*/
export type InferEffectServices<T extends Context> = T extends WithEffectContext<infer Services> ? Services : never
export type InferEffectServices<T extends Context> = [T] extends [{ 'effect/context': EffectContext.Context<infer Services> }] ? Services : never
24 changes: 12 additions & 12 deletions packages/effect/src/extensions/effect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ declare module '@orpc/server' {
handler: HandlerGen<
TInitialContext,
InferSchemaOutput<InitialInputSchema>,
TYield,
TReturn,
ORPCErrorConstructorMap<TErrorMap>
ORPCErrorConstructorMap<TErrorMap>,
TYield
>,
): DecoratedProcedure<
TInitialContext,
Expand All @@ -43,9 +43,9 @@ declare module '@orpc/server' {
handler: HandlerGen<
MergedContext<TInitialContext, TInjectedContext>,
InferSchemaOutput<InitialInputSchema>,
TYield,
TReturn,
ORPCErrorConstructorMap<TErrorMap>
ORPCErrorConstructorMap<TErrorMap>,
TYield
>,
): DecoratedProcedure<
TInitialContext,
Expand All @@ -69,9 +69,9 @@ declare module '@orpc/server' {
handler: HandlerGen<
MergedContext<TInitialContext, TInjectedContext>,
InferSchemaOutput<TInputSchema>,
TYield,
TReturn,
ORPCErrorConstructorMap<TErrorMap>
ORPCErrorConstructorMap<TErrorMap>,
TYield
>,
): DecoratedProcedure<
TInitialContext,
Expand All @@ -95,9 +95,9 @@ declare module '@orpc/server' {
handler: HandlerGen<
MergedContext<TInitialContext, TInjectedContext>,
InferSchemaOutput<InitialInputSchema>,
TYield,
TReturn,
ORPCErrorConstructorMap<TErrorMap>
ORPCErrorConstructorMap<TErrorMap>,
TYield
>,
): DecoratedProcedure<
TInitialContext,
Expand All @@ -122,9 +122,9 @@ declare module '@orpc/server' {
handler: HandlerGen<
MergedContext<TInitialContext, TInjectedContext>,
InferSchemaOutput<TInputSchema>,
TYield,
TReturn,
ORPCErrorConstructorMap<TErrorMap>
ORPCErrorConstructorMap<TErrorMap>,
TYield
>,
): DecoratedProcedure<
TInitialContext,
Expand All @@ -146,9 +146,9 @@ declare module '@orpc/server' {
handler: HandlerGen<
MergedContext<TInitialContext, TInjectedContext>,
InferSchemaOutput<TInputSchema>,
Effect.Effect<any, any, InferEffectServices<MergedContext<TInitialContext, TInjectedContext>>>,
InferSchemaInput<TOutputSchema>,
ORPCErrorConstructorMap<TErrorMap>
ORPCErrorConstructorMap<TErrorMap>,
Effect.Effect<any, any, InferEffectServices<MergedContext<TInitialContext, TInjectedContext>>>
>,
): ImplementedProcedure<
TInitialContext,
Expand Down
23 changes: 22 additions & 1 deletion packages/effect/src/handler.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { InitialInputSchema, Schema } from '@orpc/contract'
import type { DecoratedProcedure, DefaultInitialContext, ORPCErrorConstructorMap } from '@orpc/server'
import type { DecoratedProcedure, DefaultInitialContext, ORPCErrorConstructorMap, ProcedureHandler } from '@orpc/server'
import type { WithEffectContext } from './context'
import { ORPCError, os } from '@orpc/server'
import { Context, Effect } from 'effect'
Expand Down Expand Up @@ -108,4 +108,25 @@ describe('handlerGen', () => {
yield* Service2
}))
})

it('accepts explicit type arguments in ProcedureHandler order with TYield last', () => {
type Errors = ORPCErrorConstructorMap<typeof errorMap>

const handler = handlerGen<WithEffectContext<Service1>, { id: string }, number, Errors, Effect.Effect<any, any, Service1>>(
function* ({ errors }, input) {
expectTypeOf(input).toEqualTypeOf<{ id: string }>()
expectTypeOf(errors).toEqualTypeOf<Errors>()
yield* Service1
return 1
},
)
expectTypeOf(handler).toEqualTypeOf<ProcedureHandler<WithEffectContext<Service1>, { id: string }, number, Errors>>()

const defaults = handlerGen<WithEffectContext<Service1>>(function* () {
yield* Service1
})
expectTypeOf(defaults).toEqualTypeOf<
ProcedureHandler<WithEffectContext<Service1>, unknown, unknown, ORPCErrorConstructorMap<Record<never, never>>>
>()
})
})
18 changes: 9 additions & 9 deletions packages/effect/src/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,12 @@ import type { InferEffectServices, WithEffectContext } from './context'
import { Effect, Context as EffectContext } from 'effect'
import { runPromise } from './runtime'

export type InferYieldError<Eff> = [Eff] extends [never] ? never : [Eff] extends [Effect.Effect<infer _A, infer E, infer _R>] ? E : never

export interface HandlerGen<
TCurrentContext extends Context,
TInput,
TYield extends Effect.Effect<any, any, InferEffectServices<TCurrentContext>>,
TReturn,
TErrorConstructorMap extends ORPCErrorConstructorMap<any>,
TYield extends Effect.Effect<any, any, InferEffectServices<TCurrentContext>>,
> {
(
opts: ProcedureHandlerOptions<TCurrentContext, TInput, TErrorConstructorMap>,
Expand All @@ -22,6 +20,8 @@ export interface HandlerGen<
>
}

export type AnyHandlerGen = HandlerGen<any, any, any, any, any>

/**
* Creates a procedure handler from an Effect generator function.
* Inside the generator you can yield Effect operations, and `handlerGen`
Expand All @@ -31,17 +31,17 @@ export interface HandlerGen<
*/
export function handlerGen<
TCurrentContext extends Context,
TInput,
TErrorConstructorMap extends ORPCErrorConstructorMap<any>,
TYield extends Effect.Effect<any, any, InferEffectServices<TCurrentContext>>,
TReturn,
TInput = unknown,
TReturn = unknown,
TErrorConstructorMap extends ORPCErrorConstructorMap<any> = ORPCErrorConstructorMap<Record<never, never>>,
TYield extends Effect.Effect<any, any, InferEffectServices<TCurrentContext>> = Effect.Effect<any, any, InferEffectServices<TCurrentContext>>,
>(
handler: HandlerGen<TCurrentContext, TInput, TYield, TReturn, TErrorConstructorMap>,
handler: HandlerGen<TCurrentContext, TInput, TReturn, TErrorConstructorMap, TYield>,
): ProcedureHandler<TCurrentContext, TInput, TReturn, TErrorConstructorMap> {
return (opts, input) => {
const effectContext = opts.context['effect/context']
const wrap = opts.context['effect/wrap'] as WithEffectContext<any>['effect/wrap']
const ef = Effect.gen(() => handler(opts, input)) as Effect.Effect<TReturn, InferYieldError<TYield>>
const ef = Effect.gen(() => handler(opts, input)) as Effect.Effect<TReturn, unknown>

return runPromise(
typeof wrap === 'function' ? wrap(ef, { ...opts, kind: 'handler' }) : ef,
Expand Down
8 changes: 7 additions & 1 deletion packages/effect/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,16 @@ it('exports', async () => {
handlerGen: expect.any(Function),
middlewareGen: expect.any(Function),
EffectSchemaToJsonSchemaConverter: expect.any(Function),
toStandardSchema: expect.any(Function),
catchORPCError: expect.any(Function),
catchORPCErrorCode: expect.any(Function),
catchORPCErrorCodes: expect.any(Function),
createEffectClient: expect.any(Function),
})
})

it('does not export internal helpers', async () => {
const mod = await import('./index')

expect(mod).not.toHaveProperty('runPromise')
expect(mod).not.toHaveProperty('toStandardSchema')
})
2 changes: 0 additions & 2 deletions packages/effect/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ export * from './converter'
export * from './error'
export * from './handler'
export * from './middleware'
export * from './runtime'
export * from './schema'

export type {
JsonSchema,
Expand Down
14 changes: 13 additions & 1 deletion packages/effect/src/middleware.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ORPCErrorConstructorMap } from '@orpc/server'
import type { Middleware, ORPCErrorConstructorMap } from '@orpc/server'
import type { WithEffectContext } from './context'
import { os } from '@orpc/server'
import { Context, Effect } from 'effect'
Expand Down Expand Up @@ -184,4 +184,16 @@ describe('middlewareGen', () => {
return yield* next()
}))
})

it('accepts explicit type arguments in Middleware order with TYield last', () => {
const middleware = middlewareGen<WithEffectContext<Service1>, { user: 'user' }, { id: string }, number, typeof errorMap, Effect.Effect<any, any, Service1>>(
function* ({ errors, next }, input) {
expectTypeOf(input).toEqualTypeOf<{ id: string }>()
expectTypeOf(errors).toEqualTypeOf<ORPCErrorConstructorMap<typeof errorMap>>()
yield* Service1
return yield* next({ context: { user: 'user' as const } })
},
)
expectTypeOf(middleware).toEqualTypeOf<Middleware<WithEffectContext<Service1>, { user: 'user' }, { id: string }, number, typeof errorMap>>()
})
})
4 changes: 2 additions & 2 deletions packages/effect/src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ export interface MiddlewareGen<
TOutContext extends Context,
TInput,
TOutput,
TYield extends Effect.Effect<any, any, InferEffectServices<TInContext>>,
TErrorConstructorMap extends ORPCErrorConstructorMap<any>,
TYield extends Effect.Effect<any, any, InferEffectServices<TInContext>>,
> {
(
opts: MiddlewareGenOptions<TInContext, TOutput, TErrorConstructorMap>,
Expand Down Expand Up @@ -57,7 +57,7 @@ export function middlewareGen<
TErrorMap extends ErrorMap = Record<never, never>,
TYield extends Effect.Effect<any, any, InferEffectServices<TInContext>> = Effect.Effect<any, any, InferEffectServices<TInContext>>,
>(
middleware: MiddlewareGen<TInContext, TOutContext, TInput, TOutput, TYield, ORPCErrorConstructorMap<TErrorMap>>,
middleware: MiddlewareGen<TInContext, TOutContext, TInput, TOutput, ORPCErrorConstructorMap<TErrorMap>, TYield>,
): Middleware<TInContext, TOutContext, TInput, TOutput, TErrorMap> {
const mid: Middleware<TInContext, TOutContext, TInput, TOutput, TErrorMap> = (opts, input, done) => {
const next: MiddlewareGenNext<TOutput> = <U extends Context = object>(...rest: MaybeOptionalOptions<MiddlewareNextOptions<U>>) => Effect.contextWith(
Expand Down
Loading