diff --git a/packages/effect/src/context.test-d.ts b/packages/effect/src/context.test-d.ts index 5cc928ffb0..71c9c3a6e0 100644 --- a/packages/effect/src/context.test-d.ts +++ b/packages/effect/src/context.test-d.ts @@ -100,6 +100,17 @@ describe('InferEffectServices', () => { expectTypeOf>().toEqualTypeOf() }) + it('does not distribute over union contexts', () => { + expectTypeOf | WithEffectContext>>().toEqualTypeOf() + expectTypeOf | { auth: boolean }>>().toEqualTypeOf() + expectTypeOf | WithEffectContext>>().toEqualTypeOf() + }) + + it('infers services from intersection and any contexts', () => { + expectTypeOf & { auth: boolean }>>().toEqualTypeOf() + expectTypeOf>().toEqualTypeOf() + }) + it('accepts a context with more services than required', () => { expectTypeOf>().toExtend>() }) diff --git a/packages/effect/src/context.ts b/packages/effect/src/context.ts index 2fddf541fb..ce41d12356 100644 --- a/packages/effect/src/context.ts +++ b/packages/effect/src/context.ts @@ -101,7 +101,8 @@ export interface WithEffectContext { /** * 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 WithEffectContext ? Services : never +export type InferEffectServices = [T] extends [{ 'effect/context': EffectContext.Context }] ? Services : never diff --git a/packages/effect/src/extensions/effect.ts b/packages/effect/src/extensions/effect.ts index 959ab773f5..cb91e88b6b 100644 --- a/packages/effect/src/extensions/effect.ts +++ b/packages/effect/src/extensions/effect.ts @@ -18,9 +18,9 @@ declare module '@orpc/server' { handler: HandlerGen< TInitialContext, InferSchemaOutput, - TYield, TReturn, - ORPCErrorConstructorMap + ORPCErrorConstructorMap, + TYield >, ): DecoratedProcedure< TInitialContext, @@ -43,9 +43,9 @@ declare module '@orpc/server' { handler: HandlerGen< MergedContext, InferSchemaOutput, - TYield, TReturn, - ORPCErrorConstructorMap + ORPCErrorConstructorMap, + TYield >, ): DecoratedProcedure< TInitialContext, @@ -69,9 +69,9 @@ declare module '@orpc/server' { handler: HandlerGen< MergedContext, InferSchemaOutput, - TYield, TReturn, - ORPCErrorConstructorMap + ORPCErrorConstructorMap, + TYield >, ): DecoratedProcedure< TInitialContext, @@ -95,9 +95,9 @@ declare module '@orpc/server' { handler: HandlerGen< MergedContext, InferSchemaOutput, - TYield, TReturn, - ORPCErrorConstructorMap + ORPCErrorConstructorMap, + TYield >, ): DecoratedProcedure< TInitialContext, @@ -122,9 +122,9 @@ declare module '@orpc/server' { handler: HandlerGen< MergedContext, InferSchemaOutput, - TYield, TReturn, - ORPCErrorConstructorMap + ORPCErrorConstructorMap, + TYield >, ): DecoratedProcedure< TInitialContext, @@ -146,9 +146,9 @@ declare module '@orpc/server' { handler: HandlerGen< MergedContext, InferSchemaOutput, - Effect.Effect>>, InferSchemaInput, - ORPCErrorConstructorMap + ORPCErrorConstructorMap, + Effect.Effect>> >, ): ImplementedProcedure< TInitialContext, diff --git a/packages/effect/src/handler.test-d.ts b/packages/effect/src/handler.test-d.ts index 12f18e1b01..b33efd24f9 100644 --- a/packages/effect/src/handler.test-d.ts +++ b/packages/effect/src/handler.test-d.ts @@ -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' @@ -108,4 +108,25 @@ describe('handlerGen', () => { yield* Service2 })) }) + + it('accepts explicit type arguments in ProcedureHandler order with TYield last', () => { + type Errors = ORPCErrorConstructorMap + + const handler = handlerGen, { id: string }, number, Errors, Effect.Effect>( + function* ({ errors }, input) { + expectTypeOf(input).toEqualTypeOf<{ id: string }>() + expectTypeOf(errors).toEqualTypeOf() + yield* Service1 + return 1 + }, + ) + expectTypeOf(handler).toEqualTypeOf, { id: string }, number, Errors>>() + + const defaults = handlerGen>(function* () { + yield* Service1 + }) + expectTypeOf(defaults).toEqualTypeOf< + ProcedureHandler, unknown, unknown, ORPCErrorConstructorMap>> + >() + }) }) diff --git a/packages/effect/src/handler.ts b/packages/effect/src/handler.ts index 9f7d10dc8c..c9e101f3a9 100644 --- a/packages/effect/src/handler.ts +++ b/packages/effect/src/handler.ts @@ -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] extends [never] ? never : [Eff] extends [Effect.Effect] ? E : never - export interface HandlerGen< TCurrentContext extends Context, TInput, - TYield extends Effect.Effect>, TReturn, TErrorConstructorMap extends ORPCErrorConstructorMap, + TYield extends Effect.Effect>, > { ( opts: ProcedureHandlerOptions, @@ -22,6 +20,8 @@ export interface HandlerGen< > } +export type AnyHandlerGen = HandlerGen + /** * Creates a procedure handler from an Effect generator function. * Inside the generator you can yield Effect operations, and `handlerGen` @@ -31,17 +31,17 @@ export interface HandlerGen< */ export function handlerGen< TCurrentContext extends Context, - TInput, - TErrorConstructorMap extends ORPCErrorConstructorMap, - TYield extends Effect.Effect>, - TReturn, + TInput = unknown, + TReturn = unknown, + TErrorConstructorMap extends ORPCErrorConstructorMap = ORPCErrorConstructorMap>, + TYield extends Effect.Effect> = Effect.Effect>, >( - handler: HandlerGen, + handler: HandlerGen, ): ProcedureHandler { return (opts, input) => { const effectContext = opts.context['effect/context'] const wrap = opts.context['effect/wrap'] as WithEffectContext['effect/wrap'] - const ef = Effect.gen(() => handler(opts, input)) as Effect.Effect> + const ef = Effect.gen(() => handler(opts, input)) as Effect.Effect return runPromise( typeof wrap === 'function' ? wrap(ef, { ...opts, kind: 'handler' }) : ef, diff --git a/packages/effect/src/index.test.ts b/packages/effect/src/index.test.ts index 4716b465a6..28adaf8475 100644 --- a/packages/effect/src/index.test.ts +++ b/packages/effect/src/index.test.ts @@ -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') +}) diff --git a/packages/effect/src/index.ts b/packages/effect/src/index.ts index c1d0e9cb19..42f4d9fea5 100644 --- a/packages/effect/src/index.ts +++ b/packages/effect/src/index.ts @@ -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, diff --git a/packages/effect/src/middleware.test-d.ts b/packages/effect/src/middleware.test-d.ts index 105d14f0e6..79a972e1a5 100644 --- a/packages/effect/src/middleware.test-d.ts +++ b/packages/effect/src/middleware.test-d.ts @@ -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' @@ -184,4 +184,16 @@ describe('middlewareGen', () => { return yield* next() })) }) + + it('accepts explicit type arguments in Middleware order with TYield last', () => { + const middleware = middlewareGen, { user: 'user' }, { id: string }, number, typeof errorMap, Effect.Effect>( + function* ({ errors, next }, input) { + expectTypeOf(input).toEqualTypeOf<{ id: string }>() + expectTypeOf(errors).toEqualTypeOf>() + yield* Service1 + return yield* next({ context: { user: 'user' as const } }) + }, + ) + expectTypeOf(middleware).toEqualTypeOf, { user: 'user' }, { id: string }, number, typeof errorMap>>() + }) }) diff --git a/packages/effect/src/middleware.ts b/packages/effect/src/middleware.ts index bd5f456ccc..568898cd26 100644 --- a/packages/effect/src/middleware.ts +++ b/packages/effect/src/middleware.ts @@ -25,8 +25,8 @@ export interface MiddlewareGen< TOutContext extends Context, TInput, TOutput, - TYield extends Effect.Effect>, TErrorConstructorMap extends ORPCErrorConstructorMap, + TYield extends Effect.Effect>, > { ( opts: MiddlewareGenOptions, @@ -57,7 +57,7 @@ export function middlewareGen< TErrorMap extends ErrorMap = Record, TYield extends Effect.Effect> = Effect.Effect>, >( - middleware: MiddlewareGen>, + middleware: MiddlewareGen, TYield>, ): Middleware { const mid: Middleware = (opts, input, done) => { const next: MiddlewareGenNext = (...rest: MaybeOptionalOptions>) => Effect.contextWith(