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

@fukict/flux

v0.1.8

Published

Minimal state management library for Fukict framework

Readme

@fukict/flux

Minimal state management library for Fukict framework with Flux pattern and reactive subscriptions.

Features

  • Minimal API: Simple createFlux() factory function
  • Reactive Subscriptions: Subscribe to state changes with automatic updates
  • Selector Pattern: Subscribe to derived/computed values
  • Type-Safe: Full TypeScript support with type inference
  • Action Pattern: Organized state mutations through actions
  • Dev Mode Protection: Prevents direct state mutation in development
  • Zero Dependencies: No external dependencies

Installation

pnpm add @fukict/flux

Quick Start

Basic Counter Example

import { Fukict } from '@fukict/basic';
import { createFlux } from '@fukict/flux';

// Create flux store
const counterFlux = createFlux({
  state: {
    count: 0,
  },
  actions: flux => ({
    increment() {
      const state = flux.getState();
      flux.setState({ count: state.count + 1 });
    },
    decrement() {
      const state = flux.getState();
      flux.setState({ count: state.count - 1 });
    },
    setCount(value: number) {
      flux.setState({ count: value });
    },
  }),
});

// Use in component
class Counter extends Fukict {
  private unsubscribe?: () => void;

  mounted() {
    // Subscribe to state changes
    this.unsubscribe = counterFlux.subscribe(() => {
      this.update(this.props);
    });
  }

  beforeUnmount() {
    // Clean up subscription
    this.unsubscribe?.();
  }

  render() {
    const state = counterFlux.getState();
    const actions = counterFlux.actions;

    return (
      <div>
        <p>Count: {state.count}</p>
        <button on:click={actions.increment}>+</button>
        <button on:click={actions.decrement}>-</button>
      </div>
    );
  }
}

Core Concepts

Creating Flux Store

import { createFlux } from '@fukict/flux';

interface TodoState {
  todos: Todo[];
  filter: 'all' | 'active' | 'completed';
}

const todoFlux = createFlux({
  state: {
    todos: [],
    filter: 'all',
  } as TodoState,
  actions: flux => ({
    addTodo(text: string) {
      const state = flux.getState();
      flux.setState({
        todos: [...state.todos, { id: Date.now(), text, completed: false }],
      });
    },
    toggleTodo(id: number) {
      const state = flux.getState();
      flux.setState({
        todos: state.todos.map(todo =>
          todo.id === id ? { ...todo, completed: !todo.completed } : todo,
        ),
      });
    },
    setFilter(filter: TodoState['filter']) {
      flux.setState({ filter });
    },
  }),
});

Subscribing to State

class TodoList extends Fukict {
  private unsubscribe?: () => void;

  mounted() {
    // Subscribe to all state changes
    this.unsubscribe = todoFlux.subscribe(() => {
      this.update(this.props);
    });
  }

  beforeUnmount() {
    this.unsubscribe?.();
  }

  render() {
    const { todos } = todoFlux.getState();
    return (
      <ul>
        {todos.map(todo => (
          <li key={todo.id}>{todo.text}</li>
        ))}
      </ul>
    );
  }
}

Using Selectors

Selectors allow you to subscribe to derived/computed values:

// Define selector
const visibleTodosSelector = (state: TodoState) => {
  switch (state.filter) {
    case 'active':
      return state.todos.filter(t => !t.completed);
    case 'completed':
      return state.todos.filter(t => t.completed);
    default:
      return state.todos;
  }
};

class FilteredTodoList extends Fukict {
  private unsubscribe?: () => void;

  mounted() {
    // Subscribe to selector (only updates when selector result changes)
    this.unsubscribe = todoFlux.subscribe(
      visibleTodosSelector,
      visibleTodos => {
        console.log('Visible todos changed:', visibleTodos);
        this.update(this.props);
      },
    );
  }

  beforeUnmount() {
    this.unsubscribe?.();
  }

  render() {
    const visibleTodos = visibleTodosSelector(todoFlux.getState());
    return (
      <ul>
        {visibleTodos.map(todo => (
          <li key={todo.id}>{todo.text}</li>
        ))}
      </ul>
    );
  }
}

Async Actions

const userFlux = createFlux({
  state: {
    user: null as User | null,
    loading: false,
    error: null as string | null,
  },
  actions: flux => ({
    async login(email: string, password: string) {
      flux.setState({ loading: true, error: null });

      try {
        const user = await api.login(email, password);
        flux.setState({ user, loading: false });
      } catch (error) {
        flux.setState({
          error: error.message,
          loading: false,
        });
      }
    },
    logout() {
      flux.setState({ user: null });
    },
  }),
});

Subscription Patterns

Pattern 1: Top-Level Subscription

Subscribe at the root/layout component level:

class App extends Fukict {
  private unsubscribe?: () => void;

  mounted() {
    // App subscribes, all children read state
    this.unsubscribe = globalFlux.subscribe(() => {
      this.update(this.props);
    });
  }

  beforeUnmount() {
    this.unsubscribe?.();
  }

  render() {
    return (
      <div>
        <Header /> {/* Reads state, no subscription */}
        <Content /> {/* Reads state, no subscription */}
      </div>
    );
  }
}

Pattern 2: Sibling Subscriptions

Different components subscribe to different stores:

class Dashboard extends Fukict {
  render() {
    return (
      <div>
        <CounterPanel /> {/* Subscribes to counterFlux */}
        <TodoPanel /> {/* Subscribes to todoFlux */}
        <UserPanel /> {/* Subscribes to userFlux */}
      </div>
    );
  }
}

Pattern 3: Detached Subscriptions

Use fukict:detach for independent updates:

