class OpenUI::Rails::Stream::Envelope

A single, gem-defined stream frame. This is not an OpenUI protocol: it is the wire shape the Gem normalizes chunks into over Turbo Streams or Action Cable. Ordering is per message and starts at sequence 0.

Payloads never carry tools, credentials or server secrets.

Constants

COMPLETED
DELTA
ERROR
ERROR_CODES
MAX_MESSAGE_ID_BYTES
SNAPSHOT
TYPES

Attributes

data[R]
library_version[R]
message_id[R]
sequence[R]
type[R]

Public Class Methods

completed(message_id:, sequence:, library_version: nil) click to toggle source
# File lib/openui/rails/stream/envelope.rb, line 65
def completed(message_id:, sequence:, library_version: nil)
  new(
    type: COMPLETED, message_id: message_id, sequence: sequence, library_version: library_version,
    data: { "streaming" => false }
  )
end
delta(message_id:, sequence:, delta:, library_version: nil, streaming: true) click to toggle source
# File lib/openui/rails/stream/envelope.rb, line 49
def delta(message_id:, sequence:, delta:, library_version: nil, streaming: true)
  validate_string!(delta, "delta")
  new(
    type: DELTA, message_id: message_id, sequence: sequence, library_version: library_version,
    data: { "delta" => delta, "streaming" => streaming }
  )
end
error(message_id:, sequence:, code:, message: nil, library_version: nil) click to toggle source
# File lib/openui/rails/stream/envelope.rb, line 72
def error(message_id:, sequence:, code:, message: nil, library_version: nil)
  code = code.to_s
  unless ERROR_CODES.include?(code)
    raise StreamError, "unknown error code #{code.inspect}; expected one of #{ERROR_CODES.inspect}"
  end

  new(
    type: ERROR, message_id: message_id, sequence: sequence, library_version: library_version,
    data: { "code" => code, "message" => message, "streaming" => false }.compact
  )
end
new(type:, message_id:, sequence:, library_version: nil, data: {}) click to toggle source
# File lib/openui/rails/stream/envelope.rb, line 32
def initialize(type:, message_id:, sequence:, library_version: nil, data: {})
  @type = validate_type(type).dup.freeze
  @message_id = validate_message_id(message_id).dup.freeze
  @sequence = validate_sequence(sequence)
  unless library_version.nil? || library_version.is_a?(String)
    raise StreamError, "library_version must be a String"
  end
  @library_version = library_version&.dup&.freeze
  unless data.is_a?(Hash) && (data.keys.map(&:to_s) & %w[type message_id sequence library_version]).empty?
    raise StreamError, "envelope data cannot override routing metadata"
  end
  @data = JsonValue.copy(data)
  validate_data!
  freeze
end
snapshot(message_id:, sequence:, response:, library_version: nil, streaming: false) click to toggle source
# File lib/openui/rails/stream/envelope.rb, line 57
def snapshot(message_id:, sequence:, response:, library_version: nil, streaming: false)
  validate_string!(response, "response")
  new(
    type: SNAPSHOT, message_id: message_id, sequence: sequence, library_version: library_version,
    data: { "response" => response, "streaming" => streaming }
  )
end
validate_string!(value, name) click to toggle source
# File lib/openui/rails/stream/envelope.rb, line 84
def validate_string!(value, name)
  return if value.is_a?(String)

  raise StreamError, "#{name} must be a String"
end

Public Instance Methods

==(other) click to toggle source
# File lib/openui/rails/stream/envelope.rb, line 109
def ==(other)
  other.is_a?(Envelope) && other.to_h == to_h
