Skip to content

Transfer Evolution

This page is generated from the local transfer fixture and the current effect-analyzer implementation.

Every Mermaid block below comes from static analysis of the TypeScript source in apps/docs/samples/observability-transfer/evolution.

No code is executed. If the analyzer changes, rerun pnpm --dir apps/docs run generate:transfer-observability and this page will update.

One operation. One possible failure. The type system already knows what can go wrong.

flowchart LR
  A["validated #lt;- deps.validateTransfer"] -->|ok| Done((Success))
  A -->|err| AE["Validation"]
  • Effects: 3
  • Error paths: ValidationError
createSendMoneyWorkflow (generator):
1. validated = Pipes deps.validateTransfer through:
Calls deps.validateTransfer — collection
Error paths: ValidationError
Concurrency: sequential (no parallelism)
[
{
"program": "createSendMoneyWorkflow",
"stats": {
"totalEffects": 3,
"parallelCount": 0,
"raceCount": 0,
"errorHandlerCount": 0,
"retryCount": 0,
"timeoutCount": 0,
"resourceCount": 0,
"loopCount": 0,
"conditionalCount": 0,
"layerCount": 0,
"interruptionCount": 0,
"unknownCount": 0,
"decisionCount": 0,
"switchCount": 0,
"tryCatchCount": 0,
"terminalCount": 0,
"opaqueCount": 0
}
}
]

A second step, a second way to fail. The error channel grows automatically.

flowchart LR
  A["validated #lt;- deps.validateTransfer"] -->|ok| B["rate #lt;- deps.fetchRate"]
  B -->|ok| Done((Success))
  A -->|err| AE["Validation"]
  B -->|err| BE["RateUnavailable"]
  • Effects: 5
  • Error paths: RateUnavailableError, ValidationError
createSendMoneyWorkflow (generator):
1. validated = Pipes deps.validateTransfer through:
Calls deps.validateTransfer — collection
2. rate = Pipes deps.fetchRate through:
Calls deps.fetchRate
Error paths: RateUnavailableError, ValidationError
Concurrency: sequential (no parallelism)
[
{
"program": "createSendMoneyWorkflow",
"stats": {
"totalEffects": 5,
"parallelCount": 0,
"raceCount": 0,
"errorHandlerCount": 0,
"retryCount": 0,
"timeoutCount": 0,
"resourceCount": 0,
"loopCount": 0,
"conditionalCount": 0,
"layerCount": 0,
"interruptionCount": 0,
"unknownCount": 0,
"decisionCount": 0,
"switchCount": 0,
"tryCatchCount": 0,
"terminalCount": 0,
"opaqueCount": 0
}
}
]

Balance check introduces a new failure mode. The compiler tracks it, you don’t have to.

flowchart LR
  A["validated #lt;- deps.validateTransfer"] -->|ok| B["rate #lt;- deps.fetchRate"]
  B -->|ok| C["balance #lt;- deps.getBalance"]
  C -->|ok| D["converted #lt;- deps.convertCurrency"]
  D -->|ok| Done((Success))
  A -->|err| AE["Validation"]
  B -->|err| BE["RateUnavailable"]
  D -->|err| DE["InsufficientFunds"]
  • Effects: 9
  • Error paths: InsufficientFundsError, RateUnavailableError, ValidationError
createSendMoneyWorkflow (generator):
1. validated = Pipes deps.validateTransfer through:
Calls deps.validateTransfer — collection
2. rate = Pipes deps.fetchRate through:
Calls deps.fetchRate
3. balance = Pipes deps.getBalance through:
Calls deps.getBalance
4. converted = Pipes deps.convertCurrency through:
Calls deps.convertCurrency
Error paths: InsufficientFundsError, RateUnavailableError, ValidationError
Concurrency: sequential (no parallelism)
[
{
"program": "createSendMoneyWorkflow",
"stats": {
"totalEffects": 9,
"parallelCount": 0,
"raceCount": 0,
"errorHandlerCount": 0,
"retryCount": 0,
"timeoutCount": 0,
"resourceCount": 0,
"loopCount": 0,
"conditionalCount": 0,
"layerCount": 0,
"interruptionCount": 0,
"unknownCount": 0,
"decisionCount": 0,
"switchCount": 0,
"tryCatchCount": 0,
"terminalCount": 0,
"opaqueCount": 0
}
}
]

