Module: MaquinaStream::Streamable::Generated

Defined in:
lib/maquina_stream/streamable.rb

Overview

The host contract, generated by the maquina_stream macro. Included, so a host method defined in the class body overrides it.

These are the methods the engine calls on a record — Broadcaster, Manifest, Export and MaquinaStream.render between them use nothing else. A host that cannot use the macro implements this module's public methods itself and never includes it.

Constant Summary collapse

REQUIRED_COLUMNS =
{
  maquina_stream_buffer: :buffer,
  maquina_stream_append: :buffer,
  maquina_stream_sequence: SEQUENCE_COLUMN,
  maquina_stream_open?: STATUS_COLUMN,
  maquina_stream_seal!: STATUS_COLUMN
}.freeze

Instance Method Summary collapse

Instance Method Details

#maquina_stream_advance ⇒ Object

Increments the sequence and returns the new value.

Added in Phase 3. The sequence is documented as "incremented per frame", but the Broadcaster cannot write host state directly without contradicting "host owns persistence" - so it asks, through the contract, and the host's database does the incrementing atomically. That is also what keeps it monotonic under concurrent appends.



195
196
197
198
199
# File 'lib/maquina_stream/streamable.rb', line 195

def maquina_stream_advance
  maquina_stream_require_column!(SEQUENCE_COLUMN, :maquina_stream_advance)
  self.class.where(id: id).update_all("#{SEQUENCE_COLUMN} = #{SEQUENCE_COLUMN} + 1")
  reload.maquina_stream_sequence
end

#maquina_stream_append(text) ⇒ Object

Appends text to the buffer, persists it, and returns the whole buffer.

The host owns persistence, which is why this and not the broadcaster writes. Broadcaster#append calls it first and only then has something to broadcast.

Raises ContractError when the buffer column does not exist.



174
175
176
177
178
179
# File 'lib/maquina_stream/streamable.rb', line 174

def maquina_stream_append(text)
  column = buffer_column
  maquina_stream_require_column!(column, :maquina_stream_append)
  update!(column => "#{public_send(column)}#{text}")
  maquina_stream_buffer
end

#maquina_stream_buffer ⇒ Object

The raw markdown written so far, as a String. Never HTML: the buffer is what the model wrote, and rendering it is the engine's job.

Raises ContractError when the column named by buffer: does not exist.



162
163
164
# File 'lib/maquina_stream/streamable.rb', line 162

def maquina_stream_buffer
  maquina_stream_read(buffer_column, :maquina_stream_buffer).to_s
end

#maquina_stream_id ⇒ Object

A stable String, unique per message, used to build every DOM id in the message and to look the record back up in a repair request. Defaults to to_param.



154
155
156
# File 'lib/maquina_stream/streamable.rb', line 154

def maquina_stream_id
  to_param.to_s
end

#maquina_stream_open? ⇒ Boolean

Whether the stream is still being written.

This is what decides render mode and what decides cacheability: an open message is never cached, because it is about to change.

Returns:

  • (Boolean)


217
218
219
# File 'lib/maquina_stream/streamable.rb', line 217

def maquina_stream_open?
  maquina_stream_read(STATUS_COLUMN, :maquina_stream_open?).to_s == OPEN_STATUS
end

#maquina_stream_seal!(status: :complete) ⇒ Object

Closes the stream and returns the status Symbol it sealed with.

status: must be one of SEAL_STATUSES: :complete, :cancelled, :errored or :timed_out. Anything else raises ArgumentError.

Sealing only records the status. Broadcaster#seal! is what also emits the final frame, and that frame is never coalesced and never skipped — so a host seals through the broadcaster, not through this directly, unless it means to close a stream silently.



230
231
232
233
234
235
236
237
238
# File 'lib/maquina_stream/streamable.rb', line 230

def maquina_stream_seal!(status: :complete)
  unless SEAL_STATUSES.include?(status.to_sym)
    raise ArgumentError, "status must be one of #{SEAL_STATUSES.join(", ")}, got #{status.inspect}"
  end

  maquina_stream_require_column!(STATUS_COLUMN, :maquina_stream_seal!)
  update!(STATUS_COLUMN => status.to_s)
  status.to_sym
end

#maquina_stream_sequence ⇒ Object

The current frame sequence number, as an Integer. Monotonic. It moves once per frame that actually goes out, not once per append — the client uses it to notice that it missed one.



184
185
186
# File 'lib/maquina_stream/streamable.rb', line 184

def maquina_stream_sequence
  maquina_stream_read(SEQUENCE_COLUMN, :maquina_stream_sequence).to_i
end

#maquina_stream_status ⇒ Object

The recorded end state as a Symbol — one of SEAL_STATUSES — for replay and export. Nil while still open.

Export reads this to decide whether to append a status footer: a cancelled message that exports as though it were complete is a lie in a file somebody keeps.



207
208
209
210
211
# File 'lib/maquina_stream/streamable.rb', line 207

def maquina_stream_status
  return nil if maquina_stream_open?

  maquina_stream_read(STATUS_COLUMN, :maquina_stream_status)&.to_sym
end

#maquina_stream_target ⇒ Object

The Turbo broadcast target, from the stream_for: callable the macro was given. Whatever that callable returns is passed straight to Turbo::StreamsChannel.

Raises ContractError when stream_for: is not callable: the broadcast target belongs to the host, and the engine never guesses one.



246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/maquina_stream/streamable.rb', line 246

def maquina_stream_target
  resolver = self.class.maquina_stream_target_resolver
  unless resolver.respond_to?(:call)
    raise ContractError, <<~MESSAGE
      #{self.class.name} does not supply #maquina_stream_target: `stream_for:`
      is not callable. The broadcast target belongs to the host — the engine
      never guesses one. See docs/streaming.md.
    MESSAGE
  end

  resolver.call(self)
end