end
code(= data["code"]) click to toggle source
# File lib/openui/rails/stream/envelope.rb, line 93
  def code = data["code"]
  def streaming? = data["streaming"] == true
  def delta? = type == DELTA
  def snapshot? = type == SNAPSHOT
  def completed? = type == COMPLETED
  def error? = type == ERROR

  def to_h
    hash = { "message_id" => message_id, "sequence" => sequence, "type" => type }
    hash["library_version"] = library_version if library_version
    hash.merge!(data)
    hash
  end

  def to_json(*) = JSON.generate(to_h)

  def ==(other)
    other.is_a?(Envelope) && other.to_h == to_h
  end
  alias eql? ==

  def hash = to_h.hash

  private

  def validate_data!
    case type
    when DELTA then self.class.validate_string!(delta, "delta")
    when SNAPSHOT then self.class.validate_string!(response, "response")
    when ERROR
      raise StreamError, "unknown error code #{code.inspect}" unless ERROR_CODES.include?(code)
    end
    if data.key?("streaming") && ![ true, false ].include?(data["streaming"])
      raise StreamError, "streaming must be boolean"
    end
  end

  def validate_type(type)
    type = type.to_s
    raise StreamError, "unknown envelope type #{type.inspect}" unless TYPES.include?(type)

    type
  end

  def validate_message_id(message_id)
    unless message_id.is_a?(String) && !message_id.empty?
      raise StreamError, "message_id must be a non-empty String"
    end

    if message_id.bytesize > MAX_MESSAGE_ID_BYTES
      raise StreamError, "message_id exceeds #{MAX_MESSAGE_ID_BYTES} bytes"
    end

    message_id
  end

  def validate_sequence(sequence)
    unless sequence.is_a?(Integer) && sequence >= 0
      raise StreamError, "sequence must be a non-negative Integer"
    end

    sequence
  end
end
completed?(= type == COMPLETED) click to toggle source
# File lib/openui/rails/stream/envelope.rb, line 97
        def completed? = type == COMPLETED
        def error? = type == ERROR

        def to_h
          hash = { "message_id" => message_id, "sequence" => sequence, "type" => type }
          hash["library_version"] = library_version if library_version
          hash.merge!(data)
          hash
        end

        def to_json(*) = JSON.generate(to_h)

        def ==(other)
          other.is_a?(Envelope) && other.to_h == to_h
        end
        alias eql? ==

        def hash = to_h.hash

        private

        def validate_data!
          case type
          when DELTA then self.class.validate_string!(delta, "delta")
          when SNAPSHOT then self.class.validate_string!(response, "response")
          when ERROR
            raise StreamError, "unknown error code #{code.inspect}" unless ERROR_CODES.include?(code)
          end
          if data.key?("streaming") && ![ true, false ].include?(data["streaming"])
            raise StreamError, "streaming must be boolean"
          end
        end

        def validate_type(type)
          type = type.to_s
          raise StreamError, "unknown envelope type #{type.inspect}" unless TYPES.include?(type)

          type
        end

        def validate_message_id(message_id)
          unless message_id.is_a?(String) && !message_id.empty?
            raise StreamError, "message_id must be a non-empty String"
          end

          if message_id.bytesize > MAX_MESSAGE_ID_BYTES
            raise StreamError, "message_id exceeds #{MAX_MESSAGE_ID_BYTES} bytes"
          end

          message_id
        end

        def validate_sequence(sequence)
          unless sequence.is_a?(Integer) && sequence >= 0
            raise StreamError, "sequence must be a non-negative Integer"
          end

          sequence
        end
      end
    end
  end
end
delta(= data["delta"]) click to toggle source
# File lib/openui/rails/stream/envelope.rb, line 91
def delta = data["delta"]
def response = data["response"]
def code = data["code"]
def streaming? = data["streaming"] == true
def delta? = type == DELTA
def snapshot? = type == SNAPSHOT
def completed? = type == COMPLETED
def error? = type == ERROR

def to_h
  hash = { "message_id" => message_id, "sequence" => sequence, "type" => type }
  hash["library_version"] = library_version if library_version
  hash.merge!(data)
  hash
end

def to_json(*) = JSON.generate(to_h)

def ==(other)
  other.is_a?(Envelope) && other.to_h == to_h
end
alias eql? ==

def hash = to_h.hash

private

def validate_data!
  case type
  when DELTA then self.class.validate_string!(delta, "delta")
  when SNAPSHOT then self.class.validate_string!(response, "response")
  when ERROR
    raise StreamError, "unknown error code #{code.inspect}" unless ERROR_CODES.include?(code)
  end
  if data.key?("streaming") && ![ true, false ].include?(data["streaming"])
    raise StreamError, "streaming must be boolean"
  end
end

def validate_type(type)
  type = type.to_s
  raise StreamError, "unknown envelope type #{type.inspect}" unless TYPES.include?(type)

  type
end

def validate_message_id(message_id)
  unless message_id.is_a?(String) && !message_id.empty?
    raise StreamError, "message_id must be a non-empty String"
  end

  if message_id.bytesize > MAX_MESSAGE_ID_BYTES
    raise StreamError, "message_id exceeds #{MAX_MESSAGE_ID_BYTES} bytes"
  end

  message_id
end

def validate_sequence(sequence)
  unless sequence.is_a?(Integer) && sequence >= 0
    raise StreamError, "sequence must be a non-negative Integer"
  end

  sequence
