telega/dialog/engine
Compiles a Dialog into a Flow(String, ...) and dispatches events.
Each window becomes a flow step (step type = the window id). The step handler runs the render → wait → dispatch cycle:
Pending— render the window (edit-or-send) and park onWaitDataCallback— parse thedlg:scheme, guard against stale/foreign messages, runwindow.on_action, auto-answer the callback queryTextInput— runwindow.on_textif set, otherwise re-render
The engine is type-erased: telega/dialog.build() wraps every
user-typed window into a Window(String, ...) whose closures carry the
dialog’s own state codec, so the engine only ever moves the encoded state
string around. This is what lets a sub-dialog with a different state type
live inside its parent’s flow.
Sub-dialogs
Sub-dialogs deliberately do NOT use the flow engine’s
EnterSubflow/FlowStackFrame machinery: that path never re-executes
the parent step after the sub-flow returns (and sets no wait token, so
auto-resume cannot wake it), resumes sub-flow steps against the parent’s
step registry, and loses the parent’s history. Instead the sub-dialog’s
windows are compiled into the parent flow as <sub_id>.<window_id> steps
and the engine keeps its own return bookkeeping in instance data:
| key | content |
|---|---|
__dialog_sub | active sub-dialog id (absent at top level) |
__dialog_return_window | parent window that emitted StartSub |
__dialog_sub_saved | parent’s encoded state while the sub runs |
The live message, its kind, waits, TTL and persistence are shared with
the parent for free (same instance). A Back that would cross the sub
boundary cancels the sub and returns to the parent window without calling
on_sub_result.
Navigation deliberately never uses the flow GoTo action — it erases
history, which would break Back. Only Next/NextString/Back are
emitted, plus the history-preserving Jump for sub-dialog enter/return
(which must not push sub steps onto the parent history).
Types
Everything the engine needs to know about a dialog, with all window
handlers erased to the encoded-state form. Built by
telega/dialog.build(); not constructed by hand.
pub type CompiledDialog(session, error, dependencies) {
CompiledDialog(
id: String,
windows: dict.Dict(
String,
types.Window(String, session, error, dependencies),
),
initial: String,
initial_encoded: fn() -> String,
on_done: option.Option(
fn(String, bot.Context(session, error, dependencies)) -> Result(
bot.Context(session, error, dependencies),
error,
),
),
subs: dict.Dict(String, CompiledSub),
storage: types.FlowStorage(error),
ttl_ms: option.Option(Int),
labels: fn(bot.Context(session, error, dependencies)) -> types.Labels,
)
}
Constructors
-
CompiledDialog( id: String, windows: dict.Dict( String, types.Window(String, session, error, dependencies), ), initial: String, initial_encoded: fn() -> String, on_done: option.Option( fn(String, bot.Context(session, error, dependencies)) -> Result( bot.Context(session, error, dependencies), error, ), ), subs: dict.Dict(String, CompiledSub), storage: types.FlowStorage(error), ttl_ms: option.Option(Int), labels: fn(bot.Context(session, error, dependencies)) -> types.Labels, )Arguments
- windows
-
Parent windows plus every attached sub-dialog’s windows under namespaced ids (
<sub_id>.<window_id>).
A sub-dialog attachment: how to build its starting state and how to turn
its final state into the result dict for on_sub_result.
pub type CompiledSub {
CompiledSub(
id: String,
initial: String,
init: fn(String, dict.Dict(String, String)) -> String,
result: fn(String) -> dict.Dict(String, String),
)
}
Constructors
-
CompiledSub( id: String, initial: String, init: fn(String, dict.Dict(String, String)) -> String, result: fn(String) -> dict.Dict(String, String), )Arguments
- initial
-
Namespaced id of the sub-dialog’s initial window.
- init
-
fn(parent_encoded_state, args) -> sub_encoded_state - result
-
fn(sub_encoded_state) -> result dict
Values
pub fn compile(
dialog: CompiledDialog(session, error, dependencies),
) -> types.Flow(String, session, error, dependencies)
Compile a dialog into a flow. Window id = step name (identity
converters), on_error is always set (the flow engine silently swallows
errors otherwise): it logs, emits telemetry, and re-renders the current
window best-effort.
pub const flow_name_prefix: String
Flow names are prefixed so dialogs never collide with hand-written flows in the same registry.
pub fn parse_callback_data(
data: String,
) -> Result(#(String, String, types.ActionEvent), Nil)
Parse dlg:<dialog_id>:<window_id>:<action_id>[:<arg>]. Extra segments
are joined back into the arg, so args may contain :.
pub const sub_separator: String
Separator between a sub-dialog id and its window ids in step names and
callback data (address.city). Forbidden in dialog/window/sub ids.