# Scheduler GenServer that orchestrates scheduled tasks by combining `Alarm` timers with serialized callback entries persisted in SQLite. ## Overview `Scheduler` is a GenServer that sits above the `Alarm` module to provide higher-level scheduled task management. While `Alarm` handles raw timer scheduling and fire-and-forget message delivery, `Scheduler` adds a persistence layer that stores the *action to perform* (module, function, arguments) in a SQLite database. When the alarm fires, the scheduler can look up the stored entry and execute the intended callback. This module is currently in an early/prototype stage. ## How It Works 1. A caller sends a `{:new_entry, args}` message to the Scheduler 2. The Scheduler creates an alarm via `Alarm.set_timer/3`, pointing back to itself as the target process 3. The callback entry (module, function, args) is serialized with `:erlang.term_to_binary/1` and stored in the `:scheduler` SQLite database 4. When the alarm fires, the Scheduler receives a `{:alarm_triggered, scheduler_id}` cast 5. The Scheduler can look up the stored entry by `scheduler_id` and execute it ``` Caller │ ├─ GenServer.call(Scheduler, {:new_entry, args}) │ │ │ ├─ Alarm.set_timer("10:30pm", Scheduler, {:alarm_triggered, id}) │ │ │ └─ Sqler.insert(:scheduler, "scheduler", %{entry: binary}) │ v Alarm fires │ └─ GenServer.cast(Scheduler, {:alarm_triggered, scheduler_id}) │ └─ (look up entry, execute callback) ``` ## Public API ### handle_call {:new_entry, args} Creates a new scheduled entry: sets an alarm and persists the callback. ```elixir reply = GenServer.call(Scheduler, {:new_entry, args}) # => %{alarm_id: 123456, scheduler_id: 789, entry: %{module: Pushover, function: :send, args: [...]}} ``` **Returns:** a map with: | Key | Type | Description | |-----|------|-------------| | `:alarm_id` | `integer()` | The ID of the alarm created in the alarm database | | `:scheduler_id` | `integer()` | The ID of the entry in the scheduler database | | `:entry` | `map()` | The callback entry (module, function, args) | ### handle_cast {:alarm_triggered, scheduler_id} Received when an alarm fires. Logs the scheduler ID for the triggered entry. ```elixir GenServer.cast(Scheduler, {:alarm_triggered, scheduler_id}) ``` ## Entry Format Each scheduler entry is a map serialized to binary for SQLite storage: ```elixir %{ module: Pushover, # target module to call function: :send, # function name args: [%{alarm_id: 123}] # arguments to pass } ``` The entry is serialized with `:erlang.term_to_binary/1` and stored in the `entry` column as a BLOB. ## Database Schema Stored in the `:scheduler` SQLite database: | Column | Type | Description | |--------|------|-------------| | `id` | INTEGER | Primary key | | `updated_at` | INTEGER | Last update timestamp | | `entry` | TEXT (BLOB) | Serialized Erlang term (`:erlang.term_to_binary/1`) | ## Dependencies | Module | Purpose | |--------|---------| | `Alarm` | Timer scheduling — creates alarms that fire back to the Scheduler | | `Sqler` | SQLite persistence for scheduler entries (`:scheduler` database) | | `Pushover` | Example target module for push notification callbacks | | `Logger` | Event logging |