delta?(= type == DELTA) click to toggle source
# File lib/openui/rails/stream/envelope.rb, line 95
    def delta? = type == DELTA
    def snapshot? = type == SNAPSHOT
    def completed? = type == COMPLETED
    def error? = type == ERROR

    def to_h
      hash = { "message_id" => message_id, "sequence" => sequence, "type" => type }
      hash["library_version"] = library_version if library_version
      hash.merge!(data)
      hash
    end

    def to_json(*) = JSON.generate(to_h)

    def ==(other)
      other.is_a?(Envelope) && other.to_h == to_h
    end
    alias eql? ==

    def hash = to_h.hash

    private

    def validate_data!
      case type
      when DELTA then self.class.validate_string!(delta, "delta")
      when SNAPSHOT then self.class.validate_string!(response, "response")
      when ERROR
        raise StreamError, "unknown error code #{code.inspect}" unless ERROR_CODES.include?(code)
      end
      if data.key?("streaming") && ![ true, false ].include?(data["streaming"])
        raise StreamError, "streaming must be boolean"
      end
    end

    def validate_type(type)
      type = type.to_s
      raise StreamError, "unknown envelope type #{type.inspect}" unless TYPES.include?(type)

      type
    end

    def validate_message_id(message_id)
      unless message_id.is_a?(String) && !message_id.empty?
        raise StreamError, "message_id must be a non-empty String"
      end

      if message_id.bytesize > MAX_MESSAGE_ID_BYTES
        raise StreamError, "message_id exceeds #{MAX_MESSAGE_ID_BYTES} bytes"
      end

      message_id
    end

    def validate_sequence(sequence)
      unless sequence.is_a?(Integer) && sequence >= 0
        raise StreamError, "sequence must be a non-negative Integer"
      end

      sequence
    end
  end
end
error?(= type == ERROR) click to toggle source
# File lib/openui/rails/stream/envelope.rb, line 98
      def error? = type == ERROR

      def to_h
        hash = { "message_id" => message_id, "sequence" => sequence, "type" => type }
        hash["library_version"] = library_version if library_version
        hash.merge!(data)
        hash
      end

      def to_json(*) = JSON.generate(to_h)

      def ==(other)
        other.is_a?(Envelope) && other.to_h == to_h
      end
      alias eql? ==

      def hash = to_h.hash

      private

      def validate_data!
        case type
        when DELTA then self.class.validate_string!(delta, "delta")
        when SNAPSHOT then self.class.validate_string!(response, "response")
        when ERROR
          raise StreamError, "unknown error code #{code.inspect}" unless ERROR_CODES.include?(code)
        end
        if data.key?("streaming") && ![ true, false ].include?(data["streaming"])
          raise StreamError, "streaming must be boolean"
        end
      end

      def validate_type(type)
        type = type.to_s
        raise StreamError, "unknown envelope type #{type.inspect}" unless TYPES.include?(type)

        type
      end

      def validate_message_id(message_id)
        unless message_id.is_a?(String) && !message_id.empty?
          raise StreamError, "message_id must be a non-empty String"
        end

        if message_id.bytesize > MAX_MESSAGE_ID_BYTES
          raise StreamError, "message_id exceeds #{MAX_MESSAGE_ID_BYTES} bytes"
        end

        message_id
      end

      def validate_sequence(sequence)
        unless sequence.is_a?(Integer) && sequence >= 0
          raise StreamError, "sequence must be a non-negative Integer"
        end

        sequence
      end
    end
  end
end
hash(= to_h.hash) click to toggle source
# File lib/openui/rails/stream/envelope.rb, line 114
  def hash = to_h.hash

  private

  def validate_data!
    case type
    when DELTA then self.class.validate_string!(delta, "delta")
    when SNAPSHOT then self.class.validate_string!(response, "response")
    when ERROR
      raise StreamError, "unknown error code #{code.inspect}" unless ERROR_CODES.include?(code)
    end
    if data.key?("streaming") && ![ true, false ].include?(data["streaming"])
      raise StreamError, "streaming must be boolean"
    end
  end

  def validate_type(type)
    type = type.to_s
    raise StreamError, "unknown envelope type #{type.inspect}" unless TYPES.include?(type)

    type
  end

  def validate_message_id(message_id)
    unless message_id.is_a?(String) && !message_id.empty?
      raise StreamError, "message_id must be a non-empty String"
    end

    if message_id.bytesize > MAX_MESSAGE_ID_BYTES
      raise StreamError, "message_id exceeds #{MAX_MESSAGE_ID_BYTES} bytes"
    end

    message_id
  end

  def validate_sequence(sequence)
    unless sequence.is_a?(Integer) && sequence >= 0
      raise StreamError, "sequence must be a non-negative Integer"
    end

    sequence
  end
