@dr_nikson/effect-grpc 0.2.0-mvp-9971208edee70b39058f12d8c2a1fcfaeecd3b51 → 3.0.0-mvp-e2b78b1c4be4bb339577d68dfce008ef1b56218c

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +928 -0
  2. package/dist/client.internal.d.ts.map +1 -1
  3. package/dist/client.internal.js +2 -1
  4. package/dist/client.internal.js.map +1 -1
  5. package/dist/client.js +3 -0
  6. package/dist/client.js.map +1 -1
  7. package/dist/grpcException.d.ts +243 -0
  8. package/dist/grpcException.d.ts.map +1 -0
  9. package/dist/grpcException.internal.d.ts +28 -0
  10. package/dist/grpcException.internal.d.ts.map +1 -0
  11. package/dist/grpcException.internal.js +72 -0
  12. package/dist/grpcException.internal.js.map +1 -0
  13. package/dist/grpcException.js +218 -0
  14. package/dist/grpcException.js.map +1 -0
  15. package/dist/index.d.ts +1 -0
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +1 -0
  18. package/dist/index.js.map +1 -1
  19. package/dist/protoRuntime.d.ts +5 -4
  20. package/dist/protoRuntime.d.ts.map +1 -1
  21. package/dist/protoRuntime.internal.d.ts +4 -3
  22. package/dist/protoRuntime.internal.d.ts.map +1 -1
  23. package/dist/protoRuntime.internal.js +16 -2
  24. package/dist/protoRuntime.internal.js.map +1 -1
  25. package/dist/protoRuntime.js +2 -0
  26. package/dist/protoRuntime.js.map +1 -1
  27. package/dist/protocGenPlugin.d.ts.map +1 -1
  28. package/dist/protocGenPlugin.js +3 -3
  29. package/dist/protocGenPlugin.js.map +1 -1
  30. package/dist/server.d.ts +8 -4
  31. package/dist/server.d.ts.map +1 -1
  32. package/dist/server.internal.d.ts +5 -4
  33. package/dist/server.internal.d.ts.map +1 -1
  34. package/dist/server.internal.js +7 -8
  35. package/dist/server.internal.js.map +1 -1
  36. package/dist/server.js +4 -0
  37. package/dist/server.js.map +1 -1
  38. package/dist/typeUtils.js +1 -1
  39. package/dist/typeUtils.js.map +1 -1
  40. package/package.json +1 -1
