npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

cuel-x

v0.0.5

Published

Tiny, powerful state management for cuel

Readme

Cuel-X

A lightweight state management library for cuel DOM data binding components. Cuel-X provides Redux-compatible state management with proxy-based APIs for automatic validation and type safety. Complete implementation in <1000 SLoC.

Quick Start

import {setup} from 'cuel-x';

// Initialize with initial state
const {actions, connect, register, state} = setup({
    todos: {},
    filter: 'all',
    newTodo: ''
});

// Register actions, reducers, and selectors
register.todo('add', src, (state, {payload}, nextId) => {
    state[nextId] = {id: nextId, text: payload.newTodo, completed: false};
}, state.nextId);

// Connect to components
cuel('todo-input', {}, {
    ...connect(actions.todo.add, state.newTodo),
    template: `<input .value=newTodo !click=actions.todo.add>`
});

Core Concepts

Proxy-Based Architecture

Cuel-X uses proxy objects instead of strings for type-safe state management:

  • State Proxy: Access state properties with automatic validation
  • Action Proxy: Type-safe action dispatching with payload parsing
  • Registry Proxy: Register actions, reducers, and selectors with structure validation

Redux Compatibility

100% Redux compatible with built-in implementation:

  • Predictable state updates
  • Immutable state (via immer-like patterns)
  • Middleware support
  • Async thunks
  • Action creators and reducers

API Reference

setup(initialState)

Initializes the complete cuel-x system with default configuration.

Parameters:

  • initialState (Object): Initial state structure

Returns:

{
    actions: ActionProxy,    // Action dispatchers
    connect: Function,       // Component connection function
    register: RegistryProxy, // Registration API
    state: StateProxy        // State access proxy
}

State Proxy (state)

Access state properties and selectors with automatic validation:

// Direct state access
state.todos           // Access todos state
state.filter          // Access filter state

// Selector access
state.filteredTodos   // Computed selector
state.stats           // Stats selector

Action Proxy (actions)

Dispatch actions with type safety and automatic payload parsing:

// Basic action creator - note, you don't get the creator but a proxy,
// this is an API for reducer registry and connecting to cuel-DOM
actions.todo.add

Registry Proxy (register)

Register actions, reducers, middleware, domains, and selectors:

// Register domain
register.todo('todo');

// Register action and reducer
register.todo.add('addTodo', src, (state, {payload}, nextId) => {
    state[nextId] = {id: nextId, text: payload, completed: false};
}, state.nextId);

// Register selector
register.filteredTodos((filter, todos) => {
    return Object.values(todos).filter(/* filter logic */);
}, state.filter, state.todo);

// Register middleware
register(myMiddleware);

Action Creators

Cuel-X provides built-in action creators for common patterns:

import {action, asyncAction, data, src, form, asyncData, asyncSrc, asyncForm} from 'cuel-x';

// Sync actions
const addTodo = action(type);           // Basic action
const setData =   data(type);           // Extract payload.data
const setSrc  =    src(type);           // Extract payload.src
const setForm =   form(type);           // Extract FormData

// Async actions (with pending/fulfilled/rejected)
const fetchTodos = asyncAction(handler)(type);  // Basic async
const fetchData  = asyncData(  handler)(type);  // Extract payload.data
const fetchSrc   = asyncSrc(   handler)(type);  // Extract payload.src
const fetchForm  = asyncForm(  handler)(type);  // Extract FormData

Note, that you usually don't pass type to the action creators, as they are automatically initialized by the registry.

Usage Examples

// Basic action creator
register.todo.add('add', action(), (state, {payload}) => {
    // Handle action
});

// Data extraction (for form submissions)
register.user.update('update', data(), (state, {payload}) => {
    const userData = payload.data; // Already extracted
    state.user = userData;
});

// Source element extraction
register.todo.toggle('toggle', src(), (state, {payload}) => {
    const element = payload.src; // DOM element
    const id = parseInt(element.dataset.id);
    state.todos[id].completed = !state.todos[id].completed;
});

// Async action with thunk
register.todo.fetch('fetch', asyncData(async function*() {
    yield {type: 'todo/fetch/pending'};
    try {
        const todos = await api.fetchTodos();
        yield {type: 'todo/fetch/fulfilled', payload: todos};
    } catch (error) {
        yield {type: 'todo/fetch/rejected', payload: error};
    }
});

Reducers and Selectors

Reducers

Reducers modify state based on actions:

// Basic reducer
register.todo.add('add', src, (state, {payload}, nextId) => {
    const text = payload.newTodo.trim();
    if (!text) return state;
    state[nextId] = {id: nextId, text, completed: false};
}, state.nextId);

// Multiple action types
register.todo.update(['add', 'toggle'], src, (state, {payload}) => {
    // Handle multiple action types
});

// With dependencies
register.todo.delete('delete', src, (state, {payload}) => {
    delete state[payload.id];
}, state.someDependency);

Selectors

Selectors compute derived state:

// Basic selector
register.filteredTodos((filter, todos) => {
    todos = Object.values(todos);
    switch (filter) {
        case 'active': return todos.filter(t => !t.completed);
        case 'completed': return todos.filter(t => t.completed);
        default: return todos;
    }
}, state.filter, state.todo);

// Complex selector
register.stats(todos => {
    todos = Object.values(todos);
    const total = todos.length;
    const completed = todos.filter(t => t.completed).length;
    return {total, completed, active: total - completed};
}, state.todo);

Component Connection

connect(...)

Connects state and actions to cuel components:

// Connect state and actions
cuel('todo-input', {}, {
    ...connect(
        actions.todo.add,           // Action dispatcher
        state.newTodo,              // State binding
        state.filteredTodos         // Selector binding
    ),
    template: `
        <input .value=newTodo !click=add>
        <ul><li is=todo-item *=filteredTodos</li></ul>
    `
});

// Multiple connections
cuel('todo-stats', {}, {
    ...connect(
        state.stats.active,
        state.stats.active('deep.active2', 'deep.active3')
    ),
    template: '{{active}} {{deep.active2}}{{deep.active3}}'
});

Connection Patterns

// Action only
connect(actions.todo.add)

// State only  
connect(state.newTodo)

// Selector with nested properties
connect(state.stats.active('deep.active2', 'deep.active3'))

// Mixed
connect(actions.todo.add, state.newTodo, state.filteredTodos)

Advanced Features

Middleware

Register middleware for cross-cutting concerns:

const logger = (action, store) => {
    console.log('Dispatching:', action);
    yield; // Yielding undefined continues the middleware chain
    console.log('After dispatch', store.getState());
};

register(logger);

Yielding anything else than undefined stops the chain, no dispatch will happen. Yielding a Promise will pause execution until the promise resolves, but the chain is stopped. Yielding an action (an object with a type-property) will dispatch that action from the top of the chain. Same for a Promise that resolves to an action.

Domains

Organize actions with domains:

register.todo('todo');  // Set domain
register.todo.add('add', src, reducer);  // Creates 'todo/add'
register.user('user');  // Different domain
register.user.update('update', data, reducer);  // Creates 'user/update'

Custom Action Processing

Create custom action creators:

register.custom('action', formAction((parsedFormData, store) =>
    ({type, payload: myCustomProcessing(parsedFormData, store)})));

Browser Import

<script type="importmap">
{
    "imports": {
        "cuel/": "./node_modules/cuel/",
        "cuel-x": "./node_modules/cuel-x/index.mjs"
    }
}
</script>

License

MIT