effect-orpc 0.4.0 → 0.5.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 +81 -93
- package/dist/index.js +350 -80
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/effect-builder.ts +36 -22
- package/src/effect-procedure.ts +31 -7
- package/src/effect-runtime.ts +622 -80
- package/src/tests/effect-builder.proxy.test.ts +15 -17
- package/src/tests/effect-builder.test.ts +296 -139
- package/src/tests/effect-callback-shapes.test.ts +410 -0
- package/src/tests/effect-error-map.test.ts +4 -6
- package/src/tests/effect-procedure.test.ts +44 -5
- package/src/tests/parity-shared.ts +2 -2
- package/src/types/index.ts +70 -40
package/src/effect-runtime.ts
CHANGED
|
@@ -3,13 +3,14 @@ import type {
|
|
|
3
3
|
Context,
|
|
4
4
|
Middleware,
|
|
5
5
|
MiddlewareNextFnOptions,
|
|
6
|
+
MiddlewareOptions,
|
|
6
7
|
MiddlewareOutputFn,
|
|
7
8
|
MiddlewareResult,
|
|
8
9
|
ProcedureHandler,
|
|
9
10
|
ProcedureHandlerOptions,
|
|
10
11
|
} from "@orpc/server";
|
|
11
12
|
import type { Promisable } from "@orpc/shared";
|
|
12
|
-
import { Cause, Effect, Exit, Option } from "effect";
|
|
13
|
+
import { Cause, Effect, Exit, FiberRefs, Option } from "effect";
|
|
13
14
|
|
|
14
15
|
import { runWithFiberRefs } from "./fiber-context-bridge";
|
|
15
16
|
import type { EffectRuntimeRunner } from "./runtime-source";
|
|
@@ -32,6 +33,35 @@ import type {
|
|
|
32
33
|
|
|
33
34
|
type EffectTag = import("effect").Context.Tag<any, any>;
|
|
34
35
|
|
|
36
|
+
const HybridContinuationSymbol = Symbol("effect-orpc/HybridContinuation");
|
|
37
|
+
|
|
38
|
+
type MiddlewareNextTracker<T> = ReturnType<
|
|
39
|
+
typeof createMiddlewareNextTracker<T>
|
|
40
|
+
>;
|
|
41
|
+
|
|
42
|
+
type EffectMiddlewareRuntimeOptions<
|
|
43
|
+
TCurrentContext extends Context,
|
|
44
|
+
TOutput,
|
|
45
|
+
TEffectErrorMap extends EffectErrorMap,
|
|
46
|
+
TRequirementsProvided,
|
|
47
|
+
TMeta extends Meta,
|
|
48
|
+
> = EffectMiddlewareOptions<
|
|
49
|
+
TCurrentContext,
|
|
50
|
+
TOutput,
|
|
51
|
+
TEffectErrorMap,
|
|
52
|
+
TRequirementsProvided,
|
|
53
|
+
TMeta
|
|
54
|
+
> & {
|
|
55
|
+
readonly nextTracker: MiddlewareNextTracker<
|
|
56
|
+
EffectMiddlewareResult<Context, TOutput>
|
|
57
|
+
>;
|
|
58
|
+
readonly output: EffectMiddlewareOutput<
|
|
59
|
+
TOutput,
|
|
60
|
+
TEffectErrorMap,
|
|
61
|
+
TRequirementsProvided
|
|
62
|
+
>;
|
|
63
|
+
};
|
|
64
|
+
|
|
35
65
|
function toORPCErrorFromCause(
|
|
36
66
|
cause: Cause.Cause<unknown>,
|
|
37
67
|
): ORPCError<string, unknown> {
|
|
@@ -126,8 +156,7 @@ export function createEffectProcedureHandler<
|
|
|
126
156
|
const spanName = spanConfig?.name ?? opts.path.join(".");
|
|
127
157
|
const captureStackTrace =
|
|
128
158
|
spanConfig?.captureStackTrace ?? defaultCaptureStackTrace;
|
|
129
|
-
const
|
|
130
|
-
const handlerEffect = resolver(effectOpts);
|
|
159
|
+
const handlerEffect = callEffectCallback(effectFn, effectOpts);
|
|
131
160
|
const tracedEffect = Effect.withSpan(
|
|
132
161
|
runEffectPipeline({
|
|
133
162
|
baseOptions: effectOpts,
|
|
@@ -136,10 +165,11 @@ export function createEffectProcedureHandler<
|
|
|
136
165
|
Effect.map(
|
|
137
166
|
context === effectOpts.context
|
|
138
167
|
? handlerEffect
|
|
139
|
-
:
|
|
168
|
+
: callEffectCallback(effectFn, { ...effectOpts, context }),
|
|
140
169
|
(output) => ({ output, context: {} }),
|
|
141
170
|
),
|
|
142
171
|
input: opts.input,
|
|
172
|
+
runner,
|
|
143
173
|
steps: effectSteps,
|
|
144
174
|
}),
|
|
145
175
|
spanName,
|
|
@@ -204,6 +234,7 @@ export function createEffectPipelineMiddleware<
|
|
|
204
234
|
),
|
|
205
235
|
) as any,
|
|
206
236
|
input,
|
|
237
|
+
runner,
|
|
207
238
|
steps,
|
|
208
239
|
});
|
|
209
240
|
const exit = await runner.runPromiseExit(effect, {
|
|
@@ -216,6 +247,341 @@ export function createEffectPipelineMiddleware<
|
|
|
216
247
|
};
|
|
217
248
|
}
|
|
218
249
|
|
|
250
|
+
export function createEffectOrORPCMiddleware<
|
|
251
|
+
TCurrentContext extends Context,
|
|
252
|
+
TOutContext extends Context,
|
|
253
|
+
TInput,
|
|
254
|
+
TOutput,
|
|
255
|
+
TEffectErrorMap extends EffectErrorMap,
|
|
256
|
+
TRequirementsProvided,
|
|
257
|
+
TRuntimeError,
|
|
258
|
+
TMeta extends Meta,
|
|
259
|
+
>(options: {
|
|
260
|
+
runner: EffectRuntimeRunner<TRequirementsProvided, TRuntimeError>;
|
|
261
|
+
effectErrorMap: TEffectErrorMap;
|
|
262
|
+
middleware: EffectMiddleware<
|
|
263
|
+
TCurrentContext,
|
|
264
|
+
TOutContext,
|
|
265
|
+
TInput,
|
|
266
|
+
TOutput,
|
|
267
|
+
TEffectErrorMap,
|
|
268
|
+
TRequirementsProvided,
|
|
269
|
+
TMeta
|
|
270
|
+
>;
|
|
271
|
+
}): Middleware<TCurrentContext, TOutContext, TInput, TOutput, any, TMeta> {
|
|
272
|
+
const { runner, effectErrorMap, middleware } = options;
|
|
273
|
+
|
|
274
|
+
return async (opts, input, output) => {
|
|
275
|
+
const effectOptions = makeEffectMiddlewareOptions<
|
|
276
|
+
TCurrentContext,
|
|
277
|
+
TOutput,
|
|
278
|
+
TEffectErrorMap,
|
|
279
|
+
TRequirementsProvided,
|
|
280
|
+
TMeta
|
|
281
|
+
>({
|
|
282
|
+
effectErrorMap,
|
|
283
|
+
final: opts.next,
|
|
284
|
+
options: opts,
|
|
285
|
+
output,
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
return runEffectOrORPCMiddlewareCallback<
|
|
289
|
+
TCurrentContext,
|
|
290
|
+
TOutContext,
|
|
291
|
+
TInput,
|
|
292
|
+
TOutput,
|
|
293
|
+
TEffectErrorMap,
|
|
294
|
+
TRequirementsProvided,
|
|
295
|
+
TMeta
|
|
296
|
+
>({
|
|
297
|
+
effectOptions,
|
|
298
|
+
input,
|
|
299
|
+
middleware,
|
|
300
|
+
nativeNext: opts.next,
|
|
301
|
+
runner,
|
|
302
|
+
signal: opts.signal,
|
|
303
|
+
});
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
async function runEffectOrORPCMiddlewareCallback<
|
|
308
|
+
TCurrentContext extends Context,
|
|
309
|
+
TOutContext extends Context,
|
|
310
|
+
TInput,
|
|
311
|
+
TOutput,
|
|
312
|
+
TEffectErrorMap extends EffectErrorMap,
|
|
313
|
+
TRequirementsProvided,
|
|
314
|
+
TMeta extends Meta,
|
|
315
|
+
>(options: {
|
|
316
|
+
effectOptions: EffectMiddlewareRuntimeOptions<
|
|
317
|
+
TCurrentContext,
|
|
318
|
+
TOutput,
|
|
319
|
+
TEffectErrorMap,
|
|
320
|
+
TRequirementsProvided,
|
|
321
|
+
TMeta
|
|
322
|
+
>;
|
|
323
|
+
input: TInput;
|
|
324
|
+
middleware: EffectMiddleware<
|
|
325
|
+
TCurrentContext,
|
|
326
|
+
TOutContext,
|
|
327
|
+
TInput,
|
|
328
|
+
TOutput,
|
|
329
|
+
TEffectErrorMap,
|
|
330
|
+
TRequirementsProvided,
|
|
331
|
+
TMeta
|
|
332
|
+
>;
|
|
333
|
+
nativeNext: () => Promisable<MiddlewareResult<TOutContext, TOutput>>;
|
|
334
|
+
runner: EffectRuntimeRunner<TRequirementsProvided, unknown>;
|
|
335
|
+
signal: AbortSignal | undefined;
|
|
336
|
+
}): Promise<MiddlewareResult<TOutContext, TOutput>> {
|
|
337
|
+
const result = options.middleware(
|
|
338
|
+
options.effectOptions,
|
|
339
|
+
options.input,
|
|
340
|
+
options.effectOptions.output,
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
const classified = classifyEffectOrORPCMiddlewareResult<
|
|
344
|
+
TOutContext,
|
|
345
|
+
TOutput,
|
|
346
|
+
TRequirementsProvided
|
|
347
|
+
>(result);
|
|
348
|
+
|
|
349
|
+
switch (classified._tag) {
|
|
350
|
+
case "nativeContinuation":
|
|
351
|
+
return classified.result;
|
|
352
|
+
case "nativeGuardOnly":
|
|
353
|
+
return options.nativeNext();
|
|
354
|
+
case "nativeResult":
|
|
355
|
+
return classified.result;
|
|
356
|
+
case "effect": {
|
|
357
|
+
const exit = await options.runner.runPromiseExit(
|
|
358
|
+
runEffectMiddlewareResult({
|
|
359
|
+
autoNext: () => options.effectOptions.next(),
|
|
360
|
+
effect: classified.effect,
|
|
361
|
+
nextInvoked: options.effectOptions.nextTracker.nextInvoked,
|
|
362
|
+
nextResult: options.effectOptions.nextTracker.nextResult,
|
|
363
|
+
}),
|
|
364
|
+
{ signal: options.signal },
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
if (Exit.isFailure(exit)) throw toORPCErrorFromCause(exit.cause);
|
|
368
|
+
|
|
369
|
+
return exit.value as MiddlewareResult<TOutContext, TOutput>;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
type ClassifiedEffectOrORPCMiddlewareResult<
|
|
375
|
+
TOutContext extends Context,
|
|
376
|
+
TOutput,
|
|
377
|
+
TRequirementsProvided,
|
|
378
|
+
> =
|
|
379
|
+
| {
|
|
380
|
+
readonly _tag: "nativeContinuation";
|
|
381
|
+
readonly result: MiddlewareResult<TOutContext, TOutput>;
|
|
382
|
+
}
|
|
383
|
+
| { readonly _tag: "nativeGuardOnly" }
|
|
384
|
+
| {
|
|
385
|
+
readonly _tag: "nativeResult";
|
|
386
|
+
readonly result: MiddlewareResult<TOutContext, TOutput>;
|
|
387
|
+
}
|
|
388
|
+
| {
|
|
389
|
+
readonly _tag: "effect";
|
|
390
|
+
readonly effect: Effect.Effect<
|
|
391
|
+
EffectMiddlewareResult<TOutContext, TOutput> | void,
|
|
392
|
+
unknown,
|
|
393
|
+
TRequirementsProvided
|
|
394
|
+
>;
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
function classifyEffectOrORPCMiddlewareResult<
|
|
398
|
+
TOutContext extends Context,
|
|
399
|
+
TOutput,
|
|
400
|
+
TRequirementsProvided,
|
|
401
|
+
>(
|
|
402
|
+
result: unknown,
|
|
403
|
+
): ClassifiedEffectOrORPCMiddlewareResult<
|
|
404
|
+
TOutContext,
|
|
405
|
+
TOutput,
|
|
406
|
+
TRequirementsProvided
|
|
407
|
+
> {
|
|
408
|
+
if (isHybridContinuation(result)) {
|
|
409
|
+
return {
|
|
410
|
+
_tag: "nativeContinuation",
|
|
411
|
+
result: result as MiddlewareResult<TOutContext, TOutput>,
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (Effect.isEffect(result) || isGeneratorIterator(result)) {
|
|
416
|
+
return {
|
|
417
|
+
_tag: "effect",
|
|
418
|
+
effect: effectFromCallbackResult(result),
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (result === undefined) {
|
|
423
|
+
return { _tag: "nativeGuardOnly" };
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
return {
|
|
427
|
+
_tag: "nativeResult",
|
|
428
|
+
result: result as MiddlewareResult<TOutContext, TOutput>,
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function runEffectMiddlewareResult<
|
|
433
|
+
TContext extends Context,
|
|
434
|
+
TOutput,
|
|
435
|
+
TRequirementsProvided,
|
|
436
|
+
>(options: {
|
|
437
|
+
effect: Effect.Effect<
|
|
438
|
+
EffectMiddlewareResult<TContext, TOutput> | void,
|
|
439
|
+
unknown,
|
|
440
|
+
TRequirementsProvided
|
|
441
|
+
>;
|
|
442
|
+
nextInvoked: boolean;
|
|
443
|
+
nextResult: EffectMiddlewareResult<TContext, TOutput> | undefined;
|
|
444
|
+
autoNext: () => Effect.Effect<
|
|
445
|
+
EffectMiddlewareResult<TContext, TOutput>,
|
|
446
|
+
unknown,
|
|
447
|
+
TRequirementsProvided
|
|
448
|
+
>;
|
|
449
|
+
}): Effect.Effect<
|
|
450
|
+
EffectMiddlewareResult<TContext, TOutput>,
|
|
451
|
+
unknown,
|
|
452
|
+
TRequirementsProvided
|
|
453
|
+
> {
|
|
454
|
+
return Effect.flatMap(options.effect, (result) =>
|
|
455
|
+
resolveEffectMiddlewareContinuation({
|
|
456
|
+
autoNext: options.autoNext,
|
|
457
|
+
nextInvoked: options.nextInvoked,
|
|
458
|
+
nextResult: options.nextResult,
|
|
459
|
+
result,
|
|
460
|
+
}),
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function makeEffectMiddlewareOptions<
|
|
465
|
+
TCurrentContext extends Context,
|
|
466
|
+
TOutput,
|
|
467
|
+
TEffectErrorMap extends EffectErrorMap,
|
|
468
|
+
TRequirementsProvided,
|
|
469
|
+
TMeta extends Meta,
|
|
470
|
+
>(options: {
|
|
471
|
+
effectErrorMap: TEffectErrorMap;
|
|
472
|
+
final: (
|
|
473
|
+
...rest: [MiddlewareNextFnOptions<Context>?]
|
|
474
|
+
) => Promisable<MiddlewareResult<Context, TOutput>>;
|
|
475
|
+
options: Omit<
|
|
476
|
+
MiddlewareOptions<
|
|
477
|
+
TCurrentContext,
|
|
478
|
+
TOutput,
|
|
479
|
+
EffectErrorConstructorMap<TEffectErrorMap>,
|
|
480
|
+
TMeta
|
|
481
|
+
>,
|
|
482
|
+
"next"
|
|
483
|
+
>;
|
|
484
|
+
output: MiddlewareOutputFn<TOutput>;
|
|
485
|
+
}): EffectMiddlewareRuntimeOptions<
|
|
486
|
+
TCurrentContext,
|
|
487
|
+
TOutput,
|
|
488
|
+
TEffectErrorMap,
|
|
489
|
+
TRequirementsProvided,
|
|
490
|
+
TMeta
|
|
491
|
+
> {
|
|
492
|
+
const nextTracker =
|
|
493
|
+
createMiddlewareNextTracker<EffectMiddlewareResult<Context, TOutput>>();
|
|
494
|
+
|
|
495
|
+
const effectOptions = {
|
|
496
|
+
context: options.options.context,
|
|
497
|
+
path: options.options.path,
|
|
498
|
+
procedure: options.options.procedure,
|
|
499
|
+
signal: options.options.signal,
|
|
500
|
+
lastEventId: options.options.lastEventId,
|
|
501
|
+
errors: createEffectErrorConstructorMap(options.effectErrorMap),
|
|
502
|
+
next: nextTracker.wrapNext((...rest: [MiddlewareNextFnOptions<Context>?]) =>
|
|
503
|
+
makeHybridMiddlewareContinuation({
|
|
504
|
+
final: options.final,
|
|
505
|
+
nextTracker,
|
|
506
|
+
rest,
|
|
507
|
+
}),
|
|
508
|
+
),
|
|
509
|
+
nextTracker,
|
|
510
|
+
output: makeEffectMiddlewareOutput<
|
|
511
|
+
TOutput,
|
|
512
|
+
TEffectErrorMap,
|
|
513
|
+
TRequirementsProvided
|
|
514
|
+
>(options.output),
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
return effectOptions;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function makeHybridMiddlewareContinuation<TOutput>(options: {
|
|
521
|
+
final: (
|
|
522
|
+
...rest: [MiddlewareNextFnOptions<Context>?]
|
|
523
|
+
) => Promisable<MiddlewareResult<Context, TOutput>>;
|
|
524
|
+
nextTracker: MiddlewareNextTracker<EffectMiddlewareResult<Context, TOutput>>;
|
|
525
|
+
rest: [MiddlewareNextFnOptions<Context>?];
|
|
526
|
+
}): Effect.Effect<EffectMiddlewareResult<Context, TOutput>> {
|
|
527
|
+
const nextContext = options.rest[0]?.context ?? {};
|
|
528
|
+
const toEffectResult = (
|
|
529
|
+
result: any,
|
|
530
|
+
): EffectMiddlewareResult<Context, TOutput> => ({
|
|
531
|
+
output: result.output,
|
|
532
|
+
context: nextContext,
|
|
533
|
+
});
|
|
534
|
+
const effect = Effect.map(
|
|
535
|
+
withCurrentFiberContext(() => options.final(...options.rest) as any),
|
|
536
|
+
toEffectResult,
|
|
537
|
+
) as Effect.Effect<EffectMiddlewareResult<Context, TOutput>>;
|
|
538
|
+
|
|
539
|
+
return makeHybridContinuation(effect, {
|
|
540
|
+
onResult: options.nextTracker.setNextResult,
|
|
541
|
+
run: () => options.final(...options.rest) as any,
|
|
542
|
+
toEffectResult,
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
function makeHybridContinuation<TORPCResult, TEffectResult>(
|
|
547
|
+
effect: Effect.Effect<TEffectResult>,
|
|
548
|
+
options: {
|
|
549
|
+
run: () => Promisable<TORPCResult>;
|
|
550
|
+
toEffectResult: (result: TORPCResult) => TEffectResult;
|
|
551
|
+
onResult?: (result: TEffectResult) => void;
|
|
552
|
+
},
|
|
553
|
+
): Effect.Effect<TEffectResult> {
|
|
554
|
+
markHybridContinuation(effect);
|
|
555
|
+
attachPromiseLikeContinuation(effect, (onFulfilled, onRejected) =>
|
|
556
|
+
Promise.resolve(options.run() as any)
|
|
557
|
+
.then(options.toEffectResult)
|
|
558
|
+
.then((result) => {
|
|
559
|
+
options.onResult?.(result);
|
|
560
|
+
return result;
|
|
561
|
+
})
|
|
562
|
+
.then(onFulfilled, onRejected),
|
|
563
|
+
);
|
|
564
|
+
return effect;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function markHybridContinuation(value: object): void {
|
|
568
|
+
Object.defineProperty(value, HybridContinuationSymbol, {
|
|
569
|
+
configurable: true,
|
|
570
|
+
value: true,
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function attachPromiseLikeContinuation(
|
|
575
|
+
value: object,
|
|
576
|
+
then: (onFulfilled: any, onRejected: any) => PromiseLike<unknown>,
|
|
577
|
+
): void {
|
|
578
|
+
// oxlint-disable-next-line unicorn/no-thenable -- the hybrid continuation intentionally satisfies Effect and oRPC promise protocols.
|
|
579
|
+
Object.defineProperty(value, "then", {
|
|
580
|
+
configurable: true,
|
|
581
|
+
value: then,
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
|
|
219
585
|
export function createEffectProviderMiddleware<
|
|
220
586
|
TCurrentContext extends Context,
|
|
221
587
|
TInput,
|
|
@@ -246,12 +612,14 @@ export function createEffectProviderMiddleware<
|
|
|
246
612
|
TEffectErrorMap,
|
|
247
613
|
TMeta
|
|
248
614
|
>(opts, input, effectErrorMap);
|
|
249
|
-
const effect = Effect.flatMap(
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
615
|
+
const effect = Effect.flatMap(
|
|
616
|
+
callEffectCallback(provider, effectOpts),
|
|
617
|
+
(service) =>
|
|
618
|
+
Effect.provideService(
|
|
619
|
+
withCurrentFiberContext(() => opts.next()),
|
|
620
|
+
tag,
|
|
621
|
+
service,
|
|
622
|
+
),
|
|
255
623
|
);
|
|
256
624
|
const exit = await runner.runPromiseExit(effect, {
|
|
257
625
|
signal: opts.signal,
|
|
@@ -293,16 +661,18 @@ export function createEffectOptionalProviderMiddleware<
|
|
|
293
661
|
TEffectErrorMap,
|
|
294
662
|
TMeta
|
|
295
663
|
>(opts, input, effectErrorMap);
|
|
296
|
-
const effect = Effect.flatMap(
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
664
|
+
const effect = Effect.flatMap(
|
|
665
|
+
callEffectCallback(provider, effectOpts),
|
|
666
|
+
(service) =>
|
|
667
|
+
Option.match(service, {
|
|
668
|
+
onNone: () => withCurrentFiberContext(() => opts.next()),
|
|
669
|
+
onSome: (value) =>
|
|
670
|
+
Effect.provideService(
|
|
671
|
+
withCurrentFiberContext(() => opts.next()),
|
|
672
|
+
tag,
|
|
673
|
+
value,
|
|
674
|
+
),
|
|
675
|
+
}),
|
|
306
676
|
);
|
|
307
677
|
const exit = await runner.runPromiseExit(effect, {
|
|
308
678
|
signal: opts.signal,
|
|
@@ -314,6 +684,80 @@ export function createEffectOptionalProviderMiddleware<
|
|
|
314
684
|
};
|
|
315
685
|
}
|
|
316
686
|
|
|
687
|
+
function callEffectCallback(
|
|
688
|
+
callback: (...args: Array<any>) => unknown,
|
|
689
|
+
...args: Array<unknown>
|
|
690
|
+
): Effect.Effect<any, any, any> {
|
|
691
|
+
if (isGeneratorFunction(callback)) {
|
|
692
|
+
return Effect.fnUntraced(callback as any)(...args);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
return Effect.suspend(() => {
|
|
696
|
+
try {
|
|
697
|
+
return effectFromCallbackResult(callback(...args));
|
|
698
|
+
} catch (error) {
|
|
699
|
+
return Effect.fail(error);
|
|
700
|
+
}
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
function effectFromCallbackResult(
|
|
705
|
+
result: unknown,
|
|
706
|
+
): Effect.Effect<any, any, any> {
|
|
707
|
+
if (Effect.isEffect(result)) {
|
|
708
|
+
return result;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
if (isGeneratorIterator(result)) {
|
|
712
|
+
return Effect.fnUntraced(function* () {
|
|
713
|
+
return yield* result as any;
|
|
714
|
+
})();
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
if (isPromiseLike(result)) {
|
|
718
|
+
return Effect.promise(() => result);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
return Effect.succeed(result);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
function isGeneratorIterator(value: unknown): value is Generator<unknown> {
|
|
725
|
+
return (
|
|
726
|
+
typeof value === "object" &&
|
|
727
|
+
value !== null &&
|
|
728
|
+
"next" in value &&
|
|
729
|
+
typeof value.next === "function" &&
|
|
730
|
+
"throw" in value &&
|
|
731
|
+
typeof value.throw === "function"
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
function isHybridContinuation(value: unknown): boolean {
|
|
736
|
+
return (
|
|
737
|
+
(typeof value === "object" || typeof value === "function") &&
|
|
738
|
+
value !== null &&
|
|
739
|
+
HybridContinuationSymbol in value
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
|
|
744
|
+
return (
|
|
745
|
+
(typeof value === "object" || typeof value === "function") &&
|
|
746
|
+
value !== null &&
|
|
747
|
+
"then" in value &&
|
|
748
|
+
typeof value.then === "function"
|
|
749
|
+
);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
function isGeneratorFunction(
|
|
753
|
+
value: unknown,
|
|
754
|
+
): value is (...args: Array<any>) => Generator<unknown> {
|
|
755
|
+
return (
|
|
756
|
+
typeof value === "function" &&
|
|
757
|
+
value.constructor?.name === "GeneratorFunction"
|
|
758
|
+
);
|
|
759
|
+
}
|
|
760
|
+
|
|
317
761
|
// todo(utopy): make this check more comprehensive, maybe add a Symbol to the EffectMiddleware type
|
|
318
762
|
export function isEffectMiddleware(
|
|
319
763
|
value: unknown,
|
|
@@ -326,9 +770,12 @@ export function isEffectMiddleware(
|
|
|
326
770
|
unknown,
|
|
327
771
|
Meta
|
|
328
772
|
> {
|
|
773
|
+
return isGeneratorFunction(value);
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
export function isDecoratedMiddleware(value: unknown): boolean {
|
|
329
777
|
return (
|
|
330
|
-
typeof value === "function" &&
|
|
331
|
-
value.constructor?.name === "GeneratorFunction"
|
|
778
|
+
typeof value === "function" && "mapInput" in value && "concat" in value
|
|
332
779
|
);
|
|
333
780
|
}
|
|
334
781
|
|
|
@@ -383,6 +830,7 @@ function runEffectPipeline<
|
|
|
383
830
|
TRequirementsProvided
|
|
384
831
|
>;
|
|
385
832
|
input: TInput;
|
|
833
|
+
runner: EffectRuntimeRunner<TRequirementsProvided, unknown>;
|
|
386
834
|
steps: readonly EffectPipelineStep[];
|
|
387
835
|
}): Effect.Effect<
|
|
388
836
|
EffectMiddlewareResult<Context, TOutput>,
|
|
@@ -403,18 +851,22 @@ function runEffectPipeline<
|
|
|
403
851
|
const stepOptions = { ...options.baseOptions, context };
|
|
404
852
|
|
|
405
853
|
if (step._tag === "provide") {
|
|
406
|
-
return Effect.flatMap(
|
|
407
|
-
|
|
854
|
+
return Effect.flatMap(
|
|
855
|
+
callEffectCallback(step.provider, stepOptions),
|
|
856
|
+
(service) =>
|
|
857
|
+
Effect.provideService(run(index + 1, context), step.tag, service),
|
|
408
858
|
);
|
|
409
859
|
}
|
|
410
860
|
|
|
411
861
|
if (step._tag === "provideOptional") {
|
|
412
|
-
return Effect.flatMap(
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
862
|
+
return Effect.flatMap(
|
|
863
|
+
callEffectCallback(step.provider, stepOptions),
|
|
864
|
+
(service) =>
|
|
865
|
+
Option.match(service, {
|
|
866
|
+
onNone: () => run(index + 1, context),
|
|
867
|
+
onSome: (value) =>
|
|
868
|
+
Effect.provideService(run(index + 1, context), step.tag, value),
|
|
869
|
+
}),
|
|
418
870
|
);
|
|
419
871
|
}
|
|
420
872
|
|
|
@@ -422,59 +874,75 @@ function runEffectPipeline<
|
|
|
422
874
|
return Effect.provide(run(index + 1, context), step.layer);
|
|
423
875
|
}
|
|
424
876
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
877
|
+
return Effect.flatMap(Effect.getFiberRefs, (fiberRefs) => {
|
|
878
|
+
const makeThenable = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
|
|
879
|
+
makeThenableEffect(
|
|
880
|
+
effect,
|
|
881
|
+
options.runner as EffectRuntimeRunner<any, unknown>,
|
|
882
|
+
fiberRefs,
|
|
883
|
+
stepOptions.signal,
|
|
884
|
+
) as Effect.Effect<A, E, R>;
|
|
885
|
+
const nextTracker =
|
|
886
|
+
createMiddlewareNextTracker<EffectMiddlewareResult<Context, TOutput>>(
|
|
887
|
+
makeThenable,
|
|
888
|
+
);
|
|
889
|
+
const effectOptions: EffectMiddlewareOptions<
|
|
890
|
+
TCurrentContext,
|
|
891
|
+
TOutput,
|
|
892
|
+
TEffectErrorMap,
|
|
893
|
+
TRequirementsProvided,
|
|
894
|
+
TMeta
|
|
895
|
+
> = {
|
|
896
|
+
context,
|
|
897
|
+
path: stepOptions.path,
|
|
898
|
+
procedure: stepOptions.procedure,
|
|
899
|
+
signal: stepOptions.signal,
|
|
900
|
+
lastEventId: stepOptions.lastEventId,
|
|
901
|
+
errors: createEffectErrorConstructorMap(options.effectErrorMap),
|
|
902
|
+
next: nextTracker.wrapNext(
|
|
903
|
+
(...rest: [MiddlewareNextFnOptions<Context>?]) => {
|
|
904
|
+
const nextContext = rest[0]?.context ?? {};
|
|
905
|
+
return Effect.map(
|
|
906
|
+
run(index + 1, { ...context, ...nextContext }),
|
|
907
|
+
(result) => ({
|
|
908
|
+
output: result.output,
|
|
909
|
+
context: nextContext,
|
|
910
|
+
}),
|
|
911
|
+
) as Effect.Effect<EffectMiddlewareResult<Context, TOutput>>;
|
|
912
|
+
},
|
|
913
|
+
),
|
|
914
|
+
};
|
|
915
|
+
const effectOutput = makeEffectMiddlewareOutput<
|
|
916
|
+
TOutput,
|
|
917
|
+
TEffectErrorMap,
|
|
918
|
+
TRequirementsProvided
|
|
919
|
+
>((output) => ({ output, context: {} }), makeThenable);
|
|
920
|
+
const middlewareEffect = callEffectCallback(
|
|
921
|
+
step.middleware,
|
|
922
|
+
effectOptions,
|
|
923
|
+
options.input,
|
|
924
|
+
effectOutput,
|
|
925
|
+
) as Effect.Effect<EffectMiddlewareResult<Context, TOutput> | void>;
|
|
926
|
+
|
|
927
|
+
return Effect.flatMap(middlewareEffect, (result) =>
|
|
928
|
+
resolveEffectMiddlewareContinuation({
|
|
929
|
+
autoNext: () => effectOptions.next(),
|
|
930
|
+
nextInvoked: nextTracker.nextInvoked,
|
|
931
|
+
nextResult: nextTracker.nextResult,
|
|
932
|
+
result,
|
|
933
|
+
}),
|
|
934
|
+
);
|
|
935
|
+
});
|
|
472
936
|
};
|
|
473
937
|
|
|
474
938
|
return run(0, options.baseOptions.context);
|
|
475
939
|
}
|
|
476
940
|
|
|
477
|
-
function createMiddlewareNextTracker<T>(
|
|
941
|
+
function createMiddlewareNextTracker<T>(
|
|
942
|
+
makeThenable?: <A, E, R>(
|
|
943
|
+
effect: Effect.Effect<A, E, R>,
|
|
944
|
+
) => Effect.Effect<A, E, R>,
|
|
945
|
+
) {
|
|
478
946
|
let nextInvoked = false;
|
|
479
947
|
let nextResult: T | undefined;
|
|
480
948
|
|
|
@@ -485,15 +953,28 @@ function createMiddlewareNextTracker<T>() {
|
|
|
485
953
|
get nextResult() {
|
|
486
954
|
return nextResult;
|
|
487
955
|
},
|
|
956
|
+
setNextResult(result: T) {
|
|
957
|
+
nextResult = result;
|
|
958
|
+
},
|
|
488
959
|
wrapNext<Fn extends (...args: any) => Effect.Effect<T, any, any>>(
|
|
489
960
|
nextFn: Fn,
|
|
490
961
|
): Fn {
|
|
491
962
|
return ((...args: Parameters<Fn>) => {
|
|
492
963
|
nextInvoked = true;
|
|
493
|
-
|
|
964
|
+
const inner = nextFn(...args);
|
|
965
|
+
const tracked = Effect.map(inner, (result) => {
|
|
494
966
|
nextResult = result;
|
|
495
967
|
return result;
|
|
496
968
|
});
|
|
969
|
+
if (isHybridContinuation(inner)) {
|
|
970
|
+
markHybridContinuation(tracked);
|
|
971
|
+
attachPromiseLikeContinuation(
|
|
972
|
+
tracked,
|
|
973
|
+
(inner as any).then.bind(inner),
|
|
974
|
+
);
|
|
975
|
+
return tracked;
|
|
976
|
+
}
|
|
977
|
+
return makeThenable ? makeThenable(tracked) : tracked;
|
|
497
978
|
}) as Fn;
|
|
498
979
|
},
|
|
499
980
|
};
|
|
@@ -543,8 +1024,69 @@ function makeEffectMiddlewareOutput<
|
|
|
543
1024
|
TRequirementsProvided,
|
|
544
1025
|
>(
|
|
545
1026
|
output: MiddlewareOutputFn<TOutput>,
|
|
1027
|
+
makeThenable?: <A, E, R>(
|
|
1028
|
+
effect: Effect.Effect<A, E, R>,
|
|
1029
|
+
) => Effect.Effect<A, E, R>,
|
|
546
1030
|
): EffectMiddlewareOutput<TOutput, TEffectErrorMap, TRequirementsProvided> {
|
|
547
|
-
return (value: TOutput) =>
|
|
1031
|
+
return (value: TOutput) => {
|
|
1032
|
+
const effect = withCurrentFiberContext(() => output(value));
|
|
1033
|
+
return makeThenable ? makeThenable(effect) : effect;
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
function makeThenableEffect<A, E, R>(
|
|
1038
|
+
effect: Effect.Effect<A, E, R>,
|
|
1039
|
+
runner: EffectRuntimeRunner<any, unknown>,
|
|
1040
|
+
fiberRefs: FiberRefs.FiberRefs,
|
|
1041
|
+
signal: AbortSignal | undefined,
|
|
1042
|
+
): Effect.Effect<A, E, R> {
|
|
1043
|
+
if ("then" in effect) {
|
|
1044
|
+
return effect;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
attachPromiseLikeContinuation(
|
|
1048
|
+
effect,
|
|
1049
|
+
(
|
|
1050
|
+
onFulfilled: ((value: A) => unknown) | undefined,
|
|
1051
|
+
onRejected: ((error: unknown) => unknown) | undefined,
|
|
1052
|
+
) =>
|
|
1053
|
+
runNestedEffect(effect, runner, fiberRefs, signal).then(
|
|
1054
|
+
onFulfilled,
|
|
1055
|
+
onRejected,
|
|
1056
|
+
),
|
|
1057
|
+
);
|
|
1058
|
+
|
|
1059
|
+
return effect;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
async function runNestedEffect<A, E, R>(
|
|
1063
|
+
effect: Effect.Effect<A, E, R>,
|
|
1064
|
+
runner: EffectRuntimeRunner<any, unknown>,
|
|
1065
|
+
fiberRefs: FiberRefs.FiberRefs,
|
|
1066
|
+
signal: AbortSignal | undefined,
|
|
1067
|
+
): Promise<A> {
|
|
1068
|
+
const exit = await runner.runPromiseExit(withFiberRefs(effect, fiberRefs), {
|
|
1069
|
+
signal,
|
|
1070
|
+
});
|
|
1071
|
+
|
|
1072
|
+
if (Exit.isFailure(exit)) {
|
|
1073
|
+
throw toORPCErrorFromCause(exit.cause);
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
return exit.value;
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
function withFiberRefs<A, E, R>(
|
|
1080
|
+
effect: Effect.Effect<A, E, R>,
|
|
1081
|
+
parentFiberRefs: FiberRefs.FiberRefs,
|
|
1082
|
+
): Effect.Effect<A, E, R> {
|
|
1083
|
+
return Effect.fiberIdWith((fiberId) =>
|
|
1084
|
+
Effect.flatMap(Effect.getFiberRefs, (fiberRefs) =>
|
|
1085
|
+
Effect.setFiberRefs(
|
|
1086
|
+
FiberRefs.joinAs(fiberRefs, fiberId, parentFiberRefs),
|
|
1087
|
+
).pipe(Effect.andThen(effect)),
|
|
1088
|
+
),
|
|
1089
|
+
);
|
|
548
1090
|
}
|
|
549
1091
|
|
|
550
1092
|
function withCurrentFiberContext<T>(fn: () => Promisable<T>): Effect.Effect<T> {
|