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.14

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 defineStore() 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 sync and async actions
  • Dev Mode Protection: Prevents direct state mutation in development
  • DevTools Integration: Built-in support for Fukict DevTools via scope
  • Zero Dependencies: No external dependencies

Installation

pnpm add @fukict/flux

Quick Start

Basic Counter Example

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

// Create store
const counterStore = defineStore({
  scope: 'counter',
  state: {
    count: 0,
  },
  actions: {
    increment: state => ({ count: state.count + 1 }),
    decrement: state => ({ count: state.count - 1 }),
    setCount: (_state, value: number) => ({ count: value }),
  },
});

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

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

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

  render() {
    const { count } = counterStore.state;

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

Core Concepts

Creating a Store

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

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

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

Subscribing to State

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

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

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

  render() {
    const { todos } = todoStore.state;
    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 = todoStore.subscribe(
      visibleTodosSelector,
      visibleTodos => {
        console.log('Visible todos changed:', visibleTodos);
        this.update(this.props);
      },
    );
  }

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

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

Async Actions

const userStore = defineStore({
  scope: 'user',
  state: {
    user: null as User | null,
    loading: false,
    error: null as string | null,
  },
  asyncActions: {
    async login(ctx, email: string, password: string) {
      ctx.setState({ loading: true, error: null });

      try {
        const user = await api.login(email, password);
        ctx.setState({ user, loading: false });
      } catch (error) {
        ctx.setState({
          error: error.message,
          loading: false,
        });
      }
    },
  },
  actions: {
    logout: () => ({ 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 = globalStore.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 counterStore */}
        <TodoPanel /> {/* Subscribes to todoStore */}
        <UserPanel /> {/* Subscribes to userStore */}
      </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 store = defineStore({
  scope: 'example',
  state: { count: 0 },
  actions: {
    // ✅ Good: Return partial state
    increment: state => ({ count: state.count + 1 }),
  },
});

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

Advanced Usage

Multiple Stores

// User store
const userStore = defineStore({
  scope: 'user',
  state: { user: null },
  actions: {
    setUser: (_state, user: User) => ({ user }),
  },
});

// Settings store
const settingsStore = defineStore({
  scope: 'settings',
  state: { theme: 'light', language: 'en' },
  actions: {
    setTheme: (_state, theme: string) => ({ theme }),
    setLanguage: (_state, language: string) => ({ language }),
  },
});

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

  mounted() {
    this.unsubscribeUser = userStore.subscribe(() => this.update(this.props));
    this.unsubscribeSettings = settingsStore.subscribe(() =>
      this.update(this.props)
    );
  }

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

  render() {
    const { user } = userStore.state;
    const { theme } = settingsStore.state;
    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 = todoStore.subscribe(statsSelector, (stats) => {
      console.log('Stats changed:', stats);
      this.update(this.props);
    });
  }

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

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

API Reference

defineStore(config)

Creates a new store.

const store = defineStore({
  scope: 'my-store', // Required: unique identifier for DevTools
  state: initialState,
  actions: {
    // Sync actions: (state, ...args) => Partial<State>
    actionName: (state, ...args) => ({
      /* partial state */
    }),
  },
  asyncActions: {
    // Async actions: (ctx, ...args) => Promise<void>
    async asyncAction(ctx, ...args) {
      ctx.setState({
        /* partial state */
      });
    },
  },
});

store.state

Current state (readonly, protected from mutation in dev mode).

const { count } = store.state;

store.getState()

Returns current state snapshot (readonly).

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

store.setState(partial)

Updates state with partial state object.

store.setState({ count: 10 });

store.actions

Wrapped sync actions (state parameter is automatically injected).

store.actions.increment();
store.actions.add(5);

store.asyncActions

Wrapped async actions (context parameter is automatically injected).

await store.asyncActions.fetchUser('123');

store.subscribe(listener)

Subscribes to all state changes.

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

// Clean up
unsubscribe();

store.subscribe(selector, listener)

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

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

Best Practices

1. Organize Actions by Feature

// ✅ Good: Group related actions
const userStore = defineStore({
  scope: 'user',
  state: { user: null, preferences: {} },
  actions: {
    logout: () => ({ user: null }),
    updatePreference: (state, key: string, value: any) => ({
      preferences: { ...state.preferences, [key]: value },
    }),
    resetPreferences: () => ({ preferences: {} }),
  },
  asyncActions: {
    async login(ctx, credentials: Credentials) {
      /* ... */
    },
  },
});

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 = store.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 = store.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 = store.subscribe(() => this.update(this.props));
  }
  render() {
    return (
      <div>
        <Child />
      </div>
    );
  }
}

class Child extends Fukict {
  render() {
    const state = store.state; // 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