effect-orpc 0.4.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 +81 -93
- package/dist/index.js +350 -80
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/effect-builder.ts +36 -22
- package/src/effect-procedure.ts +31 -7
- package/src/effect-runtime.ts +622 -80
- package/src/tests/effect-builder.proxy.test.ts +15 -17
- package/src/tests/effect-builder.test.ts +296 -139
- package/src/tests/effect-callback-shapes.test.ts +410 -0
- package/src/tests/effect-error-map.test.ts +4 -6
- package/src/tests/effect-procedure.test.ts +44 -5
- package/src/tests/parity-shared.ts +2 -2
- package/src/types/index.ts +70 -40
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
import { oc } from "@orpc/contract";
|
|
2
|
+
import { call, createRouterClient } from "@orpc/server";
|
|
3
|
+
import type { Context as ORPCContext } from "@orpc/server";
|
|
4
|
+
import { Context, Effect, Layer, ManagedRuntime, Option, Tracer } from "effect";
|
|
5
|
+
import { describe, expect, it } from "vitest";
|
|
6
|
+
import z from "zod";
|
|
7
|
+
|
|
8
|
+
import { implementEffect } from "../contract";
|
|
9
|
+
import { eos, makeEffectORPC } from "../effect-builder";
|
|
10
|
+
import type { EffectOrORPCMiddleware } from "../types";
|
|
11
|
+
|
|
12
|
+
type TestMiddleware<
|
|
13
|
+
TOutContext extends ORPCContext = { readonly value: string },
|
|
14
|
+
> = EffectOrORPCMiddleware<
|
|
15
|
+
ORPCContext | Record<never, never>,
|
|
16
|
+
TOutContext,
|
|
17
|
+
unknown,
|
|
18
|
+
unknown,
|
|
19
|
+
Record<never, never>,
|
|
20
|
+
never,
|
|
21
|
+
Record<never, never>
|
|
22
|
+
>;
|
|
23
|
+
|
|
24
|
+
type TestMiddlewareOptions = Parameters<TestMiddleware>[0];
|
|
25
|
+
type SpanMiddlewareOptions = Parameters<
|
|
26
|
+
TestMiddleware<Record<never, never>>
|
|
27
|
+
>[0];
|
|
28
|
+
|
|
29
|
+
type MiddlewareShape = {
|
|
30
|
+
readonly name: string;
|
|
31
|
+
readonly middleware: TestMiddleware;
|
|
32
|
+
readonly expected: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function effectHandlerShapes() {
|
|
36
|
+
return [
|
|
37
|
+
{
|
|
38
|
+
name: "function*",
|
|
39
|
+
handler: function* ({ input }: { input: number }) {
|
|
40
|
+
const increment = yield* Effect.succeed(1);
|
|
41
|
+
return input + increment;
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: "named Effect.fn",
|
|
46
|
+
handler: Effect.fn("test.effect.named")(function* ({
|
|
47
|
+
input,
|
|
48
|
+
}: {
|
|
49
|
+
input: number;
|
|
50
|
+
}) {
|
|
51
|
+
const increment = yield* Effect.succeed(1);
|
|
52
|
+
return input + increment;
|
|
53
|
+
}),
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: "anonymous Effect.fn",
|
|
57
|
+
handler: Effect.fn(function* ({ input }: { input: number }) {
|
|
58
|
+
const increment = yield* Effect.succeed(1);
|
|
59
|
+
return input + increment;
|
|
60
|
+
}),
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: "Effect.gen-returning function",
|
|
64
|
+
handler: ({ input }: { input: number }) =>
|
|
65
|
+
Effect.gen(function* () {
|
|
66
|
+
const increment = yield* Effect.succeed(1);
|
|
67
|
+
return input + increment;
|
|
68
|
+
}),
|
|
69
|
+
},
|
|
70
|
+
] as const;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function providerShapes(suffix: string) {
|
|
74
|
+
return [
|
|
75
|
+
{
|
|
76
|
+
name: "function*",
|
|
77
|
+
provider: function* ({ context }: { context: { value: string } }) {
|
|
78
|
+
yield* Effect.void;
|
|
79
|
+
return { value: `${context.value}:${suffix}:generator` };
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: "named Effect.fn",
|
|
84
|
+
provider: Effect.fn("test.provider.named")(function* ({
|
|
85
|
+
context,
|
|
86
|
+
}: {
|
|
87
|
+
context: { value: string };
|
|
88
|
+
}) {
|
|
89
|
+
yield* Effect.void;
|
|
90
|
+
return { value: `${context.value}:${suffix}:named` };
|
|
91
|
+
}),
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: "anonymous Effect.fn",
|
|
95
|
+
provider: Effect.fn(function* ({
|
|
96
|
+
context,
|
|
97
|
+
}: {
|
|
98
|
+
context: { value: string };
|
|
99
|
+
}) {
|
|
100
|
+
yield* Effect.void;
|
|
101
|
+
return { value: `${context.value}:${suffix}:anonymous` };
|
|
102
|
+
}),
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: "Effect.gen-returning function",
|
|
106
|
+
provider: ({ context }: { context: { value: string } }) =>
|
|
107
|
+
Effect.gen(function* () {
|
|
108
|
+
yield* Effect.void;
|
|
109
|
+
return { value: `${context.value}:${suffix}:effect-gen` };
|
|
110
|
+
}),
|
|
111
|
+
},
|
|
112
|
+
] as const;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function optionalProviderShapes(suffix: string) {
|
|
116
|
+
return [
|
|
117
|
+
{
|
|
118
|
+
name: "function*",
|
|
119
|
+
provider: function* ({ context }: { context: { value?: string } }) {
|
|
120
|
+
yield* Effect.void;
|
|
121
|
+
return Option.map(Option.fromNullable(context.value), (value) => ({
|
|
122
|
+
value: `${value}:${suffix}:generator`,
|
|
123
|
+
}));
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
name: "named Effect.fn",
|
|
128
|
+
provider: Effect.fn("test.optional-provider.named")(function* ({
|
|
129
|
+
context,
|
|
130
|
+
}: {
|
|
131
|
+
context: { value?: string };
|
|
132
|
+
}) {
|
|
133
|
+
yield* Effect.void;
|
|
134
|
+
return Option.map(Option.fromNullable(context.value), (value) => ({
|
|
135
|
+
value: `${value}:${suffix}:named`,
|
|
136
|
+
}));
|
|
137
|
+
}),
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
name: "anonymous Effect.fn",
|
|
141
|
+
provider: Effect.fn(function* ({
|
|
142
|
+
context,
|
|
143
|
+
}: {
|
|
144
|
+
context: { value?: string };
|
|
145
|
+
}) {
|
|
146
|
+
yield* Effect.void;
|
|
147
|
+
return Option.map(Option.fromNullable(context.value), (value) => ({
|
|
148
|
+
value: `${value}:${suffix}:anonymous`,
|
|
149
|
+
}));
|
|
150
|
+
}),
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
name: "Effect.gen-returning function",
|
|
154
|
+
provider: ({ context }: { context: { value?: string } }) =>
|
|
155
|
+
Effect.gen(function* () {
|
|
156
|
+
yield* Effect.void;
|
|
157
|
+
return Option.map(Option.fromNullable(context.value), (value) => ({
|
|
158
|
+
value: `${value}:${suffix}:effect-gen`,
|
|
159
|
+
}));
|
|
160
|
+
}),
|
|
161
|
+
},
|
|
162
|
+
] as const;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function middlewareShapes(): ReadonlyArray<MiddlewareShape> {
|
|
166
|
+
return [
|
|
167
|
+
{
|
|
168
|
+
name: "native guard-only function",
|
|
169
|
+
middleware: () => {},
|
|
170
|
+
expected: "handler",
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
name: "native next-returning function",
|
|
174
|
+
middleware: ({ next }) => next({ context: { value: "native-next" } }),
|
|
175
|
+
expected: "native-next",
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
name: "function*",
|
|
179
|
+
middleware: function* ({ next }: TestMiddlewareOptions) {
|
|
180
|
+
yield* Effect.void;
|
|
181
|
+
return yield* next({ context: { value: "generator" } });
|
|
182
|
+
},
|
|
183
|
+
expected: "generator",
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
name: "named Effect.fn",
|
|
187
|
+
middleware: Effect.fn("test.middleware.named")(function* ({
|
|
188
|
+
next,
|
|
189
|
+
}: TestMiddlewareOptions) {
|
|
190
|
+
yield* Effect.void;
|
|
191
|
+
return yield* next({ context: { value: "named" } });
|
|
192
|
+
}),
|
|
193
|
+
expected: "named",
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
name: "anonymous Effect.fn",
|
|
197
|
+
middleware: Effect.fn(function* ({ next }: TestMiddlewareOptions) {
|
|
198
|
+
yield* Effect.void;
|
|
199
|
+
return yield* next({ context: { value: "anonymous" } });
|
|
200
|
+
}),
|
|
201
|
+
expected: "anonymous",
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
name: "Effect.gen-returning function",
|
|
205
|
+
middleware: ({ next }) =>
|
|
206
|
+
Effect.gen(function* () {
|
|
207
|
+
yield* Effect.void;
|
|
208
|
+
return yield* next({ context: { value: "effect-gen" } });
|
|
209
|
+
}),
|
|
210
|
+
expected: "effect-gen",
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
name: "Effect.gen-returning guard-only function",
|
|
214
|
+
middleware: () =>
|
|
215
|
+
Effect.gen(function* () {
|
|
216
|
+
yield* Effect.void;
|
|
217
|
+
}),
|
|
218
|
+
expected: "handler",
|
|
219
|
+
},
|
|
220
|
+
];
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
describe("Effect callback shapes", () => {
|
|
224
|
+
for (const { name, handler } of effectHandlerShapes()) {
|
|
225
|
+
it(`.effect supports ${name}`, async () => {
|
|
226
|
+
const procedure = eos.input(z.number()).effect(handler);
|
|
227
|
+
|
|
228
|
+
await expect(call(procedure, 41)).resolves.toBe(42);
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
it("contract implementer .effect supports Effect-returning handlers", async () => {
|
|
233
|
+
const contract = {
|
|
234
|
+
increment: oc.input(z.number()).output(z.number()),
|
|
235
|
+
};
|
|
236
|
+
const implementer = implementEffect(contract, Layer.empty);
|
|
237
|
+
|
|
238
|
+
const named = implementer.increment.effect(
|
|
239
|
+
Effect.fn("test.contract.effect")(function* ({ input }) {
|
|
240
|
+
const increment = yield* Effect.succeed(1);
|
|
241
|
+
return input + increment;
|
|
242
|
+
}),
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
await expect(call(named, 41)).resolves.toBe(42);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
for (const { name, provider } of providerShapes("provide")) {
|
|
249
|
+
it(`.provide supports ${name}`, async () => {
|
|
250
|
+
class RequestValue extends Context.Tag(`RequestValue:${name}`)<
|
|
251
|
+
RequestValue,
|
|
252
|
+
{ readonly value: string }
|
|
253
|
+
>() {}
|
|
254
|
+
|
|
255
|
+
const procedure = eos
|
|
256
|
+
.$context<{ value: string }>()
|
|
257
|
+
.provide(RequestValue, provider)
|
|
258
|
+
.effect(function* () {
|
|
259
|
+
const service = yield* RequestValue;
|
|
260
|
+
return service.value;
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
await expect(
|
|
264
|
+
call(procedure, undefined, { context: { value: "request" } }),
|
|
265
|
+
).resolves.toContain("request:provide");
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
for (const { name, provider } of optionalProviderShapes("optional")) {
|
|
270
|
+
it(`.provideOptional supports ${name}`, async () => {
|
|
271
|
+
class RequestValue extends Context.Tag(`OptionalRequestValue:${name}`)<
|
|
272
|
+
RequestValue,
|
|
273
|
+
{ readonly value: string }
|
|
274
|
+
>() {}
|
|
275
|
+
|
|
276
|
+
const procedure = eos
|
|
277
|
+
.$context<{ value?: string }>()
|
|
278
|
+
.provideOptional(RequestValue, provider)
|
|
279
|
+
.effect(function* () {
|
|
280
|
+
return yield* Effect.serviceOption(RequestValue);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
await expect(
|
|
284
|
+
call(procedure, undefined, { context: { value: "request" } }),
|
|
285
|
+
).resolves.toSatisfy(
|
|
286
|
+
(option: Option.Option<{ readonly value: string }>) =>
|
|
287
|
+
Option.isSome(option) &&
|
|
288
|
+
option.value.value.includes("request:optional"),
|
|
289
|
+
);
|
|
290
|
+
await expect(
|
|
291
|
+
call(procedure, undefined, { context: {} }),
|
|
292
|
+
).resolves.toEqual(Option.none());
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
for (const { name, middleware, expected } of middlewareShapes()) {
|
|
297
|
+
it(`.use supports ${name}`, async () => {
|
|
298
|
+
const procedure = eos.use(middleware).effect(function* ({ context }) {
|
|
299
|
+
return "value" in context ? context.value : "handler";
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
await expect(call(procedure, undefined)).resolves.toBe(expected);
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
for (const { name, middleware, expected } of middlewareShapes().filter(
|
|
307
|
+
({ name }) => !name.startsWith("native"),
|
|
308
|
+
)) {
|
|
309
|
+
it(`.middleware supports ${name}`, async () => {
|
|
310
|
+
const reusable = eos.middleware(middleware);
|
|
311
|
+
const procedure = eos.use(reusable).effect(function* ({ context }) {
|
|
312
|
+
return "value" in context ? context.value : "handler";
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
await expect(call(procedure, undefined)).resolves.toBe(expected);
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
it("Effect handlers keep their own spans inside routed procedure spans", async () => {
|
|
320
|
+
const spans: Array<{
|
|
321
|
+
readonly name: string;
|
|
322
|
+
readonly parentName: string | undefined;
|
|
323
|
+
}> = [];
|
|
324
|
+
const spanNamesById = new Map<string, string>();
|
|
325
|
+
const tracer = Tracer.make({
|
|
326
|
+
context: (f) => f(),
|
|
327
|
+
span(name, parent, context, links, startTime, kind) {
|
|
328
|
+
const spanId = `span-${spans.length + 1}`;
|
|
329
|
+
spans.push({
|
|
330
|
+
name,
|
|
331
|
+
parentName: Option.match(parent, {
|
|
332
|
+
onNone: () => undefined,
|
|
333
|
+
onSome: (span) => spanNamesById.get(span.spanId),
|
|
334
|
+
}),
|
|
335
|
+
});
|
|
336
|
+
spanNamesById.set(spanId, name);
|
|
337
|
+
const attributes = new Map<string, unknown>();
|
|
338
|
+
|
|
339
|
+
return {
|
|
340
|
+
_tag: "Span" as const,
|
|
341
|
+
name,
|
|
342
|
+
spanId,
|
|
343
|
+
traceId: "trace",
|
|
344
|
+
parent,
|
|
345
|
+
context,
|
|
346
|
+
status: { _tag: "Started" as const, startTime },
|
|
347
|
+
attributes,
|
|
348
|
+
links,
|
|
349
|
+
sampled: true,
|
|
350
|
+
kind,
|
|
351
|
+
end() {},
|
|
352
|
+
attribute(key: string, value: unknown) {
|
|
353
|
+
attributes.set(key, value);
|
|
354
|
+
},
|
|
355
|
+
event() {},
|
|
356
|
+
addLinks() {},
|
|
357
|
+
};
|
|
358
|
+
},
|
|
359
|
+
});
|
|
360
|
+
const tracedRuntime = ManagedRuntime.make(Layer.setTracer(tracer));
|
|
361
|
+
const effectFnProcedure = makeEffectORPC(tracedRuntime)
|
|
362
|
+
.use(
|
|
363
|
+
Effect.fn("custom.middleware.span")(function* ({
|
|
364
|
+
next,
|
|
365
|
+
}: SpanMiddlewareOptions) {
|
|
366
|
+
return yield* next();
|
|
367
|
+
}),
|
|
368
|
+
)
|
|
369
|
+
.effect(
|
|
370
|
+
Effect.fn("custom.handler.span")(function* () {
|
|
371
|
+
return "ok";
|
|
372
|
+
}),
|
|
373
|
+
);
|
|
374
|
+
const withSpanProcedure = makeEffectORPC(tracedRuntime).effect(() =>
|
|
375
|
+
Effect.succeed("ok").pipe(Effect.withSpan("custom.with-span.handler")),
|
|
376
|
+
);
|
|
377
|
+
const client = createRouterClient({
|
|
378
|
+
users: {
|
|
379
|
+
effectFn: effectFnProcedure,
|
|
380
|
+
withSpan: withSpanProcedure,
|
|
381
|
+
},
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
try {
|
|
385
|
+
await expect(client.users.effectFn(undefined)).resolves.toBe("ok");
|
|
386
|
+
await expect(client.users.withSpan(undefined)).resolves.toBe("ok");
|
|
387
|
+
expect(spans).toContainEqual({
|
|
388
|
+
name: "users.effectFn",
|
|
389
|
+
parentName: undefined,
|
|
390
|
+
});
|
|
391
|
+
expect(spans).toContainEqual({
|
|
392
|
+
name: "custom.handler.span",
|
|
393
|
+
parentName: "users.effectFn",
|
|
394
|
+
});
|
|
395
|
+
expect(spans).toContainEqual({
|
|
396
|
+
name: "users.withSpan",
|
|
397
|
+
parentName: undefined,
|
|
398
|
+
});
|
|
399
|
+
expect(spans).toContainEqual({
|
|
400
|
+
name: "custom.with-span.handler",
|
|
401
|
+
parentName: "users.withSpan",
|
|
402
|
+
});
|
|
403
|
+
expect(
|
|
404
|
+
spans.filter(({ name }) => name === "custom.middleware.span"),
|
|
405
|
+
).toHaveLength(1);
|
|
406
|
+
} finally {
|
|
407
|
+
await tracedRuntime.dispose();
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
});
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { fallbackORPCErrorMessage, ORPCError } from "@orpc/client";
|
|
2
|
-
import { Effect
|
|
2
|
+
import { Effect } from "effect";
|
|
3
3
|
import { describe, expect, expectTypeOf, it } from "vitest";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { eos } from "../effect-builder";
|
|
7
7
|
import type {
|
|
8
8
|
EffectErrorMap,
|
|
9
9
|
EffectErrorMapToUnion,
|
|
@@ -192,8 +192,7 @@ describe("effectErrorMapToErrorMap", () => {
|
|
|
192
192
|
});
|
|
193
193
|
|
|
194
194
|
describe("effectBuilder with EffectErrorMap", () => {
|
|
195
|
-
const
|
|
196
|
-
const effectProcedure = makeEffectORPC(runtime);
|
|
195
|
+
const effectProcedure = eos;
|
|
197
196
|
|
|
198
197
|
it("should support errors() with traditional format", () => {
|
|
199
198
|
const builder = effectProcedure.errors({
|
|
@@ -309,8 +308,7 @@ describe("effectBuilder with EffectErrorMap", () => {
|
|
|
309
308
|
});
|
|
310
309
|
|
|
311
310
|
describe("effectDecoratedProcedure.errors()", () => {
|
|
312
|
-
const
|
|
313
|
-
const effectProcedure = makeEffectORPC(runtime);
|
|
311
|
+
const effectProcedure = eos;
|
|
314
312
|
|
|
315
313
|
it("should support adding errors to procedure", () => {
|
|
316
314
|
const procedure = effectProcedure
|
|
@@ -119,16 +119,55 @@ describe("effectDecoratedProcedure", () => {
|
|
|
119
119
|
});
|
|
120
120
|
|
|
121
121
|
describe(".use", () => {
|
|
122
|
-
it("without map input", () => {
|
|
123
|
-
const mid = vi.fn()
|
|
122
|
+
it("without map input", async () => {
|
|
123
|
+
const mid = vi.fn(({ next }) =>
|
|
124
|
+
next({ context: { fromMiddleware: true } }),
|
|
125
|
+
);
|
|
124
126
|
|
|
125
127
|
const applied = decorated.use(mid);
|
|
126
128
|
expect(applied).not.toBe(decorated);
|
|
127
129
|
expect(applied).toBeInstanceOf(EffectDecoratedProcedure);
|
|
128
130
|
|
|
129
|
-
expect(applied["~effect"]).
|
|
130
|
-
|
|
131
|
-
|
|
131
|
+
expect(applied["~effect"].middlewares).toHaveLength(
|
|
132
|
+
def.middlewares.length + 1,
|
|
133
|
+
);
|
|
134
|
+
expect(applied["~effect"].middlewares[0]).toBe(def.middlewares[0]);
|
|
135
|
+
|
|
136
|
+
const wrapped = applied["~effect"].middlewares[1]!;
|
|
137
|
+
let nextCalls = 0;
|
|
138
|
+
let nextOptions: unknown;
|
|
139
|
+
const next = <TContext extends Record<PropertyKey, unknown>>(options?: {
|
|
140
|
+
context?: TContext;
|
|
141
|
+
}) => {
|
|
142
|
+
nextCalls++;
|
|
143
|
+
nextOptions = options;
|
|
144
|
+
return Promise.resolve({
|
|
145
|
+
output: "ok",
|
|
146
|
+
context: options?.context ?? ({} as TContext),
|
|
147
|
+
});
|
|
148
|
+
};
|
|
149
|
+
await expect(
|
|
150
|
+
wrapped(
|
|
151
|
+
{
|
|
152
|
+
context: {},
|
|
153
|
+
errors: {},
|
|
154
|
+
path: [],
|
|
155
|
+
procedure: applied,
|
|
156
|
+
signal: undefined,
|
|
157
|
+
lastEventId: undefined,
|
|
158
|
+
next,
|
|
159
|
+
},
|
|
160
|
+
"input",
|
|
161
|
+
vi.fn(),
|
|
162
|
+
),
|
|
163
|
+
).resolves.toEqual({
|
|
164
|
+
output: "ok",
|
|
165
|
+
context: { fromMiddleware: true },
|
|
166
|
+
});
|
|
167
|
+
expect(mid).toHaveBeenCalledOnce();
|
|
168
|
+
expect(nextCalls).toBe(1);
|
|
169
|
+
expect(nextOptions).toEqual({
|
|
170
|
+
context: { fromMiddleware: true },
|
|
132
171
|
});
|
|
133
172
|
|
|
134
173
|
// Preserves runtime
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Layer
|
|
1
|
+
import { Layer } from "effect";
|
|
2
2
|
|
|
3
3
|
import { eoc } from "../index";
|
|
4
4
|
import {
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
export type InitialContext = { db: string };
|
|
13
13
|
export type CurrentContext = InitialContext & { auth: boolean };
|
|
14
14
|
|
|
15
|
-
export const runtime =
|
|
15
|
+
export const runtime = Layer.empty;
|
|
16
16
|
|
|
17
17
|
export const typedContract = {
|
|
18
18
|
ping: eoc
|
package/src/types/index.ts
CHANGED
|
@@ -135,18 +135,17 @@ export type EffectProcedureHandler<
|
|
|
135
135
|
EffectErrorConstructorMap<TEffectErrorMap>,
|
|
136
136
|
TMeta
|
|
137
137
|
>,
|
|
138
|
-
) =>
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
| EffectErrorMapToUnion<TEffectErrorMap>
|
|
143
|
-
| ORPCError<ORPCErrorCode, unknown>,
|
|
138
|
+
) =>
|
|
139
|
+
| Effect.Effect<
|
|
140
|
+
THandlerOutput,
|
|
141
|
+
EffectOperationError<TEffectErrorMap>,
|
|
144
142
|
TRequirementsProvided
|
|
145
143
|
>
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
144
|
+
| EffectCallbackGenerator<
|
|
145
|
+
THandlerOutput,
|
|
146
|
+
TEffectErrorMap,
|
|
147
|
+
TRequirementsProvided
|
|
148
|
+
>;
|
|
150
149
|
|
|
151
150
|
export interface EffectProcedureHandlerConfig {
|
|
152
151
|
readonly effectFn: EffectProcedureHandler<any, any, any, any, any, any>;
|
|
@@ -157,6 +156,26 @@ export interface EffectProcedureHandlerConfig {
|
|
|
157
156
|
type EffectTagService<T extends EffectContext.Tag<any, any>> =
|
|
158
157
|
T extends EffectContext.Tag<any, infer S> ? S : never;
|
|
159
158
|
|
|
159
|
+
type EffectOperationError<TEffectErrorMap extends EffectErrorMap> =
|
|
160
|
+
| EffectErrorMapToUnion<TEffectErrorMap>
|
|
161
|
+
| ORPCError<ORPCErrorCode, unknown>;
|
|
162
|
+
|
|
163
|
+
type EffectCallbackGenerator<
|
|
164
|
+
TReturn,
|
|
165
|
+
TEffectErrorMap extends EffectErrorMap,
|
|
166
|
+
TRequirementsProvided,
|
|
167
|
+
> = Generator<
|
|
168
|
+
YieldWrap<
|
|
169
|
+
Effect.Effect<
|
|
170
|
+
unknown,
|
|
171
|
+
EffectOperationError<TEffectErrorMap>,
|
|
172
|
+
TRequirementsProvided
|
|
173
|
+
>
|
|
174
|
+
>,
|
|
175
|
+
TReturn,
|
|
176
|
+
never
|
|
177
|
+
>;
|
|
178
|
+
|
|
160
179
|
export type EffectProvider<
|
|
161
180
|
TCurrentContext extends Context,
|
|
162
181
|
TInput,
|
|
@@ -171,11 +190,17 @@ export type EffectProvider<
|
|
|
171
190
|
EffectErrorConstructorMap<TEffectErrorMap>,
|
|
172
191
|
TMeta
|
|
173
192
|
>,
|
|
174
|
-
) =>
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
193
|
+
) =>
|
|
194
|
+
| Effect.Effect<
|
|
195
|
+
EffectTagService<TTag>,
|
|
196
|
+
EffectOperationError<TEffectErrorMap>,
|
|
197
|
+
TRequirementsProvided
|
|
198
|
+
>
|
|
199
|
+
| EffectCallbackGenerator<
|
|
200
|
+
EffectTagService<TTag>,
|
|
201
|
+
TEffectErrorMap,
|
|
202
|
+
TRequirementsProvided
|
|
203
|
+
>;
|
|
179
204
|
|
|
180
205
|
export type EffectOptionalProvider<
|
|
181
206
|
TCurrentContext extends Context,
|
|
@@ -191,11 +216,17 @@ export type EffectOptionalProvider<
|
|
|
191
216
|
EffectErrorConstructorMap<TEffectErrorMap>,
|
|
192
217
|
TMeta
|
|
193
218
|
>,
|
|
194
|
-
) =>
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
219
|
+
) =>
|
|
220
|
+
| Effect.Effect<
|
|
221
|
+
Option.Option<EffectTagService<TTag>>,
|
|
222
|
+
EffectOperationError<TEffectErrorMap>,
|
|
223
|
+
TRequirementsProvided
|
|
224
|
+
>
|
|
225
|
+
| EffectCallbackGenerator<
|
|
226
|
+
Option.Option<EffectTagService<TTag>>,
|
|
227
|
+
TEffectErrorMap,
|
|
228
|
+
TRequirementsProvided
|
|
229
|
+
>;
|
|
199
230
|
|
|
200
231
|
interface EffectMiddlewareNext<
|
|
201
232
|
TOutput,
|
|
@@ -327,17 +358,16 @@ export type EffectOrORPCMiddleware<
|
|
|
327
358
|
) =>
|
|
328
359
|
| MiddlewareResult<TOutContext, TOutput>
|
|
329
360
|
| PromiseLike<MiddlewareResult<TOutContext, TOutput>>
|
|
330
|
-
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
>
|
|
338
|
-
>,
|
|
361
|
+
| void
|
|
362
|
+
| Effect.Effect<
|
|
363
|
+
EffectMiddlewareResult<TOutContext, TOutput> | void,
|
|
364
|
+
EffectOperationError<TEffectErrorMap>,
|
|
365
|
+
TRequirementsProvided
|
|
366
|
+
>
|
|
367
|
+
| EffectCallbackGenerator<
|
|
339
368
|
EffectMiddlewareResult<TOutContext, TOutput> | void,
|
|
340
|
-
|
|
369
|
+
TEffectErrorMap,
|
|
370
|
+
TRequirementsProvided
|
|
341
371
|
>;
|
|
342
372
|
|
|
343
373
|
export type EffectMiddlewareOptions<
|
|
@@ -384,18 +414,18 @@ export type EffectMiddleware<
|
|
|
384
414
|
TEffectErrorMap,
|
|
385
415
|
TRequirementsProvided
|
|
386
416
|
>,
|
|
387
|
-
) =>
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
| EffectErrorMapToUnion<TEffectErrorMap>
|
|
392
|
-
| ORPCError<ORPCErrorCode, unknown>,
|
|
417
|
+
) =>
|
|
418
|
+
| Effect.Effect<
|
|
419
|
+
EffectMiddlewareResult<TOutContext, TOutput> | void,
|
|
420
|
+
EffectOperationError<TEffectErrorMap>,
|
|
393
421
|
TRequirementsProvided
|
|
394
422
|
>
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
423
|
+
| EffectCallbackGenerator<
|
|
424
|
+
EffectMiddlewareResult<TOutContext, TOutput> | void,
|
|
425
|
+
TEffectErrorMap,
|
|
426
|
+
TRequirementsProvided
|
|
427
|
+
>
|
|
428
|
+
| void;
|
|
399
429
|
|
|
400
430
|
type EffectProvideStep = {
|
|
401
431
|
readonly _tag: "provide";
|