@shivaedev/effect-trpc 0.6.0 → 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 (172) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +309 -186
  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 +8 -6
  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 +191 -15
  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/{testing → server/testing}/vitest.ts +17 -17
  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/errors.d.ts +0 -11
  108. package/dist/errors.d.ts.map +0 -1
  109. package/dist/errors.js +0 -11
  110. package/dist/errors.js.map +0 -1
  111. package/dist/internal/context-bridge.d.ts.map +0 -1
  112. package/dist/internal/context-bridge.js.map +0 -1
  113. package/dist/internal/loose.d.ts +0 -4
  114. package/dist/internal/loose.d.ts.map +0 -1
  115. package/dist/internal/loose.js.map +0 -1
  116. package/dist/internal/procedure-handler.d.ts +0 -6
  117. package/dist/internal/procedure-handler.d.ts.map +0 -1
  118. package/dist/internal/procedure-handler.js.map +0 -1
  119. package/dist/internal/procedure-types.d.ts +0 -23
  120. package/dist/internal/procedure-types.d.ts.map +0 -1
  121. package/dist/internal/procedure-types.js +0 -2
  122. package/dist/internal/procedure-types.js.map +0 -1
  123. package/dist/internal/runtime.d.ts +0 -20
  124. package/dist/internal/runtime.d.ts.map +0 -1
  125. package/dist/internal/runtime.js +0 -85
  126. package/dist/internal/runtime.js.map +0 -1
  127. package/dist/internal/stack-trace.d.ts +0 -2
  128. package/dist/internal/stack-trace.d.ts.map +0 -1
  129. package/dist/internal/stack-trace.js.map +0 -1
  130. package/dist/internal/subscription-handler.d.ts +0 -7
  131. package/dist/internal/subscription-handler.d.ts.map +0 -1
  132. package/dist/internal/subscription-handler.js.map +0 -1
  133. package/dist/procedure.d.ts +0 -36
  134. package/dist/procedure.d.ts.map +0 -1
  135. package/dist/procedure.js +0 -38
  136. package/dist/procedure.js.map +0 -1
  137. package/dist/rejection-formatter.d.ts.map +0 -1
  138. package/dist/rejection-formatter.js.map +0 -1
  139. package/dist/rejection.d.ts.map +0 -1
  140. package/dist/rejection.js.map +0 -1
  141. package/dist/request-services.d.ts +0 -7
  142. package/dist/request-services.d.ts.map +0 -1
  143. package/dist/request-services.js +0 -4
  144. package/dist/request-services.js.map +0 -1
  145. package/dist/request-signal.d.ts.map +0 -1
  146. package/dist/request-signal.js.map +0 -1
  147. package/dist/testing/any-test-layer.d.ts.map +0 -1
  148. package/dist/testing/any-test-layer.js +0 -2
  149. package/dist/testing/any-test-layer.js.map +0 -1
  150. package/dist/testing/caller.d.ts +0 -12
  151. package/dist/testing/caller.d.ts.map +0 -1
  152. package/dist/testing/caller.js +0 -42
  153. package/dist/testing/caller.js.map +0 -1
  154. package/dist/testing/types.d.ts +0 -36
  155. package/dist/testing/types.d.ts.map +0 -1
  156. package/dist/testing/vitest.d.ts +0 -6
  157. package/dist/testing/vitest.d.ts.map +0 -1
  158. package/dist/testing/vitest.js.map +0 -1
  159. package/dist/types.d.ts.map +0 -1
  160. package/dist/types.js.map +0 -1
  161. package/src/adapter.ts +0 -72
  162. package/src/errors.ts +0 -19
  163. package/src/internal/loose.ts +0 -14
  164. package/src/internal/procedure-types.ts +0 -44
  165. package/src/internal/runtime.ts +0 -134
  166. package/src/procedure.ts +0 -142
  167. package/src/request-services.ts +0 -23
  168. package/src/testing/types.ts +0 -63
  169. /package/dist/{internal → server/internal}/loose.js +0 -0
  170. /package/dist/{testing → server/testing}/types.js +0 -0
  171. /package/dist/{types.d.ts → server/types.d.ts} +0 -0
  172. /package/dist/{types.js → server/types.js} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
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
+
3
20
  ## 0.6.0 - 2026-10-04
