effect-orpc 0.2.2 → 0.3.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 +198 -77
- package/dist/{chunk-VOWRLWZZ.js → chunk-IJP6L2XR.js} +6 -2
- package/dist/chunk-IJP6L2XR.js.map +1 -0
- package/dist/index.js +736 -266
- package/dist/index.js.map +1 -1
- package/dist/node.js +4 -3
- package/dist/node.js.map +1 -1
- package/package.json +1 -1
- package/src/contract.ts +34 -2
- package/src/effect-builder.ts +277 -24
- package/src/effect-procedure.ts +203 -9
- package/src/effect-runtime.ts +453 -21
- package/src/extension/create-node-proxy.ts +17 -1
- package/src/extension/state.ts +13 -15
- package/src/fiber-context-bridge.ts +13 -0
- package/src/node.ts +2 -1
- package/src/runtime-source.ts +18 -0
- package/src/tagged-error.ts +0 -9
- package/src/tests/contract.test.ts +24 -0
- package/src/tests/effect-builder.test.ts +506 -3
- package/src/tests/node-side-effect.test.ts +80 -0
- package/src/tests/parity.effect-builder.test.ts +10 -3
- package/src/tests/parity.effect-procedure.test.ts +24 -8
- package/src/tests/shared.ts +1 -25
- package/src/types/effect-builder-surface.ts +116 -0
- package/src/types/effect-procedure-surface.ts +98 -1
- package/src/types/index.ts +292 -1
- package/src/types/variants.ts +346 -13
- package/dist/chunk-VOWRLWZZ.js.map +0 -1
package/src/effect-runtime.ts
CHANGED
|
@@ -1,21 +1,38 @@
|
|
|
1
|
-
import { ORPCError } from "@orpc/contract";
|
|
1
|
+
import { ORPCError, type Meta } from "@orpc/contract";
|
|
2
2
|
import type {
|
|
3
3
|
Context,
|
|
4
|
+
Middleware,
|
|
5
|
+
MiddlewareNextFnOptions,
|
|
6
|
+
MiddlewareOutputFn,
|
|
7
|
+
MiddlewareResult,
|
|
4
8
|
ProcedureHandler,
|
|
5
9
|
ProcedureHandlerOptions,
|
|
6
10
|
} from "@orpc/server";
|
|
11
|
+
import type { Promisable } from "@orpc/shared";
|
|
7
12
|
import type { ManagedRuntime } from "effect";
|
|
8
|
-
import { Cause, Effect, Exit, FiberRefs } from "effect";
|
|
13
|
+
import { Cause, Effect, Exit, FiberRefs, Option } from "effect";
|
|
9
14
|
|
|
10
|
-
import { getCurrentFiberRefs } from "./fiber-context-bridge";
|
|
15
|
+
import { getCurrentFiberRefs, runWithFiberRefs } from "./fiber-context-bridge";
|
|
11
16
|
import type { EffectErrorConstructorMap, EffectErrorMap } from "./tagged-error";
|
|
12
17
|
import {
|
|
13
18
|
createEffectErrorConstructorMap,
|
|
14
19
|
isORPCTaggedError,
|
|
15
20
|
} from "./tagged-error";
|
|
16
|
-
import type {
|
|
21
|
+
import type {
|
|
22
|
+
EffectMiddleware,
|
|
23
|
+
EffectMiddlewareOptions,
|
|
24
|
+
EffectMiddlewareOutput,
|
|
25
|
+
EffectMiddlewareResult,
|
|
26
|
+
EffectOptionalProvider,
|
|
27
|
+
EffectPipelineStep,
|
|
28
|
+
EffectProcedureHandler,
|
|
29
|
+
EffectProvider,
|
|
30
|
+
EffectSpanConfig,
|
|
31
|
+
} from "./types";
|
|
32
|
+
|
|
33
|
+
type EffectTag = import("effect").Context.Tag<any, any>;
|
|
17
34
|
|
|
18
|
-
|
|
35
|
+
function toORPCErrorFromCause(
|
|
19
36
|
cause: Cause.Cause<unknown>,
|
|
20
37
|
): ORPCError<string, unknown> {
|
|
21
38
|
return Cause.match(cause, {
|
|
@@ -59,7 +76,7 @@ export function createEffectProcedureHandler<
|
|
|
59
76
|
TEffectErrorMap extends EffectErrorMap,
|
|
60
77
|
TRequirementsProvided,
|
|
61
78
|
TRuntimeError,
|
|
62
|
-
TMeta,
|
|
79
|
+
TMeta extends Meta,
|
|
63
80
|
>(options: {
|
|
64
81
|
runtime: ManagedRuntime.ManagedRuntime<TRequirementsProvided, TRuntimeError>;
|
|
65
82
|
effectErrorMap: TEffectErrorMap;
|
|
@@ -71,6 +88,7 @@ export function createEffectProcedureHandler<
|
|
|
71
88
|
TRequirementsProvided,
|
|
72
89
|
any
|
|
73
90
|
>;
|
|
91
|
+
effectSteps?: readonly EffectPipelineStep[];
|
|
74
92
|
spanConfig?: EffectSpanConfig;
|
|
75
93
|
defaultCaptureStackTrace: () => string | undefined;
|
|
76
94
|
}): ProcedureHandler<
|
|
@@ -84,6 +102,7 @@ export function createEffectProcedureHandler<
|
|
|
84
102
|
runtime,
|
|
85
103
|
effectErrorMap,
|
|
86
104
|
effectFn,
|
|
105
|
+
effectSteps = [],
|
|
87
106
|
spanConfig,
|
|
88
107
|
defaultCaptureStackTrace,
|
|
89
108
|
} = options;
|
|
@@ -108,27 +127,440 @@ export function createEffectProcedureHandler<
|
|
|
108
127
|
const captureStackTrace =
|
|
109
128
|
spanConfig?.captureStackTrace ?? defaultCaptureStackTrace;
|
|
110
129
|
const resolver = Effect.fnUntraced(effectFn as any);
|
|
111
|
-
const
|
|
112
|
-
|
|
130
|
+
const handlerEffect = resolver(effectOpts);
|
|
131
|
+
const tracedEffect = Effect.withSpan(
|
|
132
|
+
runEffectPipeline({
|
|
133
|
+
baseOptions: effectOpts,
|
|
134
|
+
effectErrorMap,
|
|
135
|
+
final: (context) =>
|
|
136
|
+
Effect.map(
|
|
137
|
+
context === effectOpts.context
|
|
138
|
+
? handlerEffect
|
|
139
|
+
: resolver({ ...effectOpts, context }),
|
|
140
|
+
(output) => ({ output, context: {} }),
|
|
141
|
+
),
|
|
142
|
+
input: opts.input,
|
|
143
|
+
steps: effectSteps,
|
|
144
|
+
}),
|
|
145
|
+
spanName,
|
|
146
|
+
{ captureStackTrace },
|
|
147
|
+
);
|
|
148
|
+
const exit = await runtime.runPromiseExit(
|
|
149
|
+
withParentFiberRefs(tracedEffect),
|
|
150
|
+
{ signal: opts.signal },
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
if (Exit.isFailure(exit)) {
|
|
154
|
+
throw toORPCErrorFromCause(exit.cause);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return exit.value.output as TOutput;
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function createEffectPipelineMiddleware<
|
|
162
|
+
TCurrentContext extends Context,
|
|
163
|
+
TOutput,
|
|
164
|
+
TEffectErrorMap extends EffectErrorMap,
|
|
165
|
+
TRequirementsProvided,
|
|
166
|
+
TRuntimeError,
|
|
167
|
+
TMeta extends Meta,
|
|
168
|
+
>(options: {
|
|
169
|
+
runtime: ManagedRuntime.ManagedRuntime<TRequirementsProvided, TRuntimeError>;
|
|
170
|
+
effectErrorMap: TEffectErrorMap;
|
|
171
|
+
steps: readonly EffectPipelineStep[];
|
|
172
|
+
}): Middleware<
|
|
173
|
+
TCurrentContext,
|
|
174
|
+
Record<never, never>,
|
|
175
|
+
unknown,
|
|
176
|
+
TOutput,
|
|
177
|
+
any,
|
|
178
|
+
TMeta
|
|
179
|
+
> {
|
|
180
|
+
const { runtime, effectErrorMap, steps } = options;
|
|
181
|
+
|
|
182
|
+
return async (opts, input) => {
|
|
183
|
+
const baseOptions = makeEffectOptions<
|
|
184
|
+
TCurrentContext,
|
|
185
|
+
unknown,
|
|
186
|
+
TEffectErrorMap,
|
|
187
|
+
TMeta
|
|
188
|
+
>(opts, input, effectErrorMap);
|
|
189
|
+
const effect = runEffectPipeline({
|
|
190
|
+
baseOptions,
|
|
191
|
+
effectErrorMap,
|
|
192
|
+
final: (context) =>
|
|
193
|
+
withCurrentFiberContext(() =>
|
|
194
|
+
opts.next(
|
|
195
|
+
context === opts.context
|
|
196
|
+
? undefined
|
|
197
|
+
: { context: context as Record<PropertyKey, unknown> },
|
|
198
|
+
),
|
|
199
|
+
) as any,
|
|
200
|
+
input,
|
|
201
|
+
steps,
|
|
113
202
|
});
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
203
|
+
const exit = await runtime.runPromiseExit(
|
|
204
|
+
withParentFiberRefs(effect as any),
|
|
205
|
+
{ signal: opts.signal },
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
if (Exit.isFailure(exit)) throw toORPCErrorFromCause(exit.cause);
|
|
209
|
+
|
|
210
|
+
return exit.value as MiddlewareResult<Record<never, never>, TOutput>;
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function createEffectProviderMiddleware<
|
|
215
|
+
TCurrentContext extends Context,
|
|
216
|
+
TInput,
|
|
217
|
+
TEffectErrorMap extends EffectErrorMap,
|
|
218
|
+
TRequirementsProvided,
|
|
219
|
+
TRuntimeError,
|
|
220
|
+
TMeta extends Meta,
|
|
221
|
+
TTag extends EffectTag,
|
|
222
|
+
>(options: {
|
|
223
|
+
runtime: ManagedRuntime.ManagedRuntime<TRequirementsProvided, TRuntimeError>;
|
|
224
|
+
effectErrorMap: TEffectErrorMap;
|
|
225
|
+
tag: TTag;
|
|
226
|
+
provider: EffectProvider<
|
|
227
|
+
TCurrentContext,
|
|
228
|
+
TInput,
|
|
229
|
+
TEffectErrorMap,
|
|
230
|
+
TRequirementsProvided,
|
|
231
|
+
TMeta,
|
|
232
|
+
TTag
|
|
233
|
+
>;
|
|
234
|
+
}): Middleware<TCurrentContext, Record<never, never>, TInput, any, any, TMeta> {
|
|
235
|
+
const { runtime, effectErrorMap, tag, provider } = options;
|
|
236
|
+
|
|
237
|
+
return async (opts, input) => {
|
|
238
|
+
const effectOpts = makeEffectOptions<
|
|
239
|
+
TCurrentContext,
|
|
240
|
+
TInput,
|
|
241
|
+
TEffectErrorMap,
|
|
242
|
+
TMeta
|
|
243
|
+
>(opts, input, effectErrorMap);
|
|
244
|
+
const effect = Effect.flatMap(provider(effectOpts), (service) =>
|
|
245
|
+
Effect.provideService(
|
|
246
|
+
withCurrentFiberContext(() => opts.next()),
|
|
247
|
+
tag,
|
|
248
|
+
service,
|
|
249
|
+
),
|
|
250
|
+
);
|
|
251
|
+
const exit = await runtime.runPromiseExit(withParentFiberRefs(effect), {
|
|
252
|
+
signal: opts.signal,
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
if (Exit.isFailure(exit)) throw toORPCErrorFromCause(exit.cause);
|
|
256
|
+
|
|
257
|
+
return exit.value;
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export function createEffectOptionalProviderMiddleware<
|
|
262
|
+
TCurrentContext extends Context,
|
|
263
|
+
TInput,
|
|
264
|
+
TEffectErrorMap extends EffectErrorMap,
|
|
265
|
+
TRequirementsProvided,
|
|
266
|
+
TRuntimeError,
|
|
267
|
+
TMeta extends Meta,
|
|
268
|
+
TTag extends EffectTag,
|
|
269
|
+
>(options: {
|
|
270
|
+
runtime: ManagedRuntime.ManagedRuntime<TRequirementsProvided, TRuntimeError>;
|
|
271
|
+
effectErrorMap: TEffectErrorMap;
|
|
272
|
+
tag: TTag;
|
|
273
|
+
provider: EffectOptionalProvider<
|
|
274
|
+
TCurrentContext,
|
|
275
|
+
TInput,
|
|
276
|
+
TEffectErrorMap,
|
|
277
|
+
TRequirementsProvided,
|
|
278
|
+
TMeta,
|
|
279
|
+
TTag
|
|
280
|
+
>;
|
|
281
|
+
}): Middleware<TCurrentContext, Record<never, never>, TInput, any, any, TMeta> {
|
|
282
|
+
const { runtime, effectErrorMap, tag, provider } = options;
|
|
283
|
+
|
|
284
|
+
return async (opts, input) => {
|
|
285
|
+
const effectOpts = makeEffectOptions<
|
|
286
|
+
TCurrentContext,
|
|
287
|
+
TInput,
|
|
288
|
+
TEffectErrorMap,
|
|
289
|
+
TMeta
|
|
290
|
+
>(opts, input, effectErrorMap);
|
|
291
|
+
const effect = Effect.flatMap(provider(effectOpts), (service) =>
|
|
292
|
+
Option.match(service, {
|
|
293
|
+
onNone: () => withCurrentFiberContext(() => opts.next()),
|
|
294
|
+
onSome: (value) =>
|
|
295
|
+
Effect.provideService(
|
|
296
|
+
withCurrentFiberContext(() => opts.next()),
|
|
297
|
+
tag,
|
|
298
|
+
value,
|
|
121
299
|
),
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const exit = await runtime.runPromiseExit(
|
|
300
|
+
}),
|
|
301
|
+
);
|
|
302
|
+
const exit = await runtime.runPromiseExit(withParentFiberRefs(effect), {
|
|
125
303
|
signal: opts.signal,
|
|
126
304
|
});
|
|
127
305
|
|
|
128
|
-
if (Exit.isFailure(exit))
|
|
129
|
-
|
|
306
|
+
if (Exit.isFailure(exit)) throw toORPCErrorFromCause(exit.cause);
|
|
307
|
+
|
|
308
|
+
return exit.value;
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// todo(utopy): make this check more comprehensive, maybe add a Symbol to the EffectMiddleware type
|
|
313
|
+
export function isEffectMiddleware(
|
|
314
|
+
value: unknown,
|
|
315
|
+
): value is EffectMiddleware<
|
|
316
|
+
Context,
|
|
317
|
+
Context,
|
|
318
|
+
unknown,
|
|
319
|
+
unknown,
|
|
320
|
+
EffectErrorMap,
|
|
321
|
+
unknown,
|
|
322
|
+
Meta
|
|
323
|
+
> {
|
|
324
|
+
return (
|
|
325
|
+
typeof value === "function" &&
|
|
326
|
+
value.constructor?.name === "GeneratorFunction"
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function makeEffectOptions<
|
|
331
|
+
TCurrentContext extends Context,
|
|
332
|
+
TInput,
|
|
333
|
+
TEffectErrorMap extends EffectErrorMap,
|
|
334
|
+
TMeta extends Meta,
|
|
335
|
+
>(
|
|
336
|
+
opts: Parameters<
|
|
337
|
+
Middleware<TCurrentContext, any, TInput, any, any, TMeta>
|
|
338
|
+
>[0],
|
|
339
|
+
input: TInput,
|
|
340
|
+
effectErrorMap: TEffectErrorMap,
|
|
341
|
+
): ProcedureHandlerOptions<
|
|
342
|
+
TCurrentContext,
|
|
343
|
+
TInput,
|
|
344
|
+
EffectErrorConstructorMap<TEffectErrorMap>,
|
|
345
|
+
TMeta & Record<never, never>
|
|
346
|
+
> {
|
|
347
|
+
return {
|
|
348
|
+
context: opts.context,
|
|
349
|
+
input,
|
|
350
|
+
path: opts.path,
|
|
351
|
+
procedure: opts.procedure as any,
|
|
352
|
+
signal: opts.signal,
|
|
353
|
+
lastEventId: opts.lastEventId,
|
|
354
|
+
errors: createEffectErrorConstructorMap(effectErrorMap),
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function runEffectPipeline<
|
|
359
|
+
TCurrentContext extends Context,
|
|
360
|
+
TInput,
|
|
361
|
+
TOutput,
|
|
362
|
+
TEffectErrorMap extends EffectErrorMap,
|
|
363
|
+
TRequirementsProvided,
|
|
364
|
+
TMeta extends Meta,
|
|
365
|
+
>(options: {
|
|
366
|
+
baseOptions: ProcedureHandlerOptions<
|
|
367
|
+
TCurrentContext,
|
|
368
|
+
TInput,
|
|
369
|
+
EffectErrorConstructorMap<TEffectErrorMap>,
|
|
370
|
+
TMeta
|
|
371
|
+
>;
|
|
372
|
+
effectErrorMap: TEffectErrorMap;
|
|
373
|
+
final: (
|
|
374
|
+
context: TCurrentContext,
|
|
375
|
+
) => Effect.Effect<
|
|
376
|
+
EffectMiddlewareResult<Record<never, never>, TOutput>,
|
|
377
|
+
unknown,
|
|
378
|
+
TRequirementsProvided
|
|
379
|
+
>;
|
|
380
|
+
input: TInput;
|
|
381
|
+
steps: readonly EffectPipelineStep[];
|
|
382
|
+
}): Effect.Effect<
|
|
383
|
+
EffectMiddlewareResult<Context, TOutput>,
|
|
384
|
+
unknown,
|
|
385
|
+
TRequirementsProvided
|
|
386
|
+
> {
|
|
387
|
+
const run = (
|
|
388
|
+
index: number,
|
|
389
|
+
context: TCurrentContext,
|
|
390
|
+
): Effect.Effect<
|
|
391
|
+
EffectMiddlewareResult<Context, TOutput>,
|
|
392
|
+
unknown,
|
|
393
|
+
TRequirementsProvided
|
|
394
|
+
> => {
|
|
395
|
+
const step = options.steps[index];
|
|
396
|
+
if (!step) return options.final(context);
|
|
397
|
+
|
|
398
|
+
const stepOptions = { ...options.baseOptions, context };
|
|
399
|
+
|
|
400
|
+
if (step._tag === "provide") {
|
|
401
|
+
return Effect.flatMap(step.provider(stepOptions as any), (service) =>
|
|
402
|
+
Effect.provideService(run(index + 1, context), step.tag, service),
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (step._tag === "provideOptional") {
|
|
407
|
+
return Effect.flatMap(step.provider(stepOptions as any), (service) =>
|
|
408
|
+
Option.match(service, {
|
|
409
|
+
onNone: () => run(index + 1, context),
|
|
410
|
+
onSome: (value) =>
|
|
411
|
+
Effect.provideService(run(index + 1, context), step.tag, value),
|
|
412
|
+
}),
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (step._tag === "provideLayer") {
|
|
417
|
+
return Effect.provide(run(index + 1, context), step.layer);
|
|
130
418
|
}
|
|
131
419
|
|
|
132
|
-
|
|
420
|
+
const nextTracker =
|
|
421
|
+
createMiddlewareNextTracker<EffectMiddlewareResult<Context, TOutput>>();
|
|
422
|
+
const effectOptions: EffectMiddlewareOptions<
|
|
423
|
+
TCurrentContext,
|
|
424
|
+
TOutput,
|
|
425
|
+
TEffectErrorMap,
|
|
426
|
+
TRequirementsProvided,
|
|
427
|
+
TMeta
|
|
428
|
+
> = {
|
|
429
|
+
context,
|
|
430
|
+
path: stepOptions.path,
|
|
431
|
+
procedure: stepOptions.procedure,
|
|
432
|
+
signal: stepOptions.signal,
|
|
433
|
+
lastEventId: stepOptions.lastEventId,
|
|
434
|
+
errors: createEffectErrorConstructorMap(options.effectErrorMap),
|
|
435
|
+
next: nextTracker.wrapNext(
|
|
436
|
+
(...rest: [MiddlewareNextFnOptions<Context>?]) => {
|
|
437
|
+
const nextContext = rest[0]?.context ?? {};
|
|
438
|
+
return Effect.map(
|
|
439
|
+
run(index + 1, { ...context, ...nextContext }),
|
|
440
|
+
(result) => ({
|
|
441
|
+
output: result.output,
|
|
442
|
+
context: nextContext,
|
|
443
|
+
}),
|
|
444
|
+
) as Effect.Effect<EffectMiddlewareResult<Context, TOutput>>;
|
|
445
|
+
},
|
|
446
|
+
),
|
|
447
|
+
};
|
|
448
|
+
const effectOutput = makeEffectMiddlewareOutput<
|
|
449
|
+
TOutput,
|
|
450
|
+
TEffectErrorMap,
|
|
451
|
+
TRequirementsProvided
|
|
452
|
+
>((output) => ({ output, context: {} }));
|
|
453
|
+
const middlewareEffect = Effect.fnUntraced(step.middleware)(
|
|
454
|
+
effectOptions,
|
|
455
|
+
options.input,
|
|
456
|
+
effectOutput,
|
|
457
|
+
) as Effect.Effect<EffectMiddlewareResult<Context, TOutput> | void>;
|
|
458
|
+
|
|
459
|
+
return Effect.flatMap(middlewareEffect, (result) =>
|
|
460
|
+
resolveEffectMiddlewareContinuation({
|
|
461
|
+
autoNext: () => effectOptions.next(),
|
|
462
|
+
nextInvoked: nextTracker.nextInvoked,
|
|
463
|
+
nextResult: nextTracker.nextResult,
|
|
464
|
+
result,
|
|
465
|
+
}),
|
|
466
|
+
);
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
return run(0, options.baseOptions.context);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function createMiddlewareNextTracker<T>() {
|
|
473
|
+
let nextInvoked = false;
|
|
474
|
+
let nextResult: T | undefined;
|
|
475
|
+
|
|
476
|
+
return {
|
|
477
|
+
get nextInvoked() {
|
|
478
|
+
return nextInvoked;
|
|
479
|
+
},
|
|
480
|
+
get nextResult() {
|
|
481
|
+
return nextResult;
|
|
482
|
+
},
|
|
483
|
+
wrapNext<Fn extends (...args: any) => Effect.Effect<T, any, any>>(
|
|
484
|
+
nextFn: Fn,
|
|
485
|
+
): Fn {
|
|
486
|
+
return ((...args: Parameters<Fn>) => {
|
|
487
|
+
nextInvoked = true;
|
|
488
|
+
return Effect.map(nextFn(...args), (result) => {
|
|
489
|
+
nextResult = result;
|
|
490
|
+
return result;
|
|
491
|
+
});
|
|
492
|
+
}) as Fn;
|
|
493
|
+
},
|
|
133
494
|
};
|
|
134
495
|
}
|
|
496
|
+
|
|
497
|
+
function resolveEffectMiddlewareContinuation<
|
|
498
|
+
TContext extends Context,
|
|
499
|
+
TOutput,
|
|
500
|
+
TRequirementsProvided,
|
|
501
|
+
>(options: {
|
|
502
|
+
result: EffectMiddlewareResult<TContext, TOutput> | void;
|
|
503
|
+
nextInvoked: boolean;
|
|
504
|
+
nextResult: EffectMiddlewareResult<TContext, TOutput> | undefined;
|
|
505
|
+
autoNext: () => Effect.Effect<
|
|
506
|
+
EffectMiddlewareResult<TContext, TOutput>,
|
|
507
|
+
unknown,
|
|
508
|
+
TRequirementsProvided
|
|
509
|
+
>;
|
|
510
|
+
}): Effect.Effect<
|
|
511
|
+
EffectMiddlewareResult<TContext, TOutput>,
|
|
512
|
+
unknown,
|
|
513
|
+
TRequirementsProvided
|
|
514
|
+
> {
|
|
515
|
+
const { result, nextInvoked, nextResult, autoNext } = options;
|
|
516
|
+
|
|
517
|
+
if (result !== undefined) {
|
|
518
|
+
return Effect.succeed(result);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
if (nextInvoked) {
|
|
522
|
+
if (nextResult === undefined) {
|
|
523
|
+
return Effect.die(
|
|
524
|
+
new Error(
|
|
525
|
+
"Effect middleware invoked next() but did not return its result",
|
|
526
|
+
),
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
return Effect.succeed(nextResult);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
return autoNext();
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function makeEffectMiddlewareOutput<
|
|
536
|
+
TOutput,
|
|
537
|
+
TEffectErrorMap extends EffectErrorMap,
|
|
538
|
+
TRequirementsProvided,
|
|
539
|
+
>(
|
|
540
|
+
output: MiddlewareOutputFn<TOutput>,
|
|
541
|
+
): EffectMiddlewareOutput<TOutput, TEffectErrorMap, TRequirementsProvided> {
|
|
542
|
+
return (value: TOutput) => withCurrentFiberContext(() => output(value));
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function withCurrentFiberContext<T>(fn: () => Promisable<T>): Effect.Effect<T> {
|
|
546
|
+
return Effect.flatMap(Effect.getFiberRefs, (fiberRefs) =>
|
|
547
|
+
Effect.promise(() =>
|
|
548
|
+
runWithFiberRefs(fiberRefs, () => Promise.resolve(fn())),
|
|
549
|
+
),
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function withParentFiberRefs<A, E, R>(
|
|
554
|
+
effect: Effect.Effect<A, E, R>,
|
|
555
|
+
): Effect.Effect<A, E, R> {
|
|
556
|
+
const parentFiberRefs = getCurrentFiberRefs();
|
|
557
|
+
return parentFiberRefs
|
|
558
|
+
? Effect.fiberIdWith((fiberId) =>
|
|
559
|
+
Effect.flatMap(Effect.getFiberRefs, (fiberRefs) =>
|
|
560
|
+
Effect.setFiberRefs(
|
|
561
|
+
FiberRefs.joinAs(fiberRefs, fiberId, parentFiberRefs),
|
|
562
|
+
).pipe(Effect.andThen(effect)),
|
|
563
|
+
),
|
|
564
|
+
)
|
|
565
|
+
: effect;
|
|
566
|
+
}
|
|
@@ -43,6 +43,12 @@ interface NodeProxyInternalConfig<
|
|
|
43
43
|
result: unknown,
|
|
44
44
|
receiver: unknown,
|
|
45
45
|
) => unknown;
|
|
46
|
+
wrapArgs?: (
|
|
47
|
+
context: NodeProxyContext<TTarget, TSource>,
|
|
48
|
+
prop: PropertyKey,
|
|
49
|
+
args: unknown[],
|
|
50
|
+
receiver: unknown,
|
|
51
|
+
) => unknown[];
|
|
46
52
|
}
|
|
47
53
|
|
|
48
54
|
/**
|
|
@@ -93,6 +99,15 @@ export interface NodeProxyConfig<
|
|
|
93
99
|
result: unknown,
|
|
94
100
|
receiver: unknown,
|
|
95
101
|
) => unknown;
|
|
102
|
+
/**
|
|
103
|
+
* Rewrites arguments before forwarding upstream methods.
|
|
104
|
+
*/
|
|
105
|
+
wrapArgs?: (
|
|
106
|
+
context: NodeProxyContext<TTarget, TSource>,
|
|
107
|
+
prop: PropertyKey,
|
|
108
|
+
args: unknown[],
|
|
109
|
+
receiver: unknown,
|
|
110
|
+
) => unknown[];
|
|
96
111
|
}
|
|
97
112
|
|
|
98
113
|
function createNodeProxyContext<
|
|
@@ -124,7 +139,8 @@ function createBoundMethod<
|
|
|
124
139
|
}
|
|
125
140
|
|
|
126
141
|
const wrapped = (...args: unknown[]) => {
|
|
127
|
-
const
|
|
142
|
+
const nextArgs = config.wrapArgs?.(context, prop, args, receiver) ?? args;
|
|
143
|
+
const result = Reflect.apply(value, context.upstream, nextArgs);
|
|
128
144
|
return config.wrapResult?.(context, prop, result, receiver) ?? result;
|
|
129
145
|
};
|
|
130
146
|
|
package/src/extension/state.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import type { ManagedRuntime } from "effect";
|
|
2
2
|
|
|
3
3
|
import type { EffectErrorMap } from "../tagged-error";
|
|
4
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
EffectPipelineStep,
|
|
6
|
+
EffectProcedureHandlerConfig,
|
|
7
|
+
EffectSpanConfig,
|
|
8
|
+
} from "../types";
|
|
5
9
|
|
|
6
10
|
export interface EffectExtensionState<
|
|
7
11
|
TRequirementsProvided = any,
|
|
@@ -22,6 +26,12 @@ export interface EffectExtensionState<
|
|
|
22
26
|
* @see {@link EffectSpanConfig}
|
|
23
27
|
*/
|
|
24
28
|
spanConfig?: EffectSpanConfig;
|
|
29
|
+
/**
|
|
30
|
+
* Pending Effect-native provider / middleware steps that have not been flushed
|
|
31
|
+
* into the oRPC middleware chain.
|
|
32
|
+
*/
|
|
33
|
+
effectSteps?: readonly EffectPipelineStep[];
|
|
34
|
+
effectHandler?: EffectProcedureHandlerConfig;
|
|
25
35
|
}
|
|
26
36
|
|
|
27
37
|
export interface EffectInternals<TUpstream extends object = object> {
|
|
@@ -62,25 +72,13 @@ export function getEffectInternals<TUpstream extends object>(
|
|
|
62
72
|
return target[effectInternalsSymbol];
|
|
63
73
|
}
|
|
64
74
|
|
|
65
|
-
|
|
75
|
+
function getEffectUpstream<TUpstream extends object>(
|
|
66
76
|
target: EffectProxyTarget<TUpstream>,
|
|
67
77
|
): TUpstream {
|
|
68
78
|
return getEffectInternals(target).upstream;
|
|
69
79
|
}
|
|
70
80
|
|
|
71
|
-
|
|
72
|
-
target: EffectProxyTarget,
|
|
73
|
-
): EffectExtensionState {
|
|
74
|
-
return getEffectInternals(target).state;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export function getEffectMethodCache(
|
|
78
|
-
target: EffectProxyTarget,
|
|
79
|
-
): Map<PropertyKey, unknown> {
|
|
80
|
-
return getEffectInternals(target).methodCache;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export function hasEffectState(value: unknown): value is EffectProxyTarget {
|
|
81
|
+
function hasEffectState(value: unknown): value is EffectProxyTarget {
|
|
84
82
|
return (
|
|
85
83
|
typeof value === "object" &&
|
|
86
84
|
value !== null &&
|
|
@@ -2,6 +2,10 @@ import type { FiberRefs } from "effect";
|
|
|
2
2
|
|
|
3
3
|
export interface FiberContextBridge {
|
|
4
4
|
readonly getCurrentFiberRefs: () => FiberRefs.FiberRefs | undefined;
|
|
5
|
+
readonly runWithFiberRefs?: <T>(
|
|
6
|
+
fiberRefs: FiberRefs.FiberRefs,
|
|
7
|
+
fn: () => Promise<T>,
|
|
8
|
+
) => Promise<T>;
|
|
5
9
|
}
|
|
6
10
|
|
|
7
11
|
let bridge: FiberContextBridge | undefined;
|
|
@@ -15,3 +19,12 @@ export function installFiberContextBridge(
|
|
|
15
19
|
export function getCurrentFiberRefs(): FiberRefs.FiberRefs | undefined {
|
|
16
20
|
return bridge?.getCurrentFiberRefs();
|
|
17
21
|
}
|
|
22
|
+
|
|
23
|
+
export function runWithFiberRefs<T>(
|
|
24
|
+
fiberRefs: FiberRefs.FiberRefs,
|
|
25
|
+
fn: () => Promise<T>,
|
|
26
|
+
): Promise<T> {
|
|
27
|
+
return bridge?.runWithFiberRefs
|
|
28
|
+
? bridge.runWithFiberRefs(fiberRefs, fn)
|
|
29
|
+
: fn();
|
|
30
|
+
}
|
package/src/node.ts
CHANGED
|
@@ -12,12 +12,13 @@ const fiberRefsStorage = new AsyncLocalStorage<FiberRefs.FiberRefs>();
|
|
|
12
12
|
|
|
13
13
|
const bridge: FiberContextBridge = {
|
|
14
14
|
getCurrentFiberRefs: () => fiberRefsStorage.getStore(),
|
|
15
|
+
runWithFiberRefs: (fiberRefs, fn) => fiberRefsStorage.run(fiberRefs, fn),
|
|
15
16
|
};
|
|
16
17
|
|
|
17
18
|
installFiberContextBridge(bridge);
|
|
18
19
|
|
|
19
20
|
export function withFiberContext<T>(fn: () => Promise<T>): Effect.Effect<T> {
|
|
20
21
|
return Effect.flatMap(Effect.getFiberRefs, (fiberRefs) =>
|
|
21
|
-
Effect.promise(() =>
|
|
22
|
+
Effect.promise(() => bridge.runWithFiberRefs!(fiberRefs, fn)),
|
|
22
23
|
);
|
|
23
24
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Layer, ManagedRuntime } from "effect";
|
|
2
|
+
|
|
3
|
+
export type EffectRuntimeSource<TRequirementsProvided, TRuntimeError> =
|
|
4
|
+
| ManagedRuntime.ManagedRuntime<TRequirementsProvided, TRuntimeError>
|
|
5
|
+
| Layer.Layer<TRequirementsProvided, TRuntimeError, never>;
|
|
6
|
+
|
|
7
|
+
export function toManagedRuntime<TRequirementsProvided, TRuntimeError>(
|
|
8
|
+
source: EffectRuntimeSource<TRequirementsProvided, TRuntimeError>,
|
|
9
|
+
): ManagedRuntime.ManagedRuntime<TRequirementsProvided, TRuntimeError> {
|
|
10
|
+
return ManagedRuntime.isManagedRuntime(source)
|
|
11
|
+
? (source as ManagedRuntime.ManagedRuntime<
|
|
12
|
+
TRequirementsProvided,
|
|
13
|
+
TRuntimeError
|
|
14
|
+
>)
|
|
15
|
+
: ManagedRuntime.make(
|
|
16
|
+
source as Layer.Layer<TRequirementsProvided, TRuntimeError, never>,
|
|
17
|
+
);
|
|
18
|
+
}
|
package/src/tagged-error.ts
CHANGED
|
@@ -371,15 +371,6 @@ export type EffectErrorMapItem =
|
|
|
371
371
|
| ErrorMapItem<AnySchema>
|
|
372
372
|
| AnyORPCTaggedErrorClass;
|
|
373
373
|
|
|
374
|
-
export type ORPCTaggedErrorClassToErrorMapItem<T> =
|
|
375
|
-
T extends ORPCTaggedErrorClass<any, any, infer TData>
|
|
376
|
-
? {
|
|
377
|
-
status?: number;
|
|
378
|
-
message?: string;
|
|
379
|
-
data?: TData;
|
|
380
|
-
}
|
|
381
|
-
: never;
|
|
382
|
-
|
|
383
374
|
/**
|
|
384
375
|
* Extended error map that supports both traditional oRPC errors and ORPCTaggedError classes.
|
|
385
376
|
*
|