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

@jucie-state/core

v1.0.46

Published

Core state management system with serialization and path-based access

Readme

@jucie-state/core

A powerful reactive state management system for JavaScript applications featuring fine-grained reactivity, computed values, history management, and serialization capabilities.

Features

  • 🔄 Fine-Grained Reactivity: Track changes at the property level with automatic dependency detection
  • High Performance: Optimized for frequent updates with minimal re-computations
  • 🧮 Computed Values: Reactive computations with automatic caching and dependency tracking
  • 📝 History Management: Built-in undo/redo via HistoryManager plugin
  • 💾 Serialization: Import/export state with CBOR encoding for persistence
  • 🎯 Path-Based Access: Intuitive nested object and array manipulation
  • 🔍 Powerful Queries: Built-in querying with filters and transformations
  • 🔌 Plugin Architecture: Extensible with HistoryManager, Matcher, Reactive, OnChange, and custom plugins
  • 🧪 Well Tested: Comprehensive test suite with performance benchmarks
  • 🌊 Batch Operations: Efficient batch updates with change consolidation

License and Usage

This software is provided under the MIT License with Commons Clause.

✅ What You Can Do

  • Use this library freely in personal or commercial projects
  • Include it in your paid products and applications
  • Modify and fork for your own use
  • View and learn from the source code

❌ What You Cannot Do

  • Sell this library as a standalone product or competing state management solution
  • Offer it as a paid service (SaaS) where the primary value is this library
  • Create a commercial fork that competes with this project

⚠️ No Warranty or Support

This software is provided "as-is" without any warranty, support, or guarantees:

  • No obligation to provide support or answer questions
  • No obligation to accept or implement feature requests
  • No obligation to review or merge pull requests
  • No obligation to fix bugs or security issues
  • No obligation to maintain or update the software

You are welcome to submit issues and pull requests, but there is no expectation they will be addressed. Use this software at your own risk.

See the LICENSE file for complete terms.

Installation

npm install @jucie-state/state

Quick Start

import { createState, createReactor } from '@jucie-state/state';

// Create a state instance
const state = createState();

// Set some initial data
state.set(['user'], { name: 'Alice', age: 30 });
state.set(['counter'], 0);

// Create a reactive computation
const userDisplay = createReactor(() => {
  const user = state.get(['user']);
  return `${user.name} (${user.age} years old)`;
});

console.log(userDisplay()); // "Alice (30 years old)"

// Update state - reactive values automatically update
state.set(['user', 'age'], 31);
console.log(userDisplay()); // "Alice (31 years old)"

Core Concepts

State Management

The state system uses path-based access for nested data structures:

import { createState } from '@jucie-state/state';

const state = createState({
  user: { name: 'Alice', profile: { age: 30 } },
  items: ['apple', 'banana']
});

// Get values
const user = state.get(['user']);                    // { name: 'Alice', profile: { age: 30 } }
const name = state.get(['user', 'name']);           // 'Alice'
const age = state.get(['user', 'profile', 'age']);  // 30

// Set values
state.set(['user', 'name'], 'Bob');
state.set(['user', 'profile', 'age'], 25);
state.set(['items', 2], 'cherry'); // ['apple', 'banana', 'cherry']

// Multiple gets
const [userName, userAge] = state.get(['user', 'name'], ['user', 'profile', 'age']);

Reactive Computations

Create reactive computations that automatically update when dependencies change:

import { createReactor } from '@jucie-state/state';

// Simple computed value
const totalItems = createReactor(() => {
  return state.get(['items']).length;
});

// Complex computation with multiple dependencies
const userSummary = createReactor(() => {
  const user = state.get(['user']);
  const itemCount = state.get(['items']).length;
  
  return {
    display: `${user.name} has ${itemCount} items`,
    isAdult: user.profile.age >= 18
  };
});

console.log(totalItems());    // 3
console.log(userSummary());   // { display: "Bob has 3 items", isAdult: true }

// Updates automatically when dependencies change
state.set(['items', 3], 'date');
console.log(totalItems());    // 4

Signals

Create standalone reactive values with signals:

import { createSignal } from '@jucie-state/state';

const count = createSignal(0);
const doubled = createReactor(() => count() * 2);

console.log(count());   // 0
console.log(doubled()); // 0

count(5);  // Set value by calling with argument
console.log(count());   // 5
console.log(doubled()); // 10

API Reference

State Creation

createState(initialState?, config?)

Create a new state instance.

const state = createState({
  user: { name: 'Alice' },
  counter: 0
});