4
21
 
5
22
  ### Changed
package/README.md CHANGED
@@ -1,252 +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 { makeEffectTRPC } from "@shivaedev/effect-trpc/adapter.ts"
25
- import { makeRequestServices } from "@shivaedev/effect-trpc/request-services.ts"
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") {}
26
45
 
27
- const runtime = ManagedRuntime.make(ApplicationLive)
28
- const adapter = makeEffectTRPC({ runtime })
46
+ class RequestId extends Context.Service<RequestId, string>()("example/RequestId") {}
29
47
 
30
- 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();
31
58
  const requestServices = makeRequestServices((context: RequestContext) =>
32
- Layer.succeed(Session, context.session),
33
- )
34
- 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);
35
87
  ```
36
88
 
37
- The generator receives decoded input and may yield anything supplied by the
38
- 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:
39
92
 
40
93
  ```ts
41
- const router = t.router({
42
- movie: procedure
43
- .input(Schema.Struct({ id: Schema.String }))
44
- .query(function* ({ id }) {
45
- const movies = yield* Movies
46
- return yield* movies.find(id)
47
- }),
48
- })
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);
49
102
  ```
50
103
 
51
- Effect Schema transformations are preserved across the tRPC boundary. Input
52
- uses the schema's encoded type at the caller and decoded type in the generator;
53
- output uses the schema's encoded type in the generator and decoded type at the
54
- 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
55
107
 
56
- Subscriptions return an Effect Stream. The request Layer remains open for the
57
- stream's lifetime, transport cancellation interrupts the stream, and scoped
58
- 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.
59
109
 
60
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
+
61
138
  const updates = procedure.subscription(function* () {
62
- const movies = yield* Movies
63
- return movies.updates
64
- })
139
+ const requestId = yield* RequestId;
140
+ return Stream.make(`${requestId}:one`, `${requestId}:two`);
141
+ });
65
142
  ```
66
143
 
67
- `RequestSignal` exposes tRPC's transport signal when application code needs to
68
- combine it with another disconnect signal. Build middleware, metadata, and
69
- other tRPC configuration on the ordinary tRPC procedure builder before passing
70
- 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.
71
145
 
72
- ## Request services
146
+ ### 3. Once per feature: declare client rejections
73
147
 
74
- `extendRequestServices` adds services to an existing request Layer while
75
- 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:
76
149
 
77
150
  ```ts
78
- const adminServices = extendRequestServices(
79
- requestServices,
80
- (context: AdminRequestContext) => Layer.succeed(Admin, context.admin),
81
- )
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
+ }
82
217
  ```
83
218
 
84
- 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.
85
222
 
86
- ## Errors and instrumentation
223
+ ### Every client failure: read or decode the rejection
87
224
 
88
- Explicit `TRPCError` failures pass through unchanged. The package also exports
89
- Effect helpers such as `badRequest`, `unauthorized`, `forbidden`, `notFound`,
90
- 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:
91
226
 
92
227
  ```ts
93
- 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
+ }
94
246
  ```
95
247
 
96
- Unmapped failures and defects become a redacted `INTERNAL_SERVER_ERROR`. Map
97
- 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:
98
255
 
99
256
  ```ts
100
- 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({
101
263
  runtime,
102
264
  mapError: (error) =>
103
- error instanceof MovieMissing
265
+ error instanceof OrderMissing
104
266
  ? new TRPCError({ code: "NOT_FOUND", message: error.message })
105
267
  : undefined,
106
- })
268
+ });
107
269
  ```
108
270
 
