Flezha.SDK.Ctx (flezha_sdk v0.1.0)

Copy Markdown View Source

The invocation context handed to Tasklet.run/3. use Flezha.SDK.Tasklet imports the functions below, so a tasklet writes emit(ctx, value) and complete(ctx, result); apply/3 is not imported, since it would shadow Kernel.apply/3, and a use tasklet never calls it: its defchange recorders do.

emit/2 and emit/3 (with key: and metadata:) accumulate outputs for this invocation; apply/3 folds a change into the state through the tasklet's apply_change/2 and records it for the Cell's record stream, the only recorded mutation path; now/1 is the declared clock's value, evaluated once by the core for this input, and the scheduled timestamp inside a firing; cell_key/1 is the owning Cell's key (or nil for a Single Cell). Emits, changes, timer operations and the completion are held in the invoking worker's dictionary under a per-invocation ref and are taken only when run/3 returns {:ok, _}: a failing invocation discards them all. Reserved flz.-prefixed metadata keys are refused.

A key: rides an emit only where the node's output is declared keyed/1, and a keyless emit there from a Cell with no key of its own is refused at the call.

timer_after/2 and timer_at_event_time/2 record a registration and answer {:ok, at}, the absolute timestamp cancel_timer/3 takes beside the kind; a cancel answers :ok. flz_core judges the item's timer ops when it settles: past the tasklet's max_timers_per_cell/0, or an event-time timer under another clock, the item fails, not retried. Inside a compensate/3 hook the three timer calls answer runtime.timers_unavailable and arm nothing.

spawn_steps/4, effect/4 and part/2 reach flz_core from the live activation, and only from the worker running the invocation: the same call from another process is refused.

Summary

Functions

Fold change into state with the tasklet's apply_change/2 and record it, so the Cell ships it after the invocation succeeds. The returned state is what run/3 returns in its {:ok, _}.

Cancel the kind timer (:processing_time or :event_time) its registration answered at for; one not pending changes nothing.

The owning Cell's key, or nil for a Single Cell.

Close the staged checkpoint interval at the end of this invocation. A no-op on a class that did not declare checkpoint granularity.

End the instance this tasklet runs in, with result as the result its completion reports. result is encoded as spawn_steps/4 encodes its payload; nil carries no result.

Run fun, a call to anything outside the instance (a model, a tool, a service), and keep its result: when this invocation runs again (a crash re-run, a replay, a wake, a move), the call answers the kept result and fun does not run. The result is kept, synced on the controller, before this returns; a crash between fun finishing and that point runs fun again, so give the outside service stable_id/1 as an idempotency key where it takes one.

Emit a value on the tasklet's outgoing channel.

Emit a value with options: key:, the output's natural key, only on an output declared keyed/1; metadata:, a map whose reserved flz.-prefixed keys are refused. Any other option raises ArgumentError.

The declared clock's value for this input; the scheduled timestamp inside a firing.

Send value as a part: a preview of this invocation's output on its channel, which leaves now rather than when run/3 returns, encoded as an emit of value would be. It never waits: a part the wire cannot take at once is dropped, and an invocation of a replayed input sends none.

Whether this invocation replays an input the Cell already ran.

Request spawn-or-deliver of a steps instance of factory_id in this runner's namespace, with payload as its first input: bytes as they are, anything else as JSON, and a term with no JSON form raises. Answers a Flezha.SDK.Spawn handle without waiting, read with Flezha.SDK.Spawn.await/2 or Flezha.SDK.Spawn.try_result/1 inside this invocation, or not at all.

A replay-stable 128-bit id, 32 lowercase hex digits: FNV-1a-128 over the Cell's identity, the change seq this invocation started at and a per-invocation ordinal. The same invocation on a replay derives the same ids in the same order, which is what makes an effect idempotent.

Register a processing-time timer delay_ms after now/1; {:ok, at}, the cancellation handle.

Register an event-time timer at at; {:ok, at}.

Types

emit_option()

@type emit_option() :: {:key, String.t()} | {:metadata, map()}

An emit/3 option.

t()

@type t() :: %Flezha.SDK.Ctx{
  cell_key: term(),
  channel: term(),
  core: term(),
  identity: term(),
  item: term(),
  keyed_sink?: term(),
  module: term(),
  now_ms: term(),
  offset: term(),
  owner: term(),
  part_encode: term(),
  ref: term(),
  replay?: term(),
  seq: term(),
  timers?: term(),
  token: term()
}

Functions

apply(ctx, state, change)

@spec apply(t(), term(), term()) :: term()

Fold change into state with the tasklet's apply_change/2 and record it, so the Cell ships it after the invocation succeeds. The returned state is what run/3 returns in its {:ok, _}.

cancel_timer(ctx, kind, at)

@spec cancel_timer(t(), Flezha.SDK.TimerFiring.kind(), non_neg_integer()) ::
  :ok | {:error, Flezha.SDK.Error.t()}

Cancel the kind timer (:processing_time or :event_time) its registration answered at for; one not pending changes nothing.

cell_key(ctx)

