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

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/dist/index.js CHANGED
@@ -175,6 +175,7 @@ function effectErrorMapToErrorMap(errorMap) {
175
175
  }
176
176
 
177
177
  // src/effect-runtime.ts
178
+ var HybridContinuationSymbol = /* @__PURE__ */ Symbol("effect-orpc/HybridContinuation");
178
179
  function toORPCErrorFromCause(cause) {
179
180
  if (Cause2.hasFails(cause)) {
180
181
  const reason = Cause2.findFail(cause);
@@ -237,17 +238,17 @@ function createEffectProcedureHandler(options) {
237
238
  };
238
239
  const spanName = spanConfig?.name ?? opts.path.join(".");
239
240
  const captureStackTrace = spanConfig?.captureStackTrace ?? defaultCaptureStackTrace;
240
- const resolver = Effect.fnUntraced(effectFn);
241
- const handlerEffect = resolver(effectOpts);
241
+ const handlerEffect = callEffectCallback(effectFn, effectOpts);
242
242
  const tracedEffect = Effect.withSpan(
243
243
  runEffectPipeline({
244
244
  baseOptions: effectOpts,
245
245
  effectErrorMap,
246
246
  final: (context) => Effect.map(
247
- context === effectOpts.context ? handlerEffect : resolver({ ...effectOpts, context }),
247
+ context === effectOpts.context ? handlerEffect : callEffectCallback(effectFn, { ...effectOpts, context }),
248
248
  (output) => ({ output, context: {} })
249
249
  ),
250
250
  input: opts.input,
251
+ runner,
251
252
  steps: effectSteps
252
253
  }),
253
254
  spanName,
@@ -275,6 +276,7 @@ function createEffectPipelineMiddleware(options) {
275
276
  )
276
277
  ),
277
278
  input,
279
+ runner,
278
280
  steps
279
281
  });