109
- Each procedure runs in a span named after its tRPC path. `instrument` and
110
- `instrumentStream` can add application-specific Effect logging, metrics, or
111
- 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`.
112
272
 
113
- ## 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.
114
274
 
115
- A declared rejection is a tagged Schema value the client may act on, such as a
116
- field error. `rejectWith(schema)` sends failures that match `schema` to the
117
- client as data; every other failure keeps the handling above.
275
+ ### Once per test suite: the real router caller
118
276
 
119
- 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:
120
278
 
121
279
  ```ts
122
- import { rejectionFormatter } from "@shivaedev/effect-trpc/rejection-formatter.ts"
123
- import { rejectWith } from "@shivaedev/effect-trpc/rejection.ts"
124
- import { BadRequest, Conflict } from "@shivaedev/platform/errors/taxonomy.ts"
280
+ import { expect } from "@effect/vitest";
281
+ import { makeTrpcIt } from "@shivaedev/effect-trpc/testing/vitest.ts";
125
282
 
126
- const t = initTRPC.context<RequestContext>().create({
127
- errorFormatter: rejectionFormatter,
128
- transformer: superjson,
129
- })
130
-
131
- const rename = procedure
132
- .input(Schema.Struct({ name: Schema.String }))
133
- .mutation(function* ({ name }) {
134
- return yield* profiles.rename(name).pipe(rejectWith(Schema.Union([BadRequest, Conflict])))
135
- })
136
- ```
137
-
138
- `rejectWith` encodes the matching failure with its schema and fails with a
139
- `RejectionError`, a `TRPCError` that carries the encoded value. The formatter
140
- adds it to the error data, next to tRPC's own fields:
141
-
142
- ```jsonc
143
- { "code": "CONFLICT", "httpStatus": 409, "path": "rename",
144
- "rejection": { "_tag": "Conflict", "message": "Name is taken", "field": "name" } }
145
- ```
146
-
147
- - The error message is the rejection's `message` when it has one, otherwise
148
- its `_tag`. Only the encoded declared fields cross; the failure's `cause` and
149
- anything outside the schema never do.
150
- - The code comes from the tag. tRPC derives the HTTP status from the code.
151
- Pass `rejectWith(schema, { code: (tag) => ... })` to choose another code;
152
- `rejectionCode(tag)` is the default mapping:
153
-
154
- | Tag | Code | HTTP |
155
- | --- | --- | --- |
156
- | `NotFound` | `NOT_FOUND` | 404 |
157
- | `Unauthorized` | `UNAUTHORIZED` | 401 |
158
- | `Forbidden` | `FORBIDDEN` | 403 |
159
- | `Conflict` | `CONFLICT` | 409 |
160
- | `PreconditionFailed` | `PRECONDITION_FAILED` | 412 |
161
- | `TooManyRequests` | `TOO_MANY_REQUESTS` | 429 |
162
- | `AuthUnavailable` | `SERVICE_UNAVAILABLE` | 503 |
163
- | any other, including `BadRequest` | `BAD_REQUEST` | 400 |
164
- - When a procedure's input schema rejects the input, the formatter sends a
165
- `BadRequest` rejection with the first issue's message and, unless the issue
166
- concerns the whole input, its path as `field` (`"address.city"`,
167
- `"tags.1"`). The code is `BAD_REQUEST`, as for a `BadRequest` the procedure
168
- raises, so a form shows the error on its field before the handler runs. It
169
- also carries `invalidInput: true`, which a declared rejection never gets, so
170
- a client can show its own copy for input that did not parse and the
171
- declared message otherwise:
172
-
173
- ```jsonc
174
- { "code": "BAD_REQUEST", "httpStatus": 400, "path": "rename",
175
- "rejection": { "_tag": "BadRequest", "message": "Expected a value with a length of at least 1", "field": "name", "invalidInput": true } }
176
- ```
177
-
178
- `invalidInput` is reserved: `rejectWith` does not accept a schema that
179
- encodes a field by that name, and the formatter strips it from any declared
180
- rejection, so only input that did not parse carries it.
181
- - An application with its own `errorFormatter` calls
182
- `withRejection(shape, error)` inside it.
183
- - The error channel loses the declared types and gains `RejectionError`. An
184
- effect-contract operation's `error` schema works directly:
185
- `rejectWith(SaveOrder.error)`.
186
-
187
- The browser-safe `@shivaedev/effect-trpc/client/rejection.ts` module reads the rejection from
188
- 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:
189
301
 
190
302
  ```ts
