@shivaedev/effect-trpc 0.5.1 → 0.6.1

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.
Files changed (188) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +309 -187
  3. package/dist/{client/rejection.d.ts → rejectionDecoding.d.ts} +4 -4
  4. package/dist/rejectionDecoding.d.ts.map +1 -0
  5. package/dist/{client/rejection.js → rejectionDecoding.js} +6 -4
  6. package/dist/rejectionDecoding.js.map +1 -0
  7. package/dist/server/EffectProcedureBuilder.d.ts +35 -0
  8. package/dist/server/EffectProcedureBuilder.d.ts.map +1 -0
  9. package/dist/server/EffectProcedureBuilder.js +38 -0
  10. package/dist/server/EffectProcedureBuilder.js.map +1 -0
  11. package/dist/{request-signal.d.ts → server/RequestSignal.d.ts} +1 -1
  12. package/dist/server/RequestSignal.d.ts.map +1 -0
  13. package/dist/{request-signal.js → server/RequestSignal.js} +1 -1
  14. package/dist/server/RequestSignal.js.map +1 -0
  15. package/dist/server/adapter.d.ts +29 -0
  16. package/dist/server/adapter.d.ts.map +1 -0
  17. package/dist/{adapter.js → server/adapter.js} +3 -3
  18. package/dist/server/adapter.js.map +1 -0
  19. package/dist/server/errors.d.ts +11 -0
  20. package/dist/server/errors.d.ts.map +1 -0
  21. package/dist/server/errors.js +27 -0
  22. package/dist/server/errors.js.map +1 -0
  23. package/dist/server/internal/captureStackTrace.d.ts +2 -0
  24. package/dist/server/internal/captureStackTrace.d.ts.map +1 -0
  25. package/dist/{internal/stack-trace.js → server/internal/captureStackTrace.js} +4 -4
  26. package/dist/server/internal/captureStackTrace.js.map +1 -0
  27. package/dist/{internal/context-bridge.d.ts → server/internal/contextBridge.d.ts} +4 -4
  28. package/dist/server/internal/contextBridge.d.ts.map +1 -0
  29. package/dist/{internal/context-bridge.js → server/internal/contextBridge.js} +3 -3
  30. package/dist/server/internal/contextBridge.js.map +1 -0
  31. package/dist/server/internal/loose.d.ts +4 -0
  32. package/dist/server/internal/loose.d.ts.map +1 -0
  33. package/dist/server/internal/loose.js.map +1 -0
  34. package/dist/server/internal/makeProcedureHandler.d.ts +6 -0
  35. package/dist/server/internal/makeProcedureHandler.d.ts.map +1 -0
  36. package/dist/{internal/procedure-handler.js → server/internal/makeProcedureHandler.js} +3 -3
  37. package/dist/server/internal/makeProcedureHandler.js.map +1 -0
  38. package/dist/server/internal/makeSubscriptionHandler.d.ts +7 -0
  39. package/dist/server/internal/makeSubscriptionHandler.d.ts.map +1 -0
  40. package/dist/{internal/subscription-handler.js → server/internal/makeSubscriptionHandler.js} +7 -5
  41. package/dist/server/internal/makeSubscriptionHandler.js.map +1 -0
  42. package/dist/server/internal/procedureTypes.d.ts +23 -0
  43. package/dist/server/internal/procedureTypes.d.ts.map +1 -0
  44. package/dist/server/internal/procedureTypes.js +2 -0
  45. package/dist/server/internal/procedureTypes.js.map +1 -0
  46. package/dist/server/internal/runtime.d.ts +20 -0
  47. package/dist/server/internal/runtime.d.ts.map +1 -0
  48. package/dist/server/internal/runtime.js +94 -0
  49. package/dist/server/internal/runtime.js.map +1 -0
  50. package/dist/{rejection.d.ts → server/rejection.d.ts} +5 -5
  51. package/dist/server/rejection.d.ts.map +1 -0
  52. package/dist/{rejection.js → server/rejection.js} +7 -3
  53. package/dist/server/rejection.js.map +1 -0
  54. package/dist/{rejection-formatter.d.ts → server/rejectionFormatter.d.ts} +6 -6
  55. package/dist/server/rejectionFormatter.d.ts.map +1 -0
  56. package/dist/{rejection-formatter.js → server/rejectionFormatter.js} +13 -9
  57. package/dist/server/rejectionFormatter.js.map +1 -0
  58. package/dist/server/requestServices.d.ts +7 -0
  59. package/dist/server/requestServices.d.ts.map +1 -0
  60. package/dist/server/requestServices.js +8 -0
  61. package/dist/server/requestServices.js.map +1 -0
  62. package/dist/{testing/any-test-layer.d.ts → server/testing/AnyTestLayer.d.ts} +1 -1
  63. package/dist/server/testing/AnyTestLayer.d.ts.map +1 -0
  64. package/dist/server/testing/AnyTestLayer.js +2 -0
  65. package/dist/server/testing/AnyTestLayer.js.map +1 -0
  66. package/dist/server/testing/caller.d.ts +12 -0
  67. package/dist/server/testing/caller.d.ts.map +1 -0
  68. package/dist/server/testing/caller.js +48 -0
  69. package/dist/server/testing/caller.js.map +1 -0
  70. package/dist/server/testing/types.d.ts +36 -0
  71. package/dist/server/testing/types.d.ts.map +1 -0
  72. package/dist/server/testing/types.js.map +1 -0
  73. package/dist/server/testing/vitest.d.ts +6 -0
  74. package/dist/server/testing/vitest.d.ts.map +1 -0
  75. package/dist/{testing → server/testing}/vitest.js +9 -7
  76. package/dist/server/testing/vitest.js.map +1 -0
  77. package/dist/server/types.d.ts.map +1 -0
  78. package/dist/{testing → server}/types.js.map +1 -1
  79. package/docs/north-star.md +48 -0
  80. package/docs/roadmap.md +33 -0
  81. package/package.json +192 -27
  82. package/src/{client/rejection.ts → rejectionDecoding.ts} +7 -5
  83. package/src/server/EffectProcedureBuilder.ts +134 -0
  84. package/src/{request-signal.ts → server/RequestSignal.ts} +0 -1
  85. package/src/server/adapter.ts +83 -0
  86. package/src/server/errors.ts +26 -0
  87. package/src/{internal/stack-trace.ts → server/internal/captureStackTrace.ts} +3 -4
  88. package/src/{internal/context-bridge.ts → server/internal/contextBridge.ts} +4 -8
  89. package/src/server/internal/loose.ts +12 -0
  90. package/src/{internal/procedure-handler.ts → server/internal/makeProcedureHandler.ts} +8 -10
  91. package/src/{internal/subscription-handler.ts → server/internal/makeSubscriptionHandler.ts} +12 -13
  92. package/src/server/internal/procedureTypes.ts +35 -0
  93. package/src/server/internal/runtime.ts +131 -0
  94. package/src/{rejection.ts → server/rejection.ts} +22 -19
  95. package/src/{rejection-formatter.ts → server/rejectionFormatter.ts} +25 -18
  96. package/src/server/requestServices.ts +23 -0
  97. package/src/{testing/any-test-layer.ts → server/testing/AnyTestLayer.ts} +0 -1
  98. package/src/{testing → server/testing}/caller.ts +28 -33
  99. package/src/server/testing/types.ts +55 -0
  100. package/src/server/testing/vitest.ts +47 -0
  101. package/src/{types.ts → server/types.ts} +0 -6
  102. package/dist/adapter.d.ts +0 -29
  103. package/dist/adapter.d.ts.map +0 -1
  104. package/dist/adapter.js.map +0 -1
  105. package/dist/client/rejection.d.ts.map +0 -1
  106. package/dist/client/rejection.js.map +0 -1
  107. package/dist/client.d.ts +0 -2
  108. package/dist/client.d.ts.map +0 -1
  109. package/dist/client.js +0 -2
  110. package/dist/client.js.map +0 -1
  111. package/dist/errors.d.ts +0 -11
  112. package/dist/errors.d.ts.map +0 -1
  113. package/dist/errors.js +0 -11
  114. package/dist/errors.js.map +0 -1
  115. package/dist/index.d.ts +0 -10
  116. package/dist/index.d.ts.map +0 -1
  117. package/dist/index.js +0 -7
  118. package/dist/index.js.map +0 -1
  119. package/dist/internal/context-bridge.d.ts.map +0 -1
  120. package/dist/internal/context-bridge.js.map +0 -1
  121. package/dist/internal/loose.d.ts +0 -4
  122. package/dist/internal/loose.d.ts.map +0 -1
  123. package/dist/internal/loose.js.map +0 -1
  124. package/dist/internal/procedure-handler.d.ts +0 -6
  125. package/dist/internal/procedure-handler.d.ts.map +0 -1
  126. package/dist/internal/procedure-handler.js.map +0 -1
  127. package/dist/internal/procedure-types.d.ts +0 -23
  128. package/dist/internal/procedure-types.d.ts.map +0 -1
  129. package/dist/internal/procedure-types.js +0 -2
  130. package/dist/internal/procedure-types.js.map +0 -1
  131. package/dist/internal/runtime.d.ts +0 -20
  132. package/dist/internal/runtime.d.ts.map +0 -1
  133. package/dist/internal/runtime.js +0 -85
  134. package/dist/internal/runtime.js.map +0 -1
  135. package/dist/internal/stack-trace.d.ts +0 -2
  136. package/dist/internal/stack-trace.d.ts.map +0 -1
  137. package/dist/internal/stack-trace.js.map +0 -1
  138. package/dist/internal/subscription-handler.d.ts +0 -7
  139. package/dist/internal/subscription-handler.d.ts.map +0 -1
  140. package/dist/internal/subscription-handler.js.map +0 -1
  141. package/dist/procedure.d.ts +0 -36
  142. package/dist/procedure.d.ts.map +0 -1
  143. package/dist/procedure.js +0 -38
  144. package/dist/procedure.js.map +0 -1
  145. package/dist/rejection-formatter.d.ts.map +0 -1
  146. package/dist/rejection-formatter.js.map +0 -1
  147. package/dist/rejection.d.ts.map +0 -1
  148. package/dist/rejection.js.map +0 -1
  149. package/dist/request-services.d.ts +0 -7
  150. package/dist/request-services.d.ts.map +0 -1
  151. package/dist/request-services.js +0 -4
  152. package/dist/request-services.js.map +0 -1
  153. package/dist/request-signal.d.ts.map +0 -1
  154. package/dist/request-signal.js.map +0 -1
  155. package/dist/testing/any-test-layer.d.ts.map +0 -1
  156. package/dist/testing/any-test-layer.js +0 -2
  157. package/dist/testing/any-test-layer.js.map +0 -1
  158. package/dist/testing/caller.d.ts +0 -12
  159. package/dist/testing/caller.d.ts.map +0 -1
  160. package/dist/testing/caller.js +0 -42
  161. package/dist/testing/caller.js.map +0 -1
  162. package/dist/testing/types.d.ts +0 -36
  163. package/dist/testing/types.d.ts.map +0 -1
  164. package/dist/testing/vitest.d.ts +0 -6
  165. package/dist/testing/vitest.d.ts.map +0 -1
  166. package/dist/testing/vitest.js.map +0 -1
  167. package/dist/testing.d.ts +0 -4
  168. package/dist/testing.d.ts.map +0 -1
  169. package/dist/testing.js +0 -3
  170. package/dist/testing.js.map +0 -1
  171. package/dist/types.d.ts.map +0 -1
  172. package/dist/types.js.map +0 -1
  173. package/src/adapter.ts +0 -72
  174. package/src/client.ts +0 -1
  175. package/src/errors.ts +0 -19
  176. package/src/index.ts +0 -34
  177. package/src/internal/loose.ts +0 -14
  178. package/src/internal/procedure-types.ts +0 -44
  179. package/src/internal/runtime.ts +0 -134
  180. package/src/procedure.ts +0 -142
  181. package/src/request-services.ts +0 -23
  182. package/src/testing/types.ts +0 -63
  183. package/src/testing/vitest.ts +0 -47
  184. package/src/testing.ts +0 -19
  185. /package/dist/{internal → server/internal}/loose.js +0 -0
  186. /package/dist/{testing → server/testing}/types.js +0 -0
  187. /package/dist/{types.d.ts → server/types.d.ts} +0 -0
  188. /package/dist/{types.js → server/types.js} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.1 - 2026-10-12