State Operations

get(...paths)

Get values from state using path arrays.

// Single path
const user = state.get(['user']);

// Multiple paths
const [name, age] = state.get(['user', 'name'], ['user', 'age']);

// Works with arrays
const firstItem = state.get(['items', 0]);

set(path, value)

Set a value at the specified path.

state.set(['user', 'name'], 'Bob');
state.set(['items', 0], 'apple');
state.set(['deeply', 'nested', 'value'], 42);

update(path, updater)

Update a value using a function.

// Increment counter
state.update(['counter'], count => count + 1);

// Update object properties
state.update(['user'], user => ({ ...user, lastSeen: Date.now() }));

// Update array
state.update(['items'], items => [...items, 'new item']);

remove(path)

Remove a value from state.

state.remove(['user', 'age']);     // Remove specific property
state.remove(['items', 1]);        // Remove array element

has(...paths)

Check if paths exist in state.

const hasUser = state.has(['user']);                    // true/false
const [hasName, hasAge] = state.has(['user', 'name'], ['user', 'age']);

keys(...paths)

Get object keys at specified paths.

const userKeys = state.keys(['user']);              // ['name', 'profile']
const [userKeys, profileKeys] = state.keys(['user'], ['user', 'profile']);

typeof(...paths)

Get the type of values at specified paths.

const userType = state.typeof(['user']);           // 'object'
const nameType = state.typeof(['user', 'name']);   // 'string'
const itemsType = state.typeof(['items']);         // 'array'

Reactivity

createReactor(fn, config?)

Create a reactive computation.

const computed = createReactor(() => {
  // This function re-runs when dependencies change
  return state.get(['counter']) * 2;
}, {
  immediate: true,     // Compute immediately (default: false)
  debounce: 100,      // Debounce updates (ms)
  effects: [fn1, fn2] // Side effects to run after computation
});

createSignal(initialValue, config?)

Create a reactive signal.

const reactiveValue = createSignal(42, {
  immediate: true,
  debounce: 50,
  effects: [(value) => console.log('Value changed:', value)]
});

// Get value
const current = reactiveValue();

// Set value
reactiveValue(100);

addEffect(reactive, effect)

Add side effects to reactive computations.

import { addEffect, createSignal } from '@jucie-state/state';

const counter = createSignal(0);

addEffect(counter, (value) => {
  console.log('Counter changed to:', value);
});

addEffect(counter, (value) => {
  if (value > 10) {
    console.log('Counter is high!');
  }
});

Batch Operations

batch(fn)

Batch multiple state changes to minimize re-computations.

state.batch(() => {
  state.set(['user', 'name'], 'Charlie');
  state.set(['user', 'age'], 35);
  state.set(['counter'], 10);
  // All reactive computations update once at the end
});

Queries

query(queryFn, ...paths)

Query state with filtering and transformation.

// Find all users over 18
const adults = state.query(
  (users) => users.filter(user => user.age >= 18),
  ['users']
);

// Get specific fields from multiple paths
const summary = state.query(
  ([users, settings]) => ({
    totalUsers: users.length,
    theme: settings.theme
  }),
  ['users'],
  ['settings']
);

find(predicate, ...paths)

Find the first matching item.

const adminUser = state.find(
  user => user.role === 'admin',
  ['users']
);

filter(predicate, ...paths)

Filter items based on a predicate.

const activeUsers = state.filter(
  user => user.status === 'active',
  ['users']
);

Plugins

The state system has a powerful plugin architecture that enables features like undo/redo and change tracking.

Installing Plugins

Plugins are installed using the install() method:

import { createState } from '@jucie-state/state';
import { HistoryManager } from '@jucie-state/state/plugins/HistoryManager';
import { Matcher } from '@jucie-state/state/plugins/Matcher';

const state = createState();

// Install a single plugin
state.install(HistoryManager);

// Install multiple plugins
state.install(HistoryManager, Matcher);

HistoryManager Plugin

Provides undo/redo functionality with change tracking.

import { HistoryManager } from '@jucie-state/state/plugins/HistoryManager';

const state = createState();
state.install(HistoryManager);

state.set(['counter'], 1);
state.set(['counter'], 2);
state.set(['counter'], 3);

// Undo operations
state.history.undo();          // counter back to 2
state.history.undo();          // counter back to 1

// Redo operations  
state.history.redo();          // counter back to 2

// Check history status
console.log(state.history.canUndo());    // true/false
console.log(state.history.canRedo());    // true/false
console.log(state.history.size());       // number of history entries