191
- import { decodeRejection, rejectionOf } from "@shivaedev/effect-trpc/client/rejection.ts"
192
- import { rejectedField } from "@shivaedev/platform/errors/rejected-field.ts"
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
+ ```
193
318
 
194
- rejectionOf(error) // Option<{ _tag: string, invalidInput?: true, ... }>, still encoded
195
- decodeRejection(Schema.Union([BadRequest, Conflict]))(error) // Option<BadRequest | Conflict>
196
- Option.flatMap(rejectionOf(error), rejectedField) // Option<{ field, message }>
197
- 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
+ );
198
328
  ```
199
329
 
200
- Both return `Option.none()` for any error without a tagged rejection, and
201
- `decodeRejection` also for a rejection its schema does not declare.
202
- `rejectionOf` also returns `Option.none()` when `invalidInput` is present but
203
- not `true`. An input failure still decodes as the declared `BadRequest`; the
204
- mark is read from `rejectionOf`, or from `error.data.rejection.invalidInput` on a
205
- `TRPCClientError` typed by the router. The modules under `client/` import 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.
206
335
 
207
- ## 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` |
208
351
 
209
- Install `@effect/vitest` to use the optional `testing/` modules:
352
+ `rejectWith` removes the declared schema types from the Effect's error channel and adds `RejectionError`.
353
+
354
+ ### Install, runtime ownership and limits
210
355
 
211
356
  ```sh
212
- pnpm add --save-dev @effect/vitest vitest
357
+ pnpm add @shivaedev/effect-trpc @trpc/server@11.18.0 effect@4.0.0-rc.112
213
358
  ```
214
359
 
215
- Configure the router caller and test Layer once in the application's test
216
- support:
217
-
218
- ```ts
219
- import { makeTrpcIt } from "@shivaedev/effect-trpc/testing/vitest.ts"
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.
220
361
 
221
- export const it = makeTrpcIt({
222
- adapter,
223
- createCaller: (context = defaultContext) => router.createCaller(context),
224
- layer: TestLive,
225
- })
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
226
365
  ```
227
366
 
228
- Tests receive an Effect-shaped caller. Calling the first argument creates a
229
- caller with different context for that test:
367
+ The Vitest dependency is optional unless you import the testing modules. The application owns runtime shutdown:
230
368
 
231
369
  ```ts
232
- it.effectTRPC("returns a movie", function* (trpc) {
233
- const movie = yield* trpc.movie({ id })
234
- expect(movie.id).toBe(id)
235
- })
236
-
237
- it.effectTRPC("supports another actor", function* (trpc) {
238
- const movie = yield* trpc({ session: otherSession }).movie({ id })
239
- expect(movie.id).toBe(id)
240
- })
241
- ```
242
-
243
- The test Layer is built once per worker. Its services override matching
244
- application runtime services while all other runtime services remain available.
245
- Use `around` to add application-wide test behavior such as a rollback wrapper.
246
- `effectTRPC` supports `skip`, `skipIf`, `runIf`, `only`, `each`, and `fails`.
247
- Tests install TestClock unless you pass `clock: "live"`.
248
-
249
- `makeTrpcHarnessIt` builds an application-defined Effectful harness around the
250
- caller when tests need additional services or fixtures. The harness is created
251
- inside both the test Layer and the `around` wrapper, so setup participates in
252
- 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"}