4
+
5
+ ### Added
6
+
7
+ - Testing wrappers admit the per-test `Scope` supplied by `effect-test`, preserving compatibility with its scoped runner contract.
8
+ - `@shivaedev/effect-trpc/rejectionDecoding.ts` exposes the existing runtime-independent `EncodedRejection`, `rejectionOf` and `decodeRejection` helpers from shared source code. The existing `@shivaedev/effect-trpc/client/rejection.ts` import path retains the same exports.
9
+
10
+ ### Changed
11
+
12
+ - Document the package's purpose, usage, design priorities and roadmap; publish `docs/` and `README.md` while keeping agent instructions out of the package.
13
+ - Build package JavaScript, declarations and source maps with TypeScript 7.
14
+
15
+ ### Fixed
16
+
17
+ - Redact subscription request-Layer construction defects and application-runtime acquisition failures through the configured error mapper.
18
+ - Map the Platform `InternalServerError` and `ServiceUnavailable` rejection tags to HTTP 500 and 503 respectively.
19
+
20
+ ## 0.6.0 - 2026-10-04
21
+
22
+ ### Changed
23
+
24
+ - Breaking: no root entry and no module that re-exports another. `exports` maps one `"./*.ts"` pattern to every module outside `internal/` (`src` under the `source` condition, `dist` otherwise), and `"./internal/*": null` keeps `internal/` modules private, so you import the module that defines a name, and a bundler sees only the modules you use. Where each name now lives:
25
+ - `@shivaedev/effect-trpc`: `@shivaedev/effect-trpc/client/rejection.ts` (`EncodedRejection`), `@shivaedev/effect-trpc/adapter.ts` (`EffectTRPCAdapter`, `EffectTRPCRuntime`, `MakeEffectTRPCOptions`, `makeEffectTRPC`), `@shivaedev/effect-trpc/errors.ts` (`badRequest`, `conflict`, `fail`, `forbidden`, `internalServerError`, `notFound`, `preconditionFailed`, `unauthorized`), `@shivaedev/effect-trpc/procedure.ts` (`EffectProcedureBuilder`), `@shivaedev/effect-trpc/rejection.ts` (`RejectionError`, `RejectWithOptions`, `rejectionCode`, `rejectWith`), `@shivaedev/effect-trpc/rejection-formatter.ts` (`RejectionData`, `RejectionErrorShape`, `rejectionFormatter`, `withRejection`), `@shivaedev/effect-trpc/request-services.ts` (`EffectProcedureRequestServices`, `extendRequestServices`, `makeRequestServices`), `@shivaedev/effect-trpc/request-signal.ts` (`RequestSignal`), `@shivaedev/effect-trpc/types.ts` (`EffectTRPCErrorContext`, `EffectTRPCErrorMapper`, `EffectTRPCInstrument`, `EffectTRPCStreamInstrument`, `ProcedureInfo`, `ProcedureKind`)
26
+ - `@shivaedev/effect-trpc/client`: `@shivaedev/effect-trpc/client/rejection.ts` (`decodeRejection`, `EncodedRejection`, `rejectionOf`)
27
+ - `@shivaedev/effect-trpc/testing`: `@shivaedev/effect-trpc/testing/caller.ts` (`EffectCaller`, `EffectCallerFactory`, `makeEffectCaller`, `makeEffectCallerFactory`), `@shivaedev/effect-trpc/testing/types.ts` (`CallerOptions`, `CallerResult`, `MakeTrpcHarnessItOptions`, `MakeTrpcItOptions`, `TrpcHarnessIt`, `TrpcHarnessTest`, `TrpcHarnessTester`, `TrpcIt`, `TrpcTest`, `TrpcTester`), `@shivaedev/effect-trpc/testing/vitest.ts` (`makeTrpcHarnessIt`, `makeTrpcIt`)
28
+
3
29
  ## 0.5.1 - 2026-10-04
