BRAIDGROUP
RESEARCH & DEV
62. Framework Docs

Merge State Management

Merge is Braid's state management library (braid-lang/lib/frameworks/merge/merge.br, 311 lines), inspired by Redux. It provides a centralized store with dispatch, subscriptions, computed values, undo/redo, selector caching, devtools integration, and a middleware pipeline.

Creating a Store

import merge;

let initialState = { count: 0, todos: [] };

let reducer = fn(state, action) {
    if action.type == "INCREMENT" {
        return { count: state.count + 1, todos: state.todos };
    }
    if action.type == "DECREMENT" {
        return { count: state.count - 1, todos: state.todos };
    }
    if action.type == "SET_COUNT" {
        return { count: action.payload, todos: state.todos };
    }
    if action.type == "ADD_TODO" {
        return { count: state.count, todos: state.todos + [action.payload] };
    }
    return state;
};

let store = merge.merge_createStore(reducer, initialState);

Core API

Dispatch & Get State

store.dispatch({ type: "INCREMENT" });
store.dispatch({ type: "SET_COUNT", payload: 42 });

let current = store.getState();
// { count: 42, todos: [] }

// Standalone dispatch helper
merge.merge_dispatch(store, { type: "INCREMENT" });
merge.merge_getState(store);

Subscribe to Changes

let unsubscribe = store.subscribe(fn(new_state, prev_state) {
    print("State changed:", new_state);
});

// Later:
unsubscribe();  // removes the listener

// Standalone helper
let unsub = merge.merge_subscribe(store, fn(s, p) { ... });

Computed Values (Selectors)

store.addComputed("doubleCount", ["count"], fn(state) {
    return state.count * 2;
});

store.addComputed("pendingTodos", ["todos"], fn(state) {
    return state.todos.filter(fn(t) { return !t.completed; });
});

let doubled = store.getComputed("doubleCount");  // 84
let pending = store.getComputed("pendingTodos");

// Standalone helpers
merge.merge_addComputed(store, "total", ["count"], fn(s) { return s.count; });
merge.merge_getComputed(store, "total");

Selector Caching

let result = store.select(fn(state) {
    return state.todos.filter(fn(t) { return t.completed; });
});
// Caches by selector function identity. Only recomputes when state changes.
// Access via merge.merge_select(store, selector)

Middleware

Middleware are functions that intercept dispatched actions before they reach the reducer. They can modify, delay, or cancel actions.

// Create a store with middleware
let store = merge.merge_createStore(reducer, initialState, [myMiddleware]);

// Or apply middleware after creation
merge.apply_middleware(store, [mw1, mw2]);

Built-in Middleware Factories

Logger Middleware

let logger = merge.create_logger_middleware();
// Logs every action and state transition

Persist Middleware

let storage = {
    set: fn(key, val) { window.localStorage.setItem(key, val); }
};
let persist = merge.create_persist_middleware(storage, "my-app-state");
// Automatically persists state to storage after every action (except @@merge/REPLACE)

Thunk Middleware (Async Actions)

let thunk = merge.create_thunk_middleware();
store = merge.merge_createStore(reducer, initialState, [thunk]);

// Now actions can be functions:
fn fetchUser(id) {
    return fn(dispatch, getState) {
        dispatch({ type: "FETCH_USER_START" });
        let user = api.getUser(id);
        dispatch({ type: "FETCH_USER_DONE", payload: user });
    };
}
store.dispatch(fetchUser(42));

Custom Middleware

let myMiddleware = merge.create_middleware(fn(store, action) {
    print("Action:", action.type);
    if action.type == "SECRET" {
        return 0;  // cancel the action (return 0 / nil)
    }
    return action;  // pass through
});

Undo / Redo

Merge stores history automatically — up to the last 50 states.

store.undo();           // Revert to previous state
store.redo();           // Re-apply reverted state
store.canUndo() -> bool // true if undo is available
store.canRedo() -> bool

// Standalone helpers
merge.merge_undo(store);
merge.merge_redo(store);

// History management
store.getHistory() -> list        // Full history array
store.jumpToHistory(index)        // Jump to specific point

Devtools Integration

let devtools = merge.create_devtools_hook();  // Base implementation
// Or connect to browser devtools:
let custom_hook = {
    init: fn(state) { /* send initial state */ },
    send: fn(action, state) { /* log action + state */ },
    subscribe: fn(callback) { /* listen for time-travel */ }
};