// Batch history changes
const unbatch = state.history.batch();
state.set(['user', 'name'], 'Alice');
state.set(['user', 'age'], 30);
unbatch(); // Commits all changes as single history entry

// Add custom markers for better history navigation
state.set(['step'], 1);
state.history.addMarker('Step 1 completed');
state.set(['step'], 2);
state.history.addMarker('Step 2 completed');

// Listen to history commits
const unsubscribe = state.history.onCommit((changes) => {
  console.log('History committed:', changes);
});

Configuration Options:

import { HistoryManager } from '@jucie-state/state/plugins/HistoryManager';

HistoryManager.options = {
  maxSize: 100  // Maximum history entries (default: 100)
};

state.install(HistoryManager);

Creating Custom Plugins

You can create custom plugins by extending the Plugin base class:

import Plugin from '@jucie-state/state/plugins/Plugin';

class CustomPlugin extends Plugin {
  static name = 'custom';
  static options = {
    customOption: 'default'
  };

  initialize(state, options) {
    // Called once when plugin is installed
    state.addChangeListener((marker, change) => {
      // React to changes
    });
  }

  actions(state) {
    // Return methods available on state.custom.*
    return {
      myAction: () => {
        // Custom functionality
      }
    };
  }

  reset() {
    // Called when state.reset() is invoked
  }
}

// Use the plugin
state.install(CustomPlugin);
state.custom.myAction();

Serialization

Export and Import

// Export state to CBOR format
const exported = state.export();

// Create new state from exported data
const newState = createState();
newState.import(exported);

// Or use createStateExport for direct creation
import { createStateExport } from '@jucie-state/state';
const restoredState = createStateExport(exported);

Change Tracking

onChange(listener)

Listen to all state changes.

const unsubscribe = state.onChange((changes) => {
  changes.forEach(change => {
    console.log(`${change.operation} at ${change.path.join('.')}: ${change.value}`);
  });
});

// Later, unsubscribe
unsubscribe();

Advanced Usage

Custom Effects

import { addEffect, removeEffect } from '@jucie-state/state';

const userDisplay = createReactor(() => {
  const user = state.get(['user']);
  return `${user.name} (${user.age})`;
});

// Add logging effect
const logEffect = (value) => console.log('User display updated:', value);
addEffect(userDisplay, logEffect);

// Add analytics effect
const analyticsEffect = (value) => analytics.track('user_display_changed', { value });
addEffect(userDisplay, analyticsEffect);

// Remove effects when no longer needed
removeEffect(userDisplay, logEffect);

Complex State Structures

const state = createState({
  app: {
    theme: 'dark',
    language: 'en'
  },
  users: [
    { id: 1, name: 'Alice', role: 'admin', active: true },
    { id: 2, name: 'Bob', role: 'user', active: false },
    { id: 3, name: 'Charlie', role: 'user', active: true }
  ],
  posts: [
    { id: 1, authorId: 1, title: 'Hello World', likes: 5 },
    { id: 2, authorId: 2, title: 'JavaScript Tips', likes: 12 }
  ]
});

// Complex reactive computation
const dashboardData = createReactor(() => {
  const users = state.get(['users']);
  const posts = state.get(['posts']);
  
  const activeUsers = users.filter(u => u.active);
  const totalLikes = posts.reduce((sum, p) => sum + p.likes, 0);
  const postsPerUser = posts.reduce((acc, post) => {
    acc[post.authorId] = (acc[post.authorId] || 0) + 1;
    return acc;
  }, {});
  
  return {
    activeUserCount: activeUsers.length,
    totalPosts: posts.length,
    totalLikes,
    avgLikesPerPost: posts.length > 0 ? totalLikes / posts.length : 0,
    postsPerUser
  };
});

Performance Optimization

Debounced Updates

// Debounced reactor for expensive computations
const expensiveComputation = createReactor(() => {
  const data = state.get(['largeDataset']);
  return performExpensiveOperation(data);
}, {
  debounce: 300 // Wait 300ms after last change
});

// Debounced signal for rapid updates
const searchQuery = createSignal('', {
  debounce: 150 // Debounce search input
});

// React to debounced search
const searchResults = createReactor(() => {
  const query = searchQuery();
  if (query.length < 2) return [];
  
  const users = state.get(['users']);
  return users.filter(user => 
    user.name.toLowerCase().includes(query.toLowerCase())
  );
});

Batch Operations for Performance

// Inefficient - triggers multiple updates
state.set(['users', 0, 'name'], 'Alice Updated');
state.set(['users', 0, 'email'], '[email protected]');
state.set(['users', 0, 'lastLogin'], Date.now());

