1import type * as Cause from "../Cause.ts"
2import type * as Context from "../Context.ts"
3import type * as Effect from "../Effect.ts"
4import * as Equal from "../Equal.ts"
5import type * as Exit from "../Exit.ts"
6import { format } from "../Formatter.ts"
7import { dual, identity } from "../Function.ts"
8import * as Hash from "../Hash.ts"
9import { NodeInspectSymbol } from "../Inspectable.ts"
10import { pipeArguments } from "../Pipeable.ts"
11import { hasProperty } from "../Predicate.ts"
12import type { StackFrame } from "../References.ts"
13import type * as Types from "../Types.ts"
14import { SingleShotGen } from "../Utils.ts"
15import type { FiberImpl } from "./effect.ts"
16import * as InternalRecord from "./record.ts"
17
18/** @internal */
19export const EffectTypeId = `~effect/Effect` as const
20
21/** @internal */
22export const ExitTypeId = `~effect/Exit` as const
23
24const effectVariance = {
25 _A: identity,
26 _E: identity,
27 _R: identity
28}
29
30/** @internal */
31export const identifier = `${EffectTypeId}/identifier` as const
32/** @internal */
33export type identifier = typeof identifier
34
35/** @internal */
36export const args = `${EffectTypeId}/args` as const
37/** @internal */
38export type args = typeof args
39
40/** @internal */
41export const evaluate = `${EffectTypeId}/evaluate` as const
42/** @internal */
43export type evaluate = typeof evaluate
44
45/** @internal */
46export const contA = `${EffectTypeId}/successCont` as const
47/** @internal */
48export type contA = typeof contA
49
50/** @internal */
51export const contE = `${EffectTypeId}/failureCont` as const
52/** @internal */
53export type contE = typeof contE
54
55/** @internal */
56export const contAll = `${EffectTypeId}/ensureCont` as const
57/** @internal */
58export type contAll = typeof contAll
59
60/** @internal */
61export const Yield = Symbol.for("effect/Effect/Yield")
62/** @internal */
63export type Yield = typeof Yield
64
65/** @internal */
66export const PipeInspectableProto = {
67 pipe() {
68 return pipeArguments(this, arguments)
69 },
70 toJSON(this: any) {
71 return { ...this }
72 },
73 toString() {
74 return format(this.toJSON(), { ignoreToString: true, space: 2 })
75 },
76 [NodeInspectSymbol]() {
77 return this.toJSON()
78 }
79}
80
81/** @internal */
82export const StructuralProto = {
83 [Hash.symbol](this: any): number {
84 return Hash.structureKeys(this, Object.keys(this))
85 },
86 [Equal.symbol](this: any, that: any): boolean {
87 const selfKeys = Object.keys(this)
88 const thatKeys = Object.keys(that)
89 if (selfKeys.length !== thatKeys.length) return false
90 for (let i = 0; i < selfKeys.length; i++) {
91 if (selfKeys[i] !== thatKeys[i] || !Equal.equals(this[selfKeys[i]], that[selfKeys[i]])) {
92 return false
93 }
94 }
95 return true
96 }
97}
98
99/** @internal */
100export const EffectProto = {
101 [EffectTypeId]: effectVariance,
102 ...PipeInspectableProto,
103 [Symbol.iterator]() {
104 return new SingleShotGen(this) as any
105 },
106 toJSON(this: Primitive) {
107 return {
108 _id: "Effect",
109 op: this[identifier],
110 ...(args in this ? { args: this[args] } : undefined)
111 }
112 }
113}
114
115/** @internal */
116export const isEffect = (u: unknown): u is Effect.Effect<unknown, unknown, unknown> => hasProperty(u, EffectTypeId)
117
118/** @internal */
119export const isExit = (u: unknown): u is Exit.Exit<unknown, unknown> => hasProperty(u, ExitTypeId)
120
121// ----------------------------------------------------------------------------
122// Cause
123// ----------------------------------------------------------------------------
124
125/** @internal */
126export const CauseTypeId = "~effect/Cause"
127
128/** @internal */
129export const CauseReasonTypeId = "~effect/Cause/Reason"
130
131/** @internal */
132export const isCause = (self: unknown): self is Cause.Cause<unknown> => hasProperty(self, CauseTypeId)
133
134/** @internal */
135export const isCauseReason = (self: unknown): self is Cause.Reason<unknown> => hasProperty(self, CauseReasonTypeId)
136
137/** @internal */
138export class CauseImpl<E> implements Cause.Cause<E> {
139 declare readonly [CauseTypeId]: typeof CauseTypeId
140 declare readonly reasons: ReadonlyArray<
141 Cause.Fail<E> | Cause.Die | Cause.Interrupt
142 >
143 constructor(
144 failures: ReadonlyArray<
145 Cause.Fail<E> | Cause.Die | Cause.Interrupt
146 >
147 ) {
148 this[CauseTypeId] = CauseTypeId
149 this.reasons = failures
150 }
151 pipe() {
152 return pipeArguments(this, arguments)
153 }
154 toJSON(): unknown {
155 return {
156 _id: "Cause",
157 failures: this.reasons.map((f) => f.toJSON())
158 }
159 }
160 toString() {
161 return `Cause(${format(this.reasons)})`
162 }
163 [NodeInspectSymbol]() {
164 return this.toJSON()
165 }
166 [Equal.symbol](that: any): boolean {
167 return (
168 isCause(that) &&
169 this.reasons.length === that.reasons.length &&
170 this.reasons.every((e, i) => Equal.equals(e, that.reasons[i]))
171 )
172 }
173 [Hash.symbol](): number {
174 return Hash.array(this.reasons)
175 }
176}
177
178const annotationsMap = new WeakMap<object, ReadonlyMap<string, unknown>>()
179
180/** @internal */
181export abstract class ReasonBase<Tag extends string> implements Cause.Cause.ReasonProto<Tag> {
182 readonly [CauseReasonTypeId]: typeof CauseReasonTypeId
183 readonly annotations: ReadonlyMap<string, unknown>
184 readonly _tag: Tag
185
186 constructor(
187 _tag: Tag,
188 annotations: ReadonlyMap<string, unknown>,
189 originalError: unknown
190 ) {
191 this[CauseReasonTypeId] = CauseReasonTypeId
192 this._tag = _tag
193 if (
194 annotations !== constEmptyAnnotations && typeof originalError === "object" && originalError !== null &&
195 annotations.size > 0
196 ) {
197 const prevAnnotations = annotationsMap.get(originalError)
198 if (prevAnnotations) {
199 annotations = new Map([
200 ...prevAnnotations,
201 ...annotations
202 ])
203 }
204 annotationsMap.set(originalError, annotations)
205 }
206 this.annotations = annotations
207 }
208
209 annotate(
210 annotations: Context.Context<never>,
211 options?: { readonly overwrite?: boolean | undefined }
212 ): this {
213 if (annotations.mapUnsafe.size === 0) return this
214 const newAnnotations = new Map(this.annotations)
215 annotations.mapUnsafe.forEach((value, key) => {
216 if (options?.overwrite !== true && newAnnotations.has(key)) return
217 newAnnotations.set(key, value)
218 })
219 const self = Object.assign(Object.create(Object.getPrototypeOf(this)), this)
220 self.annotations = newAnnotations
221 return self
222 }
223
224 pipe() {
225 return pipeArguments(this, arguments)
226 }
227
228 abstract toJSON(): unknown
229 abstract [Equal.symbol](that: any): boolean
230 abstract [Hash.symbol](): number
231
232 toString() {
233 return format(this)
234 }
235
236 [NodeInspectSymbol]() {
237 return this.toString()
238 }
239}
240
241/** @internal */
242export const constEmptyAnnotations: ReadonlyMap<string, unknown> = new Map<string, unknown>()
243
244/** @internal */
245export class Fail<E> extends ReasonBase<"Fail"> implements Cause.Fail<E> {
246 declare readonly error: E
247 constructor(
248 error: E,
249 annotations = constEmptyAnnotations
250 ) {
251 super("Fail", annotations, error)
252 this.error = error
253 }
254 override toString() {
255 return `Fail(${format(this.error)})`
256 }
257 toJSON(): unknown {
258 return {
259 _tag: "Fail",
260 error: this.error
261 }
262 }
263 [Equal.symbol](that: any): boolean {
264 return (
265 isFailReason(that) &&
266 Equal.equals(this.error, that.error) &&
267 Equal.equals(this.annotations, that.annotations)
268 )
269 }
270 [Hash.symbol](): number {
271 return Hash.combine(Hash.string(this._tag))(
272 Hash.combine(Hash.hash(this.error))(Hash.hash(this.annotations))
273 )
274 }
275}
276
277/** @internal */
278export const causeFromReasons = <E>(
279 reasons: ReadonlyArray<Cause.Reason<E>>
280): Cause.Cause<E> => new CauseImpl(reasons)
281
282const dedupeReasons = <E>(
283 self: ReadonlyArray<Cause.Reason<E>>,
284 that: ReadonlyArray<Cause.Reason<E>>
285): Array<Cause.Reason<E>> => {
286 // Avoid importing Array.ts into the core bundle.
287 // Snapshot both arrays before invoking user-defined hash or equality methods.
288 const buckets = new Map<number, Array<Cause.Reason<E>>>()
289 const out: Array<Cause.Reason<E>> = []
290 for (const reason of self.concat(that)) {
291 const hash = Hash.hash(reason)
292 const bucket = buckets.get(hash)
293 if (bucket === undefined) {
294 buckets.set(hash, [reason])
295 } else if (bucket.some((previous) => Equal.equals(previous, reason))) {
296 continue
297 } else {
298 bucket.push(reason)
299 }
300 out.push(reason)
301 }
302 return out
303}
304
305/** @internal */
306export const causeCombine: {
307 <E2>(that: Cause.Cause<E2>): <E>(self: Cause.Cause<E>) => Cause.Cause<E | E2>
308 <E, E2>(self: Cause.Cause<E>, that: Cause.Cause<E2>): Cause.Cause<E | E2>
309} = dual(
310 2,
311 <E, E2>(self: Cause.Cause<E>, that: Cause.Cause<E2>): Cause.Cause<E | E2> => {
312 if (self.reasons.length === 0) {
313 return that as Cause.Cause<E | E2>
314 } else if (that.reasons.length === 0) {
315 return self as Cause.Cause<E | E2>
316 }
317 const newCause = new CauseImpl<E | E2>(
318 dedupeReasons<E | E2>(self.reasons, that.reasons)
319 )
320 return Equal.equals(self, newCause) ? self : newCause
321 }
322)
323
324/** @internal */
325export const causeEmpty: Cause.Cause<never> = new CauseImpl([])
326
327/** @internal */
328export const causeFail = <E>(error: E): Cause.Cause<E> => new CauseImpl([new Fail(error)])
329
330/** @internal */
331export class Die extends ReasonBase<"Die"> implements Cause.Die {
332 declare readonly defect: unknown
333 constructor(
334 defect: unknown,
335 annotations = constEmptyAnnotations
336 ) {
337 super("Die", annotations, defect)
338 this.defect = defect
339 }
340 override toString() {
341 return `Die(${format(this.defect)})`
342 }
343 toJSON(): unknown {
344 return {
345 _tag: "Die",
346 defect: this.defect
347 }
348 }
349 [Equal.symbol](that: any): boolean {
350 return (
351 isDieReason(that) &&
352 Equal.equals(this.defect, that.defect) &&
353 Equal.equals(this.annotations, that.annotations)
354 )
355 }
356 [Hash.symbol](): number {
357 return Hash.combine(Hash.string(this._tag))(
358 Hash.combine(Hash.hash(this.defect))(Hash.hash(this.annotations))
359 )
360 }
361}
362
363/** @internal */
364export const causeDie = (defect: unknown): Cause.Cause<never> => new CauseImpl([new Die(defect)])
365
366/** @internal */
367export const causeAnnotate: {
368 (
369 annotations: Context.Context<never>,
370 options?: {
371 readonly overwrite?: boolean | undefined
372 }
373 ): <E>(self: Cause.Cause<E>) => Cause.Cause<E>
374 <E>(
375 self: Cause.Cause<E>,
376 annotations: Context.Context<never>,
377 options?: {
378 readonly overwrite?: boolean | undefined
379 }
380 ): Cause.Cause<E>
381} = dual(
382 (args) => isCause(args[0]),
383 <E>(
384 self: Cause.Cause<E>,
385 annotations: Context.Context<never>,
386 options?: {
387 readonly overwrite?: boolean | undefined
388 }
389 ): Cause.Cause<E> => {
390 if (annotations.mapUnsafe.size === 0) return self
391 return new CauseImpl(self.reasons.map((f) => f.annotate(annotations, options)))
392 }
393)
394
395/** @internal */
396export const isFailReason = <E>(
397 self: Cause.Reason<E>
398): self is Cause.Fail<E> => self._tag === "Fail"
399
400/** @internal */
401export const isDieReason = <E>(self: Cause.Reason<E>): self is Cause.Die => self._tag === "Die"
402
403/** @internal */
404export const isInterruptReason = <E>(self: Cause.Reason<E>): self is Cause.Interrupt => self._tag === "Interrupt"
405
406/** @internal */
407export interface Primitive {
408 readonly [identifier]: string
409 readonly [contA]:
410 | ((value: unknown, fiber: FiberImpl, exit?: Exit.Exit<any, any>) => Primitive | Yield)
411 | undefined
412 readonly [contE]:
413 | ((cause: Cause.Cause<unknown>, fiber: FiberImpl, exit?: Exit.Exit<any, any>) => Primitive | Yield)
414 | undefined
415 readonly [contAll]:
416 | ((
417 fiber: FiberImpl
418 ) =>
419 | ((value: unknown, fiber: FiberImpl) => Primitive | Yield)
420 | undefined)
421 | undefined
422 [evaluate](fiber: FiberImpl): Primitive | Yield
423}
424
425interface PrimitiveClass {
426 new(value: any): Primitive
427 prototype: any
428}
429
430function defaultEvaluate(_fiber: FiberImpl): Primitive | Yield {
431 return exitDie(`Effect.evaluate: Not implemented`) as any
432}
433
434/** @internal */
435export const makePrimitiveProto = <Op extends string>(options: {
436 readonly op: Op
437 readonly [evaluate]?: (
438 fiber: FiberImpl
439 ) => Primitive | Effect.Effect<any, any, any> | Yield
440 readonly [contA]?: (
441 this: Primitive,
442 value: any,
443 fiber: FiberImpl,
444 exit?: Exit.Exit<any, any>
445 ) => Primitive | Effect.Effect<any, any, any> | Yield
446 readonly [contE]?: (
447 this: Primitive,
448 cause: Cause.Cause<any>,
449 fiber: FiberImpl,
450 exit?: Exit.Exit<any, any>
451 ) => Primitive | Effect.Effect<any, any, any> | Yield
452 readonly [contAll]?: (
453 this: Primitive,
454 fiber: FiberImpl
455 ) => void | ((value: any, fiber: FiberImpl) => void)
456}): Primitive =>
457 ({
458 ...EffectProto,
459 [identifier]: options.op,
460 [evaluate]: options[evaluate] ?? defaultEvaluate,
461 [contA]: options[contA],
462 [contE]: options[contE],
463 [contAll]: options[contAll]
464 }) as any
465
466/** @internal */
467export const makePrimitive = <
468 Fn extends (...args: Array<any>) => any
469>(options: {
470 readonly op: string
471 readonly [evaluate]?: (
472 this: Primitive & {
473 readonly [args]: Parameters<Fn>[0]
474 },
475 fiber: FiberImpl
476 ) => Primitive | Effect.Effect<any, any, any> | Yield
477 readonly [contA]?: (
478 this: Primitive & {
479 readonly [args]: Parameters<Fn>[0]
480 },
481 value: any,
482 fiber: FiberImpl,
483 exit?: Exit.Exit<any, any>
484 ) => Primitive | Effect.Effect<any, any, any> | Yield
485 readonly [contE]?: (
486 this: Primitive & {
487 readonly [args]: Parameters<Fn>[0]
488 },
489 cause: Cause.Cause<any>,
490 fiber: FiberImpl,
491 exit?: Exit.Exit<any, any>
492 ) => Primitive | Effect.Effect<any, any, any> | Yield
493 readonly [contAll]?: (
494 this: Primitive & {
495 readonly [args]: Parameters<Fn>[0]
496 },
497 fiber: FiberImpl
498 ) => void | ((value: any, fiber: FiberImpl) => void)
499}): Fn => {
500 const Proto = makePrimitiveProto(options as any)
501 const PrimitiveImpl = function(this: any, value: any) {
502 this[args] = value
503 } as unknown as PrimitiveClass
504 PrimitiveImpl.prototype = Proto
505 return function(value: any) {
506 return new PrimitiveImpl(value)
507 } as Fn
508}
509
510/** @internal */
511export const makeExit = <
512 Fn extends (...args: Array<any>) => any,
513 Prop extends string
514>(options: {
515 readonly op: "Success" | "Failure"
516 readonly prop: Prop
517 readonly [evaluate]: (
518 this: Exit.Exit<unknown, unknown> & { [args]: Parameters<Fn>[0] },
519 fiber: FiberImpl<unknown, unknown>
520 ) => Primitive | Yield
521}): Fn => {
522 const Proto = {
523 [ExitTypeId]: ExitTypeId,
524 _tag: options.op,
525 get [options.prop](): any {
526 return (this as any)[args]
527 },
528 ...makePrimitiveProto(options),
529 toString(this: any) {
530 return `${options.op}(${format(this[args])})`
531 },
532 toJSON(this: any) {
533 return {
534 _id: "Exit",
535 _tag: options.op,
536 [options.prop]: this[args]
537 }
538 },
539 [Equal.symbol](this: any, that: any): boolean {
540 return (
541 isExit(that) &&
542 that._tag === this._tag &&
543 Equal.equals(this[args], (that as any)[args])
544 )
545 },
546 [Hash.symbol](this: any): number {
547 return Hash.combine(Hash.string(options.op), Hash.hash(this[args]))
548 }
549 }
550 const ExitPrimitive = function(this: any, value: unknown) {
551 this[args] = value
552 } as unknown as PrimitiveClass
553 ExitPrimitive.prototype = Proto
554 return function(value: unknown) {
555 return new ExitPrimitive(value)
556 } as Fn
557}
558
559/** @internal */
560export const exitSucceed: <A>(a: A) => Exit.Exit<A> = makeExit({
561 op: "Success",
562 prop: "value",
563 [evaluate](fiber) {
564 const cont = fiber.getCont(contA)
565 return cont ? cont[contA](this[args], fiber, this) : fiber.yieldWith(this)
566 }
567})
568
569/** @internal */
570export const StackTraceKey = {
571 key: "effect/Cause/StackTrace" satisfies typeof Cause.StackTrace.key
572} as Context.Service<Cause.StackTrace, StackFrame>
573
574/** @internal */
575export const InterruptorStackTrace = {
576 key: "effect/Cause/InterruptorStackTrace" satisfies typeof Cause.InterruptorStackTrace.key
577} as Context.Service<Cause.InterruptorStackTrace, StackFrame>
578
579/** @internal */
580export const exitFailCause: <E>(cause: Cause.Cause<E>) => Exit.Exit<never, E> = makeExit({
581 op: "Failure",
582 prop: "cause",
583 [evaluate](fiber) {
584 let cause = this[args]
585 let annotated = false
586 if (fiber.cache.stackFrame) {
587 cause = causeAnnotate(cause, { mapUnsafe: new Map([[StackTraceKey.key, fiber.cache.stackFrame]]) } as any)
588 annotated = true
589 }
590 let cont = fiber.getCont(contE)
591 const interruptedCause = fiber._interruptedCause
592 if (interruptedCause && fiber.interruptible) {
593 // Drop typed failures only when interruption skips a recovery handler.
594 // Interruptibility-restoration continuations have no identifier.
595 let skippedHandler = false
596 while (cont && fiber.interruptible) {
597 skippedHandler ||= identifier in cont
598 cont = fiber.getCont(contE)
599 }
600 if (skippedHandler) {
601 cause = causeFromReasons(cause.reasons.filter((reason) => reason._tag !== "Fail"))
602 }
603 cause = causeCombine(cause, interruptedCause)
604 annotated = true
605 }
606 return cont
607 ? cont[contE](cause, fiber, annotated ? undefined : this)
608 : fiber.yieldWith(annotated ? exitFailCause(cause) : this)
609 }
610})
611
612/** @internal */
613export const exitFail = <E>(e: E): Exit.Exit<never, E> => exitFailCause(causeFail(e))
614
615/** @internal */
616export const exitDie = (defect: unknown): Exit.Exit<never> => exitFailCause(causeDie(defect))
617
618/** @internal */
619export const withFiber: <A, E = never, R = never>(
620 evaluate: (fiber: FiberImpl<unknown, unknown>) => Effect.Effect<A, E, R>
621) => Effect.Effect<A, E, R> = makePrimitive({
622 op: "WithFiber",
623 [evaluate](fiber) {
624 return this[args](fiber)
625 }
626})
627
628/**
629 * Accesses the current fiber to compute a value without a separate `succeed`
630 * operation.
631 *
632 * @internal
633 */
634export const withFiberSucceed: <A, R = never>(
635 evaluate: (fiber: FiberImpl<unknown, unknown>) => A
636) => Effect.Effect<A, never, R> = makePrimitive({
637 op: "WithFiberSucceed",
638 [evaluate](fiber) {
639 const value = this[args](fiber)
640 const cont = fiber.getCont(contA)
641 return cont ? cont[contA](value, fiber) : fiber.yieldWith(exitSucceed(value))
642 }
643})
644
645/** @internal */
646export const YieldableError: new(
647 message?: string,
648 options?: ErrorOptions
649) => Cause.YieldableError = (function() {
650 class YieldableError extends globalThis.Error {}
651 const proto = makePrimitiveProto({
652 op: "YieldableError",
653 [evaluate]() {
654 return exitFail(this)
655 }
656 })
657 delete (proto as any).toString
658 Object.assign(
659 YieldableError.prototype,
660 proto
661 )
662 return YieldableError as any
663})()
664
665/** @internal */
666export const Error: new<A extends Record<string, any> = {}>(
667 args: Types.VoidIfEmpty<{ readonly [P in keyof A]: A[P] }>
668) => Cause.YieldableError & Readonly<A> = (function() {
669 const plainArgsSymbol = Symbol.for("effect/Data/Error/plainArgs")
670 return class Base extends YieldableError {
671 constructor(args: Record<string, any> | undefined) {
672 super(args?.message, args?.cause ? { cause: args.cause } : undefined)
673 if (args) {
674 InternalRecord.assignProperties(this, args)
675 // @effect-diagnostics-next-line floatingEffect:off
676 Object.defineProperty(this, plainArgsSymbol, {
677 value: args,
678 enumerable: false
679 })
680 }
681 }
682 override toJSON() {
683 return { ...(this as any)[plainArgsSymbol], ...this }
684 }
685 } as any
686})()
687
688/** @internal */
689export const TaggedError = <Tag extends string>(
690 tag: Tag
691): new<A extends Record<string, any> = {}>(
692 args: Types.VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }>
693) => Cause.YieldableError & { readonly _tag: Tag } & Readonly<A> => {
694 class Base extends Error<{}> {
695 readonly _tag = tag
696 }
697 ;(Base.prototype as any).name = tag
698 return Base as any
699}
700
701/** @internal */
702export const NoSuchElementErrorTypeId = "~effect/Cause/NoSuchElementError"
703
704/** @internal */
705export const isNoSuchElementError = (
706 u: unknown
707): u is Cause.NoSuchElementError => hasProperty(u, NoSuchElementErrorTypeId)
708
709/** @internal */
710export class NoSuchElementError extends TaggedError("NoSuchElementError") {
711 readonly [NoSuchElementErrorTypeId] = NoSuchElementErrorTypeId
712 constructor(message?: string) {
713 super({ message } as any)
714 }
715}
716
717/** @internal */
718export const DoneTypeId = "~effect/Cause/Done"
719
720/** @internal */
721export const isDone = (
722 u: unknown
723): u is Cause.Done => hasProperty(u, DoneTypeId)
724
725const DoneVoid: Cause.Done<void> = {
726 [DoneTypeId]: DoneTypeId,
727 _tag: "Done",
728 value: undefined
729}
730
731/** @internal */
732export const Done = <A = void>(value?: A): Cause.Done<A> => {
733 if (value === undefined) return DoneVoid as Cause.Done<A>
734 return {
735 [DoneTypeId]: DoneTypeId,
736 _tag: "Done",
737 value
738 }
739}
740
741const doneVoid = exitFail(DoneVoid)
742
743/** @internal */
744export const done = <A = void>(value?: A): Effect.Effect<never, Cause.Done<A>> => {
745 if (value === undefined) return doneVoid as any
746 return exitFail(Done(value))
747}