The owning Cell's key, or nil for a Single Cell.

checkpoint(ctx)

@spec checkpoint(t()) :: :ok

Close the staged checkpoint interval at the end of this invocation. A no-op on a class that did not declare checkpoint granularity.

complete(ctx, result)

@spec complete(t(), term()) :: :ok

End the instance this tasklet runs in, with result as the result its completion reports. result is encoded as spawn_steps/4 encodes its payload; nil carries no result.

In a class that runs in steps the rest of the iteration still runs and commits; once that commit is durable the instance ends instead of waiting for its next input. In a flow class the instance runs what its Cells already hold to its outputs, then ends; a later call before that replaces the result.

effect(ctx, name, fun, codec \\ nil)

@spec effect(
  t(),
  String.t(),
  (-> term()),
  {(term() -> binary()), (binary() -> term())} | nil
) ::
  {:ok, term()}
  | {:error, Flezha.SDK.Error.t() | {:non_retryable, Flezha.SDK.Error.t()}}

Run fun, a call to anything outside the instance (a model, a tool, a service), and keep its result: when this invocation runs again (a crash re-run, a replay, a wake, a move), the call answers the kept result and fun does not run. The result is kept, synced on the controller, before this returns; a crash between fun finishing and that point runs fun again, so give the outside service stable_id/1 as an idempotency key where it takes one.

name names the call: a non-empty string of at most 255 bytes, counted per invocation. One call runs at a time; a concurrent one is refused. The result is encoded as spawn_steps/4 encodes its payload and read back as JSON, else as the binary; codec, an {encode, decode} pair of one-argument functions, replaces both.

An invocation that took no input-log offset runs fun each time and keeps nothing. A raise in fun propagates and nothing is kept. Answers {:ok, result}, or {:error, reason} for run/3 to return as it is: {:non_retryable, error} for effect.bad_name, effect.nested, effect.outside_invocation and effect.too_large, and the retryable effect.not_recorded error.

emit(ctx, value)

@spec emit(t(), term()) :: :ok | {:error, Flezha.SDK.Error.t()}

Emit a value on the tasklet's outgoing channel.

emit(ctx, value, opts)

@spec emit(t(), term(), [emit_option()]) :: :ok | {:error, Flezha.SDK.Error.t()}

Emit a value with options: key:, the output's natural key, only on an output declared keyed/1; metadata:, a map whose reserved flz.-prefixed keys are refused. Any other option raises ArgumentError.

now(ctx)

The declared clock's value for this input; the scheduled timestamp inside a firing.

part(ctx, value)

@spec part(t(), term()) :: :ok | {:error, Flezha.SDK.Error.t()}

Send value as a part: a preview of this invocation's output on its channel, which leaves now rather than when run/3 returns, encoded as an emit of value would be. It never waits: a part the wire cannot take at once is dropped, and an invocation of a replayed input sends none.

:ok, or {:error, %Error{}}: part.no_output when the node's channel does not end in an output (a call end is not one), part.outside_invocation through a ctx whose invocation has returned or from another process.

replay(ctx)

@spec replay(t()) :: boolean()

Whether this invocation replays an input the Cell already ran.

spawn_steps(ctx, factory_id, payload, opts \\ [])

@spec spawn_steps(t(), String.t(), term(), [Flezha.SDK.Spawn.option()]) ::
  Flezha.SDK.Spawn.t()

Request spawn-or-deliver of a steps instance of factory_id in this runner's namespace, with payload as its first input: bytes as they are, anything else as JSON, and a term with no JSON form raises. Answers a Flezha.SDK.Spawn handle without waiting, read with Flezha.SDK.Spawn.await/2 or Flezha.SDK.Spawn.try_result/1 inside this invocation, or not at all.

With id: the instance starts under that id when it is free; while it runs, if_running: :deliver (the default) hands it payload as one more input and if_running: :error resolves already_running. if_running: without id: resolves the core's spawn.if_running_without_id at the call and sends nothing.

Without id: the controller generates one, and every call starts a new instance: a call reissued after a lost or timed-out answer starts a second one, and a replayed invocation resolves skipped_on_replay. A crash re-run of an input the Cell had not durably consumed asks again under the same identity, and the controller answers the instance the first ask started.

stable_id(ctx)

@spec stable_id(t()) :: String.t()

A replay-stable 128-bit id, 32 lowercase hex digits: FNV-1a-128 over the Cell's identity, the change seq this invocation started at and a per-invocation ordinal. The same invocation on a replay derives the same ids in the same order, which is what makes an effect idempotent.

timer_after(ctx, delay_ms)

@spec timer_after(t(), integer()) ::
  {:ok, non_neg_integer()} | {:error, Flezha.SDK.Error.t()}

Register a processing-time timer delay_ms after now/1; {:ok, at}, the cancellation handle.

timer_at_event_time(ctx, at)

@spec timer_at_event_time(t(), non_neg_integer()) ::
  {:ok, non_neg_integer()} | {:error, Flezha.SDK.Error.t()}

Register an event-time timer at at; {:ok, at}.