4
30
 
5
31
  ### Changed
package/README.md CHANGED
@@ -1,253 +1,375 @@
1
- # `@shivaedev/effect-trpc`
1
+ # @shivaedev/effect-trpc
2
2
 
3
- Effect-native query, mutation, and subscription procedures for tRPC, with an optional Vitest
4
- harness for calling routers as Effects.
3
+ Use Effect services, Schemas and Streams inside a tRPC API. Procedures share the application's runtime, get request services from tRPC context, and declare the failures a client may act on without exposing other failures.
5
4
 
6
- This package currently targets exact early-access versions of Effect v4 and
7
- tRPC. It is built for ShivaeDev applications and may change without a
8
- deprecation period.
5
+ ## Why you want this
9
6
 
10
- ## Install
7
+ A tRPC handler is a Promise boundary; your application is made of Effects with services, typed failures and scoped resources. Rebuilding those services in each handler makes the boundary harder to reason about. Bind a procedure once, then write the feature as a generator:
11
8
 
12
- ```sh
13
- pnpm add @shivaedev/effect-trpc @trpc/server effect
9
+ ```ts
10
+ const order = procedure
11
+ .input(Schema.Struct({ id: Schema.String }))
12
+ .query(function* ({ id }) {
13
+ const orders = yield* Orders;
14
+ return yield* orders.find(id);
15
+ });
14
16
  ```
15
17
 
16
- ## Adapter
18
+ The caller sends the input; the generator yields application services and returns its result. The same builder handles mutations and subscriptions, and a test can yield a real router call as an Effect. This package is for applications that use tRPC; Platform's native query and command path is `@shivaedev/effect-contract`.
19
+
20
+ ## Using it
21
+
22
+ ### How to think about it
23
+
24
+ tRPC owns the router, middleware, request context and transport. Effect owns the work inside the procedure: services, typed failures, Streams and scoped resources. The adapter joins those two boundaries.
17
25
 
18
- Create one managed Effect runtime for the application, then bind tRPC procedure
19
- builders to request-scoped Layers:
26
+ There are two service lifetimes. The **application runtime** supplies services shared by procedures. A **request Layer** derives services from the context tRPC passes after middleware. A resolver is a generator that receives decoded input and yields services from either source. The compiler rejects a resolver or Stream that requires a service neither source supplies.
27
+
28
+ There are also two error decisions. A **declared rejection** is a Schema-described failure you explicitly choose to send as client data. A `TRPCError` expresses an explicit transport error. Other failures and defects become an `INTERNAL_SERVER_ERROR` with the message `Internal server error`, unless an application error mapper chooses a public error.
29
+
30
+ ### 1. Once per application: runtime and request services
31
+
32
+ Create the runtime, tRPC instance and procedure builder together. This complete example uses an application service for order lookup and a request service for the request identifier:
20
33
 
21
34
  ```ts