The money moves. Two new failure modes from the external provider. Still zero runtime needed to see them.

flowchart LR
  A["validated #lt;- deps.validateTransfer"] -->|ok| B["rate #lt;- deps.fetchRate"]
  B -->|ok| C["balance #lt;- deps.getBalance"]
  C -->|ok| D["converted #lt;- deps.convertCurrency"]
  D -->|ok| E["transfer #lt;- deps.executeTransfer"]
  E -->|ok| Done((Success))
  A -->|err| AE["Validation"]
  B -->|err| BE["RateUnavailable"]
  D -->|err| DE["InsufficientFunds"]
  E -->|err| EE["TransferRejected / ProviderUnavailable"]
  • Effects: 11
  • Error paths: InsufficientFundsError, ProviderUnavailableError, RateUnavailableError, TransferRejectedError, ValidationError
createSendMoneyWorkflow (generator):
1. validated = Pipes deps.validateTransfer through:
Calls deps.validateTransfer — collection
2. rate = Pipes deps.fetchRate through:
Calls deps.fetchRate
3. balance = Pipes deps.getBalance through:
Calls deps.getBalance
4. converted = Pipes deps.convertCurrency through:
Calls deps.convertCurrency
5. transfer = Pipes deps.executeTransfer through:
Calls deps.executeTransfer
Error paths: InsufficientFundsError, ProviderUnavailableError, RateUnavailableError, TransferRejectedError, ValidationError
Concurrency: sequential (no parallelism)
[
{
"program": "createSendMoneyWorkflow",
"stats": {
"totalEffects": 11,
"parallelCount": 0,
"raceCount": 0,
"errorHandlerCount": 0,
"retryCount": 0,
"timeoutCount": 0,
"resourceCount": 0,
"loopCount": 0,
"conditionalCount": 0,
"layerCount": 0,
"interruptionCount": 0,
"unknownCount": 0,
"decisionCount": 0,
"switchCount": 0,
"tryCatchCount": 0,
"terminalCount": 0,
"opaqueCount": 0
}
}
]

The complete workflow. Six effects, six error channels, zero ambiguity. Every path is visible before a single line runs.

flowchart LR
  A["validated #lt;- deps.validateTransfer"] -->|ok| B["rate #lt;- deps.fetchRate"]
  B -->|ok| C["balance #lt;- deps.getBalance"]
  C -->|ok| D["converted #lt;- deps.convertCurrency"]
  D -->|ok| E["transfer #lt;- deps.executeTransfer"]
  E -->|ok| F["deps.sendConfirmation"]
  F -->|ok| Done((Success))
  A -->|err| AE["Validation"]
  B -->|err| BE["RateUnavailable"]
  D -->|err| DE["InsufficientFunds"]
  E -->|err| EE["TransferRejected / ProviderUnavailable"]
  F -->|err| FE["ConfirmationFailed"]
  • Effects: 13
  • Error paths: ConfirmationFailedError, InsufficientFundsError, ProviderUnavailableError, RateUnavailableError, TransferRejectedError, ValidationError
createSendMoneyWorkflow (generator):
1. validated = Pipes deps.validateTransfer through:
Calls deps.validateTransfer — collection
2. rate = Pipes deps.fetchRate through:
Calls deps.fetchRate
3. balance = Pipes deps.getBalance through:
Calls deps.getBalance
4. converted = Pipes deps.convertCurrency through:
Calls deps.convertCurrency
5. transfer = Pipes deps.executeTransfer through:
Calls deps.executeTransfer
6. Pipes deps.sendConfirmation through:
Calls deps.sendConfirmation
Error paths: ConfirmationFailedError, InsufficientFundsError, ProviderUnavailableError, RateUnavailableError, TransferRejectedError, ValidationError
Concurrency: sequential (no parallelism)
[
{
"program": "createSendMoneyWorkflow",
"stats": {
"totalEffects": 13,
"parallelCount": 0,
"raceCount": 0,
"errorHandlerCount": 0,
"retryCount": 0,
"timeoutCount": 0,
"resourceCount": 0,
"loopCount": 0,
"conditionalCount": 0,
"layerCount": 0,
"interruptionCount": 0,
"unknownCount": 0,
"decisionCount": 0,
"switchCount": 0,
"tryCatchCount": 0,
"terminalCount": 0,
"opaqueCount": 0
}
}
]