<!-- livebook:{"persist_outputs":true} -->

# "compute" nodes

```elixir
# [Optional] Setting Build Key, see https://gojourney.dev/your_keys
# (Using "Journey Livebook Demo" build key)
System.put_env("JOURNEY_BUILD_KEY", "B27AXHMERm2Z6ehZhL49v")

Mix.install(
  [
    {:ecto_sql, "~> 3.13"},
    {:postgrex, "~> 0.22"},
    {:jason, "~> 1.4"},
    {:journey, "~> 0.10"},
    {:kino, "~> 0.19"}
  ],
  start_applications: false
)

Application.put_env(:journey, :log_level, :warning)

# This livebook requires a PostgreSQL database.
# If you don't have one running, you can start one with Docker:
# docker run --rm --name postgres -p 5432:5432 -e POSTGRES_PASSWORD=postgres -d postgres:16

# Update this configuration to point to your database server
Application.put_env(:journey, Journey.Repo,
  database: "journey_compute_nodes",
  username: "postgres",
  password: "postgres",
  hostname: "localhost",
  log: false,
  port: 5432
)

Application.put_env(:journey, :ecto_repos, [Journey.Repo])

Journey.Repo.__adapter__().storage_up(Journey.Repo.config())

Application.loaded_applications()
|> Enum.map(fn {app, _, _} -> app end)
|> Enum.each(&Application.ensure_all_started/1)
```

## DB Setup

This livebook requires a PostgreSQL database. If you don't have one running, you can start one with Docker:

```bash
docker run --rm --name postgres -p 5432:5432 -e POSTGRES_PASSWORD=postgres -d postgres:16
```

## What We'll Cover

In this example, we will create a simple graph with `input` and `compute` nodes and play with them.

This tiny tutorial will demonstrate:

1. defining a graph
2. starting an execution of the graph
3. setting values on the execution
4. reading values from an execution
5. reloading an execution "after a crash" and continuing, as if nothing happened
6. a reactive computation getting unblocked when all of its upstream dependencies are provided
7. visualizing and introspecting an execution

## Define the Graph

```elixir
import Journey.Node

graph = Journey.new_graph(
  "Onboarding",
  "v1",
  [
    input(:name),
    input(:email_address),
    compute(
      :greeting, 
      [:name, :email_address],
      fn values -> 
        welcome = "Welcome, #{values.name} at #{values.email_address}"
        IO.puts(welcome)
        {:ok, welcome}
      end
    )
  ]
); :ok
```

<!-- livebook:{"output":true} -->

```
:ok
```

Note that the computation is defined to be unblocked when both inputs, `[:name, :email_address]`, are provided. It will be blocked until then.

<!-- livebook:{"break_markdown":true} -->

Visualize the graph:

```elixir
  graph
  |> Journey.Tools.generate_mermaid_graph()
  |> Kino.Mermaid.new()
```

<!-- livebook:{"output":true} -->

```mermaid
graph TD
    %% Graph
    subgraph Graph["🧩 'Onboarding', version v1"]
        execution_id[execution_id]
        last_updated_at[last_updated_at]
        name[name]
        email_address[email_address]
        greeting[["greeting<br/>(anonymous fn)"]]

        name -->  greeting
        email_address -->  greeting
    end

    %% Styling
    classDef defaultNode fill:#f8f9fa,stroke:#495057,stroke-width:2px,color:#000000

    %% Apply styles to nodes
    class execution_id,last_updated_at,name,email_address,greeting defaultNode
```

## Start an Execution

```elixir
execution = Journey.start(graph); :ok
```

<!-- livebook:{"output":true} -->

```
:ok
```

## Peek into the Execution

We can examine the state of an execution from a few different angles – a diagram, values, and detailed introspection.

<!-- livebook:{"break_markdown":true} -->

### Diagram

```elixir
execution.id
|> Journey.Tools.generate_mermaid_execution()
|> Kino.Mermaid.new()
```

