MV Whatever Hypertext as the engine of Application State - sgml/signature GitHub Wiki

HATEOAS

               +-------------------------------+
               |  Initial GET request to API   |
               |  e.g. GET https://api.example |
               +-------------------------------+
                            |
                            v
             +----------------------------------+
             | Response contains _links object  |
             | e.g.                             |
             | "_links": {                      |
             |   "self": { "href": "/resource" }|
             |   "next": { "href": "/page/2" }  |
             |   "related": { "href": "/other" }|
             | }                                |
             +----------------------------------+
                            |
      +---------------------+----------------------+
      |                     |                      |
      v                     v                      v
GET /resource        GET /page/2            GET /other

      |                     |                      |
      v                     v                      v
More _links         More _links            More _links or content
to follow           to follow              to follow

MVC Using SQLite Schema, Python Event Loops, and SQLite Triggers

SQLite Schema

-- States
CREATE TABLE states (
    name TEXT PRIMARY KEY,
    type TEXT,           -- 'normal' or 'final'
    entry_action TEXT    -- optional
);

-- Transitions
CREATE TABLE transitions (
    source TEXT,
    target TEXT,
    condition TEXT,      -- optional
    FOREIGN KEY(source) REFERENCES states(name),
    FOREIGN KEY(target) REFERENCES states(name)
);

-- Context
CREATE TABLE context (
    key TEXT PRIMARY KEY,
    value TEXT
);

-- Current state
CREATE TABLE current_state (
    name TEXT PRIMARY KEY
);

SQLite Triggers

-- Trigger: On state change, run entry action
CREATE TRIGGER run_entry_action
AFTER UPDATE ON current_state
BEGIN
    -- Clear input
    UPDATE context SET value = '' WHERE key = 'inputValue'
        AND (SELECT entry_action FROM states WHERE name = NEW.name) = 'clearInputValue';

    -- Focus input
    UPDATE context SET value = '1' WHERE key = 'isFocused'
        AND (SELECT entry_action FROM states WHERE name = NEW.name) = 'focusInput';
END;

-- Trigger: Transition from checkInputState to clearValue if input is empty
CREATE TRIGGER transition_check_input
AFTER UPDATE ON current_state
WHEN NEW.name = 'checkInputState' AND
     (SELECT value FROM context WHERE key = 'inputValue') = ''
BEGIN
    UPDATE current_state SET name = 'clearValue';
END;

-- Trigger: Transition from clearValue to checkFocus
CREATE TRIGGER transition_clear_value
AFTER UPDATE ON current_state
WHEN NEW.name = 'clearValue'
BEGIN
    UPDATE current_state SET name = 'checkFocus';
END;

-- Trigger: Transition from checkFocus to setFocus if not focused
CREATE TRIGGER transition_check_focus
AFTER UPDATE ON current_state
WHEN NEW.name = 'checkFocus' AND
     (SELECT value FROM context WHERE key = 'isFocused') = '0'
BEGIN
    UPDATE current_state SET name = 'setFocus';
END;

-- Trigger: Transition from setFocus to done
CREATE TRIGGER transition_set_focus
AFTER UPDATE ON current_state
WHEN NEW.name = 'setFocus'
BEGIN
    UPDATE current_state SET name = 'done';
END;

Python Driver

import sqlite3

conn = sqlite3.connect(":memory:")
cursor = conn.cursor()

# Load schema and triggers
cursor.executescript(open("schema.sql").read())
cursor.executescript(open("triggers.sql").read())

# Insert states
cursor.executemany("INSERT INTO states VALUES (?, ?, ?)", [
    ("checkInputState", "normal", None),
    ("clearValue", "normal", "clearInputValue"),
    ("checkFocus", "normal", None),
    ("setFocus", "normal", "focusInput"),
    ("done", "final", None)
])

# Insert context
cursor.executemany("INSERT INTO context VALUES (?, ?)", [
    ("inputValue", ""),         # Empty input triggers clearValue
    ("isFocused", "0")          # Not focused triggers setFocus
])

# Set initial state
cursor.execute("INSERT INTO current_state VALUES ('checkInputState')")
conn.commit()

State Machine

def state_machine_gen(conn):
    cursor = conn.cursor()
    while True:
        state = cursor.execute("SELECT name FROM current_state").fetchone()[0]
        yield state

        # Final state check
        state_type = cursor.execute("SELECT type FROM states WHERE name = ?", (state,)).fetchone()[0]
        if state_type == "final":
            return

        # Trigger next transition by re-setting current state
        cursor.execute("UPDATE current_state SET name = ?", (state,))
        conn.commit()

Example

gen = state_machine_gen(conn)

