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.
@@ -1,6 +1,6 @@
1
1
  import type { InferSchemaOutput } from "@orpc/contract";
2
2
  import { isContractProcedure } from "@orpc/contract";
3
- import { call, os } from "@orpc/server";
3
+ import { call, createRouterClient, os } from "@orpc/server";
4
4
  import {
5
5
  Context,
6
6
  Effect,
@@ -8,6 +8,7 @@ import {
8
8
  Layer,
9
9
  ManagedRuntime,
10
10
  Option,
11
+ Tracer,
11
12
  } from "effect";
12
13
  import { beforeEach, describe, expect, expectTypeOf, it, vi } from "vitest";
13
14
  import z from "zod";
@@ -51,6 +52,56 @@ const def = {
51
52
 
52
53
  const builder = new EffectBuilder(def);
53
54
 
55
+ type RecordedSpan = {
56
+ readonly name: string;
57
+ readonly parentName: string | undefined;
58
+ };
59
+
60
+ function makeRecordedRuntime() {
61
+ const spans: Array<RecordedSpan> = [];
62
+ const spanNamesById = new Map<string, string>();
63
+ const tracer = Tracer.make({
64
+ context: (f) => f(),
65
+ span(name, parent, context, links, startTime, kind) {
66
+ const spanId = `span-${spans.length + 1}`;
67
+ spans.push({
68
+ name,
69
+ parentName: Option.match(parent, {
70
+ onNone: () => undefined,
71
+ onSome: (span) => spanNamesById.get(span.spanId),
72
+ }),
73
+ });
74
+ spanNamesById.set(spanId, name);
75
+ const attributes = new Map<string, unknown>();
76
+
77
+ return {
78
+ _tag: "Span" as const,
79
+ name,
80
+ spanId,
81
+ traceId: "trace",
82
+ parent,
83
+ context,
84
+ status: { _tag: "Started" as const, startTime },
85
+ attributes,
86
+ links,
87
+ sampled: true,
88
+ kind,
89
+ end() {},
90
+ attribute(key: string, value: unknown) {
91
+ attributes.set(key, value);
92
+ },
93
+ event() {},
94
+ addLinks() {},
95
+ };
96
+ },
97
+ });
98
+
99
+ return {
100
+ runtime: ManagedRuntime.make(Layer.setTracer(tracer)),
101
+ spans,
102
+ };
103
+ }
104
+
54
105
  beforeEach(() => vi.clearAllMocks());
55
106
 
56
107
  describe("effectBuilder", () => {
@@ -77,15 +128,55 @@ describe("effectBuilder", () => {
77
128
  });
78
129
 
79
130
  describe(".use", () => {
80
- it("without map input", () => {
81
- const mid2 = vi.fn();
131
+ it("without map input", async () => {
132
+ const mid2 = vi.fn(({ next }) =>
133
+ next({ context: { fromMiddleware: true } }),
134
+ );
82
135
  const applied = builder.use(mid2);
83
136
 
84
137
  expect(applied).instanceOf(EffectBuilder);
85
138
  expect(applied).not.toBe(builder);
86
- expect(applied["~effect"]).toEqual({
87
- ...def,
88
- middlewares: [mid, mid2],
139
+ expect(applied["~effect"].middlewares).toHaveLength(2);
140
+ expect(applied["~effect"].middlewares[0]).toBe(mid);
141
+
142
+ const wrapped = applied["~effect"].middlewares[1]!;
143
+ const procedure = eos.effect(function* () {
144
+ return "ok";
145
+ });
146
+ let nextCalls = 0;
147
+ let nextOptions: unknown;
148
+ const next = <TContext extends Record<PropertyKey, unknown>>(options?: {
149
+ context?: TContext;
150
+ }) => {
151
+ nextCalls++;
152
+ nextOptions = options;
153
+ return Promise.resolve({
154
+ output: "ok",
155
+ context: options?.context ?? ({} as TContext),
156
+ });
157
+ };
158
+ await expect(
159
+ wrapped(
160
+ {
161
+ context: {},
162
+ errors: {},
163
+ path: [],
164
+ procedure,
165
+ signal: undefined,
166
+ lastEventId: undefined,
167
+ next,
168
+ },
169
+ "input",
170
+ vi.fn(),
171
+ ),
172
+ ).resolves.toEqual({
173
+ output: "ok",
174
+ context: { fromMiddleware: true },
175
+ });
176
+ expect(mid2).toHaveBeenCalledOnce();
177
+ expect(nextCalls).toBe(1);
178
+ expect(nextOptions).toEqual({
179
+ context: { fromMiddleware: true },
89
180
  });
90
181
  });
91
182
  });
@@ -380,6 +471,17 @@ describe("makeEffectORPC factory", () => {
380
471
  expect(result).toBe(3);
381
472
  });
382
473
 
474
+ it("supports Effect.fn handlers", async () => {
475
+ const procedure = eos.input(z.number()).effect(
476
+ Effect.fn("test.effect-handler")(function* ({ input }) {
477
+ const increment = yield* Effect.succeed(1);
478
+ return input + increment;
479
+ }),
480
+ );
481
+
482
+ await expect(call(procedure, 41)).resolves.toBe(42);
483
+ });
484
+
383
485
  it("chains builder methods correctly", () => {
384
486
  const effectBuilder = makeEffectORPC(runtime);
385
487
 
@@ -532,10 +634,8 @@ describe("effect with services", () => {
532
634
  { id: string }
533
635
  >() {}
534
636
 
535
- const effectBuilder = makeEffectORPC(runtime).$context<{
536
- user: { id: string };
537
- }>();
538
- const procedure = effectBuilder
637
+ const procedure = eos
638
+ .$context<{ user: { id: string } }>()
539
639
  .provide(CurrentUser, ({ context }) => Effect.succeed(context.user))
540
640
  .effect(function* () {
541
641
  return yield* CurrentUser;
@@ -546,6 +646,33 @@ describe("effect with services", () => {
546
646
  ).resolves.toEqual({ id: "u-1" });
547
647
  });
548
648
 
649
+ it(".provide supports generator request-scoped providers", async () => {
650
+ class UserPrefix extends Context.Tag("ProviderUserPrefix")<
651
+ UserPrefix,
652
+ { prefix: string }
653
+ >() {}
654
+ class CurrentUser extends Context.Tag("GeneratorProviderCurrentUser")<
655
+ CurrentUser,
656
+ { id: string }
657
+ >() {}
658
+
659
+ const procedure = eos
660
+ .$context<{ user: { id: string } }>()
661
+ .provide(UserPrefix, () => Effect.succeed({ prefix: "user:" }))
662
+ .provide(CurrentUser, function* ({ context }) {
663
+ expectTypeOf(context.user).toEqualTypeOf<{ id: string }>();
664
+ const prefix = yield* UserPrefix;
665
+ return { id: `${prefix.prefix}${context.user.id}` };
666
+ })
667
+ .effect(function* () {
668
+ return yield* CurrentUser;
669
+ });
670
+
671
+ await expect(
672
+ call(procedure, undefined, { context: { user: { id: "u-1" } } }),
673
+ ).resolves.toEqual({ id: "user:u-1" });
674
+ });
675
+
549
676
  it(".provide service overrides the same service from the runtime", async () => {
550
677
  class CurrentUser extends Context.Tag("CurrentUserOverride")<
551
678
  CurrentUser,
@@ -575,7 +702,7 @@ describe("effect with services", () => {
575
702
 
576
703
  it("Effect .use yield* next() without return runs handler once", async () => {
577
704
  let runs = 0;
578
- const procedure = makeEffectORPC(runtime)
705
+ const procedure = eos
579
706
  .use(function* ({ next }) {
580
707
  yield* Effect.void;
581
708
  yield* next();
@@ -591,7 +718,7 @@ describe("effect with services", () => {
591
718
 
592
719
  it("Effect .use guard-only middleware without next runs handler once", async () => {
593
720
  let runs = 0;
594
- const procedure = makeEffectORPC(runtime)
721
+ const procedure = eos
595
722
  .use(function* () {
596
723
  yield* Effect.void;
597
724
  })
@@ -625,10 +752,8 @@ describe("effect with services", () => {
625
752
  >() {}
626
753
 
627
754
  let seenUser: { id: string } | undefined;
628
- const effectBuilder = makeEffectORPC(runtime).$context<{
629
- user: { id: string };
630
- }>();
631
- const procedure = effectBuilder
755
+ const procedure = eos
756
+ .$context<{ user: { id: string } }>()
632
757
  .provide(CurrentUser, ({ context }) => Effect.succeed(context.user))
633
758
  .use(function* () {
634
759
  seenUser = yield* CurrentUser;
@@ -644,8 +769,6 @@ describe("effect with services", () => {
644
769
  });
645
770
 
646
771
  it("Effect .middleware can create reusable generator middleware", async () => {
647
- const eos = makeEffectORPC(runtime);
648
-
649
772
  const reusable = eos.middleware(function* ({ next }, input: string) {
650
773
  expectTypeOf(input).toEqualTypeOf<string>();
651
774
  return yield* next({ context: { seenInput: input } });
@@ -668,32 +791,74 @@ describe("effect with services", () => {
668
791
  { value: string }
669
792
  >() {}
670
793
 
671
- const eos = makeEffectORPC(runtime).provide(MiddlewareService, () =>
794
+ const builder = eos.provide(MiddlewareService, () =>
672
795
  Effect.succeed({ value: "provided" }),
673
796
  );
674
797
 
675
- const reusable = eos.middleware(function* ({ next }) {
798
+ const reusable = builder.middleware(function* ({ next }) {
676
799
  const service = yield* MiddlewareService;
677
800
  return yield* next({ context: { serviceValue: service.value } });
678
801
  });
679
802
 
680
- const procedure = eos.use(reusable).effect(function* ({ context }) {
803
+ const procedure = builder.use(reusable).effect(function* ({ context }) {
681
804
  return context.serviceValue;
682
805
  });
683
806
 
684
807
  await expect(call(procedure, undefined)).resolves.toBe("provided");
685
808
  });
686
809
 
810
+ it("Effect .use supports Effect.fn middleware", async () => {
811
+ const procedure = eos
812
+ .use(
813
+ Effect.fn("test.middleware")(function* ({ next }) {
814
+ yield* Effect.void;
815
+ return yield* next({ context: { fromEffectFn: true } });
816
+ }),
817
+ )
818
+ .effect(function* ({ context }) {
819
+ return context.fromEffectFn;
820
+ });
821
+
822
+ await expect(call(procedure, undefined)).resolves.toBe(true);
823
+ });
824
+
825
+ it("Effect .use supports Effect.gen-returning middleware", async () => {
826
+ const procedure = eos
827
+ .use(({ next }) =>
828
+ Effect.gen(function* () {
829
+ yield* Effect.void;
830
+ return yield* next({ context: { fromEffectGen: true } });
831
+ }),
832
+ )
833
+ .effect(function* ({ context }) {
834
+ return context.fromEffectGen;
835
+ });
836
+
837
+ await expect(call(procedure, undefined)).resolves.toBe(true);
838
+ });
839
+
840
+ it("Effect .use supports normal guard-only middleware", async () => {
841
+ let guarded = false;
842
+ const procedure = eos
843
+ .use(() => {
844
+ guarded = true;
845
+ })
846
+ .effect(function* () {
847
+ return "ok";
848
+ });
849
+
850
+ await expect(call(procedure, undefined)).resolves.toBe("ok");
851
+ expect(guarded).toBe(true);
852
+ });
853
+
687
854
  it("Effect .use can enrich context through next", async () => {
688
855
  class CurrentUser extends Context.Tag("NextCurrentUser")<
689
856
  CurrentUser,
690
857
  { id: string }
691
858
  >() {}
692
859
 
693
- const effectBuilder = makeEffectORPC(runtime).$context<{
694
- user: { id: string };
695
- }>();
696
- const procedure = effectBuilder
860
+ const procedure = eos
861
+ .$context<{ user: { id: string } }>()
697
862
  .provide(CurrentUser, ({ context }) => Effect.succeed(context.user))
698
863
  .use(function* ({ next }, _input) {
699
864
  const user = yield* CurrentUser;
@@ -709,7 +874,7 @@ describe("effect with services", () => {
709
874
  });
710
875
 
711
876
  it("Effect .use can transform downstream output", async () => {
712
- const procedure = makeEffectORPC(runtime)
877
+ const procedure = eos
713
878
  .use(function* ({ next }, _input, output) {
714
879
  const result = yield* next();
715
880
  return yield* output(`${result.output}-wrapped`);
@@ -722,7 +887,7 @@ describe("effect with services", () => {
722
887
  });
723
888
 
724
889
  it("Effect .use can transform typed downstream output after .output", async () => {
725
- const procedure = makeEffectORPC(runtime)
890
+ const procedure = eos
726
891
  .output(z.string())
727
892
  .use(function* ({ next }, _input, output) {
728
893
  const result = yield* next();
@@ -737,7 +902,7 @@ describe("effect with services", () => {
737
902
  });
738
903
 
739
904
  it("Effect .use can read typed input after .input", async () => {
740
- const procedure = makeEffectORPC(runtime)
905
+ const procedure = eos
741
906
  .input(z.object({ value: z.number() }))
742
907
  .use(function* ({ next }, input) {
743
908
  expectTypeOf(input).toMatchTypeOf<{ value: number }>();
@@ -751,7 +916,7 @@ describe("effect with services", () => {
751
916
  });
752
917
 
753
918
  it("Effect .use can read typed input and output after .input().output()", async () => {
754
- const procedure = makeEffectORPC(runtime)
919
+ const procedure = eos
755
920
  .input(z.object({ value: z.number() }))
756
921
  .output(z.string())
757
922
  .use(function* ({ next }, input, output) {
@@ -768,7 +933,7 @@ describe("effect with services", () => {
768
933
  });
769
934
 
770
935
  it("Effect .use can transform typed downstream output after .effect", async () => {
771
- const procedure = makeEffectORPC(runtime)
936
+ const procedure = eos
772
937
  .effect(function* () {
773
938
  return "ok";
774
939
  })
@@ -840,7 +1005,7 @@ describe("effect with services", () => {
840
1005
  { id: string }
841
1006
  >() {}
842
1007
 
843
- const procedure = makeEffectORPC(runtime)
1008
+ const procedure = eos
844
1009
  .$context<{ user?: { id: string } }>()
845
1010
  .provideOptional(CurrentUser, ({ context }) =>
846
1011
  Effect.succeed(Option.fromNullable(context.user)),
@@ -854,13 +1019,34 @@ describe("effect with services", () => {
854
1019
  ).resolves.toEqual(Option.some({ id: "u-6" }));
855
1020
  });
856
1021
 
1022
+ it(".provideOptional supports generator request-scoped providers", async () => {
1023
+ class CurrentUser extends Context.Tag("GeneratorOptionalCurrentUser")<
1024
+ CurrentUser,
1025
+ { id: string }
1026
+ >() {}
1027
+
1028
+ const procedure = eos
1029
+ .$context<{ user?: { id: string } }>()
1030
+ .provideOptional(CurrentUser, function* ({ context }) {
1031
+ yield* Effect.void;
1032
+ return Option.fromNullable(context.user);
1033
+ })
1034
+ .effect(function* () {
1035
+ return yield* Effect.serviceOption(CurrentUser);
1036
+ });
1037
+
1038
+ await expect(
1039
+ call(procedure, undefined, { context: { user: { id: "u-7" } } }),
1040
+ ).resolves.toEqual(Option.some({ id: "u-7" }));
1041
+ });
1042
+
857
1043
  it(".provideOptional leaves absent request-scoped services unavailable", async () => {
858
1044
  class CurrentUser extends Context.Tag("OptionalCurrentUserAbsent")<
859
1045
  CurrentUser,
860
1046
  { id: string }
861
1047
  >() {}
862
1048
 
863
- const procedure = makeEffectORPC(runtime)
1049
+ const procedure = eos
864
1050
  .$context<{ user?: { id: string } }>()
865
1051
  .provideOptional(CurrentUser, ({ context }) =>
866
1052
  Effect.succeed(Option.fromNullable(context.user)),
@@ -880,7 +1066,7 @@ describe("effect with services", () => {
880
1066
  { readonly value: string }
881
1067
  >() {}
882
1068
 
883
- makeEffectORPC(runtime)
1069
+ eos
884
1070
  .provideOptional(OptionalService, () =>
885
1071
  Effect.succeed(Option.some({ value: "provided" })),
886
1072
  )
@@ -895,12 +1081,10 @@ describe("effect with services", () => {
895
1081
 
896
1082
  describe(".traced", () => {
897
1083
  it("creates an EffectBuilder with span config", () => {
898
- const effectBuilder = makeEffectORPC(runtime);
899
-
900
- const traced = effectBuilder.traced("users.getUser");
1084
+ const traced = eos.traced("users.getUser");
901
1085
 
902
1086
  expect(traced).instanceOf(EffectBuilder);
903
- expect(traced).not.toBe(effectBuilder);
1087
+ expect(traced).not.toBe(eos);
904
1088
  expect(traced["~effect"].spanConfig).toBeDefined();
905
1089
  expect(traced["~effect"].spanConfig?.name).toBe("users.getUser");
906
1090
  expect(traced["~effect"].spanConfig?.captureStackTrace).toBeInstanceOf(
@@ -909,9 +1093,7 @@ describe(".traced", () => {
909
1093
  });
910
1094
 
911
1095
  it("preserves span config through chained methods", () => {
912
- const effectBuilder = makeEffectORPC(runtime);
913
-
914
- const procedure = effectBuilder
1096
+ const procedure = eos
915
1097
  .input(z.object({ id: z.string() }))
916
1098
  .traced("users.getUser")
917
1099
  .effect(function* () {
@@ -922,56 +1104,60 @@ describe(".traced", () => {
922
1104
  // The span wrapping happens in the handler, so we just verify the procedure was created
923
1105
  });
924
1106
 
925
- it("traced procedure handler runs successfully", async () => {
926
- const effectBuilder = makeEffectORPC(runtime);
927
-
928
- const procedure = effectBuilder
1107
+ it("traced procedure runs through a routed client", async () => {
1108
+ const procedure = eos
929
1109
  .input(z.object({ id: z.string() }))
930
1110
  .traced("users.getUser")
931
1111
  .effect(function* ({ input }) {
932
1112
  return { id: input.id, name: "Alice" };
933
1113
  });
1114
+ const client = createRouterClient({ users: { getUser: procedure } });
934
1115
 
935
- const result = await procedure["~effect"].handler({
936
- context: {},
937
- input: { id: "123" },
938
- path: ["users", "getUser"],
939
- procedure: procedure as any,
940
- signal: undefined,
941
- lastEventId: undefined,
942
- errors: {},
1116
+ await expect(client.users.getUser({ id: "123" })).resolves.toEqual({
1117
+ id: "123",
1118
+ name: "Alice",
943
1119
  });
944
-
945
- expect(result).toEqual({ id: "123", name: "Alice" });
946
1120
  });
947
1121
 
948
- it("traced procedure with Effect.fn generator syntax", async () => {
949
- const effectBuilder = makeEffectORPC(runtime);
1122
+ it("traced procedure uses the explicit span name at runtime", async () => {
1123
+ const recorded = makeRecordedRuntime();
1124
+ const effectBuilder = makeEffectORPC(recorded.runtime);
1125
+
1126
+ const procedure = effectBuilder
1127
+ .traced("custom.users.getUser")
1128
+ .effect(function* () {
1129
+ return "ok";
1130
+ });
1131
+ const client = createRouterClient({ users: { getUser: procedure } });
1132
+
1133
+ try {
1134
+ await expect(client.users.getUser(undefined)).resolves.toBe("ok");
1135
+ expect(recorded.spans).toContainEqual({
1136
+ name: "custom.users.getUser",
1137
+ parentName: undefined,
1138
+ });
1139
+ expect(recorded.spans.map(({ name }) => name)).not.toContain(
1140
+ "users.getUser",
1141
+ );
1142
+ } finally {
1143
+ await recorded.runtime.dispose();
1144
+ }
1145
+ });
950
1146
 
951
- const procedure = effectBuilder.traced("math.add").effect(function* () {
1147
+ it("traced procedure with generator syntax runs through a routed client", async () => {
1148
+ const procedure = eos.traced("math.add").effect(function* () {
952
1149
  const a = yield* Effect.succeed(10);
953
1150
  const b = yield* Effect.succeed(20);
954
1151
  return a + b;
955
1152
  });
1153
+ const client = createRouterClient({ math: { add: procedure } });
956
1154
 
957
- const result = await procedure["~effect"].handler({
958
- context: {},
959
- input: undefined,
960
- path: ["math", "add"],
961
- procedure: procedure as any,
962
- signal: undefined,
963
- lastEventId: undefined,
964
- errors: {},
965
- });
966
-
967
- expect(result).toBe(30);
1155
+ await expect(client.math.add(undefined)).resolves.toBe(30);
968
1156
  });
969
1157
 
970
1158
  it("captures stack trace at definition time", () => {
971
- const effectBuilder = makeEffectORPC(runtime);
972
-
973
1159
  // The stack trace is captured when .traced() is called
974
- const traced = effectBuilder.traced("test.procedure");
1160
+ const traced = eos.traced("test.procedure");
975
1161
 
976
1162
  const stackTrace = traced["~effect"].spanConfig?.captureStackTrace();
977
1163
  // The stack trace should be a string containing the file location
@@ -983,71 +1169,49 @@ describe(".traced", () => {
983
1169
  });
984
1170
 
985
1171
  describe("default tracing (without .traced())", () => {
986
- it("procedure without .traced() still runs successfully", async () => {
987
- const effectBuilder = makeEffectORPC(runtime);
988
-
989
- // No .traced() call - should still work and use path as span name
990
- const procedure = effectBuilder
1172
+ it("procedure without .traced() runs through a routed client", async () => {
1173
+ const procedure = eos
991
1174
  .input(z.object({ id: z.string() }))
992
1175
  .effect(function* ({ input }) {
993
1176
  return { id: input.id, name: "Bob" };
994
1177
  });
1178
+ const client = createRouterClient({ users: { findById: procedure } });
995
1179
 
996
- const result = await procedure["~effect"].handler({
997
- context: {},
998
- input: { id: "456" },
999
- path: ["users", "findById"],
1000
- procedure: procedure as any,
1001
- signal: undefined,
1002
- lastEventId: undefined,
1003
- errors: {},
1180
+ await expect(client.users.findById({ id: "456" })).resolves.toEqual({
1181
+ id: "456",
1182
+ name: "Bob",
1004
1183
  });
1005
-
1006
- expect(result).toEqual({ id: "456", name: "Bob" });
1007
1184
  });
1008
1185
 
1009
- it("uses procedure path as default span name", async () => {
1010
- const effectBuilder = makeEffectORPC(runtime);
1186
+ it("uses procedure path as default span name at runtime", async () => {
1187
+ const recorded = makeRecordedRuntime();
1188
+ const effectBuilder = makeEffectORPC(recorded.runtime);
1011
1189
 
1012
- // Without .traced(), the span name should be derived from path
1013
1190
  const procedure = effectBuilder.effect(function* () {
1014
1191
  return "hello";
1015
1192
  });
1193
+ const client = createRouterClient({ api: { v1: { greet: procedure } } });
1016
1194
 
1017
- // The procedure should work with any path
1018
- const result = await procedure["~effect"].handler({
1019
- context: {},
1020
- input: undefined,
1021
- path: ["api", "v1", "greet"],
1022
- procedure: procedure as any,
1023
- signal: undefined,
1024
- lastEventId: undefined,
1025
- errors: {},
1026
- });
1027
-
1028
- expect(result).toBe("hello");
1195
+ try {
1196
+ await expect(client.api.v1.greet(undefined)).resolves.toBe("hello");
1197
+ expect(recorded.spans).toContainEqual({
1198
+ name: "api.v1.greet",
1199
+ parentName: undefined,
1200
+ });
1201
+ } finally {
1202
+ await recorded.runtime.dispose();
1203
+ }
1029
1204
  });
1030
1205
 
1031
- it("default tracing works with Effect.fn generator", async () => {
1032
- const effectBuilder = makeEffectORPC(runtime);
1033
-
1034
- const procedure = effectBuilder.effect(function* () {
1206
+ it("default tracing works with generator handlers through a routed client", async () => {
1207
+ const procedure = eos.effect(function* () {
1035
1208
  const x = 5;
1036
1209
  const y = 10;
1037
1210
  return x * y;
1038
1211
  });
1212
+ const client = createRouterClient({ math: { multiply: procedure } });
1039
1213
 
1040
- const result = await procedure["~effect"].handler({
1041
- context: {},
1042
- input: undefined,
1043
- path: ["math", "multiply"],
1044
- procedure: procedure as any,
1045
- signal: undefined,
1046
- lastEventId: undefined,
1047
- errors: {},
1048
- });
1049
-
1050
- expect(result).toBe(50);
1214
+ await expect(client.math.multiply(undefined)).resolves.toBe(50);
1051
1215
  });
1052
1216
 
1053
1217
  it("default tracing works with services from runtime", async () => {
@@ -1068,36 +1232,29 @@ describe("default tracing (without .traced())", () => {
1068
1232
  .effect(function* ({ input }) {
1069
1233
  return yield* Greeter.greet(input.name);
1070
1234
  });
1235
+ const client = createRouterClient({ greeting: { say: procedure } });
1071
1236
 
1072
- const result = await procedure["~effect"].handler({
1073
- context: {},
1074
- input: { name: "World" },
1075
- path: ["greeting", "say"],
1076
- procedure: procedure as any,
1077
- signal: undefined,
1078
- lastEventId: undefined,
1079
- errors: {},
1080
- });
1081
-
1082
- expect(result).toBe("Hello, World!");
1083
-
1084
- await serviceRuntime.dispose();
1237
+ try {
1238
+ await expect(client.greeting.say({ name: "World" })).resolves.toBe(
1239
+ "Hello, World!",
1240
+ );
1241
+ } finally {
1242
+ await serviceRuntime.dispose();
1243
+ }
1085
1244
  });
1086
1245
 
1087
1246
  it("no spanConfig is set when .traced() is not called", () => {
1088
- const effectBuilder = makeEffectORPC(runtime);
1089
-
1090
1247
  // Without .traced(), spanConfig should be undefined
1091
- expect(effectBuilder["~effect"].spanConfig).toBeUndefined();
1248
+ expect(eos["~effect"].spanConfig).toBeUndefined();
1092
1249
 
1093
- const withInput = effectBuilder.input(z.string());
1250
+ const withInput = eos.input(z.string());
1094
1251
  expect(withInput["~effect"].spanConfig).toBeUndefined();
1095
1252
  });
1096
1253
 
1097
1254
  it("enforces the declared output schema for effect handlers", () => {
1098
1255
  const declaredOutputSchema = z.object({ name: z.string() });
1099
1256
 
1100
- makeEffectORPC(runtime)
1257
+ eos
1101
1258
  .input(z.string())
1102
1259
  .output(declaredOutputSchema)
1103
1260
  .effect(
@@ -1107,7 +1264,7 @@ describe("default tracing (without .traced())", () => {
1107
1264
  },
1108
1265
  );
1109
1266
 
1110
- const procedure = makeEffectORPC(runtime)
1267
+ const procedure = eos
1111
1268
  .output(declaredOutputSchema)
1112
1269
  // @ts-expect-error output() should constrain the effect return type
1113
1270
  .effect(function* () {
@@ -1126,14 +1283,14 @@ describe("default tracing (without .traced())", () => {
1126
1283
  { readonly value: string }
1127
1284
  >() {}
1128
1285
 
1129
- makeEffectORPC(runtime).effect(
1286
+ eos.effect(
1130
1287
  // @ts-expect-error MissingService is not available from the runtime or .provide
1131
1288
  function* () {
1132
1289
  return yield* MissingService;
1133
1290
  },
1134
1291
  );
1135
1292
 
1136
- makeEffectORPC(runtime)
1293
+ eos
1137
1294
  .provide(MissingService, () => Effect.succeed({ value: "provided" }))
1138
1295
  .effect(function* () {
1139
1296
  return yield* MissingService;
@@ -1145,7 +1302,7 @@ describe("default tracing (without .traced())", () => {
1145
1302
  "MissingMiddlewareService",
1146
1303
  )<MissingMiddlewareService, { readonly value: string }>() {}
1147
1304
 
1148
- makeEffectORPC(runtime)
1305
+ eos
1149
1306
  .use(
1150
1307
  // @ts-expect-error MissingMiddlewareService is not available from the runtime or .provide
1151
1308
  function* () {
@@ -1156,7 +1313,7 @@ describe("default tracing (without .traced())", () => {
1156
1313
  return "ok";
1157
1314
  });
1158
1315
 
1159
- makeEffectORPC(runtime)
1316
+ eos
1160
1317
  .provide(MissingMiddlewareService, () =>
1161
1318
  Effect.succeed({ value: "provided" }),
1162
1319
  )