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