// Efficient - single update
state.batch(() => {
  state.set(['users', 0, 'name'], 'Alice Updated');
  state.set(['users', 0, 'email'], '[email protected]');
  state.set(['users', 0, 'lastLogin'], Date.now());
});

// Or use update for object modifications
state.update(['users', 0], user => ({
  ...user,
  name: 'Alice Updated',
  email: '[email protected]',
  lastLogin: Date.now()
}));

Integration with Jucie Engine

The state system integrates seamlessly with the Jucie Engine:

import { Engine, ServiceProvider } from '@jucie-state/engine';

class UserService extends ServiceProvider {
  static manifest = {
    name: 'User Service',
    namespace: 'users',
    version: '1.0.0'
  };

  actions({ state }) {
    return {
      addUser: (userData) => {
        const users = state.get(['users']) || [];
        const newUser = { id: Date.now(), ...userData };
        state.set(['users'], [...users, newUser]);
        return newUser;
      },

      updateUser: (id, updates) => {
        const users = state.get(['users']) || [];
        const index = users.findIndex(u => u.id === id);
        if (index !== -1) {
          state.update(['users', index], user => ({ ...user, ...updates }));
          return state.get(['users', index]);
        }
        return null;
      },

      getActiveUsers: () => {
        return state.filter(user => user.active, ['users']);
      }
    };
  }

  getters({ state }) {
    return {
      userCount: () => (state.get(['users']) || []).length,
      hasUsers: () => (state.get(['users']) || []).length > 0
    };
  }
}

Best Practices

1. Use Path Arrays Consistently

// ✅ Good - consistent path format
state.get(['user', 'profile', 'name']);
state.set(['user', 'profile', 'name'], 'Alice');

// ❌ Avoid - mixing path formats
state.get('user.profile.name'); // This won't work

2. Batch Related Updates

// ✅ Good - batched updates
state.batch(() => {
  state.set(['user', 'name'], 'Alice');
  state.set(['user', 'email'], '[email protected]');
  state.set(['user', 'updatedAt'], Date.now());
});

// ❌ Avoid - separate updates
state.set(['user', 'name'], 'Alice');
state.set(['user', 'email'], '[email protected]');
state.set(['user', 'updatedAt'], Date.now());

3. Use Reactive Computations for Derived Data

// ✅ Good - reactive computation
const fullName = createReactor(() => {
  const user = state.get(['user']);
  return `${user.firstName} ${user.lastName}`;
});

// ❌ Avoid - manual updates
let fullName = '';
function updateFullName() {
  const user = state.get(['user']);
  fullName = `${user.firstName} ${user.lastName}`;
}

4. Clean Up Effects and Listeners

// ✅ Good - cleanup
class Component {
  constructor() {
    this.unsubscribe = state.onChange(this.handleChange.bind(this));
  }

  destroy() {
    this.unsubscribe();
  }
}

Performance

The library is highly optimized for real-world performance:

Benchmarks

State Operations:
  get (simple):        7.6M ops/sec
  set (simple):        4.8M ops/sec
  get (nested):        5.0M ops/sec
  set (nested):        3.6M ops/sec

Reactive System:
  Reactor (cached):   41.8M ops/sec
  Reactor (update):    1.5M ops/sec
  Signal (cached):    44.1M ops/sec
  Signal (write):      3.8M ops/sec

Plugins:
  HistoryManager:     ~12% overhead on writes, 0% on reads

Performance Considerations

  • Path-based access: More efficient than deep object watches
  • Fine-grained updates: Only affected computations re-run
  • Batching: Use batch operations when you have reactors (reduces overhead)
  • Debouncing: Use debounced reactors for expensive computations
  • Memory: Clean up listeners and effects when components are destroyed
  • Plugins: Minimal overhead - gets remain fast, writes have reasonable tracking cost

Testing

# Run tests
npm test

# Run tests in watch mode
npm run test:watch

# Run benchmarks
npm run bench

License

This project is licensed under the MIT License with Commons Clause.

This means you can freely use this library in your projects (including commercial ones), but you cannot sell the library itself as a standalone product or competing service.

See the LICENSE file for complete details.

Contributing

You are welcome to submit issues and pull requests, however:

  • There is no guarantee that issues will be addressed
  • There is no guarantee that pull requests will be reviewed or merged
  • This project is maintained on an as-available basis with no commitments

By contributing, you agree that your contributions will be licensed under the same MIT + Commons Clause license.