effect-orpc 1.0.0-effect-v4.4 → 1.0.0-effect-v4.6

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
@@ -7,7 +7,7 @@ Inspired by [effect-trpc](https://github.com/mikearnaldi/effect-trpc).
7
7
  ## Features
8
8
 
9
9
  - **Effect-native procedures** - Write oRPC procedures using generators with `yield*` syntax
10
- - **Type-safe service injection** - Use `ManagedRuntime<R>` to provide services to procedures with compile-time safety
10
+ - **Type-safe service injection** - Add base services with `.provide(layer)` or pass a `Layer` / `ManagedRuntime<R>` directly
11
11
  - **Tagged errors** - Create Effect-native error classes with `ORPCTaggedError` that integrate with oRPC's error handling
12
12
  - **Full oRPC compatibility** - Mix Effect procedures with standard oRPC procedures in the same router
13
13
  - **Telemetry support with automatic tracing** - Procedures are automatically traced with OpenTelemetry-compatible spans. Customize span names with `.traced()`.
@@ -29,8 +29,8 @@ Runnable demos live in the repository's `examples/` directory.
29
29
 
30
30
  ```ts
31
31
  import { os } from "@orpc/server";
32
- import { Effect, Layer, ManagedRuntime, Context } from "effect";
33
- import { makeEffectORPC, ORPCTaggedError } from "effect-orpc";
32
+ import { Effect, ManagedRuntime } from "effect";
33
+ import { eos, makeEffectORPC, ORPCTaggedError } from "effect-orpc";
34
34
 
35
35
  interface User {
36
36
  id: number;
@@ -43,46 +43,41 @@ let users: User[] = [
43
43
  { id: 3, name: "James Dane" },
44
44
  ];
45
45
 
46
- // Authenticated os with initial context & errors set
47
- const authedOs = os
48
- .errors({ UNAUTHORIZED: { status: 401 } })
49
- .$context<{ userId?: number }>()
50
- .use(({ context, errors, next }) => {
51
- if (context.userId === undefined) throw errors.UNAUTHORIZED();
52
- return next({ context: { ...context, userId: context.userId } });
53
- });
54
-
55
46
  // Define your services
56
- class UsersRepo extends Context.Service<
57
- UsersRepo,
58
- {
59
- readonly get: (id: number) => User | undefined;
60
- }
61
- >()("UsersRepo") {
62
- static readonly layer = Layer.succeed(this, {
47
+ class UsersRepo extends Effect.Service<UsersRepo>()("UsersRepo", {
48
+ accessors: true,
49
+ sync: () => ({
63
50
  get: (id: number) => users.find((u) => u.id === id),
64
- });
65
- }
51
+ }),
52
+ }) {}
66
53
 
67
54
  // Special yieldable oRPC error class
68
55
  class UserNotFoundError extends ORPCTaggedError("UserNotFoundError", {
69
56
  status: 404,
70
57
  }) {}
71
58
 
72
- // Create runtime with your services
73
- const runtime = ManagedRuntime.make(UsersRepo.layer);
74
- // Create Effect-aware oRPC builder from an other (optional) base oRPC builder and provide tagged errors
75
- const effectOs = makeEffectORPC(runtime, authedOs).errors({
76
- UserNotFoundError,
77
- });
59
+ // Create an Effect-aware oRPC builder with your service layer, context, errors,
60
+ // and middleware.
61
+ const effectProcedure = eos
62
+ .provide(UsersRepo.Default)
63
+ .errors({ UNAUTHORIZED: { status: 401 }, UserNotFoundError })
64
+ .$context<{ userId?: number }>()
65
+ .use(({ context, errors, next }) => {
66
+ if (context.userId === undefined) throw errors.UNAUTHORIZED();
67
+ return next({ context: { ...context, userId: context.userId } });
68
+ });
69
+
70
+ // Use ManagedRuntime only when scoped resources should be acquired once and
71
+ // released on shutdown, for example a shared cache, database pool, or telemetry SDK:
72
+ // const runtime = ManagedRuntime.make(UsersRepo.Default);
73
+ // const effectProcedure = makeEffectORPC(runtime).errors({ UserNotFoundError });
78
74
 
79
75
  // Create the router with mixed procedures
80
76
  export const router = {
81
77
  health: os.handler(() => "ok"),
82
78
  users: {
83
- me: effectOs.effect(function* ({ context: { userId } }) {
84
- const usersRepo = yield* UsersRepo;
85
- const user = usersRepo.get(userId);
79
+ me: effectProcedure.effect(function* ({ context: { userId } }) {
80
+ const user = yield* UsersRepo.get(userId);
86
81
  if (!user) {
87
82
  return yield* new UserNotFoundError();
88
83
  }
@@ -96,42 +91,36 @@ export type Router = typeof router;
96
91
 
97
92
  ## Type Safety
98
93
 
99
- The wrapper enforces that Effect procedures only use services provided by the `ManagedRuntime`. If you try to use a service that isn't in the runtime, you'll get a compile-time error:
94
+ The wrapper enforces that Effect procedures only use services provided by `.provide(layer)`, request-scoped `.provide(tag, provider)` calls, or an initial `Layer` / `ManagedRuntime`. If you try to use a service that isn't available, you'll get a compile-time error:
100
95
 
101
96
  ```ts
102
- import { Effect, Layer, ManagedRuntime, Context } from "effect";
103
- import { makeEffectORPC } from "effect-orpc";
97
+ import { Context, Effect, Layer } from "effect";
98
+ import { eos } from "effect-orpc";
104
99
 
105
100
  class ProvidedService extends Context.Service<
106
101
  ProvidedService,
107
- {
108
- readonly doSomething: () => Effect.Effect<string>;
109
- }
102
+ { doSomething: () => Effect.Effect<string> }
110
103
  >()("ProvidedService") {}
111
104
 
112
105
  class MissingService extends Context.Service<
113
106
  MissingService,
114
- {
115
- readonly doSomething: () => Effect.Effect<string>;
116
- }
107
+ { doSomething: () => Effect.Effect<string> }
117
108
  >()("MissingService") {}
118
109
 
119
- const runtime = ManagedRuntime.make(
120
- Layer.succeed(ProvidedService, {
121
- doSomething: () => Effect.succeed("ok"),
122
- }),
123
- );
110
+ const AppLive = Layer.succeed(ProvidedService, {
111
+ doSomething: () => Effect.succeed("ok"),
112
+ });
124
113
 
125
- const effectOs = makeEffectORPC(runtime);
114
+ const effectProcedure = eos.provide(AppLive);
126
115
 
127
- // ✅ This compiles - ProvidedService is in the runtime
128
- const works = effectOs.effect(function* () {
116
+ // ✅ This compiles - ProvidedService is provided by AppLive
117
+ const works = effectProcedure.effect(function* () {
129
118
  const service = yield* ProvidedService;
130
119
  return yield* service.doSomething();
131
120
  });
132
121
 
133
- // ❌ This fails to compile - MissingService is not in the runtime
134
- const fails = effectOs.effect(function* () {
122
+ // ❌ This fails to compile - MissingService is not provided
123
+ const fails = effectProcedure.effect(function* () {
135
124
  const service = yield* MissingService; // Type error!
136
125
  return yield* service.doSomething();
137
126
  });
@@ -148,7 +137,7 @@ const fails = effectOs.effect(function* () {
148
137
  Make sure the tagged error class is passed to the effect `.errors()` to be able to yield the error class directly and make the client recognize it as defined.
149
138
 
150
139
  ```ts
151
- const getUser = effectOs
140
+ const getUser = effectProcedure
152
141
  // Mixed error maps
153
142
  .errors({
154
143
  // Regular oRPC error
@@ -217,19 +206,19 @@ All Effect procedures are automatically traced with `Effect.withSpan`. By defaul
217
206
  const router = {
218
207
  users: {
219
208
  // Span name: "users.get"
220
- get: effectOs.input(z.object({ id: z.string() })).effect(function* ({
209
+ get: effectProcedure.input(z.object({ id: z.string() })).effect(function* ({
221
210
  input,
222
211
  }) {
223
212
  const userService = yield* UserService;
224
213
  return yield* userService.findById(input.id);
225
214
  }),
226
215
  // Span name: "users.create"
227
- create: effectOs.input(z.object({ name: z.string() })).effect(function* ({
228
- input,
229
- }) {
230
- const userService = yield* UserService;
231
- return yield* userService.create(input.name);
232
- }),
216
+ create: effectProcedure
217
+ .input(z.object({ name: z.string() }))
218
+ .effect(function* ({ input }) {
219
+ const userService = yield* UserService;
220
+ return yield* userService.create(input.name);
221
+ }),
233
222
  },
234
223
  };
235
224
  ```
@@ -237,7 +226,7 @@ const router = {
237
226
  Use `.traced()` to override the default span name:
238
227
 
239
228
  ```ts
240
- const getUser = effectOs
229
+ const getUser = effectProcedure
241
230
  .input(z.object({ id: z.string() }))
242
231
  .traced("custom.span.name") // Override the default path-based name
243
232
  .effect(function* ({ input }) {
@@ -248,7 +237,7 @@ const getUser = effectOs
248
237
 
249
238
  ### Enabling OpenTelemetry
250
239
 
251
- To enable tracing, include the OpenTelemetry layer in your runtime:
240
+ To enable tracing, include the OpenTelemetry layer in your application layer:
252
241
 
253
242
  ```ts
254
243
  import { NodeSdk } from "@effect/opentelemetry";
@@ -264,8 +253,7 @@ const TracingLive = NodeSdk.layer(
264
253
 
265
254
  const AppLive = Layer.mergeAll(UserServiceLive, TracingLive);
266
255
 
267
- const runtime = ManagedRuntime.make(AppLive);
268
- const effectOs = makeEffectORPC(runtime);
256
+ const effectProcedure = eos.provide(AppLive);
269
257
  ```
270
258
 
271
259
  ### Error Stack Traces
@@ -278,24 +266,136 @@ MyCustomError: Something went wrong
278
266
  at users.getById (/app/src/procedures.ts:41:35)
279
267
  ```
280
268
 
269
+ ## Effect middleware
270
+
271
+ `.use(...)` accepts generator-based Effect middleware in addition to native oRPC
272
+ middleware. Two patterns are supported:
273
+
274
+ **Gate** — run auth or validation side effects, then let the pipeline continue
275
+ automatically (no need to call `next`):
276
+
277
+ ```ts
278
+ effectProcedure.use(function* () {
279
+ const user = yield* CurrentUser;
280
+ yield* requireActiveUser(user);
281
+ });
282
+ ```
283
+
284
+ **Wrap** — call downstream explicitly and return the result. When porting oRPC
285
+ middleware that uses `return next(...)`, use `return yield* next(...)`:
286
+
287
+ ```ts
288
+ effectProcedure.use(function* ({ next }) {
289
+ const user = yield* CurrentUser;
290
+ yield* requireActiveUser(user);
291
+
292
+ return yield* next({
293
+ context: { userId: user.id },
294
+ });
295
+ });
296
+ ```
297
+
298
+ To transform the downstream output, capture `next()` and pass through `output`:
299
+
300
+ ```ts
301
+ effectProcedure.use(function* ({ next }, _input, output) {
302
+ const result = yield* next();
303
+ return yield* output(`${result.output}-wrapped`);
304
+ });
305
+ ```
306
+
307
+ Calling `yield* next()` without returning its result still runs the handler once,
308
+ but prefer `return yield* next(...)` so the pipeline receives your middleware
309
+ result explicitly.
310
+
311
+ ### Runtime boundaries and fiber context continuity
312
+
313
+ `effect-orpc` batches contiguous Effect-native steps into one runtime boundary.
314
+ Effect-native steps are `.provide(...)`, `.provideOptional(...)`, generator
315
+ `.use(function* ...)`, and `.effect(function* ...)`.
316
+
317
+ ```ts
318
+ eos
319
+ .provide(AppLive)
320
+ .provide(CurrentUser, ({ context }) => Effect.succeed(context.user))
321
+ .use(function* ({ next }) {
322
+ const user = yield* CurrentUser;
323
+ return yield* next({ context: { userId: user.id } });
324
+ })
325
+ .effect(function* ({ context }) {
326
+ const user = yield* CurrentUser;
327
+ return `${context.userId}:${user.id}`;
328
+ });
329
+ ```
330
+
331
+ The example above runs the provider, middleware, and handler inside a single
332
+ Effect execution boundary.
333
+
334
+ A native oRPC middleware breaks the contiguous Effect pipeline. Pending Effect
335
+ steps are flushed into one generated oRPC middleware before the native middleware:
336
+
337
+ ```ts
338
+ eos
339
+ .provide(AppLive)
340
+ .provide(CurrentUser, getCurrentUser) // Effect group #1
341
+ .use(function* ({ next }) {
342
+ return yield* next();
343
+ })
344
+ .use(({ next }) => next()) // native oRPC middleware; flushes group #1
345
+ .use(function* ({ next }) {
346
+ return yield* next();
347
+ }) // Effect group #2
348
+ .effect(function* () {
349
+ return "ok";
350
+ });
351
+ ```
352
+
353
+ That split still creates multiple runtime boundaries. If the Node bridge is
354
+ installed, however, `effect-orpc` carries the current `FiberRefs` through the
355
+ native oRPC continuation and merges them into the next Effect boundary:
356
+
357
+ ```ts
358
+ import "effect-orpc/node";
359
+ ```
360
+
361
+ Use the side-effect import when you only need continuity across internal
362
+ `effect-orpc` boundaries, such as Effect group #1 → native oRPC middleware →
363
+ Effect group #2.
364
+
365
+ Procedure-level `.provide*` after a native `.handler(...)` has no Effect handler
366
+ boundary to attach to, so it is installed as an oRPC middleware that runs its
367
+ provider Effect through the configured runtime source:
368
+
369
+ ```ts
370
+ eos
371
+ .provide(AppLive)
372
+ .handler(() => "ok") // native oRPC handler
373
+ .provide(CurrentUser, getCurrentUser); // fallback provider middleware
374
+ ```
375
+
376
+ If you want `.provide*` and Effect middleware to batch with the handler, use
377
+ `.effect(function* ...)` instead of `.handler(...)`.
378
+
281
379
  ## Request-Scoped Fiber Context
282
380
 
283
- If you run `effect-orpc` inside a framework such as Hono, the handler executes
284
- through the runtime boundary and will not automatically inherit request-local
285
- `FiberRef` state from outer middleware.
381
+ The `/node` entrypoint installs a bridge backed by `AsyncLocalStorage`. It has
382
+ two uses:
286
383
 
287
- To preserve request-scoped logs, tracing annotations, and
288
- other fiber-local state, wrap the framework continuation with `withFiberContext` from
289
- `effect-orpc/node`.
384
+ - `import "effect-orpc/node"` installs the bridge passively. This is enough for
385
+ `effect-orpc` to propagate `FiberRefs` across its own split runtime boundaries.
386
+ - `withFiberContext(() => next())` actively seeds the bridge from an external
387
+ Effect scope, such as framework middleware wrapping an oRPC handler.
388
+
389
+ Use `withFiberContext` when request-local `FiberRef` state is created outside the
390
+ oRPC pipeline and should be visible inside handlers:
290
391
 
291
392
  ```ts
292
393
  import { Hono } from "hono";
293
- import { Effect, ManagedRuntime } from "effect";
294
- import { makeEffectORPC } from "effect-orpc";
394
+ import { Effect } from "effect";
395
+ import { eos } from "effect-orpc";
295
396
  import { withFiberContext } from "effect-orpc/node";
296
397
 
297
- const runtime = ManagedRuntime.make(AppLive);
298
- const effectOs = makeEffectORPC(runtime);
398
+ const effectProcedure = eos.provide(AppLive);
299
399
  const app = new Hono();
300
400
 
301
401
  app.use("*", async (c, next) => {
@@ -311,31 +411,27 @@ app.use("*", async (c, next) => {
311
411
  });
312
412
  ```
313
413
 
314
- When a captured fiber context and the `ManagedRuntime` both provide the same
315
- service, `effect-orpc` prioritizes the captured context. The runtime is treated
316
- as the application-wide base layer, while `withFiberContext` preserves the
317
- more specific request-scoped values from outer middleware. This prevents
318
- request-local references such as request IDs, logging annotations, tracing
319
- context, or scoped overrides from being replaced by runtime defaults when the
320
- handler crosses the runtime boundary.
414
+ Importing `withFiberContext` from `effect-orpc/node` also installs the bridge, so
415
+ you do not need a separate side-effect import.
321
416
 
322
- The reason for the separate `/node` entrypoint is that `withFiberContext` relies
323
- on Node/Bun's `AsyncLocalStorage` from `node:async_hooks` to carry Effect
324
- `FiberRef` state across framework async boundaries. The main package stays
325
- runtime-agnostic.
417
+ When a captured fiber context and the application `Layer` / `ManagedRuntime`
418
+ both provide the same service, `effect-orpc` prioritizes the captured context.
419
+ The application layer is treated as the base layer, while the bridge preserves more specific
420
+ request-scoped values such as request IDs, logging annotations, tracing context,
421
+ or scoped overrides when crossing runtime boundaries.
326
422
 
327
- If you do not need framework-to-handler fiber propagation, you do not need the
328
- `/node` entrypoint at all.
423
+ The main package stays runtime-agnostic; `/node` is separate because the bridge
424
+ relies on `AsyncLocalStorage` from `node:async_hooks`.
329
425
 
330
426
  ## Contract-First Usage
331
427
 
332
- Use `implementEffect(contract, runtime)` when you already have an oRPC contract
333
- and want to keep contract-first enforcement while adding Effect-native handlers.
334
- Use `makeEffectORPC(runtime, builder?)` when you want to build procedures
335
- directly from an oRPC builder.
428
+ Use `implementEffect(contract, layerOrRuntime)` when you already have an oRPC
429
+ contract and want to keep contract-first enforcement while adding Effect-native
430
+ handlers. Use `eos.provide(layer)` when you want to build procedures directly
431
+ from the default Effect-aware builder.
336
432
 
337
433
  ```ts
338
- import { Effect, ManagedRuntime } from "effect";
434
+ import { Effect } from "effect";
339
435
  import { eoc, implementEffect } from "effect-orpc";
340
436
  import z from "zod";
341
437
 
@@ -355,8 +451,7 @@ const contract = {
355
451
  },
356
452
  };
357
453
 
358
- const runtime = ManagedRuntime.make(UsersRepo.Default);
359
- const oe = implementEffect(contract, runtime);
454
+ const oe = implementEffect(contract, UsersRepo.Default);
360
455
 
361
456
  export const router = oe.router({
362
457
  users: {
@@ -378,34 +473,46 @@ directly from the `ORPCTaggedError` class.
378
473
 
379
474
  ## API Reference
380
475
 
381
- ### `makeEffectORPC(runtime, builder?)`
476
+ ### `eos`
382
477
 
383
- Creates an Effect-aware procedure builder.
478
+ The default Effect-aware procedure builder. Provide your application services
479
+ with `.provide(layer)`:
384
480
 
385
- - `runtime` - A `ManagedRuntime<R, E>` instance that provides services for Effect procedures
386
- - `builder` (optional) - An oRPC Builder instance to wrap. Defaults to `os` from `@orpc/server`
481
+ ```ts
482
+ const effectProcedure = eos.provide(AppLive);
483
+ ```
387
484
 
388
- Returns an `EffectBuilder` instance.
485
+ Use `makeEffectORPC(runtime)` when a scoped `Layer` should be acquired once and
486
+ released by your application shutdown path, such as a shared cache, database
487
+ pool, HTTP client, or telemetry SDK:
389
488
 
390
489
  ```ts
391
- // With default builder
392
- const effectOs = makeEffectORPC(runtime);
490
+ const runtime = ManagedRuntime.make(AppLive);
393
491
 
394
- // With customized builder
395
- const effectAuthedOs = makeEffectORPC(runtime, authedBuilder);
492
+ const effectProcedure = makeEffectORPC(runtime);
493
+
494
+ // later, during app shutdown
495
+ await runtime.dispose();
496
+ ```
497
+
498
+ `makeEffectORPC(builder)` is also available when you need to wrap an existing
499
+ oRPC builder:
500
+
501
+ ```ts
502
+ const effectAuthedOs = makeEffectORPC(authedBuilder).provide(AppLive);
396
503
  ```
397
504
 
398
- ### `implementEffect(contract, runtime)`
505
+ ### `implementEffect(contract, layerOrRuntime)`
399
506
 
400
507
  Creates an Effect-aware contract implementer.
401
508
 
402
509
  - `contract` - An oRPC contract router built with `oc`
403
- - `runtime` - A `ManagedRuntime<R, E>` instance that provides services for Effect procedures
510
+ - `layerOrRuntime` - A `Layer<R, E, never>` provided per call, or a user-owned `ManagedRuntime<R, E>` when the application should control acquisition and release (for example, a shared cache, database pool, or telemetry SDK)
404
511
 
405
512
  Returns a contract-shaped implementer tree whose leaves support `.effect(...)`.
406
513
 
407
514
  ```ts
408
- const oe = implementEffect(contract, runtime);
515
+ const oe = implementEffect(contract, AppLive);
409
516
 
410
517
  const router = oe.router({
411
518
  users: {
@@ -445,26 +552,28 @@ const contract = {
445
552
 
446
553
  Wraps an oRPC Builder with Effect support. Available methods:
447
554
 
448
- | Method | Description |
449
- | ------------------- | ------------------------------------------------------------------------------- |
450
- | `.$config(config)` | Set or override the builder config |
451
- | `.$context<U>()` | Set or override the initial context type |
452
- | `.$meta(meta)` | Set or override the initial metadata |
453
- | `.$route(route)` | Set or override the initial route configuration |
454
- | `.$input(schema)` | Set or override the initial input schema |
455
- | `.errors(map)` | Add type-safe custom errors |
456
- | `.meta(meta)` | Set procedure metadata (merged with existing) |
457
- | `.route(route)` | Configure OpenAPI route (merged with existing) |
458
- | `.input(schema)` | Define input validation schema |
459
- | `.output(schema)` | Define output validation schema |
460
- | `.use(middleware)` | Add middleware |
461
- | `.traced(name)` | Add a traceable span for telemetry (optional, defaults to the procedure's path) |
462
- | `.handler(handler)` | Define a non-Effect handler (standard oRPC handler) |
463
- | `.effect(handler)` | Define the Effect handler |
464
- | `.prefix(prefix)` | Prefix all procedures in the router (for OpenAPI) |
465
- | `.tag(...tags)` | Add tags to all procedures in the router (for OpenAPI) |
466
- | `.router(router)` | Apply all options to a router |
467
- | `.lazy(loader)` | Create and apply options to a lazy-loaded router |
555
+ | Method | Description |
556
+ | ------------------- | ------------------------------------------------------------------------------------ |
557
+ | `.$config(config)` | Set or override the builder config |
558
+ | `.$context<U>()` | Set or override the initial context type |
559
+ | `.$meta(meta)` | Set or override the initial metadata |
560
+ | `.$route(route)` | Set or override the initial route configuration |
561
+ | `.$input(schema)` | Set or override the initial input schema |
562
+ | `.errors(map)` | Add type-safe custom errors |
563
+ | `.meta(meta)` | Set procedure metadata (merged with existing) |
564
+ | `.route(route)` | Configure OpenAPI route (merged with existing) |
565
+ | `.input(schema)` | Define input validation schema |
566
+ | `.output(schema)` | Define output validation schema |
567
+ | `.provide(layer)` | Provide a base Effect layer to downstream Effect middleware and handlers |
568
+ | `.provide(tag, fn)` | Provide a request-scoped Effect service to downstream Effect middleware and handlers |
569
+ | `.use(middleware)` | Add middleware |
570
+ | `.traced(name)` | Add a traceable span for telemetry (optional, defaults to the procedure's path) |
571
+ | `.handler(handler)` | Define a non-Effect handler (standard oRPC handler) |
572
+ | `.effect(handler)` | Define the Effect handler |
573
+ | `.prefix(prefix)` | Prefix all procedures in the router (for OpenAPI) |
574
+ | `.tag(...tags)` | Add tags to all procedures in the router (for OpenAPI) |
575
+ | `.router(router)` | Apply all options to a router |
576
+ | `.lazy(loader)` | Create and apply options to a lazy-loaded router |
468
577
 
469
578
  ### `EffectDecoratedProcedure`
470
579
 
@@ -475,6 +584,8 @@ The result of calling `.effect()`. Extends standard oRPC `DecoratedProcedure` wi
475
584
  | `.errors(map)` | Add more custom errors |
476
585
  | `.meta(meta)` | Update metadata (merged with existing) |
477
586
  | `.route(route)` | Update route configuration (merged) |
587
+ | `.provide(layer)` | Provide a base Effect layer |
588
+ | `.provide(tag, fn)` | Provide a request-scoped Effect service |
478
589
  | `.use(middleware)` | Add middleware |
479
590
  | `.callable(options?)` | Make procedure directly invocable |
480
591
  | `.actionable(options?)` | Make procedure compatible with server actions |
@@ -6,9 +6,13 @@ function installServiceContextBridge(nextBridge) {
6
6
  function getCurrentServices() {
7
7
  return bridge?.getCurrentServices();
8
8
  }
9
+ function runWithServices(services, fn) {
10
+ return bridge?.runWithServices?.(services, fn) ?? fn();
11
+ }
9
12
 
10
13
  export {
11
14
  installServiceContextBridge,
12
- getCurrentServices
15
+ getCurrentServices,
16
+ runWithServices
13
17
  };
14
- //# sourceMappingURL=chunk-I5EWBI42.js.map
18
+ //# sourceMappingURL=chunk-SXPXGZUW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/service-context-bridge.ts"],"sourcesContent":["import type { Context } from \"effect\";\n\nexport interface ServiceContextBridge {\n readonly getCurrentServices: () => Context.Context<any> | undefined;\n readonly runWithServices?: <T>(\n services: Context.Context<any>,\n fn: () => Promise<T>,\n ) => Promise<T>;\n}\n\nlet bridge: ServiceContextBridge | undefined;\n\nexport function installServiceContextBridge(\n nextBridge: ServiceContextBridge | undefined,\n): void {\n bridge = nextBridge;\n}\n\nexport function getCurrentServices(): Context.Context<any> | undefined {\n return bridge?.getCurrentServices();\n}\n\nexport function runWithServices<T>(\n services: Context.Context<any>,\n fn: () => Promise<T>,\n): Promise<T> {\n return bridge?.runWithServices?.(services, fn) ?? fn();\n}\n"],"mappings":";AAUA,IAAI;AAEG,SAAS,4BACd,YACM;AACN,WAAS;AACX;AAEO,SAAS,qBAAuD;AACrE,SAAO,QAAQ,mBAAmB;AACpC;AAEO,SAAS,gBACd,UACA,IACY;AACZ,SAAO,QAAQ,kBAAkB,UAAU,EAAE,KAAK,GAAG;AACvD;","names":[]}