end
response(= data["response"]) click to toggle source
# File lib/openui/rails/stream/envelope.rb, line 92
def response = data["response"]
def code = data["code"]
def streaming? = data["streaming"] == true
def delta? = type == DELTA
def snapshot? = type == SNAPSHOT
def completed? = type == COMPLETED
def error? = type == ERROR

def to_h
  hash = { "message_id" => message_id, "sequence" => sequence, "type" => type }
  hash["library_version"] = library_version if library_version
  hash.merge!(data)
  hash
end

def to_json(*) = JSON.generate(to_h)

def ==(other)
  other.is_a?(Envelope) && other.to_h == to_h
end
alias eql? ==

def hash = to_h.hash

private

def validate_data!
  case type
  when DELTA then self.class.validate_string!(delta, "delta")
  when SNAPSHOT then self.class.validate_string!(response, "response")
  when ERROR
    raise StreamError, "unknown error code #{code.inspect}" unless ERROR_CODES.include?(code)
  end
  if data.key?("streaming") && ![ true, false ].include?(data["streaming"])
    raise StreamError, "streaming must be boolean"
  end
end

def validate_type(type)
  type = type.to_s
  raise StreamError, "unknown envelope type #{type.inspect}" unless TYPES.include?(type)

  type
end

def validate_message_id(message_id)
  unless message_id.is_a?(String) && !message_id.empty?
    raise StreamError, "message_id must be a non-empty String"
  end

  if message_id.bytesize > MAX_MESSAGE_ID_BYTES
    raise StreamError, "message_id exceeds #{MAX_MESSAGE_ID_BYTES} bytes"
  end

  message_id
end

def validate_sequence(sequence)
  unless sequence.is_a?(Integer) && sequence >= 0
    raise StreamError, "sequence must be a non-negative Integer"
  end

  sequence
end
snapshot?(= type == SNAPSHOT) click to toggle source
# File lib/openui/rails/stream/envelope.rb, line 96
      def snapshot? = type == SNAPSHOT
      def completed? = type == COMPLETED
      def error? = type == ERROR

      def to_h
        hash = { "message_id" => message_id, "sequence" => sequence, "type" => type }
        hash["library_version"] = library_version if library_version
        hash.merge!(data)
        hash
      end

      def to_json(*) = JSON.generate(to_h)

      def ==(other)
        other.is_a?(Envelope) && other.to_h == to_h
      end
      alias eql? ==

      def hash = to_h.hash

      private

      def validate_data!
        case type
        when DELTA then self.class.validate_string!(delta, "delta")
        when SNAPSHOT then self.class.validate_string!(response, "response")
        when ERROR
          raise StreamError, "unknown error code #{code.inspect}" unless ERROR_CODES.include?(code)
        end
        if data.key?("streaming") && ![ true, false ].include?(data["streaming"])
          raise StreamError, "streaming must be boolean"
        end
      end

      def validate_type(type)
        type = type.to_s
        raise StreamError, "unknown envelope type #{type.inspect}" unless TYPES.include?(type)

        type
      end

      def validate_message_id(message_id)
        unless message_id.is_a?(String) && !message_id.empty?
          raise StreamError, "message_id must be a non-empty String"
        end

        if message_id.bytesize > MAX_MESSAGE_ID_BYTES
          raise StreamError, "message_id exceeds #{MAX_MESSAGE_ID_BYTES} bytes"
        end

        message_id
      end

      def validate_sequence(sequence)
        unless sequence.is_a?(Integer) && sequence >= 0
          raise StreamError, "sequence must be a non-negative Integer"
        end

        sequence
      end
    end
  end
