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

recrux

v0.3.6

Published

A functional, action centered, typescript flavored, and slightly opinionated toolbelt for redux. Core values:

Readme

(WIP) Recrux

A functional, action centered, typescript flavored, and slightly opinionated toolbelt for redux. Core values:

  • Compose reducers and actions in a similar way to plain function composition.
  • Never trap developers, always allow for plain redux.
  • Create actions using a name, namespace and a reducer for that action
  • Promote the re-use of reducer logic without complex abstractions

Examples

Reducer composition

The most basic reducer composition

import { composeReducer } from 'recrux'

const GET_DATA = "example/GET_DATA"
const GET_DATA_FULFILL = `${GET_DATA}_FULFILL`
const GET_DATA_ERROR = `${GET_DATA}_ERROR`

const defaultState = {
    data: [],
    error: null,
    loading: false
}

export const baseReducer = composeReducer(
    (state = defaultState) => state,
    (state, {payload, type}) => type === GET_DATA ? ({
        ...state,
        loading: true
    }) : state,
    (state, {payload, type}) => type === GET_DATA_FULFILL ? ({
        ...state,
        loading: false,
        data: payload
    }) : state,
    (state, {payload, type}) => type === GET_DATA_ERROR ? ({
        ...state,
        loading: false,
        error: payload
    }),
    (state, {payload, type}) => {
        switch(type) {
            case "SWITCHEROO":
                return ({
                    ...state,
                    message: "Back to switch statements eh..."
                })
            default:
                return state;
        }
    }
);

export const moreComposition = composeReducer(
    baseReducer,
    (state, action) => action.type === "moreComposition" ? ({
        ...state,
        magic: true
    }) : state
)

Composition using a map { [actionType: string] : Reducer}

import { composeReducer, fromMap } from 'recrux'

const GET_DATA = "example/GET_DATA"
const GET_DATA_FULFILL = `${GET_DATA}_FULFILL`
const GET_DATA_ERROR = `${GET_DATA}_ERROR`

const defaultState = {
    data: [],
    error: null,
    loading: false
}

export default composeReducer(
    (state = defaultState) => state,
    fromMap({
        [GET_DATA]: (state, {payload}) => ({
            ...state,
            loading: true
        }),
        [GET_DATA_FULFILL]: (state, {payload}) => ({
            ...state,
            data: payload,
            loading: false
        }),
        [GET_DATA_ERROR]: (state, {payload}) => ({
            ...state,
            error: payload,
            loading: false
        })
    })
)

Using action factories

// actions.js
import { createAsyncFactory } from 'recrux'
const namespace = "actionDefaults"
export const defaultAsync = createAsyncFactory({
    namespace,
    actionName: "ASYNC_STATE",
    requestReducer: (state, {payload}) => ({
            ...state,
            loading: true
        }),
    fulfillReducer: (state, {payload}) => ({
            ...state,
            data: payload,
            loading: false
        }),
    errorReducer: (state, {payload}) => ({
            ...state,
            error: payload,
            loading: false
        })
})
// reducer.js

import { createFactory, createAsyncFactory } from 'recrux'
import { defaultAsync } from './actions'

const namespace = "examples"

const defaultState = {
    data: [],
    error: null,
    loading: false
}
const getData = defaultAsync.assign({
    namespace,
    actionName: "GET_DATA"
})

export default composeReducer(
    (state = defaultState) => state,
    getData.reducer
)

Scoping actions


// Merge and assign actions (like with objects)
import { createFactory, composeReducer, scopeReducers } from 'recrux'

const namespace = "myComponent"
const defaultState = {
    right: {
        open: false
    },
    left: {
        open: false
    },
    table: {
        data: [],
        error: null,
        loading: false
    }
}

type OpenState = {open: boolean}

const toggleOpenRight = createFactory<OpenState>({
    namespace,
    actionName: "toggleOpenRight",
    reducer: (state) => ({
        ...state,
        open: !state.open
    })
});


const toggleOpenLeft = toggleOpenLeft.assign({
    actionName: "toggleOpenLeft"
})

const getTableData = defaultAsync.assign({
    namespace,
    actionName: "getTableData"
})


// assign will behave like object assign. It wont call parent action reducers if you override it.
// The only thing it will keep in the below example is namespace
const removeTableError = toggleOpenRight.assign({
    actionName: "removeTableError",
    reducer: (state) => ({
        ...state,
        error: null
    })
})


// Merge will also call the reducer from the parent action, even if you pass it as a param
const getTableDataWithAlert = getTableData.merge({
    actionName: "getTableDataWithAlert",
    errorReducer: (state, {payload}) => ({
        ...state,
        alert: payload
    })
})

export default composeReducer(
    (state = defaultState) => state,
    scopeReducers({
        right: toggleOpenRight.reducer,
        left: toggleOpenLeft.reducer,
        table: composeReducer(
            getTableData.reducer,
            getTableDataWithAlert.reducer,
            removeTableError.reducer
        )
    })
)