mvu

MVU

A concurrency-safe Model-View-Update (The Elm Architecture) implementation for Crystal.

Installation

Add the dependency to your shard.yml:

dependencies:
  unicode_grapheme:
    github: shpeckman/unicode_grapheme

Then run shards install.

Usage

module App
  struct Increment; include MVU::Msg; end
  struct Decrement; include MVU::Msg; end

  struct Model
    include MVU::Model

    getter count : Int32

    def initialize(@count = 0)
    end

    def update(msg : MVU::Msg) : {self, MVU::Cmd}
      case msg
      when Increment then {Model.new(@count + 1), MVU::Cmd.none}
      when Decrement then {Model.new(@count - 1), MVU::Cmd.none}
      else {self, MVU::Cmd.none}
      end
    end

    def view : String
      "Count: #{@count}"
    end
  end
end

program = MVU::Program.new(App::Model.new)
program.run do |model|
  puts model.view
end

Commands (Cmd)

Commands represent side-effects that may yield a new Msg.

MVU::Cmd.none             # No side-effects
MVU::Cmd.sync { ... }     # Run a blocking block that returns a Msg?
MVU::Cmd.of { ... }       # Run an async block (spawned) that returns a Msg?
MVU::Cmd.batch([...])     # Combine multiple commands

Subscriptions (Sub)

Subscriptions allow listening to ongoing external events, like I/O or timers. To use them, implement subscription_ids and subscription(id) in your model. The Program automatically manages the lifecycle of subscriptions.

def subscription_ids : Array(MVU::SubId)
  return MVU::Sub::NO_IDS unless @listening
  [:timer]
end

def subscription(id : MVU::SubId) : MVU::Sub
  case id
  when :timer
    MVU::Sub.new(id) do |dispatch, cancel|
      until cancel.closed?
        sleep 1
        dispatch.call(Tick.new) unless cancel.closed?
      end
    end
  else
    raise "Unknown sub: #{id}"
  end
end

Middleware

Middleware allows hooking into the update loop (e.g., for logging or persistence).

class Logger(M) < MVU::Middleware(M)
  def call(model : M, msg : MVU::Msg, next_fn : Proc(M, MVU::Msg, {M, MVU::Cmd})) : {M, MVU::Cmd}
    puts "Received: #{msg}"
    next_fn.call(model, msg)
  end
end

MVU::Program.new(App::Model.new, middlewares: [Logger(App::Model).new])
Repository

mvu

Owner
Statistic
  • 0
  • 0
  • 0
  • 1
  • 0
  • about 2 hours ago
  • August 12, 2026
License

MIT License

Links
Synced at

Wed, 12 Aug 2026 17:15:48 GMT

Languages