end
streaming?(= data["streaming"] == true) click to toggle source
# File lib/openui/rails/stream/envelope.rb, line 94
  def streaming? = data["streaming"] == true
  def delta? = type == DELTA
  def snapshot? = type == SNAPSHOT
  def completed? = type == COMPLETED
  def error? = type == ERROR

  def to_h
    hash = { "message_id" => message_id, "sequence" => sequence, "type" => type }
    hash["library_version"] = library_version if library_version
    hash.merge!(data)
    hash
  end

  def to_json(*) = JSON.generate(to_h)

  def ==(other)
    other.is_a?(Envelope) && other.to_h == to_h
  end
  alias eql? ==

  def hash = to_h.hash

  private

  def validate_data!
    case type
    when DELTA then self.class.validate_string!(delta, "delta")
    when SNAPSHOT then self.class.validate_string!(response, "response")
    when ERROR
      raise StreamError, "unknown error code #{code.inspect}" unless ERROR_CODES.include?(code)
    end
    if data.key?("streaming") && ![ true, false ].include?(data["streaming"])
      raise StreamError, "streaming must be boolean"
    end
  end

  def validate_type(type)
    type = type.to_s
    raise StreamError, "unknown envelope type #{type.inspect}" unless TYPES.include?(type)

    type
  end

  def validate_message_id(message_id)
    unless message_id.is_a?(String) && !message_id.empty?
      raise StreamError, "message_id must be a non-empty String"
    end

    if message_id.bytesize > MAX_MESSAGE_ID_BYTES
      raise StreamError, "message_id exceeds #{MAX_MESSAGE_ID_BYTES} bytes"
    end

    message_id
  end

  def validate_sequence(sequence)
    unless sequence.is_a?(Integer) && sequence >= 0
      raise StreamError, "sequence must be a non-negative Integer"
    end

    sequence
  end
end
    
to_h() click to toggle source
# File lib/openui/rails/stream/envelope.rb, line 100
def to_h
  hash = { "message_id" => message_id, "sequence" => sequence, "type" => type }
  hash["library_version"] = library_version if library_version
  hash.merge!(data)
  hash
end
to_json(*) click to toggle source
# File lib/openui/rails/stream/envelope.rb, line 107
    def to_json(*) = JSON.generate(to_h)

    def ==(other)
      other.is_a?(Envelope) && other.to_h == to_h
    end
    alias eql? ==

    def hash = to_h.hash

    private

    def validate_data!
      case type
      when DELTA then self.class.validate_string!(delta, "delta")
      when SNAPSHOT then self.class.validate_string!(response, "response")
      when ERROR
        raise StreamError, "unknown error code #{code.inspect}" unless ERROR_CODES.include?(code)
      end
      if data.key?("streaming") && ![ true, false ].include?(data["streaming"])
        raise StreamError, "streaming must be boolean"
      end
    end

    def validate_type(type)
      type = type.to_s
      raise StreamError, "unknown envelope type #{type.inspect}" unless TYPES.include?(type)

      type
    end

    def validate_message_id(message_id)
      unless message_id.is_a?(String) && !message_id.empty?
        raise StreamError, "message_id must be a non-empty String"
      end

      if message_id.bytesize > MAX_MESSAGE_ID_BYTES
        raise StreamError, "message_id exceeds #{MAX_MESSAGE_ID_BYTES} bytes"
      end

      message_id
    end

    def validate_sequence(sequence)
      unless sequence.is_a?(Integer) && sequence >= 0
        raise StreamError, "sequence must be a non-negative Integer"
      end

      sequence
    end
  end
end
validate_data!() click to toggle source
# File lib/openui/rails/stream/envelope.rb, line 118
def validate_data!
  case type
  when DELTA then self.class.validate_string!(delta, "delta")
  when SNAPSHOT then self.class.validate_string!(response, "response")
  when ERROR
    raise StreamError, "unknown error code #{code.inspect}" unless ERROR_CODES.include?(code)
  end
  if data.key?("streaming") && ![ true, false ].include?(data["streaming"])
    raise StreamError, "streaming must be boolean"
  end
end
validate_message_id(message_id) click to toggle source
# File lib/openui/rails/stream/envelope.rb, line 137
def validate_message_id(message_id)
  unless message_id.is_a?(String) && !message_id.empty?
    raise StreamError, "message_id must be a non-empty String"
  end

  if message_id.bytesize > MAX_MESSAGE_ID_BYTES
    raise StreamError, "message_id exceeds #{MAX_MESSAGE_ID_BYTES} bytes"
  end

  message_id
end
validate_sequence(sequence) click to toggle source
# File lib/openui/rails/stream/envelope.rb, line 149
def validate_sequence(sequence)
  unless sequence.is_a?(Integer) && sequence >= 0
    raise StreamError, "sequence must be a non-negative Integer"
  end

  sequence
end
validate_type(type) click to toggle source
# File lib/openui/rails/stream/envelope.rb, line 130
def validate_type(type)
  type = type.to_s
  raise StreamError, "unknown envelope type #{type.inspect}" unless TYPES.include?(type)

  type
end