package/README.md ADDED
@@ -0,0 +1,928 @@
1
+ # effect-grpc
2
+
3
+ Type-safe gRPC and Protocol Buffer support for the Effect ecosystem
4
+
5
+ ## Overview
6
+
7
+ `effect-grpc` provides a seamless integration between gRPC/Protocol Buffers and the Effect TypeScript. It enables you to build type-safe, composable gRPC services and clients with all the benefits of Effect's powerful error handling, dependency injection, and functional programming patterns.
8
+
9
+ **Built on battle-tested foundations:** This library is a thin wrapper around industry-standard, production-proven gRPC libraries including [Connect-RPC](https://connectrpc.com/) (Buf's modern gRPC implementation) and [@bufbuild/protobuf](https://github.com/bufbuild/protobuf-es) (official Protocol Buffers runtime for JavaScript/TypeScript), bringing Effect's functional programming benefits to the established gRPC ecosystem.
10
+
11
+ ### Key Features
12
+
13
+ - **Full Type Safety** - Generated TypeScript code from Protocol Buffers with complete type inference
14
+ - **Effect Integration** - Native support for Effect's error handling, tracing, and dependency injection
15
+ - **Code Generation** - Automatic client and server code generation via `protoc-gen-effect` plugin
16
+ - **Connect-RPC** - Built on Connect-RPC for maximum compatibility with gRPC and gRPC-Web
17
+ - **Modular Architecture** - Clean separation between service definitions and implementations
18
+ - **Zero Boilerplate** - Minimal setup required to get started
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ npm install @dr_nikson/effect-grpc
24
+ # gRPC runtime deps
25
+ npm install @bufbuild/protobuf @connectrpc/connect
26
+ ```
27
+
28
+ For code generation, you'll also need:
29
+
30
+ ```bash
31
+ npm install --save-dev @bufbuild/buf @bufbuild/protoc-gen-es
32
+ ```
33
+
34
+ ## Quick Start
35
+
36
+ This guide will walk you through setting up a simple gRPC service with effect-grpc.
37
+
38
+ ### 1. Define Your Protocol Buffer
39
+
40
+ Create a `.proto` file defining your service:
41
+
42
+ ```protobuf
43
+ // proto/hello.proto
44
+ syntax = "proto3";
45
+
46
+ package example.v1;
47
+
48
+ service HelloService {
49
+ rpc SayHello(HelloRequest) returns (HelloResponse);
50
+ }
51
+
52
+ message HelloRequest {
53
+ string name = 1;
54
+ }
55
+
56
+ message HelloResponse {
57
+ string message = 1;
58
+ }
59
+ ```
60
+
61
+ ### 2. Configure Code Generation
62
+
63
+ Create a `buf.gen.yaml` configuration file:
64
+
65
+ ```yaml
66
+ # buf.gen.yaml
67
+ version: v2
68
+ inputs:
69
+ - directory: proto
70
+ plugins:
71
+ # Generate base Protocol Buffer TypeScript code
72
+ - local: protoc-gen-es
73
+ opt: target=ts,import_extension=js
74
+ out: src/generated
75
+ # Generate Effect-specific code
76
+ - local: protoc-gen-effect
77
+ opt: target=ts,import_extension=js
78
+ out: src/generated
79
+ ```
80
+
81
+ ### 3. Generate TypeScript Code
82
+
83
+ Add the following script to your `package.json`:
84
+
85
+ ```json
86
+ {
87
+ "scripts": {
88
+ "generate:proto": "buf generate"
89
+ }
90
+ }
91
+ ```
92
+
93
+ Then run:
94
+
95
+ ```bash
96
+ npm run generate:proto
97
+ ```
98
+
99
+ This will generate TypeScript files in `src/generated/` with full Effect integration.
100
+
101
+ ### 4. Implement the Server
102
+
103
+ ```typescript
104
+ // src/server.ts
105
+ import { Context, Effect, Layer, LogLevel, Logger } from "effect";
106
+ import { HandlerContext } from "@connectrpc/connect";
107
+ import { EffectGrpcServer } from "@dr_nikson/effect-grpc";
108
+ import { NodeRuntime } from "@effect/platform-node";
109
+
110
+ import * as effectProto from "./generated/example/v1/hello_effect.js";
111
+ import * as proto from "./generated/example/v1/hello_pb.js";
112
+
113
+ // Create a tag for dependency injection
114
+ const HelloServiceTag = effectProto.HelloService.makeTag<HandlerContext>(
115
+ "HandlerContext"
116
+ );
117
+ type HelloServiceTag = Context.Tag.Identifier<typeof HelloServiceTag>;
118
+
119
+ // Implement the service
120
+ const HelloServiceLive: effectProto.HelloService<HandlerContext> = {
121
+ sayHello(request: proto.HelloRequest) {
122
+ return Effect.succeed({
123
+ message: `Hello, ${request.name}!`
124
+ });
125
+ }
126
+ };
127
+
128
+ // Create the service layer
129
+ const helloServiceLayer = effectProto.HelloService.liveLayer(HelloServiceLive)(
130
+ HelloServiceTag
131
+ );
132
+
133
+ // Build and run the gRPC server
134
+ const program = Effect.gen(function* () {
135
+ const helloService = yield* HelloServiceTag;
136
+
137
+ const server: EffectGrpcServer.GrpcServer<"HelloService"> =
138
+ EffectGrpcServer
139
+ .GrpcServerBuilder()
140
+ .withService(helloService)
141
+ .build();
142
+
143
+ return yield* server.run();
144
+ });
145
+
146
+ // Provide dependencies and run
147
+ const layer = Layer.empty.pipe(
148
+ Layer.provideMerge(helloServiceLayer),
149
+ Layer.provideMerge(Logger.minimumLogLevel(LogLevel.Info))
150
+ );
151
+
152
+ NodeRuntime.runMain(Effect.provide(program, layer));
153
+ ```
154
+
155
+ ### 5. Implement the Client
156
+
157
+ ```typescript
158
+ // src/client.ts
159
+ import { Effect, Layer, Logger, LogLevel } from "effect";
160
+ import { EffectGrpcClient } from "@dr_nikson/effect-grpc";
161
+ import { NodeRuntime } from "@effect/platform-node";
162
+
163
+ import * as effectProto from "./generated/example/v1/hello_effect.js";
164
+
165
+ // Create a client tag
166
+ const HelloServiceClientTag = effectProto.HelloServiceClient.makeTag<object>("{}");
167
+ type HelloServiceClientTag = typeof HelloServiceClientTag;
168
+
169
+ // Create the client layer with configuration
170
+ const helloClientLayer = effectProto.HelloServiceClient.liveLayer(
171
+ HelloServiceClientTag
172
+ ).pipe(
173
+ Layer.provideMerge(
174
+ Layer.succeed(
175
+ effectProto.HelloServiceConfigTag,
176
+ EffectGrpcClient.GrpcClientConfig({
177
+ baseUrl: "http://localhost:8000"
178
+ })
179
+ )
180
+ )
181
+ );
182
+
183
+ // Use the client
184
+ const program = Effect.gen(function* () {
185
+ const client = yield* HelloServiceClientTag;
186
+
187
+ const response = yield* client.sayHello({
188
+ name: "World"
189
+ }, {});
190
+
191
+ yield* Effect.log(`Server responded: ${response.message}`);
192
+ });
193
+
194
+ // Provide dependencies and run
195
+ const dependencies = Layer.empty.pipe(
196
+ Layer.provideMerge(helloClientLayer),
197
+ Layer.provideMerge(EffectGrpcClient.liveGrpcClientRuntimeLayer()),
198
+ Layer.provideMerge(Logger.minimumLogLevel(LogLevel.Info))
199
+ );
200
+
201
+ NodeRuntime.runMain(Effect.provide(program, dependencies));
202
+ ```
203
+
204
+ ## Complete Example
205
+
206
+ For a complete working example, see the [`packages/example`](packages/example) directory in this repository. It demonstrates:
207
+
208
+ - Protocol Buffer definition and code generation
209
+ - Server implementation with Effect
210
+ - Client implementation with Effect
211
+ - Proper project structure and configuration
212
+
213
+ To run the example:
214
+
215
+ ```bash
216
+ # Clone the repository
217
+ git clone https://github.com/dr_nikson/effect-grpc.git
218
+ cd effect-grpc
219
+
220
+ # Install dependencies
221
+ pnpm install
222
+
223
+ # Build the library
224
+ pnpm -r run build
225
+
226
+ # In one terminal, start the server
227
+ cd packages/example
228
+ node dist/server.js
229
+
230
+ # In another terminal, run the client
231
+ node dist/client.js
232
+ ```
233
+
234
+ ## API Reference
235
+
236
+ This section documents the public API exported by `@dr_nikson/effect-grpc`. The library excludes internal runtime APIs from public documentation.
237
+
238
+ ### Server API (`EffectGrpcServer`)
239
+
240
+ The server API provides tools for building and running gRPC servers within Effect programs.
241
+
242
+ #### Core Types
243
+
244
+ ##### `GrpcServer<Services>`
245
+
246
+ Represents a running gRPC server instance.
247
+
248
+ **Type Parameters:**
249
+ - `Services` - Union type of all service tags registered with this server
250
+
251
+ **Methods:**
252
+ - `run(): Effect.Effect<never, never, Scope.Scope>` - Starts the server and returns an Effect that requires a Scope
253
+
254
+ **Example:**
255
+ ```typescript
256
+ const server: EffectGrpcServer.GrpcServer<"UserService" | "ProductService"> =
257
+ EffectGrpcServer.GrpcServerBuilder()
258
+ .withService(userService)
259
+ .withService(productService)
260
+ .build();
261
+
262
+ // Run with proper resource management
263
+ const program = Effect.scoped(server.run());
264
+ ```
265
+
266
+ ##### `GrpcServerBuilder<Ctx, Services>`
267
+
268
+ Fluent builder interface for constructing gRPC servers.
269
+
270
+ **Type Parameters:**
271
+ - `Ctx` - Context type available to service handlers (defaults to `HandlerContext`)
272
+ - `Services` - Union of currently registered service tags
273
+
274
+ **Methods:**
275
+ - `withContextTransformer<Ctx1>(f: (ctx: Ctx) => Effect.Effect<Ctx1>): GrpcServerBuilder<Ctx1, never>` - Transform the handler context (must be called before adding services)
276
+ - `withService<S>(service: S): GrpcServerBuilder<Ctx, Services | Tag<S>>` - Add a service (enforces unique tags)
277
+ - `build(): GrpcServer<Services>` - Build the server (requires at least one service)
278
+
279
+ **Example:**
280
+ ```typescript
281
+ // Simple server with HandlerContext
282
+ const server = EffectGrpcServer.GrpcServerBuilder()
283
+ .withService(myService)
284
+ .build();
285
+
286
+ // Server with custom context
287
+ interface AppContext {
288
+ userId: string;
289
+ requestId: string;
290
+ }
291
+
292
+ const serverWithCtx = EffectGrpcServer.GrpcServerBuilder()
293
+ .withContextTransformer((handlerCtx: HandlerContext) =>
294
+ Effect.succeed({
295
+ userId: handlerCtx.requestHeader.get("user-id") ?? "anonymous",
296
+ requestId: crypto.randomUUID()
297
+ })
298
+ )
299
+ .withService(myService)
300
+ .build();
301
+ ```
302
+
303
+ ##### `GrpcService<Tag, Proto, Ctx>`
304
+
305
+ Represents a gRPC service implementation bound to a specific Protocol Buffer definition.
306
+
307
+ **Type Parameters:**
308
+ - `Tag` - Unique identifier for this service (typically the fully-qualified protobuf name)
309
+ - `Proto` - The Protocol Buffer service definition from generated code
310
+ - `Ctx` - Context type available to service method handlers
311
+
312
+ **Note:** Instances are typically created by the `protoc-gen-effect` code generator.
313
+
314
+ **Example:**
315
+ ```typescript
316
+ // Generated by protoc-gen-effect
317
+ const userService: EffectGrpcServer.GrpcService<
318
+ "com.example.UserService",
319
+ typeof UserServiceProto,
320
+ HandlerContext
321
+ > = EffectGrpcServer.GrpcService("com.example.UserService", UserServiceProto)(
322
+ (exec) => ({
323
+ getUser: (req, ctx) => exec.unary(req, ctx, (req, ctx) =>
324
+ Effect.succeed({ user: { id: req.userId, name: "John" } })
325
+ )
326
+ })
327
+ );
328
+ ```
329
+
330
+ #### Factory Functions
331
+
332
+ ##### `GrpcServerBuilder()`
333
+
334
+ Creates a new server builder instance with default `HandlerContext`.
335
+
336
+ **Returns:** `GrpcServerBuilder<HandlerContext, never>`
337
+
338
+ **Example:**
339
+ ```typescript
340
+ const builder = EffectGrpcServer.GrpcServerBuilder();
341
+ ```
342
+
343
+ ##### `GrpcService(tag, definition)`
344
+
345
+ Creates a GrpcService factory (typically used by code generators).
346
+
347
+ **Parameters:**
348
+ - `tag` - Unique service identifier
349
+ - `definition` - Protocol Buffer service definition
350
+
351
+ **Returns:** Function that accepts implementation and returns `GrpcService`
352
+
353
+ **Example:**
354
+ ```typescript
355
+ // This is typically generated, not written manually
356
+ const createService = EffectGrpcServer.GrpcService(
357
+ "com.example.MyService",
358
+ MyServiceProto
359
+ );
360
+
361
+ const service = createService<HandlerContext>((exec) => ({
362
+ myMethod: (req, ctx) => exec.unary(req, ctx, (req, ctx) =>
363
+ Effect.succeed({ result: "success" })
364
+ )
365
+ }));
366
+ ```
367
+
368
+ ---
369
+
370
+ ### Client API (`EffectGrpcClient`)
371
+
372
+ The client API provides tools for making gRPC calls from Effect programs.
373
+
374
+ #### Core Types
375
+
376
+ ##### `GrpcClientRuntime`
377
+
378
+ The runtime service that creates executors for invoking gRPC methods.
379
+
380
+ **Methods:**
381
+ - `makeExecutor<Shape>(serviceDefinition, methodNames, config): Effect.Effect<ClientExecutor<Shape>>` - Creates an executor for specified service methods
382
+
383
+ **Note:** This is primarily used by generated client code, not called directly by users.
384
+
385
+ **Example:**
386
+ ```typescript
387
+ const program = Effect.gen(function* () {
388
+ const runtime = yield* EffectGrpcClient.GrpcClientRuntime;
389
+ const config = yield* MyServiceConfigTag;
390
+
391
+ const executor = yield* runtime.makeExecutor(
392
+ MyServiceProto,
393
+ ["getUser", "listUsers"],
394
+ config
395
+ );
396
+
397
+ return executor;
398
+ });
399
+ ```
400
+
401
+ ##### `GrpcClientConfig<Service>`
402
+
403
+ Configuration for connecting to a gRPC service.
404
+
405
+ **Type Parameters:**
406
+ - `Service` - The fully-qualified service name (e.g., "com.example.v1.UserService")
407
+
408
+ **Properties:**
409
+ - `baseUrl: string` - Base URL for gRPC requests (e.g., "http://localhost:8000")
410
+ - `binaryOptions?: Partial<BinaryReadOptions & BinaryWriteOptions>` - Protocol Buffer binary format options
411
+ - `acceptCompression?: Compression[]` - Accepted response compression algorithms (defaults to ["gzip", "br"])
412
+ - `sendCompression?: Compression` - Compression algorithm for request messages
413
+ - `compressMinBytes?: number` - Minimum message size for compression (defaults to 1024 bytes)
414
+ - `defaultTimeoutMs?: number` - Default timeout for all requests in milliseconds
415
+
416
+ **Example:**
417
+ ```typescript
418
+ const config = EffectGrpcClient.GrpcClientConfig({
419
+ baseUrl: "https://api.example.com",
420
+ defaultTimeoutMs: 5000,
421
+ acceptCompression: ["gzip", "br"],
422
+ sendCompression: "gzip",
423
+ compressMinBytes: 1024
424
+ });
425
+
426
+ // Create a config tag for dependency injection
427
+ const UserServiceConfigTag = EffectGrpcClient.GrpcClientConfig.makeTag(
428
+ "com.example.v1.UserService"
429
+ );
430
+
431
+ // Provide the config in a layer
432
+ const configLayer = Layer.succeed(UserServiceConfigTag, config);
433
+ ```
434
+
435
+ ##### `RequestMeta`
436
+
437
+ Metadata attached to individual gRPC requests.
438
+
439
+ **Properties:**
440
+ - `headers?: Headers` - HTTP headers to send with the request
441
+ - `contextValues?: ContextValues` - Connect-RPC context values (e.g., timeout overrides)
442
+
443
+ **Example:**
444
+ ```typescript
445
+ const meta: EffectGrpcClient.RequestMeta = {
446
+ headers: new Headers({
447
+ "Authorization": "Bearer token123",
448
+ "X-Request-ID": crypto.randomUUID()
449
+ }),
450
+ contextValues: {
451
+ timeout: 3000 // Override default timeout for this request
452
+ }
453
+ };
454
+
455
+ // Use with generated client
456
+ const response = yield* client.getUser({ userId: "123" }, meta);
457
+ ```
458
+
459
+ #### Context Tags
460
+
461
+ ##### `GrpcClientRuntime`
462
+
463
+ Tag for accessing the gRPC client runtime service.
464
+
465
+ **Usage:**
466
+ ```typescript
467
+ const program = Effect.gen(function* () {
468
+ const runtime = yield* EffectGrpcClient.GrpcClientRuntime;
469
+ // runtime is now available
470
+ });
471
+ ```
472
+
473
+ #### Factory Functions
474
+
475
+ ##### `liveGrpcClientRuntimeLayer()`
476
+
477
+ Creates the live implementation layer for `GrpcClientRuntime`.
478
+
479
+ **Returns:** `Layer.Layer<GrpcClientRuntime>`
480
+
481
+ **Example:**
482
+ ```typescript
483
+ const layer = Layer.empty.pipe(
484
+ Layer.provideMerge(EffectGrpcClient.liveGrpcClientRuntimeLayer())
485
+ );
486
+ ```
487
+
488
+ ##### `GrpcClientConfig(opts)`
489
+
490
+ Creates a client configuration object.
491
+
492
+ **Parameters:**
493
+ - `opts` - Configuration options (omit the `_Service` type parameter)
494
+
495
+ **Returns:** `GrpcClientConfig<Service>`
496
+
497
+ **Example:**
498
+ ```typescript
499
+ const config = EffectGrpcClient.GrpcClientConfig({
500
+ baseUrl: "http://localhost:8000",
501
+ defaultTimeoutMs: 5000
502
+ });
503
+ ```
504
+
505
+ ##### `GrpcClientConfig.makeTag(service)`
506
+
507
+ Creates a Context tag for service-specific configuration.
508
+
509
+ **Parameters:**
510
+ - `service` - Fully-qualified service name
511
+
512
+ **Returns:** `Context.Tag<GrpcClientConfig<Service>, GrpcClientConfig<Service>>`
513
+
514
+ **Example:**
515
+ ```typescript
516
+ const UserServiceConfigTag = EffectGrpcClient.GrpcClientConfig.makeTag(
517
+ "com.example.v1.UserService"
518
+ );
519
+ ```
520
+
521
+ ---
522
+
523
+ ### Generated Code API
524
+
525
+ The `protoc-gen-effect` plugin generates TypeScript code from `.proto` files with Effect integration. This section documents the structure of generated code.
526
+
527
+ #### Service Implementation (Server-side)
528
+
529
+ For each service in your `.proto` file, the generator creates:
530
+
531
+ ##### `{ServiceName}Id`
532
+
533
+ Constant and type for the service identifier.
534
+
535
+ **Example:**
536
+ ```typescript
537
+ export const UserServiceId = "com.example.v1.UserService" as const;
538
+ export type UserServiceId = typeof UserServiceId;
539
+ ```
540
+
541
+ ##### `{ServiceName}Service<Ctx>`
542
+
543
+ Interface defining the service implementation contract.
544
+
545
+ **Type Parameters:**
546
+ - `Ctx` - Context type available in method handlers
547
+
548
+ **Example:**
549
+ ```typescript
550
+ export interface UserService<Ctx> {
551
+ getUser(
552
+ request: GetUserRequest,
553
+ ctx: Ctx
554
+ ): Effect.Effect<MessageInitShape<typeof GetUserResponseSchema>>;
555
+
556
+ listUsers(
557
+ request: ListUsersRequest,
558
+ ctx: Ctx
559
+ ): Effect.Effect<MessageInitShape<typeof ListUsersResponseSchema>>;
560
+ }
561
+ ```
562
+
563
+ ##### `{ServiceName}Service.makeTag(ctxKey)`
564
+
565
+ Creates a Context tag for the service.
566
+
567
+ **Parameters:**
568
+ - `ctxKey` - String identifier for the context type (e.g., "HandlerContext")
569
+
570
+ **Returns:** `Context.Tag<{ServiceName}GrpcService<Ctx>, {ServiceName}GrpcService<Ctx>>`
571
+
572
+ **Example:**
573
+ ```typescript
574
+ const UserServiceTag = UserService.makeTag<HandlerContext>("HandlerContext");
575
+ ```
576
+
577
+ ##### `{ServiceName}Service.liveLayer(impl)`
578
+
579
+ Creates a layer from a service implementation.
580
+
581
+ **Parameters:**
582
+ - `impl` - Implementation of the service interface
583
+
584
+ **Returns:** Function accepting a tag and returning a `Layer`
585
+
586
+ **Example:**
587
+ ```typescript
588
+ const UserServiceLive: UserService<HandlerContext> = {
589
+ getUser(request) {
590
+ return Effect.succeed({ user: { id: request.userId, name: "John" } });
591
+ },
592
+ listUsers(request) {
593
+ return Effect.succeed({ users: [] });
594
+ }
595
+ };
596
+
597
+ const UserServiceTag = UserService.makeTag<HandlerContext>("HandlerContext");
598
+ const layer = UserService.liveLayer(UserServiceLive)(UserServiceTag);
599
+ ```
600
+
601
+ ##### `{ServiceName}GrpcService<Ctx>`
602
+
603
+ Type alias for the complete gRPC service (used with server builder).
604
+
605
+ **Example:**
606
+ ```typescript
607
+ type UserServiceGrpc = UserServiceGrpcService<HandlerContext>;
608
+ ```
609
+
610
+ #### Client Implementation
611
+
612
+ For each service, the generator also creates client-side types:
613
+
614
+ ##### `{ServiceName}Client<Meta>`
615
+
616
+ Interface defining the client API.
617
+
618
+ **Type Parameters:**
619
+ - `Meta` - Type of metadata passed with each request
620
+
621
+ **Example:**
622
+ ```typescript
623
+ export interface UserServiceClient<Meta> {
624
+ getUser(
625
+ request: MessageInitShape<typeof GetUserRequestSchema>,
626
+ meta: Meta
627
+ ): Effect.Effect<GetUserResponse>;
628
+
629
+ listUsers(
630
+ request: MessageInitShape<typeof ListUsersRequestSchema>,
631
+ meta: Meta
632
+ ): Effect.Effect<ListUsersResponse>;
633
+ }
634
+ ```
635
+
636
+ ##### `{ServiceName}Client.makeTag(metaKey)`
637
+
638
+ Creates a Context tag for the client.
639
+
640
+ **Parameters:**
641
+ - `metaKey` - String identifier for the metadata type
642
+
643
+ **Returns:** `Context.Tag<{ServiceName}Client<Meta>, {ServiceName}Client<Meta>>`
644
+
645
+ **Example:**
646
+ ```typescript
647
+ const UserServiceClientTag = UserServiceClient.makeTag<object>("{}");
648
+ ```
649
+
650
+ ##### `{ServiceName}Client.liveLayer(transformMeta, tag)` / `{ServiceName}Client.liveLayer(tag)`
651
+
652
+ Creates a layer for the client (two overloads).
653
+
654
+ **Overload 1: With metadata transformation**
655
+ ```typescript
656
+ liveLayer<Tag extends {ServiceName}ClientTag<Meta>, Meta>(
657
+ transformMeta: (meta: Meta) => EffectGrpcClient.RequestMeta,
658
+ tag: Tag
659
+ ): Layer.Layer<...>
660
+ ```
661
+
662
+ **Overload 2: Default metadata (empty object)**
663
+ ```typescript
664
+ liveLayer<Tag extends {ServiceName}ClientTag<object>>(
665
+ tag: Tag
666
+ ): Layer.Layer<...>
667
+ ```
668
+
669
+ **Example:**
670
+ ```typescript
671
+ // With custom metadata transformation
672
+ interface MyMeta {
673
+ authToken: string;
674
+ }
675
+
676
+ const clientLayerWithMeta = UserServiceClient.liveLayer(
677
+ (meta: MyMeta) => ({
678
+ headers: new Headers({ "Authorization": `Bearer ${meta.authToken}` })
679
+ }),
680
+ UserServiceClientTag
681
+ ).pipe(
682
+ Layer.provideMerge(
683
+ Layer.succeed(UserServiceConfigTag, config)
684
+ )
685
+ );
686
+
687
+ // With default empty metadata
688
+ const clientLayer = UserServiceClient.liveLayer(
689
+ UserServiceClientTag
690
+ ).pipe(
691
+ Layer.provideMerge(
692
+ Layer.succeed(UserServiceConfigTag, config)
693
+ )
694
+ );
695
+ ```
696
+
697
+ ##### `{ServiceName}ConfigTag`
698
+
699
+ Pre-created config tag for the service.
700
+
701
+ **Example:**
702
+ ```typescript
703
+ export const UserServiceConfigTag: UserServiceConfigTag =
704
+ EffectGrpcClient.GrpcClientConfig.makeTag(UserServiceId);
705
+
706
+ // Use it to provide configuration
707
+ const configLayer = Layer.succeed(
708
+ UserServiceConfigTag,
709
+ EffectGrpcClient.GrpcClientConfig({ baseUrl: "http://localhost:8000" })
710
+ );
711
+ ```
712
+
713
+ ## Advanced Usage
714
+
715
+ ### Error Handling with GrpcException
716
+
717
+ effect-grpc provides `GrpcException`, a typed error that extends Effect's `Data.TaggedError` for handling gRPC errors. All generated service methods return `Effect<Success, GrpcException>`.
718
+
719
+ ```typescript
720
+ import { Effect } from "effect";
721
+ import { HandlerContext } from "@connectrpc/connect";
722
+ import { GrpcException } from "@dr_nikson/effect-grpc";
723
+ import { Code } from "@connectrpc/connect";
724
+
725
+ import * as effectProto from "./generated/example/v1/user_effect.js";
726
+ import * as proto from "./generated/example/v1/user_pb.js";
727
+
728
+ // Implement the service with error handling
729
+ const UserServiceLive: effectProto.UserService<HandlerContext> = {
730
+ getUser(request: proto.GetUserRequest) {
731
+ return Effect.gen(function* () {
732
+ // Input validation with gRPC status codes
733
+ if (!request.userId) {
734
+ return yield* Effect.fail(
735
+ GrpcException.create(Code.InvalidArgument, "User ID is required")
736
+ );
737
+ }
738
+
739
+ // Convert unknown errors to GrpcException
740
+ const user = yield* Effect.tryPromise({
741
+ try: () => database.findUser(request.userId),
742
+ catch: (error) => GrpcException.from(Code.Internal, error)
743
+ });
744
+
745
+ if (!user) {
746
+ return yield* Effect.fail(
747
+ GrpcException.create(Code.NotFound, "User not found")
748
+ );
749
+ }
750
+
751
+ return { user };
752
+ });
753
+ }
754
+ };
755
+ ```
756
+
757
+ **GrpcException API:**
758
+ - `GrpcException.create(code, message, cause?)` - Create a new exception
759
+ - `GrpcException.from(code, cause)` - Convert any error to GrpcException
760
+ - `GrpcException.withDescription(error, desc)` - Add context description
761
+
762
+ For gRPC status codes and error handling best practices, see [Connect RPC Error Handling](https://connectrpc.com/docs/node/errors).
763
+
764
+ ### Dependency Injection
765
+
766
+ Leverage Effect's powerful dependency injection to compose your services with external dependencies:
767
+
768
+ ```typescript
769
+ import { Context, Effect, Layer } from "effect";
770
+ import { HandlerContext } from "@connectrpc/connect";
771
+ import * as effectProto from "./generated/user_effect.js";
772
+
773
+ // Define a database service tag
774
+ class DatabaseService extends Context.Tag("DatabaseService")<
775
+ DatabaseService,
776
+ {
777
+ readonly getUser: (id: string) => Effect.Effect<User>;
778
+ readonly saveUser: (user: User) => Effect.Effect<void>;
779
+ }
780
+ >() {}
781
+
782
+ // Implement your gRPC service with database dependency
783
+ const UserServiceLive: effectProto.UserService<HandlerContext> = {
784
+ getUser(request) {
785
+ return Effect.gen(function* () {
786
+ // Access the database service from context
787
+ const db = yield* DatabaseService;
788
+
789
+ // Use it in your business logic
790
+ const user = yield* db.getUser(request.userId);
791
+
792
+ return { user };
793
+ });
794
+ },
795
+
796
+ updateUser(request) {
797
+ return Effect.gen(function* () {
798
+ const db = yield* DatabaseService;
799
+
800
+ yield* db.saveUser(request.user);
801
+
802
+ return { success: true };
803
+ });
804
+ }
805
+ };
806
+
807
+ // Create service tag and layer
808
+ const UserServiceTag = effectProto.UserService.makeTag<HandlerContext>("HandlerContext");
809
+ const userServiceLayer = effectProto.UserService.liveLayer(UserServiceLive)(UserServiceTag);
810
+
811
+ // Create a mock database layer for testing
812
+ const mockDatabaseLayer = Layer.succeed(DatabaseService, {
813
+ getUser: (id) => Effect.succeed({ id, name: "Mock User" }),
814
+ saveUser: (_user) => Effect.succeed(void 0)
815
+ });
816
+
817
+ // Compose all layers together
818
+ const appLayer = Layer.empty.pipe(
819
+ Layer.provideMerge(mockDatabaseLayer),
820
+ Layer.provideMerge(userServiceLayer)
821
+ );
822
+
823
+ // Build and run your server with all dependencies
824
+ const program = Effect.gen(function* () {
825
+ const userService = yield* UserServiceTag;
826
+
827
+ const server = EffectGrpcServer.GrpcServerBuilder()
828
+ .withService(userService)
829
+ .build();
830
+
831
+ return yield* server.run();
832
+ }).pipe(
833
+ Effect.provide(appLayer)
834
+ );
835
+ ```
836
+
837
+ ### Request Metadata and Headers
838
+
839
+ Send custom headers and metadata with requests:
840
+
841
+ ```typescript
842
+ const client = yield* HelloClientTag;
843
+
844
+ const response = yield* client.sayHello(
845
+ { name: "World" },
846
+ {
847
+ headers: new Headers({
848
+ "Authorization": "Bearer your-token",
849
+ "X-Request-ID": "123456"
850
+ }),
851
+ contextValues: {
852
+ timeout: 5000 // 5 second timeout
853
+ }
854
+ }
855
+ );
856
+ ```
857
+
858
+
859
+ ## Development
860
+
861
+ ### Building from Source
862
+
863
+ ```bash
864
+ # Clone the repository
865
+ git clone https://github.com/dr_nikson/effect-grpc.git
866
+ cd effect-grpc
867
+
868
+ # Install dependencies
869
+ pnpm install
870
+
871
+ # Build all packages
872
+ pnpm -r run build
873
+
874
+ # Run type tests
875
+ pnpm -r run test:types
876
+ ```
877
+
878
+ ### Project Structure
879
+
880
+ ```
881
+ effect-grpc/
882
+ ├── packages/
883
+ │ ├── effect-grpc/ # Core library
884
+ │ │ ├── src/
885
+ │ │ │ ├── client.ts # Client implementation
886
+ │ │ │ ├── server.ts # Server implementation
887
+ │ │ │ └── index.ts # Public exports
888
+ │ │ └── bin/
889
+ │ │ └── protoc-gen-effect # Protocol Buffer plugin
890
+ │ └── example/ # Example implementation
891
+ │ ├── proto/ # Protocol Buffer definitions
892
+ │ ├── src/
893
+ │ │ ├── generated/ # Generated TypeScript code
894
+ │ │ ├── server.ts # Example server
895
+ │ │ └── client.ts # Example client
896
+ │ └── buf.gen.yaml # Buf configuration
897
+ └── README.md
898
+ ```
899
+
900
+ ## Roadmap
901
+
902
+ - [ ] Support for streaming RPCs (server-streaming, client-streaming, bidirectional)
903
+ - [ ] Interceptor/middleware support
904
+ - [ ] Built-in retry policies with Effect
905
+ - [ ] gRPC reflection support
906
+ - [ ] Browser/gRPC-Web support
907
+ - [ ] Performance optimizations
908
+ - [ ] More comprehensive examples
909
+
910
+ ## Contributing
911
+
912
+ Contributions are welcome! Please feel free to submit a Pull Request.
913
+
914
+ 1. Fork the repository
915
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
916
+ 3. Commit your changes (`git commit -m 'Add some amazing feature'`)
917
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
918
+ 5. Open a Pull Request
919
+
920
+ ## License
921
+
922
+ [Apache License Version 2.0](LICENSE)
923
+
924
+ ## Acknowledgments
925
+
926
+ - [Effect](https://effect.website/) - The core framework this library builds upon
927
+ - [Connect-RPC](https://connectrpc.com/) - The modern gRPC implementation
928
+ - [Buf](https://buf.build/) - Protocol Buffer tooling