@orpc/experimental-effect 2.0.0-beta.30 → 2.0.0-beta.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  <a href="https://codecov.io/gh/middleapi/orpc">
5
5
  <img alt="codecov" src="https://codecov.io/gh/middleapi/orpc/branch/main/graph/badge.svg">
6
6
  </a>
7
- <a href="https://npmx.dev/package/@orpc/experimental-effect">
7
+ <a href="https://www.npmjs.com/package/@orpc/experimental-effect">
8
8
  <img alt="weekly downloads" src="https://img.shields.io/npm/dw/%40orpc%2Fexperimental-effect?logo=npm" />
9
9
  </a>
10
10
  <a href="https://app.codspeed.io/middleapi/orpc?utm_source=badge">
@@ -29,44 +29,44 @@ You can read the documentation [here](https://orpc.dev).
29
29
 
30
30
  **Core**
31
31
 
32
- - [@orpc/contract](https://npmx.dev/package/@orpc/contract): Define API contract as the single source of truth.
33
- - [@orpc/server](https://npmx.dev/package/@orpc/server): Build APIs or implement contracts.
34
- - [@orpc/client](https://npmx.dev/package/@orpc/client): Consume APIs with end-to-end type safety.
35
- - [@orpc/openapi](https://npmx.dev/package/@orpc/openapi): Add OpenAPI compatibility to APIs.
32
+ - [@orpc/contract](https://www.npmjs.com/package/@orpc/contract): Define API contract as the single source of truth.
33
+ - [@orpc/server](https://www.npmjs.com/package/@orpc/server): Build APIs or implement contracts.
34
+ - [@orpc/client](https://www.npmjs.com/package/@orpc/client): Consume APIs with end-to-end type safety.
35
+ - [@orpc/openapi](https://www.npmjs.com/package/@orpc/openapi): Add OpenAPI compatibility to APIs.
36
36
 
37
37
  **Schema validation**
38
38
 
39
- - [@orpc/zod](https://npmx.dev/package/@orpc/zod): Integrate with [Zod](https://zod.dev/).
40
- - [@orpc/valibot](https://npmx.dev/package/@orpc/valibot): Integrate with [Valibot](https://valibot.dev/).
41
- - [@orpc/arktype](https://npmx.dev/package/@orpc/arktype): Integrate with [ArkType](https://arktype.io/).
39
+ - [@orpc/zod](https://www.npmjs.com/package/@orpc/zod): Integrate with [Zod](https://zod.dev/).
40
+ - [@orpc/valibot](https://www.npmjs.com/package/@orpc/valibot): Integrate with [Valibot](https://valibot.dev/).
41
+ - [@orpc/arktype](https://www.npmjs.com/package/@orpc/arktype): Integrate with [ArkType](https://arktype.io/).
42
42
 
43
43
  **Built-in features**
44
44
 
45
- - [@orpc/publisher](https://npmx.dev/package/@orpc/publisher): Pub/Sub with memory, Redis, and Upstash adapters.
46
- - [@orpc/ratelimit](https://npmx.dev/package/@orpc/ratelimit): Rate limiting with memory, Redis, and Upstash adapters.
47
- - [@orpc/hibernation](https://npmx.dev/package/@orpc/hibernation): Leverage Hibernation APIs like [Cloudflare's Hibernation WebSocket](https://developers.cloudflare.com/durable-objects/best-practices/websockets/#durable-objects-hibernation-websocket-api).
48
- - [@orpc/json-schema](https://npmx.dev/package/@orpc/json-schema): Smart coercion for OpenAPI requests.
45
+ - [@orpc/publisher](https://www.npmjs.com/package/@orpc/publisher): Pub/Sub with memory, Redis, and Upstash adapters.
46
+ - [@orpc/ratelimit](https://www.npmjs.com/package/@orpc/ratelimit): Rate limiting with memory, Redis, and Upstash adapters.
47
+ - [@orpc/hibernation](https://www.npmjs.com/package/@orpc/hibernation): Leverage Hibernation APIs like [Cloudflare's Hibernation WebSocket](https://developers.cloudflare.com/durable-objects/best-practices/websockets/#durable-objects-hibernation-websocket-api).
48
+ - [@orpc/json-schema](https://www.npmjs.com/package/@orpc/json-schema): Smart coercion for OpenAPI requests.
49
49
 
50
50
  **Framework & ecosystem integrations**
51
51
 
52
- - [@orpc/next](https://npmx.dev/package/@orpc/next): Integrate with [Next.js Server Functions](https://nextjs.org/docs/app/getting-started/mutating-data).
53
- - [@orpc/ai-sdk](https://npmx.dev/package/@orpc/ai-sdk): Turn contracts and procedures into [AI SDK](https://ai-sdk.dev/) tools.
54
- - [@orpc/tanstack-query](https://npmx.dev/package/@orpc/tanstack-query): Integrate with [TanStack Query](https://tanstack.com/query/latest).
55
- - [@orpc/pinia-colada](https://npmx.dev/package/@orpc/pinia-colada): Integrate with [Pinia Colada](https://pinia-colada.esm.dev/).
56
- - [@orpc/swr](https://npmx.dev/package/@orpc/swr): Integrate with [SWR](https://swr.vercel.app/).
57
- - [@orpc/experimental-msw](https://npmx.dev/package/@orpc/experimental-msw): Mock procedures with [Mock Service Worker](https://mswjs.io/).
58
- - [@orpc/experimental-effect](https://npmx.dev/package/@orpc/experimental-effect): Integrate with [Effect](https://effect.website/).
59
- - [@orpc/nest](https://npmx.dev/package/@orpc/nest): Implement your contract with [NestJS](https://nestjs.com/).
60
- - [@orpc/node](https://npmx.dev/package/@orpc/node): [Node.js](https://nodejs.org/) plugins for static file serving and large uploads.
61
- - [@orpc/bun](https://npmx.dev/package/@orpc/bun): Adapters for [Bun's Redis](https://bun.sh/).
62
- - [@orpc/cloudflare](https://npmx.dev/package/@orpc/cloudflare): Adapters for [Cloudflare's RateLimit and Durable Objects](https://developers.cloudflare.com/workers/).
63
- - [@orpc/trpc](https://npmx.dev/package/@orpc/trpc): Reuse existing [tRPC](https://trpc.io/) routers within oRPC.
52
+ - [@orpc/next](https://www.npmjs.com/package/@orpc/next): Integrate with [Next.js Server Functions](https://nextjs.org/docs/app/getting-started/mutating-data).
53
+ - [@orpc/ai-sdk](https://www.npmjs.com/package/@orpc/ai-sdk): Turn contracts and procedures into [AI SDK](https://ai-sdk.dev/) tools.
54
+ - [@orpc/tanstack-query](https://www.npmjs.com/package/@orpc/tanstack-query): Integrate with [TanStack Query](https://tanstack.com/query/latest).
55
+ - [@orpc/pinia-colada](https://www.npmjs.com/package/@orpc/pinia-colada): Integrate with [Pinia Colada](https://pinia-colada.esm.dev/).
56
+ - [@orpc/swr](https://www.npmjs.com/package/@orpc/swr): Integrate with [SWR](https://swr.vercel.app/).
57
+ - [@orpc/experimental-msw](https://www.npmjs.com/package/@orpc/experimental-msw): Mock procedures with [Mock Service Worker](https://mswjs.io/).
58
+ - [@orpc/experimental-effect](https://www.npmjs.com/package/@orpc/experimental-effect): Integrate with [Effect](https://effect.website/).
59
+ - [@orpc/nest](https://www.npmjs.com/package/@orpc/nest): Implement your contract with [NestJS](https://nestjs.com/).
60
+ - [@orpc/node](https://www.npmjs.com/package/@orpc/node): [Node.js](https://nodejs.org/) plugins for static file serving and large uploads.
61
+ - [@orpc/bun](https://www.npmjs.com/package/@orpc/bun): Adapters for [Bun's Redis](https://bun.sh/).
62
+ - [@orpc/cloudflare](https://www.npmjs.com/package/@orpc/cloudflare): Adapters for [Cloudflare's RateLimit and Durable Objects](https://developers.cloudflare.com/workers/).
63
+ - [@orpc/trpc](https://www.npmjs.com/package/@orpc/trpc): Reuse existing [tRPC](https://trpc.io/) routers within oRPC.
64
64
 
65
65
  **Observability**
66
66
 
67
- - [@orpc/opentelemetry](https://npmx.dev/package/@orpc/opentelemetry): Integrate with [OpenTelemetry](https://opentelemetry.io/) for distributed tracing.
68
- - [@orpc/pino](https://npmx.dev/package/@orpc/pino): Integrate with [Pino](https://getpino.io/) for logging.
69
- - [@orpc/evlog](https://npmx.dev/package/@orpc/evlog): Integrate with [Evlog](https://evlog.dev/) for logging.
67
+ - [@orpc/opentelemetry](https://www.npmjs.com/package/@orpc/opentelemetry): Integrate with [OpenTelemetry](https://opentelemetry.io/) for distributed tracing.
68
+ - [@orpc/pino](https://www.npmjs.com/package/@orpc/pino): Integrate with [Pino](https://getpino.io/) for logging.
69
+ - [@orpc/evlog](https://www.npmjs.com/package/@orpc/evlog): Integrate with [Evlog](https://evlog.dev/) for logging.
70
70
 
71
71
  ## Sponsors
72
72
 
@@ -113,6 +113,7 @@ Like what we build over at [middleapi](https://github.com/middleapi)? You can he
113
113
  <td align="center"><a href="https://github.com/itigoore01?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="shota"><img src="https://avatars.githubusercontent.com/u/11831107?u=c976a6dc7e055eb026304c46c99100ed22b0c8e0&amp;v=4" width="139" alt="shota"/><br />shota</a></td>
114
114
  <td align="center"><a href="https://github.com/ellis-driscoll?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="Ellis Driscoll"><img src="https://avatars.githubusercontent.com/u/70685966?u=c5f95bc33b5991d9744abe00052542e4a2ed3cb9&amp;v=4" width="139" alt="Ellis Driscoll"/><br />Ellis Driscoll</a></td>
115
115
  <td align="center"><a href="https://github.com/hoangbn?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="Hoang Nguyen"><img src="https://avatars.githubusercontent.com/u/38968280?u=c90084c6de65c56facabab7ba13a72a49ddbc3e4&amp;v=4" width="139" alt="Hoang Nguyen"/><br />Hoang Nguyen</a></td>
116
+ <td align="center"><a href="https://opencollective.com/guest-ac41de3b?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="Orestis Ioannou"><img src="https://images.opencollective.com/guest-ac41de3b/avatar/460.png" width="139" alt="Orestis Ioannou"/><br />Orestis Ioannou</a></td>
116
117
  </tr>
117
118
  </table>
118
119
 
@@ -135,7 +136,7 @@ Like what we build over at [middleapi](https://github.com/middleapi)? You can he
135
136
  <td align="center"><a href="https://rigtch.fm/?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="Igor Makowski"><img src="https://avatars.githubusercontent.com/u/56691628?u=ee8c879478f7c151b9156aef6c74243fa3e247a8&amp;v=4" width="119" alt="Igor Makowski"/><br />Igor Makowski</a></td>
136
137
  <td align="center"><a href="https://blog.cwang.io/?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="hanayashiki"><img src="https://avatars.githubusercontent.com/u/26056783?u=06c3b9205a16fd41a871e82da1cc2a09306d53f5&amp;v=4" width="119" alt="hanayashiki"/><br />hanayashiki</a></td>
137
138
  <td align="center"><a href="https://dubinets.io/?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="Lev Dubinets"><img src="https://avatars.githubusercontent.com/u/3114081?u=f547f5d5012cab54851f1b1ad72d10e537f78fc2&amp;v=4" width="119" alt="Lev Dubinets"/><br />Lev Dubinets</a></td>
138
- <td align="center"><a href="https://bika.ai/?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="Kelly Peilin Chan"><img src="https://avatars.githubusercontent.com/u/520852?u=6b0f7105f694e7b5cacf410a3f04c7044b469dc8&amp;v=4" width="119" alt="Kelly Peilin Chan"/><br />Kelly Peilin Chan</a></td>
139
+ <td align="center"><a href="https://kellychan.im/?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="Kelly Peilin Chan"><img src="https://avatars.githubusercontent.com/u/520852?u=6b0f7105f694e7b5cacf410a3f04c7044b469dc8&amp;v=4" width="119" alt="Kelly Peilin Chan"/><br />Kelly Peilin Chan</a></td>
139
140
  </tr>
140
141
  <tr>
141
142
  <td align="center"><a href="https://guyariely.com/?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="Guy Ariely"><img src="https://avatars.githubusercontent.com/u/42813496?u=edb6b7f563bf28e160a290832e7da57c0506f8ca&amp;v=4" width="119" alt="Guy Ariely"/><br />Guy Ariely</a></td>
package/dist/index.d.mts CHANGED
@@ -1,8 +1,22 @@
1
+ import { AnyNestedClient, Client, ClientRest } from '@orpc/client';
2
+ import { Effect, Schema } from 'effect';
1
3
  export { H as HandlerGen, I as InferYieldError, W as WithEffectContext, h as handlerGen } from './shared/experimental-effect.BupPDCRJ.mjs';
2
4
  import { AnySchema, Schema as Schema$1 } from '@orpc/contract';
3
5
  import { JsonSchemaConverter, JsonSchemaConverterDirection, JsonSchema } from '@orpc/json-schema';
4
6
  import { AnyORPCError, ORPCError, ORPCErrorCode } from '@orpc/server';
5
- import { Effect, Schema } from 'effect';
7
+
8
+ type EffectClient<T extends AnyNestedClient> = T extends Client<infer UContext, infer UInput, infer UOutput, infer UError> ? (...rest: ClientRest<UContext, UInput>) => Effect.Effect<UOutput, UError> : {
9
+ [K in keyof T]: T[K] extends AnyNestedClient ? EffectClient<T[K]> : never;
10
+ };
11
+ /**
12
+ * Creates a client whose procedures return lazy effects instead of promises,
13
+ * so you can `yield*` calls inside Effect generators. Errors are captured in
14
+ * the error channel with their original types preserved, and interrupting an
15
+ * effect aborts the underlying call.
16
+ *
17
+ * @see {@link https://orpc.dev/docs/integrations/effect#client-calls | Effect Integration - Client Calls}
18
+ */
19
+ declare function createEffectClient<T extends AnyNestedClient>(client: T): EffectClient<T>;
6
20
 
7
21
  /**
8
22
  * A JSON Schema converter for Effect Schema, built on top of
@@ -19,7 +33,9 @@ declare class EffectSchemaToJsonSchemaConverter implements JsonSchemaConverter {
19
33
 
20
34
  /**
21
35
  * Recovers from `ORPCError` failures in the error channel of an effect.
22
- * Other failures re-fail with their original cause.
36
+ * Other failures re-fail with their original cause. Because failure types
37
+ * like `Error` can also hold `ORPCError` instances at runtime, the handler
38
+ * can also receive such hidden errors with unknown code and data.
23
39
  *
24
40
  * Supports both data-first `catchORPCError(effect, handler)` and data-last
25
41
  * `effect.pipe(catchORPCError(handler))` styles.
@@ -36,13 +52,15 @@ declare class EffectSchemaToJsonSchemaConverter implements JsonSchemaConverter {
36
52
  * @see {@link https://orpc.dev/docs/integrations/effect#catching-orpcerrors | Effect Integration - Catching ORPCErrors}
37
53
  */
38
54
  declare const catchORPCError: {
39
- <E, A2, E2, R2>(f: (error: Extract<E, AnyORPCError>) => Effect.Effect<A2, E2, R2>): <A, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A | A2, E2 | Exclude<E, AnyORPCError>, R | R2>;
40
- <A, E, R, A2, E2, R2>(self: Effect.Effect<A, E, R>, f: (error: Extract<E, AnyORPCError>) => Effect.Effect<A2, E2, R2>): Effect.Effect<A | A2, E2 | Exclude<E, AnyORPCError>, R | R2>;
55
+ <E, A2, E2, R2>(f: (error: E extends AnyORPCError ? E : AnyORPCError extends E ? ORPCError<ORPCErrorCode, unknown> : never) => Effect.Effect<A2, E2, R2>): <A, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A | A2, E2 | Exclude<E, AnyORPCError>, R | R2>;
56
+ <A, E, R, A2, E2, R2>(self: Effect.Effect<A, E, R>, f: (error: E extends AnyORPCError ? E : AnyORPCError extends E ? ORPCError<ORPCErrorCode, unknown> : never) => Effect.Effect<A2, E2, R2>): Effect.Effect<A | A2, E2 | Exclude<E, AnyORPCError>, R | R2>;
41
57
  };
42
58
  /**
43
59
  * Recovers from `ORPCError` failures with a matching code in the error channel of an effect.
44
60
  * Other failures re-fail with their original cause. The `code` argument is
45
- * suggested and restricted to the codes present in the error channel.
61
+ * suggested and restricted to the codes present in the error channel, and is
62
+ * unrestricted when the channel contains failure types like `Error` that can
63
+ * hold hidden `ORPCError` instances, whose data is unknown.
46
64
  *
47
65
  * Supports both data-first `catchORPCErrorCode(effect, code, handler)` and
48
66
  * data-last `effect.pipe(catchORPCErrorCode(code, handler))` styles.
@@ -51,7 +69,7 @@ declare const catchORPCError: {
51
69
  * ```ts
52
70
  * declare const program: Effect.Effect<
53
71
  * string,
54
- * ORPCError<'NOT_FOUND', { id: string }> | ORPCError<'CONFLICT', undefined> | TypeError
72
+ * ORPCError<'NOT_FOUND', { id: string }> | ORPCError<'CONFLICT', undefined>
55
73
  * >
56
74
  *
57
75
  * const recovered = program.pipe(
@@ -62,14 +80,15 @@ declare const catchORPCError: {
62
80
  * @see {@link https://orpc.dev/docs/integrations/effect#catching-orpcerrors | Effect Integration - Catching ORPCErrors}
63
81
  */
64
82
  declare const catchORPCErrorCode: {
65
- <const TCode extends E extends ORPCError<infer TCode, any> ? TCode : never, E, A2, E2, R2>(code: TCode, f: (error: Extract<E, ORPCError<TCode, any>>) => Effect.Effect<A2, E2, R2>): <A, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A | A2, E2 | Exclude<E, ORPCError<TCode, any>>, R | R2>;
66
- <A, E, R, const TCode extends E extends ORPCError<infer TCode, any> ? TCode : never, A2, E2, R2>(self: Effect.Effect<A, E, R>, code: TCode, f: (error: Extract<E, ORPCError<TCode, any>>) => Effect.Effect<A2, E2, R2>): Effect.Effect<A | A2, E2 | Exclude<E, ORPCError<TCode, any>>, R | R2>;
83
+ <const TCode extends E extends ORPCError<infer TCode, any> ? TCode : AnyORPCError extends E ? ORPCErrorCode : never, E, A2, E2, R2>(code: TCode, f: (error: E extends AnyORPCError ? Extract<E, ORPCError<TCode, any>> : AnyORPCError extends E ? ORPCError<TCode, unknown> : never) => Effect.Effect<A2, E2, R2>): <A, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A | A2, E2 | Exclude<E, ORPCError<TCode, any>>, R | R2>;
84
+ <A, E, R, const TCode extends E extends ORPCError<infer TCode, any> ? TCode : AnyORPCError extends E ? ORPCErrorCode : never, A2, E2, R2>(self: Effect.Effect<A, E, R>, code: TCode, f: (error: E extends AnyORPCError ? Extract<E, ORPCError<TCode, any>> : AnyORPCError extends E ? ORPCError<TCode, unknown> : never) => Effect.Effect<A2, E2, R2>): Effect.Effect<A | A2, E2 | Exclude<E, ORPCError<TCode, any>>, R | R2>;
67
85
  };
68
86
  /**
69
87
  * Recovers from `ORPCError` failures using a map of codes to handlers in the
70
88
  * error channel of an effect. Other failures re-fail with their original
71
89
  * cause. The keys are suggested and restricted to the codes present in the
72
- * error channel.
90
+ * error channel, and are unrestricted when the channel contains failure types
91
+ * like `Error` that can hold hidden `ORPCError` instances, whose data is unknown.
73
92
  *
74
93
  * Supports both data-first `catchORPCErrorCodes(effect, cases)` and data-last
75
94
  * `effect.pipe(catchORPCErrorCodes(cases))` styles.
@@ -78,7 +97,7 @@ declare const catchORPCErrorCode: {
78
97
  * ```ts
79
98
  * declare const program: Effect.Effect<
80
99
  * string,
81
- * ORPCError<'NOT_FOUND', { id: string }> | ORPCError<'CONFLICT', undefined> | TypeError
100
+ * ORPCError<'NOT_FOUND', { id: string }> | ORPCError<'CONFLICT', undefined>
82
101
  * >
83
102
  *
84
103
  * const recovered = program.pipe(
@@ -93,10 +112,10 @@ declare const catchORPCErrorCode: {
93
112
  */
94
113
  declare const catchORPCErrorCodes: {
95
114
  <E, Cases extends {
96
- [K in Extract<E, AnyORPCError>['code']]?: (error: Extract<E, ORPCError<K, any>>) => Effect.Effect<any, any, any>;
97
- } & (unknown extends E ? object : {
98
- [K in Exclude<keyof Cases, Extract<E, AnyORPCError>['code']>]: never;
99
- })>(cases: Cases): <A, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A | {
115
+ [K in E extends ORPCError<infer TCode, any> ? TCode : AnyORPCError extends E ? ORPCErrorCode : never]?: string extends K ? (error: never) => Effect.Effect<any, any, any> : K extends infer C extends ORPCErrorCode ? (error: E extends AnyORPCError ? Extract<E, ORPCError<C, any>> : AnyORPCError extends E ? ORPCError<C, unknown> : never) => Effect.Effect<any, any, any> : never;
116
+ } & {
117
+ [K in Exclude<keyof Cases, E extends ORPCError<infer TCode, any> ? TCode : AnyORPCError extends E ? ORPCErrorCode : never>]: never;
118
+ }>(cases: Cases): <A, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A | {
100
119
  [K in keyof Cases]: Cases[K] extends (...args: any[]) => Effect.Effect<infer A2, any, any> ? A2 : never;
101
120
  }[keyof Cases], Exclude<E, ORPCError<keyof Cases & ORPCErrorCode, any>> | {
102
121
  [K in keyof Cases]: Cases[K] extends (...args: any[]) => Effect.Effect<any, infer E2, any> ? E2 : never;
@@ -104,10 +123,10 @@ declare const catchORPCErrorCodes: {
104
123
  [K in keyof Cases]: Cases[K] extends (...args: any[]) => Effect.Effect<any, any, infer R2> ? R2 : never;
105
124
  }[keyof Cases]>;
106
125
  <A, E, R, Cases extends {
107
- [K in Extract<E, AnyORPCError>['code']]?: (error: Extract<E, ORPCError<K, any>>) => Effect.Effect<any, any, any>;
108
- } & (unknown extends E ? object : {
109
- [K in Exclude<keyof Cases, Extract<E, AnyORPCError>['code']>]: never;
110
- })>(self: Effect.Effect<A, E, R>, cases: Cases): Effect.Effect<A | {
126
+ [K in E extends ORPCError<infer TCode, any> ? TCode : AnyORPCError extends E ? ORPCErrorCode : never]?: string extends K ? (error: never) => Effect.Effect<any, any, any> : K extends infer C extends ORPCErrorCode ? (error: E extends AnyORPCError ? Extract<E, ORPCError<C, any>> : AnyORPCError extends E ? ORPCError<C, unknown> : never) => Effect.Effect<any, any, any> : never;
127
+ } & {
128
+ [K in Exclude<keyof Cases, E extends ORPCError<infer TCode, any> ? TCode : AnyORPCError extends E ? ORPCErrorCode : never>]: never;
129
+ }>(self: Effect.Effect<A, E, R>, cases: Cases): Effect.Effect<A | {
111
130
  [K in keyof Cases]: Cases[K] extends (...args: any[]) => Effect.Effect<infer A2, any, any> ? A2 : never;
112
131
  }[keyof Cases], Exclude<E, ORPCError<keyof Cases & ORPCErrorCode, any>> | {
113
132
  [K in keyof Cases]: Cases[K] extends (...args: any[]) => Effect.Effect<any, infer E2, any> ? E2 : never;
@@ -126,5 +145,5 @@ declare function runPromise<T>(effect: Effect.Effect<T, unknown>, options?: RunP
126
145
 
127
146
  declare function toStandardSchema<S extends Schema.ConstraintDecoder<any>>(schema: S): Schema$1<S['Encoded'], S['Type']> & S;
128
147
 
129
- export { EffectSchemaToJsonSchemaConverter, catchORPCError, catchORPCErrorCode, catchORPCErrorCodes, runPromise, toStandardSchema };
130
- export type { RunPromiseOptions };
148
+ export { EffectSchemaToJsonSchemaConverter, catchORPCError, catchORPCErrorCode, catchORPCErrorCodes, createEffectClient, runPromise, toStandardSchema };
149
+ export type { EffectClient, RunPromiseOptions };
package/dist/index.d.ts CHANGED
@@ -1,8 +1,22 @@
1
+ import { AnyNestedClient, Client, ClientRest } from '@orpc/client';
2
+ import { Effect, Schema } from 'effect';
1
3
  export { H as HandlerGen, I as InferYieldError, W as WithEffectContext, h as handlerGen } from './shared/experimental-effect.BupPDCRJ.js';
2
4
  import { AnySchema, Schema as Schema$1 } from '@orpc/contract';
3
5
  import { JsonSchemaConverter, JsonSchemaConverterDirection, JsonSchema } from '@orpc/json-schema';
4
6
  import { AnyORPCError, ORPCError, ORPCErrorCode } from '@orpc/server';
5
- import { Effect, Schema } from 'effect';
7
+
8
+ type EffectClient<T extends AnyNestedClient> = T extends Client<infer UContext, infer UInput, infer UOutput, infer UError> ? (...rest: ClientRest<UContext, UInput>) => Effect.Effect<UOutput, UError> : {
9
+ [K in keyof T]: T[K] extends AnyNestedClient ? EffectClient<T[K]> : never;
10
+ };
11
+ /**
12
+ * Creates a client whose procedures return lazy effects instead of promises,
13
+ * so you can `yield*` calls inside Effect generators. Errors are captured in
14
+ * the error channel with their original types preserved, and interrupting an
15
+ * effect aborts the underlying call.
16
+ *
17
+ * @see {@link https://orpc.dev/docs/integrations/effect#client-calls | Effect Integration - Client Calls}
18
+ */
19
+ declare function createEffectClient<T extends AnyNestedClient>(client: T): EffectClient<T>;
6
20
 
7
21
  /**
8
22
  * A JSON Schema converter for Effect Schema, built on top of
@@ -19,7 +33,9 @@ declare class EffectSchemaToJsonSchemaConverter implements JsonSchemaConverter {
19
33
 
20
34
  /**
21
35
  * Recovers from `ORPCError` failures in the error channel of an effect.
22
- * Other failures re-fail with their original cause.
36
+ * Other failures re-fail with their original cause. Because failure types
37
+ * like `Error` can also hold `ORPCError` instances at runtime, the handler
38
+ * can also receive such hidden errors with unknown code and data.
23
39
  *
24
40
  * Supports both data-first `catchORPCError(effect, handler)` and data-last
25
41
  * `effect.pipe(catchORPCError(handler))` styles.
@@ -36,13 +52,15 @@ declare class EffectSchemaToJsonSchemaConverter implements JsonSchemaConverter {
36
52
  * @see {@link https://orpc.dev/docs/integrations/effect#catching-orpcerrors | Effect Integration - Catching ORPCErrors}
37
53
  */
38
54
  declare const catchORPCError: {
39
- <E, A2, E2, R2>(f: (error: Extract<E, AnyORPCError>) => Effect.Effect<A2, E2, R2>): <A, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A | A2, E2 | Exclude<E, AnyORPCError>, R | R2>;
40
- <A, E, R, A2, E2, R2>(self: Effect.Effect<A, E, R>, f: (error: Extract<E, AnyORPCError>) => Effect.Effect<A2, E2, R2>): Effect.Effect<A | A2, E2 | Exclude<E, AnyORPCError>, R | R2>;
55
+ <E, A2, E2, R2>(f: (error: E extends AnyORPCError ? E : AnyORPCError extends E ? ORPCError<ORPCErrorCode, unknown> : never) => Effect.Effect<A2, E2, R2>): <A, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A | A2, E2 | Exclude<E, AnyORPCError>, R | R2>;
56
+ <A, E, R, A2, E2, R2>(self: Effect.Effect<A, E, R>, f: (error: E extends AnyORPCError ? E : AnyORPCError extends E ? ORPCError<ORPCErrorCode, unknown> : never) => Effect.Effect<A2, E2, R2>): Effect.Effect<A | A2, E2 | Exclude<E, AnyORPCError>, R | R2>;
41
57
  };
42
58
  /**
43
59
  * Recovers from `ORPCError` failures with a matching code in the error channel of an effect.
44
60
  * Other failures re-fail with their original cause. The `code` argument is
45
- * suggested and restricted to the codes present in the error channel.
61
+ * suggested and restricted to the codes present in the error channel, and is
62
+ * unrestricted when the channel contains failure types like `Error` that can
63
+ * hold hidden `ORPCError` instances, whose data is unknown.
46
64
  *
47
65
  * Supports both data-first `catchORPCErrorCode(effect, code, handler)` and
48
66
  * data-last `effect.pipe(catchORPCErrorCode(code, handler))` styles.
@@ -51,7 +69,7 @@ declare const catchORPCError: {
51
69
  * ```ts
52
70
  * declare const program: Effect.Effect<
53
71
  * string,
54
- * ORPCError<'NOT_FOUND', { id: string }> | ORPCError<'CONFLICT', undefined> | TypeError
72
+ * ORPCError<'NOT_FOUND', { id: string }> | ORPCError<'CONFLICT', undefined>
55
73
  * >
56
74
  *
57
75
  * const recovered = program.pipe(
@@ -62,14 +80,15 @@ declare const catchORPCError: {
62
80
  * @see {@link https://orpc.dev/docs/integrations/effect#catching-orpcerrors | Effect Integration - Catching ORPCErrors}
63
81
  */
64
82
  declare const catchORPCErrorCode: {
65
- <const TCode extends E extends ORPCError<infer TCode, any> ? TCode : never, E, A2, E2, R2>(code: TCode, f: (error: Extract<E, ORPCError<TCode, any>>) => Effect.Effect<A2, E2, R2>): <A, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A | A2, E2 | Exclude<E, ORPCError<TCode, any>>, R | R2>;
66
- <A, E, R, const TCode extends E extends ORPCError<infer TCode, any> ? TCode : never, A2, E2, R2>(self: Effect.Effect<A, E, R>, code: TCode, f: (error: Extract<E, ORPCError<TCode, any>>) => Effect.Effect<A2, E2, R2>): Effect.Effect<A | A2, E2 | Exclude<E, ORPCError<TCode, any>>, R | R2>;
83
+ <const TCode extends E extends ORPCError<infer TCode, any> ? TCode : AnyORPCError extends E ? ORPCErrorCode : never, E, A2, E2, R2>(code: TCode, f: (error: E extends AnyORPCError ? Extract<E, ORPCError<TCode, any>> : AnyORPCError extends E ? ORPCError<TCode, unknown> : never) => Effect.Effect<A2, E2, R2>): <A, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A | A2, E2 | Exclude<E, ORPCError<TCode, any>>, R | R2>;
84
+ <A, E, R, const TCode extends E extends ORPCError<infer TCode, any> ? TCode : AnyORPCError extends E ? ORPCErrorCode : never, A2, E2, R2>(self: Effect.Effect<A, E, R>, code: TCode, f: (error: E extends AnyORPCError ? Extract<E, ORPCError<TCode, any>> : AnyORPCError extends E ? ORPCError<TCode, unknown> : never) => Effect.Effect<A2, E2, R2>): Effect.Effect<A | A2, E2 | Exclude<E, ORPCError<TCode, any>>, R | R2>;
67
85
  };
68
86
  /**
69
87
  * Recovers from `ORPCError` failures using a map of codes to handlers in the
70
88
  * error channel of an effect. Other failures re-fail with their original
71
89
  * cause. The keys are suggested and restricted to the codes present in the
72
- * error channel.
90
+ * error channel, and are unrestricted when the channel contains failure types
91
+ * like `Error` that can hold hidden `ORPCError` instances, whose data is unknown.
73
92
  *
74
93
  * Supports both data-first `catchORPCErrorCodes(effect, cases)` and data-last
75
94
  * `effect.pipe(catchORPCErrorCodes(cases))` styles.
@@ -78,7 +97,7 @@ declare const catchORPCErrorCode: {
78
97
  * ```ts
79
98
  * declare const program: Effect.Effect<
80
99
  * string,
81
- * ORPCError<'NOT_FOUND', { id: string }> | ORPCError<'CONFLICT', undefined> | TypeError
100
+ * ORPCError<'NOT_FOUND', { id: string }> | ORPCError<'CONFLICT', undefined>
82
101
  * >
83
102
  *
84
103
  * const recovered = program.pipe(
@@ -93,10 +112,10 @@ declare const catchORPCErrorCode: {
93
112
  */
94
113
  declare const catchORPCErrorCodes: {
95
114
  <E, Cases extends {
96
- [K in Extract<E, AnyORPCError>['code']]?: (error: Extract<E, ORPCError<K, any>>) => Effect.Effect<any, any, any>;
97
- } & (unknown extends E ? object : {
98
- [K in Exclude<keyof Cases, Extract<E, AnyORPCError>['code']>]: never;
99
- })>(cases: Cases): <A, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A | {
115
+ [K in E extends ORPCError<infer TCode, any> ? TCode : AnyORPCError extends E ? ORPCErrorCode : never]?: string extends K ? (error: never) => Effect.Effect<any, any, any> : K extends infer C extends ORPCErrorCode ? (error: E extends AnyORPCError ? Extract<E, ORPCError<C, any>> : AnyORPCError extends E ? ORPCError<C, unknown> : never) => Effect.Effect<any, any, any> : never;
116
+ } & {
117
+ [K in Exclude<keyof Cases, E extends ORPCError<infer TCode, any> ? TCode : AnyORPCError extends E ? ORPCErrorCode : never>]: never;
118
+ }>(cases: Cases): <A, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A | {
100
119
  [K in keyof Cases]: Cases[K] extends (...args: any[]) => Effect.Effect<infer A2, any, any> ? A2 : never;
101
120
  }[keyof Cases], Exclude<E, ORPCError<keyof Cases & ORPCErrorCode, any>> | {
102
121
  [K in keyof Cases]: Cases[K] extends (...args: any[]) => Effect.Effect<any, infer E2, any> ? E2 : never;
@@ -104,10 +123,10 @@ declare const catchORPCErrorCodes: {
104
123
  [K in keyof Cases]: Cases[K] extends (...args: any[]) => Effect.Effect<any, any, infer R2> ? R2 : never;
105
124
  }[keyof Cases]>;
106
125
  <A, E, R, Cases extends {
107
- [K in Extract<E, AnyORPCError>['code']]?: (error: Extract<E, ORPCError<K, any>>) => Effect.Effect<any, any, any>;
108
- } & (unknown extends E ? object : {
109
- [K in Exclude<keyof Cases, Extract<E, AnyORPCError>['code']>]: never;
110
- })>(self: Effect.Effect<A, E, R>, cases: Cases): Effect.Effect<A | {
126
+ [K in E extends ORPCError<infer TCode, any> ? TCode : AnyORPCError extends E ? ORPCErrorCode : never]?: string extends K ? (error: never) => Effect.Effect<any, any, any> : K extends infer C extends ORPCErrorCode ? (error: E extends AnyORPCError ? Extract<E, ORPCError<C, any>> : AnyORPCError extends E ? ORPCError<C, unknown> : never) => Effect.Effect<any, any, any> : never;
127
+ } & {
128
+ [K in Exclude<keyof Cases, E extends ORPCError<infer TCode, any> ? TCode : AnyORPCError extends E ? ORPCErrorCode : never>]: never;
129
+ }>(self: Effect.Effect<A, E, R>, cases: Cases): Effect.Effect<A | {
111
130
  [K in keyof Cases]: Cases[K] extends (...args: any[]) => Effect.Effect<infer A2, any, any> ? A2 : never;
112
131
  }[keyof Cases], Exclude<E, ORPCError<keyof Cases & ORPCErrorCode, any>> | {
113
132
  [K in keyof Cases]: Cases[K] extends (...args: any[]) => Effect.Effect<any, infer E2, any> ? E2 : never;
@@ -126,5 +145,5 @@ declare function runPromise<T>(effect: Effect.Effect<T, unknown>, options?: RunP
126
145
 
127
146
  declare function toStandardSchema<S extends Schema.ConstraintDecoder<any>>(schema: S): Schema$1<S['Encoded'], S['Type']> & S;
128
147
 
129
- export { EffectSchemaToJsonSchemaConverter, catchORPCError, catchORPCErrorCode, catchORPCErrorCodes, runPromise, toStandardSchema };
130
- export type { RunPromiseOptions };
148
+ export { EffectSchemaToJsonSchemaConverter, catchORPCError, catchORPCErrorCode, catchORPCErrorCodes, createEffectClient, runPromise, toStandardSchema };
149
+ export type { EffectClient, RunPromiseOptions };
package/dist/index.mjs CHANGED
@@ -1,11 +1,44 @@
1
+ import { RECURSIVE_CLIENT_UNWRAP_KEYS, resolveClientRest } from '@orpc/client';
2
+ import { isTypescriptObject, anyAbortSignal, getOwn } from '@orpc/shared';
3
+ import { Effect, Schema, Function } from 'effect';
1
4
  import { StandardJsonSchemaConverter } from '@orpc/json-schema';
2
- import { Schema, Function, Effect } from 'effect';
3
5
  import { ORPCError } from '@orpc/server';
4
6
  export { h as handlerGen, r as runPromise } from './shared/experimental-effect.C9oJcd5q.mjs';
5
7
  export { t as toStandardSchema } from './shared/experimental-effect.YQKompVf.mjs';
6
- import '@orpc/shared';
7
8
  import '@orpc/contract';
8
9
 
10
+ function callAsEffect(client, ...rest) {
11
+ const [input, options] = resolveClientRest(rest);
12
+ return Effect.tryPromise({
13
+ try: (signal) => client(input, {
14
+ ...options,
15
+ signal: anyAbortSignal([options.signal, signal])
16
+ }),
17
+ catch: (error) => error
18
+ });
19
+ }
20
+ function createEffectClient(client) {
21
+ const cache = /* @__PURE__ */ new Map();
22
+ const proxy = new Proxy((...rest) => callAsEffect(client, ...rest), {
23
+ get(target, prop) {
24
+ if (typeof prop !== "string" || RECURSIVE_CLIENT_UNWRAP_KEYS.has(prop)) {
25
+ return Reflect.get(target, prop);
26
+ }
27
+ let effectClient = cache.get(prop);
28
+ if (effectClient === void 0) {
29
+ const value = client[prop];
30
+ if (!isTypescriptObject(value)) {
31
+ return value;
32
+ }
33
+ effectClient = createEffectClient(value);
34
+ cache.set(prop, effectClient);
35
+ }
36
+ return effectClient;
37
+ }
38
+ });
39
+ return proxy;
40
+ }
41
+
9
42
  class EffectSchemaToJsonSchemaConverter {
10
43
  converter = new StandardJsonSchemaConverter();
11
44
  condition(schema, _direction) {
@@ -34,10 +67,10 @@ const catchORPCErrorCodes = Function.dual(
34
67
  2,
35
68
  (self, cases) => self.pipe(
36
69
  Effect.catchIf(
37
- (error) => error instanceof ORPCError && typeof cases[error.code] === "function",
38
- (error) => cases[error.code](error)
70
+ (error) => error instanceof ORPCError && typeof getOwn(cases, error.code) === "function",
71
+ (error) => getOwn(cases, error.code)(error)
39
72
  )
40
73
  )
41
74
  );
42
75
 
43
- export { EffectSchemaToJsonSchemaConverter, catchORPCError, catchORPCErrorCode, catchORPCErrorCodes };
76
+ export { EffectSchemaToJsonSchemaConverter, catchORPCError, catchORPCErrorCode, catchORPCErrorCodes, createEffectClient };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@orpc/experimental-effect",
3
3
  "type": "module",
4
- "version": "2.0.0-beta.30",
4
+ "version": "2.0.0-beta.32",
5
5
  "description": "Effect integration for oRPC: build typesafe procedures with Effect",
6
6
  "license": "MIT",
7
7
  "funding": [
@@ -51,13 +51,14 @@
51
51
  "effect": ">=4.0.0-beta.90"
52
52
  },
53
53
  "dependencies": {
54
- "@orpc/contract": "2.0.0-beta.30",
55
- "@orpc/json-schema": "2.0.0-beta.30",
56
- "@orpc/shared": "2.0.0-beta.30",
57
- "@orpc/server": "2.0.0-beta.30"
54
+ "@orpc/client": "2.0.0-beta.32",
55
+ "@orpc/contract": "2.0.0-beta.32",
56
+ "@orpc/json-schema": "2.0.0-beta.32",
57
+ "@orpc/server": "2.0.0-beta.32",
58
+ "@orpc/shared": "2.0.0-beta.32"
58
59
  },
59
60
  "devDependencies": {
60
- "effect": "4.0.0-rc.109"
61
+ "effect": "4.0.0-rc.112"
61
62
  },
62
63
  "scripts": {
63
64
  "build": "unbuild",