280
282
  const exit = await runner.runPromiseExit(effect, {
@@ -284,12 +286,152 @@ function createEffectPipelineMiddleware(options) {
284
286
  return exit.value;
285
287
  };
286
288
  }
289
+ function createEffectOrORPCMiddleware(options) {
290
+ const { runner, effectErrorMap, middleware } = options;
291
+ return async (opts, input, output) => {
292
+ const effectOptions = makeEffectMiddlewareOptions({
293
+ effectErrorMap,
294
+ final: opts.next,
295
+ options: opts,
296
+ output
297
+ });
298
+ return runEffectOrORPCMiddlewareCallback({
299
+ effectOptions,
300
+ input,
301
+ middleware,
302
+ nativeNext: opts.next,
303
+ runner,
304
+ signal: opts.signal
305
+ });
306
+ };
307
+ }
308
+ async function runEffectOrORPCMiddlewareCallback(options) {
309
+ const result = options.middleware(
310
+ options.effectOptions,
311
+ options.input,
312
+ options.effectOptions.output
313
+ );
314
+ const classified = classifyEffectOrORPCMiddlewareResult(result);
315
+ switch (classified._tag) {
316
+ case "nativeContinuation":
317
+ return classified.result;
318
+ case "nativeGuardOnly":
319
+ return options.nativeNext();
320
+ case "nativeResult":
321
+ return classified.result;
322
+ case "effect": {
323
+ const exit = await options.runner.runPromiseExit(
324
+ runEffectMiddlewareResult({
325
+ autoNext: () => options.effectOptions.next(),
326
+ effect: classified.effect,
327
+ nextInvoked: options.effectOptions.nextTracker.nextInvoked,
328
+ nextResult: options.effectOptions.nextTracker.nextResult
329
+ }),
330
+ { signal: options.signal }
331
+ );
332
+ if (Exit.isFailure(exit)) throw toORPCErrorFromCause(exit.cause);
333
+ return exit.value;
334
+ }
335
+ }
336
+ }
337
+ function classifyEffectOrORPCMiddlewareResult(result) {
338
+ if (isHybridContinuation(result)) {
339
+ return {
340
+ _tag: "nativeContinuation",
341
+ result
342
+ };
343
+ }
344
+ if (Effect.isEffect(result) || isGeneratorIterator(result)) {
345
+ return {
346
+ _tag: "effect",
347
+ effect: effectFromCallbackResult(result)
348
+ };
349
+ }
350
+ if (result === void 0) {
351
+ return { _tag: "nativeGuardOnly" };
352
+ }
353
+ return {
354
+ _tag: "nativeResult",
355
+ result
356
+ };
357
+ }
358
+ function runEffectMiddlewareResult(options) {
359
+ return Effect.flatMap(
360
+ options.effect,
361
+ (result) => resolveEffectMiddlewareContinuation({
362
+ autoNext: options.autoNext,
363
+ nextInvoked: options.nextInvoked,
364
+ nextResult: options.nextResult,
365
+ result
366
+ })
367
+ );
368
+ }
369
+ function makeEffectMiddlewareOptions(options) {
370
+ const nextTracker = createMiddlewareNextTracker();
371
+ const effectOptions = {
372
+ context: options.options.context,
373
+ path: options.options.path,
374
+ procedure: options.options.procedure,
375
+ signal: options.options.signal,
376
+ lastEventId: options.options.lastEventId,
377
+ errors: createEffectErrorConstructorMap(options.effectErrorMap),
378
+ next: nextTracker.wrapNext(
379
+ (...rest) => makeHybridMiddlewareContinuation({
380
+ final: options.final,
381
+ nextTracker,
382
+ rest
383
+ })
384
+ ),
385
+ nextTracker,
386
+ output: makeEffectMiddlewareOutput(options.output)
387
+ };
388
+ return effectOptions;
389
+ }
390
+ function makeHybridMiddlewareContinuation(options) {
391
+ const nextContext = options.rest[0]?.context ?? {};
392
+ const toEffectResult = (result) => ({
393
+ output: result.output,
394
+ context: nextContext
395
+ });
396
+ const effect = Effect.map(
397
+ withCurrentServiceContext(() => options.final(...options.rest)),
398
+ toEffectResult
399
+ );
400
+ return makeHybridContinuation(effect, {
401
+ onResult: options.nextTracker.setNextResult,
402
+ run: () => options.final(...options.rest),
403
+ toEffectResult
404
+ });
405
+ }
406
+ function makeHybridContinuation(effect, options) {
407
+ markHybridContinuation(effect);
408
+ attachPromiseLikeContinuation(
409
+ effect,
410
+ (onFulfilled, onRejected) => Promise.resolve(options.run()).then(options.toEffectResult).then((result) => {
411
+ options.onResult?.(result);
412
+ return result;
413
+ }).then(onFulfilled, onRejected)
414
+ );
415
+ return effect;
416
+ }
417
+ function markHybridContinuation(value) {
418
+ Object.defineProperty(value, HybridContinuationSymbol, {
419
+ configurable: true,
420
+ value: true
421
+ });
422
+ }
423
+ function attachPromiseLikeContinuation(value, then) {
424
+ Object.defineProperty(value, "then", {
425
+ configurable: true,
426
+ value: then
427
+ });
428
+ }
287
429
  function createEffectProviderMiddleware(options) {
288
430
  const { runner, effectErrorMap, tag, provider } = options;
289
431
  return async (opts, input) => {
290
432
  const effectOpts = makeEffectOptions(opts, input, effectErrorMap);
291
433
  const effect = Effect.flatMap(
292
- provider(effectOpts),
434
+ callEffectCallback(provider, effectOpts),
293
435
  (service) => Effect.provideService(
294
436
  withCurrentServiceContext(() => opts.next()),
295
437
  tag,
@@ -308,7 +450,7 @@ function createEffectOptionalProviderMiddleware(options) {
308
450
  return async (opts, input) => {
309
451
  const effectOpts = makeEffectOptions(opts, input, effectErrorMap);
310
452
  const effect = Effect.flatMap(
311
- provider(effectOpts),
453
+ callEffectCallback(provider, effectOpts),
312
454
  (service) => Option.match(service, {
313
455
  onNone: () => withCurrentServiceContext(() => opts.next()),
314
456
  onSome: (value) => Effect.provideService(
@@ -325,9 +467,50 @@ function createEffectOptionalProviderMiddleware(options) {
325
467
  return exit.value;
326
468
  };
327
469
  }
328
- function isEffectMiddleware(value) {
470
+ function callEffectCallback(callback, ...args) {
471
+ if (isGeneratorFunction(callback)) {
472
+ return Effect.fnUntraced(callback)(...args);
473
+ }
474
+ return Effect.suspend(() => {
475
+ try {
476
+ return effectFromCallbackResult(callback(...args));
477
+ } catch (error) {
478
+ return Effect.fail(error);
479
+ }
480
+ });
481
+ }
482
+ function effectFromCallbackResult(result) {
483
+ if (Effect.isEffect(result)) {
484
+ return result;
485
+ }
486
+ if (isGeneratorIterator(result)) {
487
+ return Effect.fnUntraced(function* () {
488
+ return yield* result;
489
+ })();
490
+ }
491
+ if (isPromiseLike(result)) {
492
+ return Effect.promise(() => result);
493
+ }
494
+ return Effect.succeed(result);
495
+ }
496
+ function isGeneratorIterator(value) {
497
+ return typeof value === "object" && value !== null && "next" in value && typeof value.next === "function" && "throw" in value && typeof value.throw === "function";
498
+ }
499
+ function isHybridContinuation(value) {
500
+ return (typeof value === "object" || typeof value === "function") && value !== null && HybridContinuationSymbol in value;
501
+ }
502
+ function isPromiseLike(value) {
503
+ return (typeof value === "object" || typeof value === "function") && value !== null && "then" in value && typeof value.then === "function";
504
+ }
505
+ function isGeneratorFunction(value) {
329
506
  return typeof value === "function" && value.constructor?.name === "GeneratorFunction";
330
507
  }
508
+ function isEffectMiddleware(value) {
509
+ return isGeneratorFunction(value);
510
+ }
511
+ function isDecoratedMiddleware(value) {
512
+ return typeof value === "function" && "mapInput" in value && "concat" in value;
513
+ }
331
514
  function makeEffectOptions(opts, input, effectErrorMap) {
332
515
  return {
333
516
  context: opts.context,
@@ -346,13 +529,13 @@ function runEffectPipeline(options) {
346
529
  const stepOptions = { ...options.baseOptions, context };
347
530
  if (step._tag === "provide") {
348
531
  return Effect.flatMap(
349
- step.provider(stepOptions),
532
+ callEffectCallback(step.provider, stepOptions),
350
533
  (service) => Effect.provideService(run(index + 1, context), step.tag, service)
351
534
  );
352
535
  }
353
536
  if (step._tag === "provideOptional") {
354
537
  return Effect.flatMap(
355
- step.provider(stepOptions),
538
+ callEffectCallback(step.provider, stepOptions),
356
539
  (service) => Option.match(service, {
357
540
  onNone: () => run(index + 1, context),
358
541
  onSome: (value) => Effect.provideService(run(index + 1, context), step.tag, value)
@@ -362,46 +545,57 @@ function runEffectPipeline(options) {
362
545
  if (step._tag === "provideLayer") {
363
546
  return Effect.provide(run(index + 1, context), step.layer);
364
547
  }
365
- const nextTracker = createMiddlewareNextTracker();
366
- const effectOptions = {
367
- context,
368
- path: stepOptions.path,
369
- procedure: stepOptions.procedure,
370
- signal: stepOptions.signal,
371
- lastEventId: stepOptions.lastEventId,
372
- errors: createEffectErrorConstructorMap(options.effectErrorMap),
373
- next: nextTracker.wrapNext(
374
- (...rest) => {
375
- const nextContext = rest[0]?.context ?? {};
376
- return Effect.map(
377
- run(index + 1, { ...context, ...nextContext }),
378
- (result) => ({
379
- output: result.output,
380
- context: nextContext
381
- })
382
- );
383
- }
384
- )
385
- };
386
- const effectOutput = makeEffectMiddlewareOutput((output) => ({ output, context: {} }));
387
- const middlewareEffect = Effect.fnUntraced(step.middleware)(
388
- effectOptions,
389
- options.input,
390
- effectOutput
391
- );
392
- return Effect.flatMap(
393
- middlewareEffect,
394
- (result) => resolveEffectMiddlewareContinuation({
395
- autoNext: () => effectOptions.next(),
396
- nextInvoked: nextTracker.nextInvoked,
397
- nextResult: nextTracker.nextResult,
398
- result
399
- })
400
- );
548
+ return Effect.flatMap(Effect.context(), (services) => {
549
+ const makeThenable = (effect) => makeThenableEffect(
550
+ effect,
551
+ options.runner,
552
+ services,
553
+ stepOptions.signal
554
+ );
555
+ const nextTracker = createMiddlewareNextTracker(
556
+ makeThenable
557
+ );
558
+ const effectOptions = {
559
+ context,
560
+ path: stepOptions.path,
561
+ procedure: stepOptions.procedure,
562
+ signal: stepOptions.signal,
563
+ lastEventId: stepOptions.lastEventId,
564
+ errors: createEffectErrorConstructorMap(options.effectErrorMap),
565
+ next: nextTracker.wrapNext(
566
+ (...rest) => {
567
+ const nextContext = rest[0]?.context ?? {};
568
+ return Effect.map(
569
+ run(index + 1, { ...context, ...nextContext }),
570
+ (result) => ({
571
+ output: result.output,
572
+ context: nextContext
573
+ })
574
+ );
575
+ }
576
+ )
577
+ };
578
+ const effectOutput = makeEffectMiddlewareOutput((output) => ({ output, context: {} }), makeThenable);
579
+ const middlewareEffect = callEffectCallback(
580
+ step.middleware,
581
+ effectOptions,
582
+ options.input,
583
+ effectOutput
584
+ );
585
+ return Effect.flatMap(
586
+ middlewareEffect,
587
+ (result) => resolveEffectMiddlewareContinuation({
588
+ autoNext: () => effectOptions.next(),
589
+ nextInvoked: nextTracker.nextInvoked,
590
+ nextResult: nextTracker.nextResult,
591
+ result
592
+ })
593
+ );
594
+ });
401
595
  };
402
596
  return run(0, options.baseOptions.context);
403
597
  }
404
- function createMiddlewareNextTracker() {
598
+ function createMiddlewareNextTracker(makeThenable) {
405
599
  let nextInvoked = false;
406
600
  let nextResult;
407
601
  return {
@@ -411,13 +605,26 @@ function createMiddlewareNextTracker() {
411
605
  get nextResult() {
412
606
  return nextResult;
413
607
  },
608
+ setNextResult(result) {
609
+ nextResult = result;
610
+ },
414
611
  wrapNext(nextFn) {
415
612
  return ((...args) => {
416
613
  nextInvoked = true;
417
- return Effect.map(nextFn(...args), (result) => {
614
+ const inner = nextFn(...args);
615
+ const tracked = Effect.map(inner, (result) => {
418
616
  nextResult = result;
419
617
  return result;
420
618
  });
619
+ if (isHybridContinuation(inner)) {
620
+ markHybridContinuation(tracked);
621
+ attachPromiseLikeContinuation(
622
+ tracked,
623
+ inner.then.bind(inner)
624
+ );
625
+ return tracked;
626
+ }
627
+ return makeThenable ? makeThenable(tracked) : tracked;
421
628
  });
422
629
  }
423
630
  };
@@ -439,8 +646,34 @@ function resolveEffectMiddlewareContinuation(options) {
439
646
  }
440
647
  return autoNext();
441
648
  }
442
- function makeEffectMiddlewareOutput(output) {
443
- return (value) => withCurrentServiceContext(() => output(value));
649
+ function makeEffectMiddlewareOutput(output, makeThenable) {
650
+ return (value) => {
651
+ const effect = withCurrentServiceContext(() => output(value));
652
+ return makeThenable ? makeThenable(effect) : effect;
653
+ };
654
+ }
655
+ function makeThenableEffect(effect, runner, services, signal) {
656
+ if ("then" in effect) {
657
+ return effect;
658
+ }
659
+ attachPromiseLikeContinuation(
660
+ effect,
661
+ (onFulfilled, onRejected) => runNestedEffect(effect, runner, services, signal).then(
662
+ onFulfilled,
663
+ onRejected
664
+ )
665
+ );
666
+ return effect;
667
+ }
668
+ async function runNestedEffect(effect, runner, services, signal) {
669
+ const exit = await runWithServices(
670
+ services,
671
+ () => runner.runPromiseExit(effect, { signal })
672
+ );
673
+ if (Exit.isFailure(exit)) {
674
+ throw toORPCErrorFromCause(exit.cause);
675
+ }
676
+ return exit.value;
444
677
  }
445
678
  function withCurrentServiceContext(fn) {
446
679
  return Effect.flatMap(
@@ -825,15 +1058,33 @@ function createEffectProcedureProxy(target, decorated) {
825
1058
  return (middleware, mapInput) => {
826
1059
  const def = getEffectProcedureDef(context);
827
1060
  if (!mapInput && isEffectMiddleware(middleware)) {
828
- return new EffectDecoratedProcedure(
829
- appendEffectStep(def, {
830
- _tag: "middleware",
831
- middleware
832
- })
833
- );
1061
+ const step = {
1062
+ _tag: "middleware",
1063
+ middleware
1064
+ };
1065
+ if (def.effectHandler) {
1066
+ return new EffectDecoratedProcedure(
1067
+ appendEffectStep(def, step)
1068
+ );
1069
+ }
1070
+ return new EffectDecoratedProcedure({
1071
+ ...def,
1072
+ middlewares: addMiddleware(
1073
+ def.middlewares,
1074
+ createEffectPipelineMiddleware({
1075
+ effectErrorMap: state.effectErrorMap,
1076
+ runner: state.runner,
1077
+ steps: [step]
1078
+ })
1079
+ )
1080
+ });
834
1081
  }
835
1082
  const flushedDef = flushEffectSteps(def);
836
- const mapped = mapInput ? decorateMiddleware(middleware).mapInput(mapInput) : middleware;
1083
+ const mapped = mapInput ? decorateMiddleware(middleware).mapInput(mapInput) : isDecoratedMiddleware(middleware) ? middleware : createEffectOrORPCMiddleware({
1084
+ effectErrorMap: state.effectErrorMap,
1085
+ middleware,
1086
+ runner: state.runner
1087
+ });
837
1088
  return new EffectDecoratedProcedure({
838
1089
  ...flushedDef,
839
1090
  middlewares: addMiddleware(flushedDef.middlewares, mapped)
@@ -1137,25 +1388,22 @@ function createEffectBuilderProxy(target) {
1137
1388
  case "middleware":
1138
1389
  return getOrCreateVirtualMethod2(context, prop, () => {
1139
1390
  return (middleware) => {
1140
- if (isEffectMiddleware(middleware)) {
1141
- const effectMiddleware = createEffectPipelineMiddleware({
1142
- effectErrorMap: state.effectErrorMap,
1143
- runner: state.runner,
1144
- steps: [
1145
- ...state.effectSteps ?? [],
1146
- { _tag: "middleware", middleware }
1147
- ]
1148
- });
1149
- return Reflect.apply(
1150
- Reflect.get(source, "middleware", source),
1151
- source,
1152
- [effectMiddleware]
1153
- );
1154
- }
1391
+ const effectMiddleware = isEffectMiddleware(middleware) ? createEffectPipelineMiddleware({
1392
+ effectErrorMap: state.effectErrorMap,
1393
+ runner: state.runner,
1394
+ steps: [
1395
+ ...state.effectSteps ?? [],
1396
+ { _tag: "middleware", middleware }
1397
+ ]
1398
+ }) : createEffectOrORPCMiddleware({
1399
+ effectErrorMap: state.effectErrorMap,
1400
+ middleware,
1401
+ runner: state.runner
1402
+ });
1155
1403
  return Reflect.apply(
1156
1404
  Reflect.get(source, "middleware", source),
1157
1405
  source,
1158
- [middleware]
1406
+ [effectMiddleware]
1159
1407
  );
1160
1408
  };
1161
1409
  });
@@ -1187,7 +1435,7 @@ function createEffectBuilderProxy(target) {
1187
1435
  case "use":
1188
1436
  return getOrCreateVirtualMethod2(context, prop, () => {
1189
1437
  return (middleware, ...rest) => {
1190
- if (isEffectMiddleware(middleware) && rest.length === 0) {
1438
+ if (rest.length === 0 && isEffectMiddleware(middleware)) {
1191
1439
  return wrapBuilderLike(
1192
1440
  source,
1193
1441
  appendEffectStep2(state, {
@@ -1200,7 +1448,14 @@ function createEffectBuilderProxy(target) {
1200
1448
  const nextBuilder = Reflect.apply(
1201
1449
  Reflect.get(flushed.builder, "use", flushed.builder),
1202
1450
  flushed.builder,
1203
- [middleware, ...rest]
1451
+ [
1452
+ rest.length === 0 && !isDecoratedMiddleware(middleware) ? createEffectOrORPCMiddleware({
1453
+ effectErrorMap: flushed.state.effectErrorMap,
1454
+ middleware,
1455
+ runner: flushed.state.runner
1456
+ }) : middleware,
1457
+ ...rest
1458
+ ]
1204
1459
  );
1205
1460
  return wrapBuilderLike(nextBuilder, flushed.state);
1206
1461
  };
@@ -1217,10 +1472,16 @@ function createEffectBuilderProxy(target) {
1217
1472
  });
1218
1473
  case "handler":
1219
1474
  return getOrCreateVirtualMethod2(context, prop, () => {
1220
- return (handler) => new EffectDecoratedProcedure({
1221
- ...effectDef,
1222
- handler
1223
- });
1475
+ return (handler) => {
1476
+ const flushed = flushEffectSteps2(source, state);
1477
+ return new EffectDecoratedProcedure({
1478
+ ...flushed.builder["~orpc"],
1479
+ effectErrorMap: flushed.state.effectErrorMap,
1480
+ runner: flushed.state.runner,
1481
+ effectSteps: flushed.state.effectSteps,
1482
+ handler
1483
+ });
1484
+ };
1224
1485
  });
1225
1486
  case "router":
1226
1487
  return getOrCreateVirtualMethod2(context, prop, () => {