store.connectDevtools(custom_hook);
// Each dispatch automatically calls devtools.send(action, state)

merge.merge_connectDevtools(store, custom_hook);

Replace Reducer

store.replaceReducer(fn(state, action) {
    // New reducer logic
});
// Dispatches @@merge/REPLACE internally

Counter Example

import bond;
import merge;

let counterReducer = fn(state, action) {
    if action.type == "INCREMENT" { return { count: state.count + 1 }; }
    if action.type == "DECREMENT" { return { count: state.count - 1 }; }
    if action.type == "RESET"     { return { count: 0 }; }
    return state;
};

let store = merge.merge_createStore(counterReducer, { count: 0 });

store.addComputed("isPositive", ["count"], fn(s) {
    return s.count >= 0;
});

fn Counter() {
    return bond.div({ class: "counter" }, [
        bond.h1("Count: " + string(store.getState().count), {}),
        bond.button("+", { onClick: fn() { store.dispatch({ type: "INCREMENT" }); } }),
        bond.button("-", { onClick: fn() { store.dispatch({ type: "DECREMENT" }); } }),
        bond.button("Reset", { onClick: fn() { store.dispatch({ type: "RESET" }); } }),
        bond.p("Positive: " + string(store.getComputed("isPositive")), {})
    ]);
}

Todo List with Middleware

import bond;
import merge;

let todoReducer = fn(state, action) {
    if action.type == "ADD_TODO" {
        return { todos: state.todos + [{
            id: state.todos.length + 1,
            text: action.payload,
            completed: false
        }]};
    }
    if action.type == "TOGGLE_TODO" {
        let new_todos = [];
        let i = 0;
        while i < state.todos.length {
            let t = state.todos[i];
            if t.id == action.payload {
                t.completed = !t.completed;
            }
            new_todos.push(t);
            i = i + 1;
        }
        return { todos: new_todos };
    }
    if action.type == "REMOVE_TODO" {
        let filtered = [];
        let i = 0;
        while i < state.todos.length {
            if state.todos[i].id != action.payload {
                filtered.push(state.todos[i]);
            }
            i = i + 1;
        }
        return { todos: filtered };
    }
    return state;
};

let logger = merge.create_logger_middleware();
let thunk = merge.create_thunk_middleware();

let store = merge.merge_createStore(todoReducer, { todos: [] }, [logger, thunk]);

store.addComputed("activeCount", ["todos"], fn(s) {
    let count = 0;
    let i = 0;
    while i < s.todos.length {
        if !s.todos[i].completed { count = count + 1; }
        i = i + 1;
    }
    return count;
});

store.addComputed("completedCount", ["todos"], fn(s) {
    let count = 0;
    let i = 0;
    while i < s.todos.length {
        if s.todos[i].completed { count = count + 1; }
        i = i + 1;
    }
    return count;
});

// Async action via thunk
fn addTodoAsync(text) {
    return fn(dispatch, _getState) {
        dispatch({ type: "ADD_TODO", payload: text });
    };
}

store.dispatch(addTodoAsync("Learn Braid Merge"));
store.dispatch({ type: "TOGGLE_TODO", payload: 1 });

print("Active:", store.getComputed("activeCount"));
print("Completed:", store.getComputed("completedCount"));

// Undo the last action
store.undo();

Async Actions without Thunk

Merge also supports async actions natively via the async_type field:

store.dispatch({
    async_type: "FETCH_USER",  // dispatches FETCH_USER_RESOLVED on completion
    payload: fn(dispatch, getState) {
        return api.getUser();
    }
});
// Automatically dispatches { type: "FETCH_USER_RESOLVED", payload: <result> }

Store Internals

// The store object contains:
{
    state,            // current state
    listeners,        // subscriber functions
    middleware,       // middleware pipeline
    reducer,          // reducer function
    computed,         // computed values cache
    computed_deps,    // dependency map for computed values
    compute_fns,      // compute functions
    history,          // state history (max 50)
    history_index,    // current position in history
    devtools,         // devtools hook
    selector_cache,   // cache for select()
    dispatch,         // fn(action) -&gt; void
    getState,         // fn() -> state
    subscribe,        // fn(listener) -> unsubscribe
    undo,             // fn() -> void
    redo,             // fn() -> void
    select,           // fn(selector) -> result
    addComputed,      // fn(name, deps, fn)
    getComputed,      // fn(name) -> value
    connectDevtools,  // fn(devtools_hook)
    replaceReducer,   // fn(new_reducer)
    ...
}