22
- import { initTRPC } from "@trpc/server"
23
- import { Layer, ManagedRuntime, Schema } from "effect"
24
- import {
25
- makeEffectTRPC,
26
- makeRequestServices,
27
- } from "@shivaedev/effect-trpc"
35
+ import { initTRPC } from "@trpc/server";
36
+ import { Context, Effect, Layer, ManagedRuntime, Schema } from "effect";
37
+ import { makeEffectTRPC } from "@shivaedev/effect-trpc/adapter.ts";
38
+ import { makeRequestServices } from "@shivaedev/effect-trpc/request-services.ts";
39
+
40
+ const Order = Schema.Struct({ id: Schema.String, name: Schema.String });
41
+
42
+ class Orders extends Context.Service<Orders, {
43
+ readonly find: (id: string) => Effect.Effect<typeof Order.Type>;
44
+ }>()("example/Orders") {}
28
45
 
29
- const runtime = ManagedRuntime.make(ApplicationLive)
30
- const adapter = makeEffectTRPC({ runtime })
46
+ class RequestId extends Context.Service<RequestId, string>()("example/RequestId") {}
31
47
 
32
- const t = initTRPC.context<RequestContext>().create()
48
+ interface RequestContext {
49
+ readonly requestId: string;
50
+ }
51
+
52
+ const ApplicationLive = Layer.succeed(Orders, {
53
+ find: (id) => Effect.succeed({ id, name: "Desk lamps" }),
54
+ });
55
+ const runtime = ManagedRuntime.make(ApplicationLive);
56
+ const adapter = makeEffectTRPC({ runtime });
57
+ const t = initTRPC.context<RequestContext>().create();
33
58
  const requestServices = makeRequestServices((context: RequestContext) =>
34
- Layer.succeed(Session, context.session),
35
- )
36
- const procedure = adapter.procedure(t.procedure, requestServices)
59
+ Layer.succeed(RequestId, context.requestId),
60
+ );
61
+ const procedure = adapter.procedure(t.procedure, requestServices);
62
+
63
+ export const router = t.router({
64
+ order: procedure.input(Schema.Struct({ id: Schema.String })).query(function* ({ id }) {
65
+ const orders = yield* Orders;
66
+ return { order: yield* orders.find(id), requestId: yield* RequestId };
67
+ }),
68
+ });
69
+
70
+ const result = await router.createCaller({ requestId: "request-1" }).order({ id: "order-1" });
71
+ ```
72
+
73
+ `result` is `{ order: { id: "order-1", name: "Desk lamps" }, requestId: "request-1" }`. The runtime supplies `Orders`; the context supplies `RequestId`. Replace the example lookup Layer with the application's implementation.
74
+
75
+ Build tRPC middleware before calling `adapter.procedure`. A middleware-added context value can become a service:
76
+
77
+ ```ts
78
+ class Actor extends Context.Service<Actor, string>()("example/Actor") {}
79
+
80
+ const authenticated = t.procedure.use(({ ctx, next }) =>
81
+ next({ ctx: { ...ctx, actor: `actor:${ctx.requestId}` } }),
82
+ );
83
+ const actorServices = makeRequestServices((context: RequestContext & { readonly actor: string }) =>
84
+ Layer.succeed(Actor, context.actor),
85
+ );
86
+ const authenticatedProcedure = adapter.procedure(authenticated, actorServices);
37
87
  ```
38
88
 
39
- The generator receives decoded input and may yield anything supplied by the
40
- application runtime or request Layer:
89
+ The example middleware derives a value to show the context boundary. Authentication and authorization policy belong to the application.
90
+
91
+ Use `extendRequestServices(base, additional)` when another procedure needs services derived from the base request Layer:
41
92
 
42
93
  ```ts
43
- const router = t.router({
44
- movie: procedure
45
- .input(Schema.Struct({ id: Schema.String }))
46
- .query(function* ({ id }) {
47
- const movies = yield* Movies
48
- return yield* movies.find(id)
49
- }),
50
- })
94
+ import { extendRequestServices } from "@shivaedev/effect-trpc/request-services.ts";
95
+
96
+ class RequestLabel extends Context.Service<RequestLabel, string>()("example/RequestLabel") {}
97
+
98
+ const labeledServices = extendRequestServices(requestServices, () =>
99
+ Layer.effect(RequestLabel, Effect.map(RequestId, (id) => `request:${id}`)),
100
+ );
101
+ const labeledProcedure = adapter.procedure(t.procedure, labeledServices);
51
102
  ```
52
103
 
53
- Effect Schema transformations are preserved across the tRPC boundary. Input
54
- uses the schema's encoded type at the caller and decoded type in the generator;
55
- output uses the schema's encoded type in the generator and decoded type at the
56
- caller.
104
+ The additional Layer can yield a service supplied by the base Layer. Typed request-Layer failures go through the adapter's error mapper, just like resolver failures; a synchronous Layer-construction defect is redacted.
105
+
106
+ ### 2. Once per feature: input, result and procedure kind
57
107
 
58
- Subscriptions return an Effect Stream. The request Layer remains open for the
59
- stream's lifetime, transport cancellation interrupts the stream, and scoped
60
- finalizers run when it ends:
108
+ `input(schema)` decodes the caller's encoded value into the generator's input. `output(schema)` also decodes: the generator returns the schema's encoded value, and the caller receives its decoded value.
61
109
 
62
110
  ```ts
111
+ const increment = procedure
112
+ .input(Schema.Struct({ value: Schema.NumberFromString }))
113
+ .output(Schema.NumberFromString)
114
+ .query(function* ({ value }) {
115
+ yield* Effect.void;
116
+ return String(value + 1);
117
+ });
118
+ ```
119
+
120
+ For input `{ value: "4" }`, the generator sees `value` as the number `4` and returns the string `"5"`; the caller receives the number `5`. The input and result types follow these transformations.
121
+
122
+ A mutation uses the same generator and service model:
123
+
124
+ ```ts
125
+ const uppercase = procedure.input(Schema.String).mutation(function* (name) {
126
+ yield* Effect.void;
127
+ return name.toUpperCase();
128
+ });
129
+ ```
130
+
131
+ Calling this mutation with `"changed"` returns `"CHANGED"`. Choosing `mutation` makes it a tRPC mutation; transaction policy stays in the application.
132
+
133
+ A subscription generator returns an Effect Stream:
134
+
135
+ ```ts
136
+ import { Stream } from "effect";
137
+
63
138
  const updates = procedure.subscription(function* () {
64
- const movies = yield* Movies
65
- return movies.updates
66
- })
139
+ const requestId = yield* RequestId;
140
+ return Stream.make(`${requestId}:one`, `${requestId}:two`);
141
+ });
67
142
  ```
68
143
 
69
- `RequestSignal` exposes tRPC's transport signal when application code needs to
70
- combine it with another disconnect signal. Build middleware, metadata, and
71
- other tRPC configuration on the ordinary tRPC procedure builder before passing
72
- it to `adapter.procedure`.
144
+ tRPC observes an `AsyncIterable`. The subscription resolver can yield request services. The Stream's scoped finalizers run when it ends, and aborting the tRPC caller's transport signal ends pending stream iteration and runs its finalizers. `output(schema)` decodes each emitted value.
73
145
 
74
- ## Request services
146
+ ### 3. Once per feature: declare client rejections
75
147
 
76
- `extendRequestServices` adds services to an existing request Layer while
77
- retaining its context and requirements:
148
+ Use a tagged Schema for a failure the client may understand. Configure the formatter on the tRPC instance, then apply `rejectWith` to the Effect that may fail:
78
149
 
79
150
  ```ts