try:
    while True:
        state = next(gen)
        print(f"🟒 Transitioned to: {state}")
except StopIteration:
    print("βœ… State machine completed.")

Output

🟒 Transitioned to: checkInputState
🟒 Transitioned to: clearValue
🟒 Transitioned to: checkFocus
🟒 Transitioned to: setFocus
🟒 Transitioned to: done
βœ… State machine completed.

Robustness

Category Error Eliminated How It's Prevented
Control Flow Infinite loops / runaway recursion No loops used; transitions are bounded by declarative triggers
State Drift Invalid or skipped states Transitions are enforced via foreign keys and trigger logic
Race Conditions Concurrent mutation of state/context SQLite's transactional integrity ensures atomic updates
Guard Evaluation Misapplied or skipped guard conditions Guards encoded in SQL WHEN clauses, not procedural logic
Entry Action Execution Forgotten or duplicated entry actions Triggers fire automatically on state change
Transition Ordering Out-of-order or ambiguous transitions Deterministic trigger firing order via SQLite engine
Context Mutation Inconsistent context updates Context is mutated via SQL triggers with scoped conditions
Final State Handling Failure to detect or halt on final state Final state type checked explicitly before yielding
Auditability Missing transition logs or state history Can be extended with append-only log tables and triggers
Reproducibility Non-deterministic execution paths Entire flow encoded in schema and metadata; replayable via SQL
Dependency Injection Implicit dependencies in procedural code All dependencies (guards/actions) are explicit in schema
Loop Exit Conditions Incorrect or missing loop termination No loops used; generator yields and triggers drive progression

State Machine Diagram

                          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                          β”‚        context table       β”‚
                          │────────────────────────────│
                          β”‚ key          | value       β”‚
                          │──────────────┼─────────────│
                          β”‚ inputValue   | "..."       β”‚
                          β”‚ isFocused    | "0" or "1"  β”‚
                          β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                 β”‚
                                 β”‚ (read/write via triggers)
                                 β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚      current_state         │──▢│         states table        β”‚
│────────────────────────────│   │────────────────────────────│
β”‚ name         | active      β”‚   β”‚ name        | entry_action β”‚
│──────────────┼─────────────│   │─────────────┼──────────────│
β”‚ "checkInput" |     βœ“       β”‚   β”‚ "clearValue"| "clearInput" β”‚
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       β”‚                                β”‚
       β”‚ UPDATE                         β”‚ JOIN
       β–Ό                                β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚      transitions table     β”‚   β”‚       triggers (SQL)       β”‚
│────────────────────────────│   │────────────────────────────│
β”‚ source      | target       β”‚   β”‚ ON UPDATE current_state    β”‚
β”‚ condition   |              β”‚   β”‚ WHEN context matches guard β”‚
│─────────────┼──────────────│   β”‚ β†’ UPDATE current_state     β”‚
β”‚ "checkInput"| "clearValue" β”‚   β”‚ β†’ UPDATE context           β”‚
β”‚             |              β”‚   β”‚ β†’ INSERT INTO audit_log    │◄───┐
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
       β–²                         β–²                                  β”‚
       β”‚                         β”‚                                  β”‚
       β”‚     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”              β”‚
       β”‚     β”‚         audit_log table (optional)    β”‚β—„β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       β”‚     │───────────────────────────────────────│
       β”‚     β”‚ timestamp | from_state | to_state     β”‚
       β”‚     β”‚ context_snapshot | transition_id      β”‚
       β”‚     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       β”‚
       β”‚
       β–Ό
                β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                β”‚ Python generator           β”‚
                │────────────────────────────│
                β”‚ yield current_state        β”‚
                β”‚ next() β†’ triggers          β”‚
                β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                          β–²
                          β”‚
                          β”‚
                β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                β”‚  deterministic    │◄─── Declarative logic ensures
                β”‚  transitions      β”‚     reproducible outcomes
                β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

MVC

MVVM

Constructors vs Getters and Setters

Why should I create a method just to hold a one line function? (+Constructor)

How painful is it going to be to refactor two, three, four, five or more getters/setters vs one constructor?(+Constructor)

How hard is it going to be to document two, three, four, five or more getters/setters vs one constructor?(+Constructor)

Is there going to be a default value which will be documented? (+Constructor)

Do I like documentation and expect people to read? (+Constructor)

Will the initial value be undefined?(+Setter)

Is there a set of equivalent forms (shorthand, international, nicknames) which will all be acceptable as syntatically correct for required arguments? (+Setter)

Is there a set of optional arguments with default values? (+Setter)

Is there a common need to stringify and parse the initial value? (+Setter)

Do I dislike documentation and expect people to experiment? (+Setter)