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