80
- const adminServices = extendRequestServices(
81
- requestServices,
82
- (context: AdminRequestContext) => Layer.succeed(Admin, context.admin),
83
- )
151
+ import { rejectionFormatter } from "@shivaedev/effect-trpc/rejection-formatter.ts";
152
+ import { rejectWith } from "@shivaedev/effect-trpc/rejection.ts";
153
+ import superjson from "superjson";
154
+
155
+ class Conflict extends Schema.TaggedError<Conflict>()("Conflict", {
156
+ field: Schema.String,
157
+ message: Schema.String,
158
+ }) {}
159
+
160
+ const publicTRPC = initTRPC.create({
161
+ errorFormatter: rejectionFormatter,
162
+ transformer: superjson,
163
+ });
164
+ const publicProcedure = adapter.procedure(
165
+ publicTRPC.procedure,
166
+ makeRequestServices(() => Layer.empty),
167
+ );
168
+ const rename = publicProcedure.input(Schema.Struct({ name: Schema.String })).mutation(function* ({ name }) {
169
+ if (name === "taken") {
170
+ return yield* Effect.fail(new Conflict({ field: "name", message: "Name is taken" })).pipe(rejectWith(Conflict));
171
+ }
172
+ return `renamed:${name}`;
173
+ });
174
+
175
+ export const publicRouter = publicTRPC.router({ rename });
176
+ export type PublicRouter = typeof publicRouter;
177
+ ```
178
+
179
+ With the formatter installed, the declared failure crosses as `data.rejection`:
180
+
181
+ ```json
182
+ {
183
+ "_tag": "Conflict",
184
+ "field": "name",
185
+ "message": "Name is taken"
186
+ }
187
+ ```
188
+
189
+ `rejectWith` encodes only the declared fields into a `RejectionError`; the error's cause is absent. The error message is its string `message`, or its `_tag` when there is no string message. Successful results, undeclared failures and defects pass through `rejectWith` unchanged. A failure the schema cannot encode becomes a defect.
190
+
191
+ The tag selects the default tRPC code and HTTP status:
192
+
193
+ | Tag | tRPC code | HTTP status |
194
+ | --- | --- | --- |
195
+ | `NotFound` | `NOT_FOUND` | 404 |
196
+ | `Unauthorized` | `UNAUTHORIZED` | 401 |
197
+ | `Forbidden` | `FORBIDDEN` | 403 |
198
+ | `Conflict` | `CONFLICT` | 409 |
199
+ | `PreconditionFailed` | `PRECONDITION_FAILED` | 412 |
200
+ | `TooManyRequests` | `TOO_MANY_REQUESTS` | 429 |
201
+ | `AuthUnavailable` | `SERVICE_UNAVAILABLE` | 503 |
202
+ | Any other tag, including `BadRequest` | `BAD_REQUEST` | 400 |
203
+
204
+ Pass `rejectWith(schema, { code: (tag) => ... })` to choose the code. If the application has its own formatter, return `withRejection(shape, error)` from it; existing shape fields are preserved.
205
+
206
+ An input validation failure uses the same data location. The formatter sends a `BadRequest` with the first issue's message, its dotted field path such as `address.city` or `tags.1`, and `invalidInput: true`. A whole-input issue has no `field`.
207
+
208
+ For `Schema.Struct({ name: Schema.NonEmptyString })` and input `{ name: "" }`, the rejection is:
209
+
210
+ ```json
211
+ {
212
+ "_tag": "BadRequest",
213
+ "field": "name",
214
+ "invalidInput": true,
215
+ "message": "Expected a value with a length of at least 1"
216
+ }
84
217
  ```
85
218
 
86
- Layer construction failures use the same error handling as procedure failures.
219
+ `invalidInput` is reserved for input validation. A declared rejection schema cannot encode that field, and the formatter strips it from a manually constructed declared rejection. The input failure still decodes as a `BadRequest` schema that has `message` and an optional `field`.
220
+
221
+ An operation declared with `@shivaedev/effect-contract` supplies an error schema too. Use `rejectWith(operation.error)` on the server and `decodeRejection(operation.error)` on the client. The generated rejection class and its fields survive that round trip; a contract without declared rejections leaves other failures opaque. This reuses the error vocabulary without changing the application's tRPC transport.
87
222
 
88
- ## Errors and instrumentation
223
+ ### Every client failure: read or decode the rejection
89
224
 
90
- Explicit `TRPCError` failures pass through unchanged. The package also exports
91
- Effect helpers such as `badRequest`, `unauthorized`, `forbidden`, `notFound`,
92
- and `conflict`:
225
+ Keep the rejection Schema in a shared module the client can import. The `Conflict` class above belongs in that module; the client needs the router's type and the rejection Schema, rather than the server runtime. With `PublicRouter` exported from `router.ts` and `Conflict` exported from `rejections.ts`, a client call looks like this:
93
226
 
94
227
  ```ts
