effect-orpc 0.1.4 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -318,6 +318,55 @@ runtime-agnostic.
318
318
  If you do not need framework-to-handler fiber propagation, you do not need the
319
319
  `/node` entrypoint at all.
320
320
 
321
+ ## Contract-First Usage
322
+
323
+ Use `implementEffect(contract, runtime)` when you already have an oRPC contract
324
+ and want to keep contract-first enforcement while adding Effect-native handlers.
325
+ Use `makeEffectORPC(runtime, builder?)` when you want to build procedures
326
+ directly from an oRPC builder.
327
+
328
+ ```ts
329
+ import { Effect, ManagedRuntime } from "effect";
330
+ import { eoc, implementEffect } from "effect-orpc";
331
+ import z from "zod";
332
+
333
+ class UsersRepo extends Effect.Service<UsersRepo>()("UsersRepo", {
334
+ accessors: true,
335
+ sync: () => ({
336
+ list: (amount: number) =>
337
+ Array.from({ length: amount }, (_, index) => `user-${index + 1}`),
338
+ }),
339
+ }) {}
340
+
341
+ const contract = {
342
+ users: {
343
+ list: eoc
344
+ .input(z.object({ amount: z.number().int().positive() }))
345
+ .output(z.array(z.string())),
346
+ },
347
+ };
348
+
349
+ const runtime = ManagedRuntime.make(UsersRepo.Default);
350
+ const oe = implementEffect(contract, runtime);
351
+
352
+ export const router = oe.router({
353
+ users: {
354
+ list: oe.users.list.effect(function* ({ input }) {
355
+ return yield* UsersRepo.list(input.amount);
356
+ }),
357
+ },
358
+ });
359
+ ```
360
+
361
+ Contract leaves keep the contract-defined input, output, and error surface.
362
+ They add `.effect(...)` alongside existing implementer methods such as
363
+ `.handler(...)` and `.use(...)`, but do not expose contract-changing builder
364
+ methods like `.input(...)` or `.output(...)`.
365
+
366
+ If your contract declares tagged Effect error classes, prefer `eoc.errors(...)`
367
+ instead of raw `oc.errors(...)` so the error schema and metadata are derived
368
+ directly from the `ORPCTaggedError` class.
369
+
321
370
  ## API Reference
322
371
 
323
372
  ### `makeEffectORPC(runtime, builder?)`
@@ -337,6 +386,52 @@ const effectOs = makeEffectORPC(runtime);
337
386
  const effectAuthedOs = makeEffectORPC(runtime, authedBuilder);
338
387
  ```
339
388
 
389
+ ### `implementEffect(contract, runtime)`
390
+
391
+ Creates an Effect-aware contract implementer.
392
+
393
+ - `contract` - An oRPC contract router built with `oc`
394
+ - `runtime` - A `ManagedRuntime<R, E>` instance that provides services for Effect procedures
395
+
396
+ Returns a contract-shaped implementer tree whose leaves support `.effect(...)`.
397
+
398
+ ```ts
399
+ const oe = implementEffect(contract, runtime);
400
+
401
+ const router = oe.router({
402
+ users: {
403
+ list: oe.users.list.effect(function* ({ input }) {
404
+ return yield* UsersRepo.list(input.amount);
405
+ }),
406
+ },
407
+ });
408
+ ```
409
+
410
+ ### `eoc`
411
+
412
+ An Effect-aware wrapper around oRPC's `oc` contract builder.
413
+
414
+ Use it when you want contract definitions to accept `ORPCTaggedError` classes
415
+ directly in `.errors(...)` without duplicating the error schema.
416
+
417
+ ```ts
418
+ class UserNotFoundError extends ORPCTaggedError("UserNotFoundError", {
419
+ code: "NOT_FOUND",
420
+ schema: z.object({ userId: z.string() }),
421
+ }) {}
422
+
423
+ const contract = {
424
+ users: {
425
+ find: eoc
426
+ .errors({
427
+ NOT_FOUND: UserNotFoundError,
428
+ })
429
+ .input(z.object({ userId: z.string() }))
430
+ .output(z.object({ userId: z.string() })),
431
+ },
432
+ };
433
+ ```
434
+
340
435
  ### `EffectBuilder`
341
436
 
342
437
  Wraps an oRPC Builder with Effect support. Available methods: