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

@sladg/apex-state

v3.11.4

Published

Advanced state management wrapper around Valtio with sync paths, aggregations, and side effects

Readme

@sladg/apex-state

Reactive state management for React built on Valtio. Declare what your fields need — validation, conditional UI, sync, listeners — and the store handles the rest. Optional Rust/WASM accelerator for complex workloads (up to 367x faster).

Quick Start

npm install @sladg/apex-state valtio zod react
import { createGenericStore } from '@sladg/apex-state'
import { z } from 'zod'

// 1. Define your state shape
type FormState = {
  user: { name: string; email: string; age: number }
  preferences: { newsletter: boolean; theme: 'light' | 'dark' }
}

// 2. Create a typed store (WASM-accelerated by default)
const store = createGenericStore<FormState>()

// 3. Wrap your app with Provider
const App = () => (
  <store.Provider initialState={{
    user: { name: '', email: '', age: 0 },
    preferences: { newsletter: false, theme: 'light' },
  }}>
    <UserForm />
  </store.Provider>
)

// 4. Use hooks to read/write state and declare concerns
const UserForm = () => {
  store.useConcerns('user-form', {
    'user.email': {
      validationState: { schema: z.string().email('Invalid email') },
    },
  })

  const { value, setValue, validationState } = store.useFieldStore('user.email')

  return (
    <div>
      <input
        value={value}
        onChange={(e) => setValue(e.target.value)}
        className={validationState?.isError ? 'error' : ''}
      />
      {validationState?.isError && <span>{validationState.errors[0]}</span>}
    </div>
  )
}

Features

| Feature | Description | Details | |---|---|---| | Type-safe paths | DeepKey<T> / DeepValue<T, P> — compile-time path safety with configurable depth | | | Concerns | Validation (Zod), BoolLogic conditions, dynamic text | Concerns Guide | | Side effects | Sync paths, flip paths, aggregations, listeners | Side Effects Guide | | WASM mode | Rust-powered pipeline for bulk operations (up to 367x faster) | Architecture | | Composable hooks | Buffered, throttled, transformed field wrappers | Store & Hooks | | Record/wildcard | Record<string, V> with _() hash key paths | Wildcard Guide | | Testing mock | Drop-in vi.mock replacement with call tracking | Testing Mock |

Full Example

import { createGenericStore } from '@sladg/apex-state'
import { z } from 'zod'

type OrderState = {
  product: { name: string; quantity: number; price: number }
  shipping: { address: string; express: boolean; standard: boolean }
  payment: { method: 'card' | 'cash'; cardNumber: string }
  status: 'draft' | 'submitted'
}

const store = createGenericStore<OrderState>()

const OrderForm = () => {
  // Side effects: auto-flip booleans
  store.useSideEffects('order', {
    flipPaths: [['shipping.express', 'shipping.standard']],
  })

  // Concerns: declarative validation and conditional UI
  store.useConcerns('order', {
    'product.quantity': {
      validationState: { schema: z.number().min(1).max(100) },
      disabledWhen: { boolLogic: { IS_EQUAL: ['status', 'submitted'] } },
    },
    'payment.cardNumber': {
      validationState: { schema: z.string().regex(/^\d{16}$/) },
      visibleWhen: { boolLogic: { IS_EQUAL: ['payment.method', 'card'] } },
    },
  })

  const { value, setValue, validationState, disabledWhen } =
    store.useFieldStore('product.quantity')

  return (
    <input
      type="number"
      value={value}
      onChange={(e) => setValue(Number(e.target.value))}
      disabled={disabledWhen}
      className={validationState?.isError ? 'error' : ''}
    />
  )
}

const App = () => (
  <store.Provider initialState={{
    product: { name: 'Widget', quantity: 1, price: 29.99 },
    shipping: { address: '', express: false, standard: true },
    payment: { method: 'card', cardNumber: '' },
    status: 'draft',
  }}>
    <OrderForm />
  </store.Provider>
)

Reading and Writing State

const store = createGenericStore<MyState>()

// useStore — simple [value, setter] tuple (like useState)
const NameInput = () => {
  const [name, setName] = store.useStore('user.name')
  return <input value={name} onChange={(e) => setName(e.target.value)} />
}

// useFieldStore — object API with concerns merged in
const EmailInput = () => {
  const { value, setValue, validationState, disabledWhen } =
    store.useFieldStore('user.email')
  return (
    <input
      value={value}
      onChange={(e) => setValue(e.target.value)}
      disabled={disabledWhen}
      className={validationState?.isError ? 'error' : ''}
    />
  )
}

// useJitStore — bulk operations and non-reactive reads
const ImportButton = () => {
  const { setChanges, getState } = store.useJitStore()

  const handleImport = (data: Record<string, unknown>) => {
    setChanges([
      ['user.name', data.name, {}],
      ['user.email', data.email, {}],
      ['user.age', data.age, {}],
    ])
  }

  return <button onClick={() => handleImport({ name: 'Alice', email: '[email protected]', age: 30 })}>Import</button>
}

Validation with Zod

import { createGenericStore } from '@sladg/apex-state'
import { z } from 'zod'

type ProfileState = {
  user: { name: string; email: string; age: number; bio: string }
}

const store = createGenericStore<ProfileState>()

const ProfileForm = () => {
  // Register validation schemas for multiple fields at once
  store.useConcerns('profile-validation', {
    'user.name': {
      validationState: { schema: z.string().min(2, 'Name too short').max(50) },
    },
    'user.email': {
      validationState: { schema: z.string().email('Enter a valid email') },
    },
    'user.age': {
      validationState: { schema: z.number().min(18, 'Must be 18+').max(120) },
    },
    'user.bio': {
      validationState: { schema: z.string().max(500, 'Bio too long') },
    },
  })

  const email = store.useFieldStore('user.email')
  const age = store.useFieldStore('user.age')

  return (
    <form>
      <div>
        <input value={email.value} onChange={(e) => email.setValue(e.target.value)} />
        {email.validationState?.isError && (
          <ul>{email.validationState.errors.map((err, i) => <li key={i}>{err}</li>)}</ul>
        )}
      </div>
      <div>
        <input
          type="number"
          value={age.value}
          onChange={(e) => age.setValue(Number(e.target.value))}
        />
        {age.validationState?.isError && <span>{age.validationState.errors[0]}</span>}
      </div>
    </form>
  )
}

Conditional UI with BoolLogic

store.useConcerns('conditional-ui', {
  // Disable when another field has a specific value
  'user.email': {
    disabledWhen: { boolLogic: { IS_EQUAL: ['status', 'submitted'] } },
  },

  // Show only when multiple conditions are true
  'payment.cardNumber': {
    visibleWhen: {
      boolLogic: {
        AND: [
          { IS_EQUAL: ['payment.method', 'card'] },
          { EXISTS: 'user.email' },
        ],
      },
    },
  },

  // Make readonly when value exceeds threshold
  'order.total': {
    readonlyWhen: { boolLogic: { GT: ['order.total', 10000] } },
  },

  // Complex nested logic with OR, NOT
  'shipping.express': {
    disabledWhen: {
      boolLogic: {
        OR: [
          { IS_EQUAL: ['status', 'shipped'] },
          { NOT: { EXISTS: 'shipping.address' } },
        ],
      },
    },
  },
})

// Available BoolLogic operators:
// IS_EQUAL, EXISTS, IS_EMPTY, GT, LT, GTE, LTE, IN, AND, OR, NOT

Side Effects

Sync Paths

store.useSideEffects('sync', {
  syncPaths: [
    ['billing.email', 'shipping.email'],   // changing one updates the other
    ['billing.phone', 'shipping.phone'],
  ],
})
// When user types in billing.email, shipping.email updates automatically

Flip Paths

store.useSideEffects('flips', {
  flipPaths: [
    ['isActive', 'isInactive'],       // setting isActive=true -> isInactive=false
    ['isExpanded', 'isCollapsed'],
  ],
})

Aggregations

store.useSideEffects('agg', {
  aggregations: [
    // Target is ALWAYS first. Multiple pairs with same target form a group.
    ['summary.price', 'legs.0.price'],
    ['summary.price', 'legs.1.price'],
    ['summary.price', 'legs.2.price'],
  ],
})
// summary.price = leg price if ALL legs match, undefined if they differ

Listeners

store.useSideEffects('listeners', {
  listeners: [
    {
      path: 'user.profile',       // watch changes under this path
      scope: 'user.profile',      // receive scoped state and relative paths
      fn: (changes, state) => {
        // changes: [['name', 'Alice', {}]] — paths relative to scope
        // state: { name: 'Alice', email: '...' } — user.profile sub-object
        return [['audit.lastEdit', Date.now(), {}]] // return FULL paths for new changes
      },
    },
  ],
})

Dynamic Text

store.useConcerns('dynamic-text', {
  'legs.0.strike': {
    dynamicTooltip: { template: 'Current strike: {{legs.0.strike}}' },
    dynamicLabel: { template: 'Strike for {{legs.0.product}}' },
    dynamicPlaceholder: { template: 'Enter value (min {{legs.0.minStrike}})' },
  },
})

const { dynamicTooltip, dynamicLabel } = store.useFieldConcerns('legs.0.strike')
// dynamicTooltip -> "Current strike: 105"
// dynamicLabel -> "Strike for AAPL"

Composable Field Hooks

import { useBufferedField, useThrottledField, useTransformedField } from '@sladg/apex-state'

// Buffer edits locally, commit/cancel explicitly
const PriceEditor = () => {
  const raw = store.useFieldStore('product.price')
  const buffered = useBufferedField(raw)

  return (
    <div>
      <input value={buffered.value} onChange={(e) => buffered.setValue(Number(e.target.value))} />
      <button onClick={buffered.commit} disabled={!buffered.isDirty}>Save</button>
      <button onClick={buffered.cancel}>Cancel</button>
    </div>
  )
}

// Throttle rapid setValue calls (e.g., slider input)
const VolumeSlider = () => {
  const raw = store.useFieldStore('audio.volume')
  const throttled = useThrottledField(raw, { ms: 100 })
  return <input type="range" value={throttled.value} onChange={(e) => throttled.setValue(Number(e.target.value))} />
}

// Transform display format (cents <-> dollars)
const CurrencyInput = () => {
  const raw = store.useFieldStore('price')
  const formatted = useTransformedField(raw, {
    to: (cents: number) => (cents / 100).toFixed(2),    // store -> display
    from: (dollars: string) => Math.round(parseFloat(dollars) * 100), // display -> store
  })
  return <input value={formatted.value} onChange={(e) => formatted.setValue(e.target.value)} />
}

// Chain them: buffered + transformed
const raw = store.useFieldStore('price')
const buffered = useBufferedField(raw)
const display = useTransformedField(buffered, {
  to: (cents) => (cents / 100).toFixed(2),
  from: (dollars) => Math.round(parseFloat(dollars) * 100),
})
// display has: value, setValue, commit, cancel, isDirty

Hash Key Paths for Record Types

import { _ } from '@sladg/apex-state'

// _() marks a segment as a hash key for Record-typed paths
const strikePath = `portfolio.legs.${_('l1')}.strike`
// -> typed string containing HASH_KEY marker

// Use with concerns — applies to ALL keys in the Record
store.useConcerns('wildcards', {
  [strikePath]: {
    validationState: { schema: z.number().min(0) },
    disabledWhen: { boolLogic: { IS_EQUAL: ['status', 'locked'] } },
  },
})

// Multiple hash keys for deeply nested Records
const nestedPath = `books.${_('b1')}.products.${_('p1')}.legs.${_('l1')}.notional`

Type-Safe Paths with Configurable Depth

Deeply nested state types can cause TypeScript errors like TS2589 ("Type instantiation is excessively deep") or slow IDE autocomplete. DeepKey<T, Depth> solves this with a configurable recursion limit — reduce depth for wide types to speed up compilation, increase it for deeply nested structures, and get clear feedback when the limit is reached.

type State = {
  user: { name: string; address: { street: string; city: string } }
  count: number
}

// DeepKey<State> = "user" | "count" | "user.name" | "user.address" | "user.address.street" | "user.address.city"
type AllPaths = DeepKey<State>

Default Depth

DefaultDepth is set to 20 — enough for most real-world types. Uses loop unrolling (2 levels per recursion call) to stay well within TypeScript's recursion limits while supporting deeply nested structures.

Custom Depth

Tune the depth limit per use-site to balance compilation speed vs path coverage:

// Shallow — faster tsc for wide types with many top-level keys
type ShallowPaths = DeepKey<State, 10>

// Deeper — for types nested beyond 20 levels (e.g., recursive data models)
type DeepPaths = DeepKey<State, 30>

Depth Limit Marker (??)

When DeepKey hits the depth limit on an object type, or encounters an any-typed property (unknowable structure), it emits a ?? marker in IDE autocomplete:

some.deep.path.??    ← depth limit reached
data.payload.??      ← property typed as `any`

This tells you exactly where path generation stopped. To fix it: increase depth (DeepKey<T, 30>), restructure your type, or replace any with a concrete type.

Depth Propagation

The Depth parameter propagates to all dependent types, keeping the entire type system consistent:

// All accept a Depth parameter (defaults to DefaultDepth = 20)
type Paths = DeepKey<State, 10>
type BoolPaths = DeepKeyFiltered<State, boolean, 10>
type Syncs = SyncPair<State, 10>
type Logic = BoolLogic<State, 10>
type Effects = SideEffects<State, Meta, 10>
type Clear = ClearPathRule<State, 10>

Testing with the Mock Module

// __mocks__/@sladg/apex-state.ts
export * from '@sladg/apex-state/testing'

// your-test.test.ts
import { vi, describe, it, expect, beforeEach } from 'vitest'
import { __mocked, createGenericStore } from '@sladg/apex-state/testing'
import { renderHook } from '@testing-library/react'

vi.mock('@sladg/apex-state')

type FormState = { user: { email: string; name: string }; count: number }

beforeEach(() => __mocked.reset())

it('seeds state with type-safe chaining', () => {
  __mocked
    .set<FormState>({ user: { email: '', name: '' }, count: 0 })
    .set('user.email', '[email protected]')
    .set('count', 42)

  expect(__mocked.getState()).toEqual({
    user: { email: '[email protected]', name: '' },
    count: 42,
  })
})

it('tracks setValue calls from hooks', () => {
  const store = createGenericStore<FormState>()
  const hook = renderHook(() => store.useStore('user.email'))

  hook.result.current[1]('[email protected]')

  expect(__mocked.state.calls).toContainEqual({
    path: 'user.email',
    value: '[email protected]',
    meta: undefined,
  })
})

it('tracks concern and side effect registrations', () => {
  const store = createGenericStore<FormState>()
  store.useConcerns('form', { 'user.email': { disabledWhen: { boolLogic: { AND: [] } } } })
  store.useSideEffects('effects', { listeners: [] })

  expect(__mocked.state.effects).toHaveLength(2)
  expect(__mocked.state.effects[0]?.type).toBe('concerns')
  expect(__mocked.state.effects[1]?.type).toBe('sideEffects')
})

WASM vs Legacy Mode

WASM is the default. Pass { useLegacyImplementation: true } for pure JS:

// WASM (default) — Rust-powered pipeline, faster for complex state
const wasmStore = createGenericStore<MyState>()

// Legacy JS — pure JavaScript, no WASM binary needed
const legacyStore = createGenericStore<MyState>({ useLegacyImplementation: true })

Performance

Benchmarked with 60 variants across 3 Record layers, 75 syncs, 40 flips, 100 BoolLogic conditions, 85 listeners:

| Operation | Legacy | WASM | Winner | |---|---|---|---| | Single field edit | 0.5us | 1.4us | Legacy 2.6x | | 7 changes + cascading listeners | 41.8ms | 0.11ms | WASM 367x | | 60 bulk price changes | 596ms | 2.9ms | WASM 207x | | 135 changes (full catalog refresh) | 621ms | 2.99ms | WASM 208x |

Both modes produce identical state — verified across all 16 benchmark scenarios. See Benchmark Comparison for the full analysis.

API Overview

Store Creation

createGenericStore<T>(config?) returns all hooks:

| Hook | Returns | Use case | |---|---|---| | Provider | React context | Wraps component tree with initialState | | useStore(path) | [value, setValue] | Simple read/write (like useState) | | useFieldStore(path) | { value, setValue, ...concerns } | Form fields with merged concerns | | useJitStore() | { proxyValue, setChanges, getState } | Bulk updates, non-reactive reads | | useConcerns(id, config) | void | Register validation/BoolLogic/dynamic text | | useSideEffects(id, config) | void | Register sync/flip/aggregation/listeners | | useFieldConcerns(path) | EvaluatedConcerns | Read concern results for a path | | withConcerns(selection) | { useFieldStore } | Scoped field store with selected concerns |

Built-in Concerns

| Concern | Returns | Config | |---|---|---| | validationState | { isError, errors[] } | { schema: ZodSchema } | | disabledWhen | boolean | { boolLogic: BoolLogic } | | visibleWhen | boolean | { boolLogic: BoolLogic } | | readonlyWhen | boolean | { boolLogic: BoolLogic } | | dynamicLabel | string | { template: '{{path}}' } | | dynamicTooltip | string | { template: '{{path}}' } | | dynamicPlaceholder | string | { template: '{{path}}' } |

BoolLogic Operators

IS_EQUAL, EXISTS, IS_EMPTY, GT, LT, GTE, LTE, IN, AND, OR, NOT

Architecture

setValue("email", "[email protected]")
  |
  +--[WASM/Rust]--> shadow state + sync + flip + BoolLogic (Rust)
  |                   |
  |                   v
  |                 execute listeners + Zod validators (JS)
  |                   |
  |                   v
  |                 pipelineFinalize -> diff -> final changes (Rust)
  |
  +--[Legacy JS]--> sync -> flip -> listeners -> applyBatch
  |
  v
valtio proxy -> React re-render

Dual-layer design: JS/React owns reactivity and rendering. Rust/WASM owns heavy computation (graphs, diffing, pipeline orchestration). The boundary is thin: paths cross as strings, values as JSON. WASM decides the execution plan, JS executes user functions.

See WASM Architecture for the full specification.

Development

npm install            # Install dependencies
npm run wasm:build     # Compile Rust -> WASM
npm run build          # Bundle TypeScript + WASM
npm run test           # Run tests
npm run code:check     # Lint + type check
npm run wasm:check     # Rust lint + check

WASM Prerequisites

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup target add wasm32-unknown-unknown
cargo install wasm-pack

Documentation

| Document | Covers | |---|---| | Store & Hooks | Hook reference, composable hooks, patterns | | Concerns Guide | Concern lifecycle, built-ins, custom concerns | | Side Effects Guide | Sync, flip, aggregation, listener API | | WASM Architecture | JS/WASM boundary, data flow, ownership model | | Benchmark Comparison | Legacy vs WASM across 16 scenarios | | Wildcard Paths | _() hash key utility for Record types | | String Interpolation | Template helpers for dynamic text concerns | | Testing Mock | Mock module for consumer tests (vi.mock) | | Record Migration | Migration patterns for dynamic Record types | | Debug Timing | Performance debugging utilities | | Full Index | Complete documentation index |

Roadmap

  • Aggregation modes — Planned: SUM, AVG, COUNT, MIN, MAX, and custom reducer functions (currently consensus mode only).
  • Nested sub-stores — Component-level state that participates in the parent's pipeline.

License

MIT