95
- return yield* notFound("Movie not found")
228
+ import { createTRPCClient, httpBatchLink } from "@trpc/client";
229
+ import { Option } from "effect";
230
+ import { decodeRejection, rejectionOf } from "@shivaedev/effect-trpc/client/rejection.ts";
231
+ import superjson from "superjson";
232
+ import { Conflict } from "./rejections.ts";
233
+ import type { PublicRouter } from "./router.ts";
234
+
235
+ const client = createTRPCClient<PublicRouter>({
236
+ links: [httpBatchLink({ transformer: superjson, url: "/trpc" })],
237
+ });
238
+
239
+ try {
240
+ await client.rename.mutate({ name: "taken" });
241
+ } catch (error) {
242
+ const encoded = rejectionOf(error);
243
+ const decoded = decodeRejection(Conflict)(error);
244
+ const inputDidNotParse = Option.exists(encoded, (rejection) => rejection.invalidInput === true);
245
+ }
96
246
  ```
97
247
 
98
- Unmapped failures and defects become a redacted `INTERNAL_SERVER_ERROR`. Map
99
- application errors deliberately when they should cross the API boundary:
248
+ `encoded` is an `Option` holding the tagged encoded data. `decoded` is an `Option<Conflict>` holding a decoded class instance. For this call, both contain the `Conflict` rejection and `inputDidNotParse` is false. Both Options are `None` when there is no tagged rejection; decoding also returns `None` when the client schema does not accept it. `rejectionOf` rejects a present `invalidInput` value other than `true`. Client decoding requires a schema with no decoding-service requirements.
249
+
250
+ ### Once per application: explicit errors and instrumentation
251
+
252
+ An explicit `TRPCError` failure retains its code and message. For example, `yield* notFound("Order not found")` from `errors.ts` produces a `NOT_FOUND` failure without declared rejection data.
253
+
254
+ Use `mapError` when an application failure should become a public transport error:
100
255
 
101
256
  ```ts
102
- const adapter = makeEffectTRPC({
257
+ import { TRPCError } from "@trpc/server";
258
+ import { Data } from "effect";
259
+
260
+ class OrderMissing extends Data.TaggedError("OrderMissing")<{ readonly message: string }> {}
261
+
262
+ const mappedAdapter = makeEffectTRPC({
103
263
  runtime,
104
264
  mapError: (error) =>
105
- error instanceof MovieMissing
265
+ error instanceof OrderMissing
106
266
  ? new TRPCError({ code: "NOT_FOUND", message: error.message })
107
267
  : undefined,
108
- })
268
+ });
109
269
  ```
110
270
 
111
- Each procedure runs in a span named after its tRPC path. `instrument` and
112
- `instrumentStream` can add application-specific Effect logging, metrics, or
113
- tracing without changing procedure definitions.
271
+ The mapper receives the procedure path and an `origin` of `failure` or `defect`. Returning `undefined` keeps the internal error redacted. A defect thrown by the mapper is also redacted. Pure Effect interruption maps to `CLIENT_CLOSED_REQUEST` with `Request cancelled`.
114
272
 
115
- ## Declared rejections
273
+ `instrument(effect, procedure)` wraps resolver work and can read request services. `instrumentStream(stream, procedure)` wraps subscription streams. Both receive the procedure path and kind; an Effect-instrumentation defect is redacted.
116
274
 
117
- A declared rejection is a tagged Schema value the client may act on, such as a
118
- field error. `rejectWith(schema)` sends failures that match `schema` to the
119
- client as data; every other failure keeps the handling above.
275
+ ### Once per test suite: the real router caller
120
276
 
121
- Install the formatter once, then declare the rejections a procedure may send:
277
+ Create the test helper in the application's test support. These definitions continue the first complete example:
122
278
 
123
279
  ```ts
124
- import { rejectionFormatter, rejectWith } from "@shivaedev/effect-trpc"
125
- import { BadRequest, Conflict } from "@shivaedev/platform/errors"
280
+ import { expect } from "@effect/vitest";
281
+ import { makeTrpcIt } from "@shivaedev/effect-trpc/testing/vitest.ts";
126
282
 
127
- const t = initTRPC.context<RequestContext>().create({
128
- errorFormatter: rejectionFormatter,
129
- transformer: superjson,
130
- })
131
-
132
- const rename = procedure
133
- .input(Schema.Struct({ name: Schema.String }))
134
- .mutation(function* ({ name }) {
135
- return yield* profiles.rename(name).pipe(rejectWith(Schema.Union([BadRequest, Conflict])))
136
- })
137
- ```
138
-
139
- `rejectWith` encodes the matching failure with its schema and fails with a
140
- `RejectionError`, a `TRPCError` that carries the encoded value. The formatter
141
- adds it to the error data, next to tRPC's own fields:
142
-
143
- ```jsonc
144
- { "code": "CONFLICT", "httpStatus": 409, "path": "rename",
145
- "rejection": { "_tag": "Conflict", "message": "Name is taken", "field": "name" } }
146
- ```
147
-
148
- - The error message is the rejection's `message` when it has one, otherwise
149
- its `_tag`. Only the encoded declared fields cross; the failure's `cause` and
150
- anything outside the schema never do.
151
- - The code comes from the tag. tRPC derives the HTTP status from the code.
152
- Pass `rejectWith(schema, { code: (tag) => ... })` to choose another code;
153
- `rejectionCode(tag)` is the default mapping:
154
-
155
- | Tag | Code | HTTP |
156
- | --- | --- | --- |
157
- | `NotFound` | `NOT_FOUND` | 404 |
158
- | `Unauthorized` | `UNAUTHORIZED` | 401 |
159
- | `Forbidden` | `FORBIDDEN` | 403 |
160
- | `Conflict` | `CONFLICT` | 409 |
161
- | `PreconditionFailed` | `PRECONDITION_FAILED` | 412 |
162
- | `TooManyRequests` | `TOO_MANY_REQUESTS` | 429 |
163
- | `AuthUnavailable` | `SERVICE_UNAVAILABLE` | 503 |
164
- | any other, including `BadRequest` | `BAD_REQUEST` | 400 |
165
- - When a procedure's input schema rejects the input, the formatter sends a
166
- `BadRequest` rejection with the first issue's message and, unless the issue
167
- concerns the whole input, its path as `field` (`"address.city"`,
168
- `"tags.1"`). The code is `BAD_REQUEST`, as for a `BadRequest` the procedure
169
- raises, so a form shows the error on its field before the handler runs. It
170
- also carries `invalidInput: true`, which a declared rejection never gets, so
171
- a client can show its own copy for input that did not parse and the
172
- declared message otherwise:
173
-
174
- ```jsonc
175
- { "code": "BAD_REQUEST", "httpStatus": 400, "path": "rename",
176
- "rejection": { "_tag": "BadRequest", "message": "Expected a value with a length of at least 1", "field": "name", "invalidInput": true } }
177
- ```
178
-
179
- `invalidInput` is reserved: `rejectWith` does not accept a schema that
180
- encodes a field by that name, and the formatter strips it from any declared
181
- rejection, so only input that did not parse carries it.
182
- - An application with its own `errorFormatter` calls
183
- `withRejection(shape, error)` inside it.
184
- - The error channel loses the declared types and gains `RejectionError`. An
185
- effect-contract operation's `error` schema works directly:
186
- `rejectWith(SaveOrder.error)`.
187
-
188
- The browser-safe `@shivaedev/effect-trpc/client` entry reads the rejection from
189
- a `TRPCClientError`:
283
+ const TestLive = Layer.succeed(Orders, {
284
+ find: (id) => Effect.succeed({ id, name: "Test lamps" }),
285
+ });
286
+ const it = makeTrpcIt({
287
+ adapter,
288
+ createCaller: (context = { requestId: "default" }) => router.createCaller(context),
289
+ layer: TestLive,
290
+ });
291
+
292
+ it.effectTRPC("reads the order for this request", function* (trpc) {
293
+ const result = yield* trpc({ requestId: "other" }).order({ id: "order-1" });
294
+ expect(result).toEqual({ order: { id: "order-1", name: "Test lamps" }, requestId: "other" });
295
+ });
296
+ ```
297
+
298
+ The test yields the real router's caller as an Effect. Test services override matching runtime services, while other runtime services remain available. `trpc.order(...)` uses the default context; `trpc(context).order(...)` chooses another context. Caller inputs, outputs and context retain their types.
299
+
300
+ Use `makeTrpcHarnessIt` to return application fixtures together with the caller:
190
301
 
