effect-orpc 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +133 -146
- package/dist/index.js +421 -133
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/contract.ts +15 -12
- package/src/effect-builder.ts +63 -40
- package/src/effect-enhance-router.ts +3 -3
- package/src/effect-procedure.ts +38 -14
- package/src/effect-runtime.ts +647 -115
- package/src/extension/state.ts +3 -5
- package/src/index.ts +1 -0
- package/src/runtime-source.ts +70 -12
- package/src/tagged-error.ts +1 -1
- package/src/tests/contract.test.ts +5 -8
- package/src/tests/effect-builder.proxy.test.ts +15 -17
- package/src/tests/effect-builder.test.ts +352 -161
- package/src/tests/effect-callback-shapes.test.ts +410 -0
- package/src/tests/effect-error-map.test.ts +12 -14
- package/src/tests/effect-procedure.test.ts +53 -11
- package/src/tests/parity-shared.ts +2 -2
- package/src/types/effect-builder-surface.ts +1 -1
- package/src/types/index.ts +76 -51
- package/src/types/variants.ts +5 -5
package/src/extension/state.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
1
|
+
import type { EffectRuntimeRunner } from "../runtime-source";
|
|
3
2
|
import type { EffectErrorMap } from "../tagged-error";
|
|
4
3
|
import type {
|
|
5
4
|
EffectPipelineStep,
|
|
@@ -17,10 +16,9 @@ export interface EffectExtensionState<
|
|
|
17
16
|
*/
|
|
18
17
|
effectErrorMap: EffectErrorMap;
|
|
19
18
|
/**
|
|
20
|
-
*
|
|
21
|
-
* @see {@link ManagedRuntime.ManagedRuntime}
|
|
19
|
+
* Executes Effect values with the services configured for this builder.
|
|
22
20
|
*/
|
|
23
|
-
|
|
21
|
+
runner: EffectRuntimeRunner<TRequirementsProvided, TRuntimeError>;
|
|
24
22
|
/**
|
|
25
23
|
* Configuration for Effect span tracing.
|
|
26
24
|
* @see {@link EffectSpanConfig}
|
package/src/index.ts
CHANGED
package/src/runtime-source.ts
CHANGED
|
@@ -1,18 +1,76 @@
|
|
|
1
|
-
import { Layer, ManagedRuntime } from "effect";
|
|
1
|
+
import { Effect, Exit, FiberRefs, Layer, ManagedRuntime } from "effect";
|
|
2
|
+
|
|
3
|
+
import { getCurrentFiberRefs } from "./fiber-context-bridge";
|
|
2
4
|
|
|
3
5
|
export type EffectRuntimeSource<TRequirementsProvided, TRuntimeError> =
|
|
4
6
|
| ManagedRuntime.ManagedRuntime<TRequirementsProvided, TRuntimeError>
|
|
5
7
|
| Layer.Layer<TRequirementsProvided, TRuntimeError, never>;
|
|
6
8
|
|
|
7
|
-
export
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
9
|
+
export interface EffectRuntimeRunner<TRequirementsProvided, TRuntimeError> {
|
|
10
|
+
readonly runtime?: ManagedRuntime.ManagedRuntime<
|
|
11
|
+
TRequirementsProvided,
|
|
12
|
+
TRuntimeError
|
|
13
|
+
>;
|
|
14
|
+
readonly runPromiseExit: <A, E>(
|
|
15
|
+
effect: Effect.Effect<A, E, TRequirementsProvided>,
|
|
16
|
+
options?: { readonly signal?: AbortSignal },
|
|
17
|
+
) => Promise<Exit.Exit<A, E | TRuntimeError>>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function makeEffectRuntimeRunner<
|
|
21
|
+
TRequirementsProvided = never,
|
|
22
|
+
TRuntimeError = never,
|
|
23
|
+
>(
|
|
24
|
+
source?: EffectRuntimeSource<TRequirementsProvided, TRuntimeError>,
|
|
25
|
+
): EffectRuntimeRunner<TRequirementsProvided, TRuntimeError> {
|
|
26
|
+
if (source === undefined) {
|
|
27
|
+
return {
|
|
28
|
+
runPromiseExit: (effect, options) =>
|
|
29
|
+
Effect.runPromiseExit(withParentFiberRefs(effect as any), {
|
|
30
|
+
signal: options?.signal,
|
|
31
|
+
}) as Promise<Exit.Exit<any, any>>,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (ManagedRuntime.isManagedRuntime(source)) {
|
|
36
|
+
const runtime = source as ManagedRuntime.ManagedRuntime<
|
|
37
|
+
TRequirementsProvided,
|
|
38
|
+
TRuntimeError
|
|
39
|
+
>;
|
|
40
|
+
return {
|
|
41
|
+
runtime,
|
|
42
|
+
runPromiseExit: (effect, options) =>
|
|
43
|
+
runtime.runPromiseExit(withParentFiberRefs(effect), {
|
|
44
|
+
signal: options?.signal,
|
|
45
|
+
}),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const layer = source as Layer.Layer<
|
|
50
|
+
TRequirementsProvided,
|
|
51
|
+
TRuntimeError,
|
|
52
|
+
never
|
|
53
|
+
>;
|
|
54
|
+
return {
|
|
55
|
+
runPromiseExit: (effect, options) =>
|
|
56
|
+
Effect.runPromiseExit(
|
|
57
|
+
withParentFiberRefs(Effect.provide(effect as any, layer)),
|
|
58
|
+
{ signal: options?.signal },
|
|
59
|
+
) as Promise<Exit.Exit<any, any>>,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function withParentFiberRefs<A, E, R>(
|
|
64
|
+
effect: Effect.Effect<A, E, R>,
|
|
65
|
+
): Effect.Effect<A, E, R> {
|
|
66
|
+
const parentFiberRefs = getCurrentFiberRefs();
|
|
67
|
+
return parentFiberRefs
|
|
68
|
+
? Effect.fiberIdWith((fiberId) =>
|
|
69
|
+
Effect.flatMap(Effect.getFiberRefs, (fiberRefs) =>
|
|
70
|
+
Effect.setFiberRefs(
|
|
71
|
+
FiberRefs.joinAs(fiberRefs, fiberId, parentFiberRefs),
|
|
72
|
+
).pipe(Effect.andThen(effect)),
|
|
73
|
+
),
|
|
74
|
+
)
|
|
75
|
+
: effect;
|
|
18
76
|
}
|
package/src/tagged-error.ts
CHANGED
|
@@ -341,7 +341,7 @@ export function ORPCTaggedError<
|
|
|
341
341
|
*
|
|
342
342
|
* @example
|
|
343
343
|
* ```ts
|
|
344
|
-
* const handler =
|
|
344
|
+
* const handler = effectProcedure.effect(function* () {
|
|
345
345
|
* const result = yield* someOperation.pipe(
|
|
346
346
|
* Effect.catchTag('UserNotFoundError', (e) =>
|
|
347
347
|
* Effect.fail(toORPCError(e))
|
|
@@ -76,14 +76,11 @@ describe("implementEffect", () => {
|
|
|
76
76
|
};
|
|
77
77
|
});
|
|
78
78
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
} finally {
|
|
85
|
-
await procedure["~effect"].runtime.dispose();
|
|
86
|
-
}
|
|
79
|
+
expect(procedure["~effect"].runner.runtime).toBeUndefined();
|
|
80
|
+
await expect(call(procedure, { amount: 2 })).resolves.toEqual({
|
|
81
|
+
next: 3,
|
|
82
|
+
requestId: "layer",
|
|
83
|
+
});
|
|
87
84
|
});
|
|
88
85
|
|
|
89
86
|
it("preserves contract enforcement at the root router", async () => {
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { isLazy, isProcedure, os, unlazy } from "@orpc/server";
|
|
2
|
-
import { Layer, ManagedRuntime } from "effect";
|
|
3
2
|
import { describe, expect, it } from "vitest";
|
|
4
3
|
import z from "zod";
|
|
5
4
|
|
|
6
|
-
import { EffectBuilder, makeEffectORPC } from "../effect-builder";
|
|
5
|
+
import { EffectBuilder, eos, makeEffectORPC } from "../effect-builder";
|
|
7
6
|
import { EffectDecoratedProcedure } from "../effect-procedure";
|
|
8
7
|
import { ORPCTaggedError } from "../tagged-error";
|
|
9
|
-
const runtime = ManagedRuntime.make(Layer.empty);
|
|
10
8
|
|
|
11
9
|
function makeCustomBuilder(meta: Record<string, unknown> = {}): {
|
|
12
10
|
"~orpc": (typeof os)["~orpc"];
|
|
@@ -32,7 +30,7 @@ function makeCustomBuilder(meta: Record<string, unknown> = {}): {
|
|
|
32
30
|
|
|
33
31
|
describe("effectBuilder proxy compatibility", () => {
|
|
34
32
|
it("preserves instanceof and virtual reflection surface", () => {
|
|
35
|
-
const builder =
|
|
33
|
+
const builder = eos;
|
|
36
34
|
|
|
37
35
|
expect(builder).toBeInstanceOf(EffectBuilder);
|
|
38
36
|
expect("~orpc" in builder).toBe(true);
|
|
@@ -82,7 +80,7 @@ describe("effectBuilder proxy compatibility", () => {
|
|
|
82
80
|
});
|
|
83
81
|
|
|
84
82
|
it("keeps extracted forwarded and intercepted methods callable", () => {
|
|
85
|
-
const builder =
|
|
83
|
+
const builder = eos.$meta({ mode: "dev" });
|
|
86
84
|
|
|
87
85
|
const meta = builder.meta;
|
|
88
86
|
const prefixed = builder.prefix;
|
|
@@ -102,8 +100,8 @@ describe("effectBuilder proxy compatibility", () => {
|
|
|
102
100
|
});
|
|
103
101
|
|
|
104
102
|
it("preserves wrapped chaining across forwarded and intercepted methods", () => {
|
|
105
|
-
const routedBuilder =
|
|
106
|
-
const builder =
|
|
103
|
+
const routedBuilder = eos.prefix("/api").tag("users");
|
|
104
|
+
const builder = eos
|
|
107
105
|
.$meta({ scope: "users" })
|
|
108
106
|
.errors({ BAD_REQUEST: { message: "bad request" } })
|
|
109
107
|
.traced("users.list")
|
|
@@ -121,7 +119,7 @@ describe("effectBuilder proxy compatibility", () => {
|
|
|
121
119
|
({ input }: { input: { id: string } }) => input,
|
|
122
120
|
);
|
|
123
121
|
expect(procedure).toBeInstanceOf(EffectDecoratedProcedure);
|
|
124
|
-
expect(procedure["~effect"].runtime).
|
|
122
|
+
expect(procedure["~effect"].runner.runtime).toBeUndefined();
|
|
125
123
|
});
|
|
126
124
|
|
|
127
125
|
it("preserves tagged class support in errors()", () => {
|
|
@@ -130,7 +128,7 @@ describe("effectBuilder proxy compatibility", () => {
|
|
|
130
128
|
schema: z.object({ userId: z.string() }),
|
|
131
129
|
}) {}
|
|
132
130
|
|
|
133
|
-
const builder =
|
|
131
|
+
const builder = eos.errors({
|
|
134
132
|
UserNotFoundError,
|
|
135
133
|
});
|
|
136
134
|
|
|
@@ -141,7 +139,7 @@ describe("effectBuilder proxy compatibility", () => {
|
|
|
141
139
|
});
|
|
142
140
|
|
|
143
141
|
it("keeps handler, effect, router, and lazy return behavior unchanged", async () => {
|
|
144
|
-
const builder =
|
|
142
|
+
const builder = eos;
|
|
145
143
|
const procedure = builder.effect(function* () {
|
|
146
144
|
return { output: "pong" };
|
|
147
145
|
});
|
|
@@ -155,15 +153,15 @@ describe("effectBuilder proxy compatibility", () => {
|
|
|
155
153
|
|
|
156
154
|
expect(handled).toBeInstanceOf(EffectDecoratedProcedure);
|
|
157
155
|
expect(effected).toBeInstanceOf(EffectDecoratedProcedure);
|
|
158
|
-
expect(routed.ping["~effect"].runtime).
|
|
156
|
+
expect(routed.ping["~effect"].runner.runtime).toBeUndefined();
|
|
159
157
|
expect(isLazy(lazied)).toBe(true);
|
|
160
158
|
|
|
161
159
|
const { default: resolved } = await unlazy(lazied as any);
|
|
162
|
-
expect(resolved.ping["~effect"].runtime).
|
|
160
|
+
expect(resolved.ping["~effect"].runner.runtime).toBeUndefined();
|
|
163
161
|
});
|
|
164
162
|
|
|
165
163
|
it("preserves decorated procedure proxy reflection and extracted methods", () => {
|
|
166
|
-
const procedure =
|
|
164
|
+
const procedure = eos
|
|
167
165
|
.route({ path: "/users" })
|
|
168
166
|
.handler(({ input }) => input);
|
|
169
167
|
|
|
@@ -206,7 +204,7 @@ describe("effectBuilder proxy compatibility", () => {
|
|
|
206
204
|
});
|
|
207
205
|
|
|
208
206
|
it("keeps callable procedure clients executable while exposing the procedure surface", async () => {
|
|
209
|
-
const procedure =
|
|
207
|
+
const procedure = eos.handler(({ input }) => ({
|
|
210
208
|
echoed: input,
|
|
211
209
|
}));
|
|
212
210
|
|
|
@@ -214,14 +212,14 @@ describe("effectBuilder proxy compatibility", () => {
|
|
|
214
212
|
|
|
215
213
|
await expect(callable("hello")).resolves.toEqual({ echoed: "hello" });
|
|
216
214
|
expect(callable).toSatisfy(isProcedure);
|
|
217
|
-
expect(callable["~effect"].runtime).
|
|
215
|
+
expect(callable["~effect"].runner.runtime).toBeUndefined();
|
|
218
216
|
expect(callable.route({ path: "/echo" })).toBeInstanceOf(
|
|
219
217
|
EffectDecoratedProcedure,
|
|
220
218
|
);
|
|
221
219
|
});
|
|
222
220
|
|
|
223
221
|
it("applies builder route and middleware enhancements to routed procedures", () => {
|
|
224
|
-
const builder =
|
|
222
|
+
const builder = eos;
|
|
225
223
|
const middleware = builder.middleware(({ next }) => next({}));
|
|
226
224
|
const procedure = builder.route({ path: "/ping" }).handler(() => "pong");
|
|
227
225
|
|
|
@@ -238,7 +236,7 @@ describe("effectBuilder proxy compatibility", () => {
|
|
|
238
236
|
});
|
|
239
237
|
|
|
240
238
|
it("rewraps unknown builder-like methods and passes through non-builder results", () => {
|
|
241
|
-
const builder = makeEffectORPC(
|
|
239
|
+
const builder = makeEffectORPC(makeCustomBuilder() as any) as any;
|
|
242
240
|
|
|
243
241
|
const customBuilderLike = builder.customBuilderLike;
|
|
244
242
|
const customValue = builder.customValue;
|