<!-- livebook:{"output":true} -->

```mermaid
graph TD
    %% Graph
    subgraph Graph["🧩 'Onboarding', version v1, EXECJ1AG1T37Y5H63H2AHAG9"]
        execution_id["✅ execution_id"]
        last_updated_at["✅ last_updated_at"]
        name["⬜ name"]
        email_address["⬜ email_address"]
        greeting[["🚫 greeting<br/>(anonymous fn)"]]

        name -->  greeting
        email_address -->  greeting
    end

    %% Styling
    classDef setNode fill:#e1f5fe,stroke:#01579b,stroke-width:2px,color:#000000
    classDef computingNode fill:#fff8e1,stroke:#f57f17,stroke-width:2px,color:#000000
    classDef errorNode fill:#f8bbd0,stroke:#b71c1c,stroke-width:2px,color:#000000
    classDef neutralNode fill:#f8f9fa,stroke:#495057,stroke-width:2px,color:#000000

    %% Apply styles to nodes
    class last_updated_at,execution_id setNode
    class greeting,email_address,name neutralNode
```

### Values

```elixir
Journey.values_all(execution)
```

<!-- livebook:{"output":true} -->

```
%{
  name: :not_set,
  last_updated_at: {:set, 1776923565},
  execution_id: {:set, "EXECJ1AG1T37Y5H63H2AHAG9"},
  email_address: :not_set,
  greeting: :not_set
}
```

### Detailed Introspection

```elixir
Journey.Tools.introspect(execution.id) |> IO.puts()
```

<!-- livebook:{"output":true} -->

```
Execution summary:
- ID: 'EXECJ1AG1T37Y5H63H2AHAG9'
- Graph: 'Onboarding' | 'v1'
- Archived at: not archived
- Created at: 2026-04-23 05:52:45Z UTC | 0 seconds ago
- Last updated at: 2026-04-23 05:52:45Z UTC | 0 seconds ago
- Duration: 0 seconds
- Revision: 0
- # of Values: 2 (set) / 5 (total)
- # of Computations: 1

Values:
- Set:
  - execution_id: 'EXECJ1AG1T37Y5H63H2AHAG9' | :input
    set at 2026-04-23 05:52:45Z | rev: 0

  - last_updated_at: '1776923565' | :input
    set at 2026-04-23 05:52:45Z | rev: 0


- Not set:
  - email_address: <unk> | :input
  - greeting: <unk> | :compute
  - name: <unk> | :input  

Computations:
- Completed:


- Outstanding:
  - greeting: ⬜ :not_set (not yet attempted) | :compute
       :and
        ├─ 🛑 :name | &provided?/1
        └─ 🛑 :email_address | &provided?/1
```

<!-- livebook:{"output":true} -->

```
:ok
```

## `:name` Is Set, `:greeting` Is Still Blocked

```elixir
execution = 
  execution
  |> Journey.set(:name, "Luigi"); :ok
```

<!-- livebook:{"output":true} -->

```
:ok
```

```elixir
Journey.values(execution)
```

<!-- livebook:{"output":true} -->

```
%{name: "Luigi", last_updated_at: 1776923565, execution_id: "EXECJ1AG1T37Y5H63H2AHAG9"}
```

Notice that `:greeting` is still blocked, waiting for `:email_address`:

```elixir
execution.id
|> Journey.Tools.generate_mermaid_execution()
|> Kino.Mermaid.new()
```

<!-- livebook:{"output":true} -->

```mermaid
graph TD
    %% Graph
    subgraph Graph["🧩 'Onboarding', version v1, EXECJ1AG1T37Y5H63H2AHAG9"]
        execution_id["✅ execution_id"]
        last_updated_at["✅ last_updated_at"]
        name["✅ name"]
        email_address["⬜ email_address"]
        greeting[["🚫 greeting<br/>(anonymous fn)"]]

        name -->  greeting
        email_address -->  greeting
    end

    %% Styling
    classDef setNode fill:#e1f5fe,stroke:#01579b,stroke-width:2px,color:#000000
    classDef computingNode fill:#fff8e1,stroke:#f57f17,stroke-width:2px,color:#000000
    classDef errorNode fill:#f8bbd0,stroke:#b71c1c,stroke-width:2px,color:#000000
    classDef neutralNode fill:#f8f9fa,stroke:#495057,stroke-width:2px,color:#000000

    %% Apply styles to nodes
    class name,last_updated_at,execution_id setNode
    class greeting,email_address neutralNode
```

```elixir
Journey.Tools.introspect(execution.id) |> IO.puts()
```

<!-- livebook:{"output":true} -->

```
Execution summary:
- ID: 'EXECJ1AG1T37Y5H63H2AHAG9'
- Graph: 'Onboarding' | 'v1'
- Archived at: not archived
- Created at: 2026-04-23 05:52:45Z UTC | 0 seconds ago
- Last updated at: 2026-04-23 05:52:45Z UTC | 0 seconds ago
- Duration: 0 seconds
- Revision: 1
- # of Values: 3 (set) / 5 (total)
- # of Computations: 1

Values:
- Set:
  - last_updated_at: '1776923565' | :input
    set at 2026-04-23 05:52:45Z | rev: 1

  - name: '"Luigi"' | :input
    set at 2026-04-23 05:52:45Z | rev: 1

  - execution_id: 'EXECJ1AG1T37Y5H63H2AHAG9' | :input
    set at 2026-04-23 05:52:45Z | rev: 0


- Not set:
  - email_address: <unk> | :input
  - greeting: <unk> | :compute  

Computations:
- Completed:


- Outstanding:
  - greeting: ⬜ :not_set (not yet attempted) | :compute
       :and
        ├─ ✅ :name | &provided?/1 | rev 1
        └─ 🛑 :email_address | &provided?/1
```

<!-- livebook:{"output":true} -->

```
:ok
```

## Reload the Execution After a Crash and Continue

As long as you have the ID of an execution, you can load it and continue as if nothing happened – even if the infrastructure suffered an outage and everything was down for a week.

```elixir
execution_id = execution.id
```

<!-- livebook:{"output":true} -->

```
"EXECJ1AG1T37Y5H63H2AHAG9"
```

```elixir
execution = Journey.load(execution_id); :ok
```

<!-- livebook:{"output":true} -->

```
:ok
```

Note that the `:name` value that we set before "the crash" is still here, even after the execution was reloaded.

This is because as soon as an execution value is set or computed, it is persisted in PostgreSQL.

```elixir
Journey.values(execution)
```

<!-- livebook:{"output":true} -->

```
%{name: "Luigi", last_updated_at: 1776923565, execution_id: "EXECJ1AG1T37Y5H63H2AHAG9"}
```

## Set `:email_address`, Watch `:greeting` Become Unblocked

At this point, `name` is set, but `greeting` is still blocked -- it still needs `email_address`. As soon as we set `email_address`, `greeting` will compute its value.

```elixir
execution = 
  execution
  |> Journey.set(:email_address, "luigi@example.com")

{:ok, value, _revision} = Journey.get(execution, :greeting, wait: :any); value
```

<!-- livebook:{"output":true} -->

```
Welcome, Luigi at luigi@example.com
```

<!-- livebook:{"output":true} -->

```
"Welcome, Luigi at luigi@example.com"
```

Note that the string appears twice – one is the IO.puts from the `:greeting` compute function; and the other is the cell's return value.

```elixir
Journey.values_all(execution)
```

<!-- livebook:{"output":true} -->

```
%{
  name: {:set, "Luigi"},
  last_updated_at: {:set, 1776923565},
  execution_id: {:set, "EXECJ1AG1T37Y5H63H2AHAG9"},
  email_address: {:set, "luigi@example.com"},
  greeting: {:set, "Welcome, Luigi at luigi@example.com"}
}
```