191
302
  ```ts
192
- import { decodeRejection, rejectionOf } from "@shivaedev/effect-trpc/client"
193
- import { rejectedField } from "@shivaedev/platform/errors"
303
+ import { makeTrpcHarnessIt } from "@shivaedev/effect-trpc/testing/vitest.ts";
304
+
305
+ const harnessIt = makeTrpcHarnessIt({
306
+ adapter,
307
+ createCaller: (context = { requestId: "default" }) => router.createCaller(context),
308
+ layer: TestLive,
309
+ makeHarness: (trpc) => Effect.map(Orders, (orders) => ({ orders, trpc })),
310
+ });
311
+
312
+ harnessIt.effectTRPC("provides the caller and fixture services", function* ({ orders, trpc }) {
313
+ const expected = yield* orders.find("order-1");
314
+ const result = yield* trpc.order({ id: "order-1" });
315
+ expect(result.order).toEqual(expected);
316
+ });
317
+ ```
194
318
 
195
- rejectionOf(error) // Option<{ _tag: string, invalidInput?: true, ... }>, still encoded
196
- decodeRejection(Schema.Union([BadRequest, Conflict]))(error) // Option<BadRequest | Conflict>
197
- Option.flatMap(rejectionOf(error), rejectedField) // Option<{ field, message }>
198
- Option.exists(rejectionOf(error), (rejection) => rejection.invalidInput === true) // the input did not parse
319
+ Harness creation runs with the test Layer's services. The testing module delegates to `@shivaedev/effect-test`; its runner options include `around` and `clock`. See [Effect Test](https://github.com/ShivaeDev/platform/tree/main/packages/effect-test#readme) for the runner's lifecycle and clock model.
320
+
321
+ For a caller wrapper outside Vitest, `makeEffectCaller(adapter, caller, services)` turns an existing caller into an Effect-shaped caller. `makeEffectCallerFactory(adapter, createCaller, services)` adds the context-selection form used above. The adapter's `runWithServices(services, evaluate)` is the bridge for those overrides:
322
+
323
+ ```ts
324
+ const resultWithOverride = await adapter.runWithServices(
325
+ Context.make(Orders, { find: (id) => Effect.succeed({ id, name: "Override lamps" }) }),
326
+ () => router.createCaller({ requestId: "ambient" }).order({ id: "order-1" }),
327
+ );
199
328
  ```
200
329
 
201
- Both return `Option.none()` for any error without a tagged rejection, and
202
- `decodeRejection` also for a rejection its schema does not declare.
203
- `rejectionOf` also returns `Option.none()` when `invalidInput` is present but
204
- not `true`. An input failure still decodes as the declared `BadRequest`; the
205
- mark is read from `rejectionOf`, or from `error.data.rejection.invalidInput` on a
206
- `TRPCClientError` typed by the router. The client entry imports only `effect`.
330
+ The supplied services override matching application runtime services; other runtime services remain available. Request services still come from the caller's context.
331
+
332
+ ### API by module
333
+
334
+ Import each name from its defining `@shivaedev/effect-trpc/<module>.ts`; there is no root entry.
207
335
 
208
- ## Vitest
336
+ | Module | Public names |
337
+ | --- | --- |
338
+ | `adapter.ts` | `makeEffectTRPC`, `EffectTRPCAdapter` (`procedure`, `runWithServices`), `EffectTRPCRuntime`, `MakeEffectTRPCOptions` |
339
+ | `procedure.ts` | `EffectProcedureBuilder`: `input`, `output`, `query`, `mutation`, `subscription` |
340
+ | `request-services.ts` | `makeRequestServices`, `extendRequestServices`, `EffectProcedureRequestServices` |
341
+ | `request-signal.ts` | `RequestSignal`, an Effect reference typed as `AbortSignal \| undefined` |
342
+ | `types.ts` | `ProcedureKind`, `ProcedureInfo`, `EffectTRPCErrorContext`, `EffectTRPCErrorMapper`, `EffectTRPCInstrument`, `EffectTRPCStreamInstrument` |
343
+ | `errors.ts` | `fail`, `badRequest`, `unauthorized`, `forbidden`, `notFound`, `conflict`, `preconditionFailed`, `internalServerError` |
344
+ | `rejection.ts` | `rejectWith`, `rejectionCode`, `RejectionError`, `DeclaredRejection`, `RejectWithOptions` |
345
+ | `rejection-formatter.ts` | `rejectionFormatter`, `withRejection`, `RejectionData`, `RejectionErrorShape` |
346
+ | `rejectionDecoding.ts`, `client/rejection.ts` | `rejectionOf`, `decodeRejection`, `EncodedRejection` |
347
+ | `testing/caller.ts` | `makeEffectCaller`, `makeEffectCallerFactory`, `EffectCaller`, `EffectCallerFactory` |
348
+ | `testing/vitest.ts` | `makeTrpcIt`, `makeTrpcHarnessIt` |
349
+ | `testing/types.ts` | `CallerOptions`, `CallerResult`, `MakeTrpcItOptions`, `MakeTrpcHarnessItOptions`, `TrpcIt`, `TrpcTest`, `TrpcTester`, `TrpcHarnessIt`, `TrpcHarnessTest`, `TrpcHarnessTester` |
350
+ | `testing/any-test-layer.ts` | `AnyTestLayer` |
209
351
 
210
- Install `@effect/vitest` to use the optional testing entrypoint:
352
+ `rejectWith` removes the declared schema types from the Effect's error channel and adds `RejectionError`.
353
+
354
+ ### Install, runtime ownership and limits
211
355
 