class Parent extends Fukict {
  render() {
    return (
      <div>
        <ChildA /> {/* Parent updates trigger ChildA update */}
        <ChildB fukict:detach={true} /> {/* Independent updates */}
      </div>
    );
  }
}

State Mutation Protection

In development mode, Flux prevents direct state mutation:

const flux = createFlux({
  state: { count: 0 },
  actions: flux => ({
    // ✅ Good: Use setState
    increment() {
      const state = flux.getState();
      flux.setState({ count: state.count + 1 });
    },
  }),
});

// ❌ Bad: Direct mutation (warning in dev mode)
const state = flux.getState();
state.count++; // Warning: [Flux] Direct state mutation is not allowed

Advanced Usage

Multiple Stores

// User store
const userFlux = createFlux({
  state: { user: null },
  actions: (flux) => ({
    setUser(user: User) {
      flux.setState({ user });
    },
  }),
});

// Settings store
const settingsFlux = createFlux({
  state: { theme: 'light', language: 'en' },
  actions: (flux) => ({
    setTheme(theme: string) {
      flux.setState({ theme });
    },
    setLanguage(language: string) {
      flux.setState({ language });
    },
  }),
});

// Use both in component
class Profile extends Fukict {
  private unsubscribeUser?: () => void;
  private unsubscribeSettings?: () => void;

  mounted() {
    this.unsubscribeUser = userFlux.subscribe(() => this.update(this.props));
    this.unsubscribeSettings = settingsFlux.subscribe(() =>
      this.update(this.props)
    );
  }

  beforeUnmount() {
    this.unsubscribeUser?.();
    this.unsubscribeSettings?.();
  }

  render() {
    const { user } = userFlux.getState();
    const { theme } = settingsFlux.getState();
    return <div class={theme}>{user?.name}</div>;
  }
}

Computed Values with Selectors

const statsSelector = (state: TodoState) => ({
  total: state.todos.length,
  completed: state.todos.filter((t) => t.completed).length,
  active: state.todos.filter((t) => !t.completed).length,
});

class TodoStats extends Fukict {
  private unsubscribe?: () => void;

  mounted() {
    this.unsubscribe = todoFlux.subscribe(statsSelector, (stats) => {
      console.log('Stats changed:', stats);
      this.update(this.props);
    });
  }

  beforeUnmount() {
    this.unsubscribe?.();
  }

  render() {
    const stats = statsSelector(todoFlux.getState());
    return (
      <div>
        <p>Total: {stats.total}</p>
        <p>Completed: {stats.completed}</p>
        <p>Active: {stats.active}</p>
      </div>
    );
  }
}

Store Composition

const createAppFlux = () => {
  const userFlux = createFlux({
    state: { user: null },
    actions: (flux) => ({ /*...*/ }),
  });

  const todoFlux = createFlux({
    state: { todos: [] },
    actions: (flux) => ({ /*...*/ }),
  });

  return {
    user: userFlux,
    todo: todoFlux,
  };
};

const appFlux = createAppFlux();

// Use in components
appFlux.user.getState();
appFlux.user.actions.setUser(...);
appFlux.todo.getState();
appFlux.todo.actions.addTodo(...);

API Reference

createFlux(config)

Creates a new flux store.

const flux = createFlux({
  state: initialState,
  actions: flux => ({
    actionName(...args) {
      // Action implementation
      flux.setState(newState);
    },
  }),
});

flux.getState()

Returns current state (protected from mutation in dev mode).

const state = flux.getState();
console.log(state.count);

flux.setState(partial)

Updates state with partial state object.

flux.setState({ count: 10 });

flux.actions

Actions object defined in createFlux config.

const actions = flux.actions;
actions.increment();

flux.subscribe(listener)

Subscribes to all state changes.

const unsubscribe = flux.subscribe(() => {
  console.log('State changed');
});

// Clean up
unsubscribe();

flux.subscribe(selector, listener)

Subscribes to selector changes (only triggers when selector result changes).

const unsubscribe = flux.subscribe(
  state => state.user.name,
  name => {
    console.log('Name changed:', name);
  },
);

Best Practices

1. Organize Actions by Feature

// ✅ Good: Group related actions
const userFlux = createFlux({
  state: { user: null, preferences: {} },
  actions: flux => ({
    // Auth actions
    login(credentials) {
      /*...*/
    },
    logout() {
      /*...*/
    },

    // Preference actions
    updatePreference(key, value) {
      /*...*/
    },
    resetPreferences() {
      /*...*/
    },
  }),
});

2. Use Selectors for Derived State

// ✅ Good: Selector for computed values
const expensiveSelector = (state) => {
  return state.items.filter(/*...*/).map(/*...*/).reduce(/*...*/);
};

// ❌ Bad: Compute in render
render() {
  const state = flux.getState();
  const result = state.items.filter(/*...*/).map(/*...*/).reduce(/*...*/);
}

3. Clean Up Subscriptions

// ✅ Good: Always clean up
class MyComponent extends Fukict {
  private unsubscribe?: () => void;

  mounted() {
    this.unsubscribe = flux.subscribe(() => this.update(this.props));
  }

  beforeUnmount() {
    this.unsubscribe?.(); // Essential!
  }
}

4. Subscribe at the Right Level

// ✅ Good: Subscribe at parent, read in children
class App extends Fukict {
  mounted() {
    this.unsubscribe = flux.subscribe(() => this.update(this.props));
  }
  render() {
    return (
      <div>
        <Child />
      </div>
    );
  }
}

class Child extends Fukict {
  render() {
    const state = flux.getState(); // Just read
    return <div>{state.value}</div>;
  }
}

Examples

See examples/infra-flux for complete examples:

  • Counter with async actions
  • Todo list with filters
  • User profile with settings
  • Selector patterns

Related Packages

License

MIT