@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.
- package/CHANGELOG.md +26 -0
- package/README.md +309 -187
- package/dist/{client/rejection.d.ts → rejectionDecoding.d.ts} +4 -4
- package/dist/rejectionDecoding.d.ts.map +1 -0
- package/dist/{client/rejection.js → rejectionDecoding.js} +6 -4
- package/dist/rejectionDecoding.js.map +1 -0
- package/dist/server/EffectProcedureBuilder.d.ts +35 -0
- package/dist/server/EffectProcedureBuilder.d.ts.map +1 -0
- package/dist/server/EffectProcedureBuilder.js +38 -0
- package/dist/server/EffectProcedureBuilder.js.map +1 -0
- package/dist/{request-signal.d.ts → server/RequestSignal.d.ts} +1 -1
- package/dist/server/RequestSignal.d.ts.map +1 -0
- package/dist/{request-signal.js → server/RequestSignal.js} +1 -1
- package/dist/server/RequestSignal.js.map +1 -0
- package/dist/server/adapter.d.ts +29 -0
- package/dist/server/adapter.d.ts.map +1 -0
- package/dist/{adapter.js → server/adapter.js} +3 -3
- package/dist/server/adapter.js.map +1 -0
- package/dist/server/errors.d.ts +11 -0
- package/dist/server/errors.d.ts.map +1 -0
- package/dist/server/errors.js +27 -0
- package/dist/server/errors.js.map +1 -0
- package/dist/server/internal/captureStackTrace.d.ts +2 -0
- package/dist/server/internal/captureStackTrace.d.ts.map +1 -0
- package/dist/{internal/stack-trace.js → server/internal/captureStackTrace.js} +4 -4
- package/dist/server/internal/captureStackTrace.js.map +1 -0
- package/dist/{internal/context-bridge.d.ts → server/internal/contextBridge.d.ts} +4 -4
- package/dist/server/internal/contextBridge.d.ts.map +1 -0
- package/dist/{internal/context-bridge.js → server/internal/contextBridge.js} +3 -3
- package/dist/server/internal/contextBridge.js.map +1 -0
- package/dist/server/internal/loose.d.ts +4 -0
- package/dist/server/internal/loose.d.ts.map +1 -0
- package/dist/server/internal/loose.js.map +1 -0
- package/dist/server/internal/makeProcedureHandler.d.ts +6 -0
- package/dist/server/internal/makeProcedureHandler.d.ts.map +1 -0
- package/dist/{internal/procedure-handler.js → server/internal/makeProcedureHandler.js} +3 -3
- package/dist/server/internal/makeProcedureHandler.js.map +1 -0
- package/dist/server/internal/makeSubscriptionHandler.d.ts +7 -0
- package/dist/server/internal/makeSubscriptionHandler.d.ts.map +1 -0
- package/dist/{internal/subscription-handler.js → server/internal/makeSubscriptionHandler.js} +7 -5
- package/dist/server/internal/makeSubscriptionHandler.js.map +1 -0
- package/dist/server/internal/procedureTypes.d.ts +23 -0
- package/dist/server/internal/procedureTypes.d.ts.map +1 -0
- package/dist/server/internal/procedureTypes.js +2 -0
- package/dist/server/internal/procedureTypes.js.map +1 -0
- package/dist/server/internal/runtime.d.ts +20 -0
- package/dist/server/internal/runtime.d.ts.map +1 -0
- package/dist/server/internal/runtime.js +94 -0
- package/dist/server/internal/runtime.js.map +1 -0
- package/dist/{rejection.d.ts → server/rejection.d.ts} +5 -5
- package/dist/server/rejection.d.ts.map +1 -0
- package/dist/{rejection.js → server/rejection.js} +7 -3
- package/dist/server/rejection.js.map +1 -0
- package/dist/{rejection-formatter.d.ts → server/rejectionFormatter.d.ts} +6 -6
- package/dist/server/rejectionFormatter.d.ts.map +1 -0
- package/dist/{rejection-formatter.js → server/rejectionFormatter.js} +13 -9
- package/dist/server/rejectionFormatter.js.map +1 -0
- package/dist/server/requestServices.d.ts +7 -0
- package/dist/server/requestServices.d.ts.map +1 -0
- package/dist/server/requestServices.js +8 -0
- package/dist/server/requestServices.js.map +1 -0
- package/dist/{testing/any-test-layer.d.ts → server/testing/AnyTestLayer.d.ts} +1 -1
- package/dist/server/testing/AnyTestLayer.d.ts.map +1 -0
- package/dist/server/testing/AnyTestLayer.js +2 -0
- package/dist/server/testing/AnyTestLayer.js.map +1 -0
- package/dist/server/testing/caller.d.ts +12 -0
- package/dist/server/testing/caller.d.ts.map +1 -0
- package/dist/server/testing/caller.js +48 -0
- package/dist/server/testing/caller.js.map +1 -0
- package/dist/server/testing/types.d.ts +36 -0
- package/dist/server/testing/types.d.ts.map +1 -0
- package/dist/server/testing/types.js.map +1 -0
- package/dist/server/testing/vitest.d.ts +6 -0
- package/dist/server/testing/vitest.d.ts.map +1 -0
- package/dist/{testing → server/testing}/vitest.js +9 -7
- package/dist/server/testing/vitest.js.map +1 -0
- package/dist/server/types.d.ts.map +1 -0
- package/dist/{testing → server}/types.js.map +1 -1
- package/docs/north-star.md +48 -0
- package/docs/roadmap.md +33 -0
- package/package.json +192 -27
- package/src/{client/rejection.ts → rejectionDecoding.ts} +7 -5
- package/src/server/EffectProcedureBuilder.ts +134 -0
- package/src/{request-signal.ts → server/RequestSignal.ts} +0 -1
- package/src/server/adapter.ts +83 -0
- package/src/server/errors.ts +26 -0
- package/src/{internal/stack-trace.ts → server/internal/captureStackTrace.ts} +3 -4
- package/src/{internal/context-bridge.ts → server/internal/contextBridge.ts} +4 -8
- package/src/server/internal/loose.ts +12 -0
- package/src/{internal/procedure-handler.ts → server/internal/makeProcedureHandler.ts} +8 -10
- package/src/{internal/subscription-handler.ts → server/internal/makeSubscriptionHandler.ts} +12 -13
- package/src/server/internal/procedureTypes.ts +35 -0
- package/src/server/internal/runtime.ts +131 -0
- package/src/{rejection.ts → server/rejection.ts} +22 -19
- package/src/{rejection-formatter.ts → server/rejectionFormatter.ts} +25 -18
- package/src/server/requestServices.ts +23 -0
- package/src/{testing/any-test-layer.ts → server/testing/AnyTestLayer.ts} +0 -1
- package/src/{testing → server/testing}/caller.ts +28 -33
- package/src/server/testing/types.ts +55 -0
- package/src/server/testing/vitest.ts +47 -0
- package/src/{types.ts → server/types.ts} +0 -6
- package/dist/adapter.d.ts +0 -29
- package/dist/adapter.d.ts.map +0 -1
- package/dist/adapter.js.map +0 -1
- package/dist/client/rejection.d.ts.map +0 -1
- package/dist/client/rejection.js.map +0 -1
- package/dist/client.d.ts +0 -2
- package/dist/client.d.ts.map +0 -1
- package/dist/client.js +0 -2
- package/dist/client.js.map +0 -1
- package/dist/errors.d.ts +0 -11
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js +0 -11
- package/dist/errors.js.map +0 -1
- package/dist/index.d.ts +0 -10
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -7
- package/dist/index.js.map +0 -1
- package/dist/internal/context-bridge.d.ts.map +0 -1
- package/dist/internal/context-bridge.js.map +0 -1
- package/dist/internal/loose.d.ts +0 -4
- package/dist/internal/loose.d.ts.map +0 -1
- package/dist/internal/loose.js.map +0 -1
- package/dist/internal/procedure-handler.d.ts +0 -6
- package/dist/internal/procedure-handler.d.ts.map +0 -1
- package/dist/internal/procedure-handler.js.map +0 -1
- package/dist/internal/procedure-types.d.ts +0 -23
- package/dist/internal/procedure-types.d.ts.map +0 -1
- package/dist/internal/procedure-types.js +0 -2
- package/dist/internal/procedure-types.js.map +0 -1
- package/dist/internal/runtime.d.ts +0 -20
- package/dist/internal/runtime.d.ts.map +0 -1
- package/dist/internal/runtime.js +0 -85
- package/dist/internal/runtime.js.map +0 -1
- package/dist/internal/stack-trace.d.ts +0 -2
- package/dist/internal/stack-trace.d.ts.map +0 -1
- package/dist/internal/stack-trace.js.map +0 -1
- package/dist/internal/subscription-handler.d.ts +0 -7
- package/dist/internal/subscription-handler.d.ts.map +0 -1
- package/dist/internal/subscription-handler.js.map +0 -1
- package/dist/procedure.d.ts +0 -36
- package/dist/procedure.d.ts.map +0 -1
- package/dist/procedure.js +0 -38
- package/dist/procedure.js.map +0 -1
- package/dist/rejection-formatter.d.ts.map +0 -1
- package/dist/rejection-formatter.js.map +0 -1
- package/dist/rejection.d.ts.map +0 -1
- package/dist/rejection.js.map +0 -1
- package/dist/request-services.d.ts +0 -7
- package/dist/request-services.d.ts.map +0 -1
- package/dist/request-services.js +0 -4
- package/dist/request-services.js.map +0 -1
- package/dist/request-signal.d.ts.map +0 -1
- package/dist/request-signal.js.map +0 -1
- package/dist/testing/any-test-layer.d.ts.map +0 -1
- package/dist/testing/any-test-layer.js +0 -2
- package/dist/testing/any-test-layer.js.map +0 -1
- package/dist/testing/caller.d.ts +0 -12
- package/dist/testing/caller.d.ts.map +0 -1
- package/dist/testing/caller.js +0 -42
- package/dist/testing/caller.js.map +0 -1
- package/dist/testing/types.d.ts +0 -36
- package/dist/testing/types.d.ts.map +0 -1
- package/dist/testing/vitest.d.ts +0 -6
- package/dist/testing/vitest.d.ts.map +0 -1
- package/dist/testing/vitest.js.map +0 -1
- package/dist/testing.d.ts +0 -4
- package/dist/testing.d.ts.map +0 -1
- package/dist/testing.js +0 -3
- package/dist/testing.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/src/adapter.ts +0 -72
- package/src/client.ts +0 -1
- package/src/errors.ts +0 -19
- package/src/index.ts +0 -34
- package/src/internal/loose.ts +0 -14
- package/src/internal/procedure-types.ts +0 -44
- package/src/internal/runtime.ts +0 -134
- package/src/procedure.ts +0 -142
- package/src/request-services.ts +0 -23
- package/src/testing/types.ts +0 -63
- package/src/testing/vitest.ts +0 -47
- package/src/testing.ts +0 -19
- /package/dist/{internal → server/internal}/loose.js +0 -0
- /package/dist/{testing → server/testing}/types.js +0 -0
- /package/dist/{types.d.ts → server/types.d.ts} +0 -0
- /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
|
-
#
|
|
1
|
+
# @shivaedev/effect-trpc
|
|
2
2
|
|
|
3
|
-
Effect
|
|
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
|
-
|
|
7
|
-
tRPC. It is built for ShivaeDev applications and may change without a
|
|
8
|
-
deprecation period.
|
|
5
|
+
## Why you want this
|
|
9
6
|
|
|
10
|
-
|
|
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
|
-
```
|
|
13
|
-
|
|
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
|
-
|
|
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
|
-
|
|
19
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
30
|
-
const adapter = makeEffectTRPC({ runtime })
|
|
46
|
+
class RequestId extends Context.Service<RequestId, string>()("example/RequestId") {}
|
|
31
47
|
|
|
32
|
-
|
|
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(
|
|
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
|
|
40
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
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
|
|
65
|
-
return
|
|
66
|
-
})
|
|
139
|
+
const requestId = yield* RequestId;
|
|
140
|
+
return Stream.make(`${requestId}:one`, `${requestId}:two`);
|
|
141
|
+
});
|
|
67
142
|
```
|
|
68
143
|
|
|
69
|
-
`
|
|
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
|
-
|
|
146
|
+
### 3. Once per feature: declare client rejections
|
|
75
147
|
|
|
76
|
-
`
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
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
|
-
|
|
223
|
+
### Every client failure: read or decode the rejection
|
|
89
224
|
|
|
90
|
-
|
|
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
|
-
|
|
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
|
-
|
|
99
|
-
|
|
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
|
-
|
|
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
|
|
265
|
+
error instanceof OrderMissing
|
|
106
266
|
? new TRPCError({ code: "NOT_FOUND", message: error.message })
|
|
107
267
|
: undefined,
|
|
108
|
-
})
|
|
268
|
+
});
|
|
109
269
|
```
|
|
110
270
|
|
|
111
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
125
|
-
import {
|
|
280
|
+
import { expect } from "@effect/vitest";
|
|
281
|
+
import { makeTrpcIt } from "@shivaedev/effect-trpc/testing/vitest.ts";
|
|
126
282
|
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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 {
|
|
193
|
-
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
357
|
+
pnpm add @shivaedev/effect-trpc @trpc/server@11.18.0 effect@4.0.0-rc.112
|
|
214
358
|
```
|
|
215
359
|
|
|
216
|
-
|
|
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
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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
|
-
|
|
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
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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
|
|
8
|
-
export declare
|
|
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:
|
|
11
|
-
//# sourceMappingURL=
|
|
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
|
|
5
|
-
|
|
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=
|
|
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"}
|