212
356
  ```sh
213
- pnpm add --save-dev @effect/vitest vitest
357
+ pnpm add @shivaedev/effect-trpc @trpc/server@11.18.0 effect@4.0.0-rc.112
214
358
  ```
215
359
 
216
- Configure the router caller and test Layer once in the application's test
217
- support:
218
-
219
- ```ts
220
- import { makeTrpcIt } from "@shivaedev/effect-trpc/testing"
360
+ Use the exact peer versions in this package's `package.json`. Node.js 24 or later is required. The rejection example also uses `superjson`; configure the same transformer on the tRPC client.
221
361
 
222
- export const it = makeTrpcIt({
223
- adapter,
224
- createCaller: (context = defaultContext) => router.createCaller(context),
225
- layer: TestLive,
226
- })
362
+ ```sh
363
+ pnpm add @trpc/client@11.18.0 superjson@2.2.6
364
+ pnpm add --save-dev @effect/vitest@4.0.0-rc.112 vitest@4.1.11
227
365
  ```
228
366
 
229
- Tests receive an Effect-shaped caller. Calling the first argument creates a
230
- caller with different context for that test:
367
+ The Vitest dependency is optional unless you import the testing modules. The application owns runtime shutdown:
231
368
 
232
369
  ```ts
233
- it.effectTRPC("returns a movie", function* (trpc) {
234
- const movie = yield* trpc.movie({ id })
235
- expect(movie.id).toBe(id)
236
- })
237
-
238
- it.effectTRPC("supports another actor", function* (trpc) {
239
- const movie = yield* trpc({ session: otherSession }).movie({ id })
240
- expect(movie.id).toBe(id)
241
- })
242
- ```
243
-
244
- The test Layer is built once per worker. Its services override matching
245
- application runtime services while all other runtime services remain available.
246
- Use `around` to add application-wide test behavior such as a rollback wrapper.
247
- `effectTRPC` supports `skip`, `skipIf`, `runIf`, `only`, `each`, and `fails`.
248
- Tests install TestClock unless you pass `clock: "live"`.
249
-
250
- `makeTrpcHarnessIt` builds an application-defined Effectful harness around the
251
- caller when tests need additional services or fixtures. The harness is created
252
- inside both the test Layer and the `around` wrapper, so setup participates in
253
- the same scoped lifecycle as the test body.
370
+ await runtime.dispose();
371
+ ```
372
+
373
+ Choose an application server and tRPC transport separately. The rejection tests exercise tRPC's fetch handler and client in process; the cancellation test exercises a caller signal and Stream iterator. Those checks do not establish deployed HTTP or WebSocket disconnect behavior through a host or proxy. Test that boundary in the consuming application.
374
+
375
+ For the package's design priorities, read [north star](https://github.com/ShivaeDev/platform/blob/main/packages/effect-trpc/docs/north-star.md). Implementation status, next work and maintainer questions live in the [roadmap](https://github.com/ShivaeDev/platform/blob/main/packages/effect-trpc/docs/roadmap.md).
@@ -4,8 +4,8 @@ export declare const EncodedRejection: Schema.StructWithRest<Schema.Struct<{
4
4
  readonly invalidInput: Schema.optionalKey<Schema.Literal<true>>;
5
5
  }>, readonly [Schema.$Record<Schema.String, Schema.Unknown>]>;
6
6
  export type EncodedRejection = typeof EncodedRejection.Type;
7
- export declare const rejectionOf: (error: unknown) => Option.Option<EncodedRejection>;
8
- export declare const decodeRejection: <S extends Schema.ConstraintDecoder<{
7
+ export declare function rejectionOf(error: unknown): Option.Option<EncodedRejection>;
8
+ export declare function decodeRejection<TSchema extends Schema.ConstraintDecoder<{
9
9
  readonly _tag: string;
10
- }>>(schema: S) => ((error: unknown) => Option.Option<S["Type"]>);
11
- //# sourceMappingURL=rejection.d.ts.map
10
+ }>>(schema: TSchema): (error: unknown) => Option.Option<TSchema["Type"]>;
11
+ //# sourceMappingURL=rejectionDecoding.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rejectionDecoding.d.ts","sourceRoot":"","sources":["../src/rejectionDecoding.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAExC,eAAO,MAAM,gBAAgB;;;6DAG5B,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,OAAO,gBAAgB,CAAC,IAAI,CAAC;AAI5D,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAE3E;AAED,wBAAgB,eAAe,CAAC,OAAO,SAAS,MAAM,CAAC,iBAAiB,CAAC;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,EAClG,MAAM,EAAE,OAAO,GACb,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAGpD"}
@@ -1,9 +1,11 @@
1
1
  import { Option, Schema } from "effect";
2
2
  export const EncodedRejection = Schema.StructWithRest(Schema.Struct({ _tag: Schema.String, invalidInput: Schema.optionalKey(Schema.Literal(true)) }), [Schema.Record(Schema.String, Schema.Unknown)]);
3
3
  const decodeCarrier = Schema.decodeUnknownOption(Schema.Struct({ data: Schema.Struct({ rejection: EncodedRejection }) }));
4
- export const rejectionOf = (error) => Option.map(decodeCarrier(error), ({ data }) => data.rejection);
5
- export const decodeRejection = (schema) => {
4
+ export function rejectionOf(error) {
5
+ return Option.map(decodeCarrier(error), ({ data }) => data.rejection);
6
+ }
7
+ export function decodeRejection(schema) {
6
8
  const decode = Schema.decodeUnknownOption(schema);
7
9
  return (error) => Option.flatMap(rejectionOf(error), (rejection) => decode(rejection));
8
- };
9
- //# sourceMappingURL=rejection.js.map
10
+ }
11
+ //# sourceMappingURL=rejectionDecoding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rejectionDecoding.js","sourceRoot":"","sources":["../src/rejectionDecoding.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAExC,MAAM,CAAC,MAAM,gBAAgB,GAAG,MAAM,CAAC,cAAc,CACpD,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,EAAE,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,EAC9F,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAC9C,CAAC;AAIF,MAAM,aAAa,GAAG,MAAM,CAAC,mBAAmB,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,gBAAgB,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAE1H,MAAM,UAAU,WAAW,CAAC,KAAc;IACzC,OAAO,MAAM,CAAC,GAAG,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;AACvE,CAAC;AAED,MAAM,UAAU,eAAe,CAC9B,MAAe;IAEf,MAAM,MAAM,GAAG,MAAM,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAClD,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC;AACxF,CAAC"}