```elixir
execution.id
|> Journey.Tools.generate_mermaid_execution()
|> Kino.Mermaid.new()
```

<!-- livebook:{"output":true} -->

```mermaid
graph TD
    %% Graph
    subgraph Graph["🧩 'Onboarding', version v1, EXECJ1AG1T37Y5H63H2AHAG9"]
        execution_id["✅ execution_id"]
        last_updated_at["✅ last_updated_at"]
        name["✅ name"]
        email_address["✅ email_address"]
        greeting[["✅ greeting<br/>(anonymous fn)"]]

        name -->  greeting
        email_address -->  greeting
    end

    %% Styling
    classDef setNode fill:#e1f5fe,stroke:#01579b,stroke-width:2px,color:#000000
    classDef computingNode fill:#fff8e1,stroke:#f57f17,stroke-width:2px,color:#000000
    classDef errorNode fill:#f8bbd0,stroke:#b71c1c,stroke-width:2px,color:#000000
    classDef neutralNode fill:#f8f9fa,stroke:#495057,stroke-width:2px,color:#000000

    %% Apply styles to nodes
    class greeting,email_address,name,last_updated_at,execution_id setNode
```

```elixir
Journey.Tools.introspect(execution.id) |> IO.puts()
```

<!-- livebook:{"output":true} -->

```
Execution summary:
- ID: 'EXECJ1AG1T37Y5H63H2AHAG9'
- Graph: 'Onboarding' | 'v1'
- Archived at: not archived
- Created at: 2026-04-23 05:52:45Z UTC | 1 seconds ago
- Last updated at: 2026-04-23 05:52:45Z UTC | 1 seconds ago
- Duration: 0 seconds
- Revision: 4
- # of Values: 5 (set) / 5 (total)
- # of Computations: 1

Values:
- Set:
  - greeting: '"Welcome, Luigi at luigi@example.com"' | :compute
    computed at 2026-04-23 05:52:45Z | rev: 4

  - last_updated_at: '1776923565' | :input
    set at 2026-04-23 05:52:45Z | rev: 4

  - email_address: '"luigi@example.com"' | :input
    set at 2026-04-23 05:52:45Z | rev: 2

  - name: '"Luigi"' | :input
    set at 2026-04-23 05:52:45Z | rev: 1

  - execution_id: 'EXECJ1AG1T37Y5H63H2AHAG9' | :input
    set at 2026-04-23 05:52:45Z | rev: 0


- Not set:
  

Computations:
- Completed:
  - :greeting (CMPL526T0Y4BT6EEA7ZL44H): ✅ :success | :compute | rev 4
    started: 2026-04-23 05:52:45Z | completed: 2026-04-23 05:52:45Z (0s)
    inputs used:
       :name (rev 1)
       :email_address (rev 2)

- Outstanding:

```

<!-- livebook:{"output":true} -->

```
:ok
```

## Summary

In this Livebook, we played with Journey's basic functionality: defining and executing reactive graphs with input and compute nodes.

More specifically:

* We defined a graph with two input nodes and one self-computing node. We then started an execution of the graph, and set a value for `:name`. Then we set another value for `:email_address`, and watched the greeting computation kick off and compute the greeting.

* In the process, we reloaded the execution after an imagined infrastructure crash, and continued with the execution as if nothing happened.

* We fetched the execution's values with `Journey.values()` and `Journey.values_all()`.

* We fetched a particular value of `:greeting`, waiting for it to become available, with `Journey.get()`.

* We visualized the graph (`Journey.Tools.generate_mermaid_graph()`) and the execution (`Journey.Tools.generate_mermaid_execution()`), at its various stages.

* We introspected the execution's current state with `Journey.Tools.introspect()` -- what values are set, which values are not set. Has the computation completed? If not, what is it blocking on?
