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

v4.7.0

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. Rust/WASM pipeline handles all heavy computation: graph traversal, expression evaluation, listener dispatch.

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]?.message}</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, ValueLogic conditional values, dynamic text | Concerns Guide | | Side effects | Sync paths, flip paths, aggregations, listeners | Side Effects Guide | | WASM mode | Rust-powered pipeline for bulk operations (full catalog refresh in 7.4ms) | 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 |

Side effects enforce data invariants — when X changes, Y must follow (sync, flip, aggregate, listen). Concerns annotate fields with UI meaning — given settled state, is this field valid, disabled, visible? They run at different times and write to different proxies; neither drives the other.

Side effects and declarative concerns (boolLogic, valueLogic, validationState) are evaluated synchronously inside the WASM pipeline — results are available immediately after the state change. Custom evaluate() concerns use valtio's effect() and are not guaranteed synchronous — valtio may batch or defer their evaluation to the next microtask.

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>
)

Concerns

Concerns describe what a field means for the UI given the current state — valid, disabled, visible, what label to show. They run after state settles and write to a separate _concerns proxy, never to state itself.

store.useConcerns('checkout', {
  // Zod validation
  'user.email': {
    validationState: { schema: z.string().email('Enter a valid email') },
    disabledWhen: { boolLogic: { IS_EQUAL: ['status', 'submitted'] } },
    dynamicTooltip: { template: 'Sending confirmation to {{user.email}}' },
  },

  // Card number: validate + show only when paying by card
  'payment.cardNumber': {
    validationState: { schema: z.string().regex(/^\d{16}$/, 'Must be 16 digits') },
    visibleWhen: { boolLogic: { IS_EQUAL: ['payment.method', 'card'] } },
  },

  // Lock order total above threshold
  'order.total': {
    readonlyWhen: { boolLogic: { GT: ['order.total', 10000] } },
  },

  // ValueLogic: label adapts to stock level
  'product.quantity': {
    dynamicLabel: {
      valueLogic: {
        IF: { LTE: ['product.quantity', 5] },
        THEN: 'Quantity (low stock)',
        ELSE: 'Quantity',
      },
    },
  },
})

// Read all concerns for a field via useFieldStore
const { value, setValue, validationState, disabledWhen, dynamicTooltip } =
  store.useFieldStore('user.email')

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

Side Effects

Side effects describe what must change when state changes — enforcing invariants, keeping related fields in sync, reacting to mutations. They run inside the pipeline and can write back to state.

store.useSideEffects('checkout', {
  // Sync billing ↔ shipping contact details bidirectionally
  syncPaths: [
    ['billing.email', 'shipping.email'],
    ['billing.phone', 'shipping.phone'],
  ],

  // Express and standard shipping are mutually exclusive
  flipPaths: [
    ['shipping.express', 'shipping.standard'],
  ],

  // Order total = consensus across items (undefined if they differ)
  aggregations: [
    ['order.total', 'items.0.price'],
    ['order.total', 'items.1.price'],
    ['order.total', 'items.2.price'],
  ],

  // Stamp audit trail on any profile change
  listeners: [
    {
      path: 'user',
      scope: 'user',
      fn: (_changes, _state) => [['audit.lastModified', Date.now(), {}]],
    },
  ],
})

Reading and Writing State

// useStore — simple [value, setter] tuple (like useState)
const [name, setName] = store.useStore('user.name')

// useFieldStore — object API with all concerns merged in
const { value, setValue, validationState, disabledWhen } =
  store.useFieldStore('user.email')

// useJitStore — bulk updates and non-reactive reads
const { setChanges } = store.useJitStore()
setChanges([
  ['user.name', 'Alice', {}],
  ['user.email', '[email protected]', {}],
])

Composable Field Hooks

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

// Buffer edits locally, commit/cancel explicitly
const raw = store.useFieldStore('product.price')
const buffered = useBufferedField(raw)
// buffered.value, buffered.setValue, buffered.commit(), buffered.cancel(), buffered.isDirty

// Throttle rapid updates (e.g. range sliders)
const throttled = useThrottledField(raw, { ms: 100 })

// Transform display format (cents ↔ dollars)
const formatted = useTransformedField(raw, {
  to: (cents: number) => (cents / 100).toFixed(2),
  from: (dollars: string) => Math.round(parseFloat(dollars) * 100),
})

// Chain: buffered + transformed
const display = useTransformedField(useBufferedField(raw), {
  to: (cents) => (cents / 100).toFixed(2),
  from: (dollars) => Math.round(parseFloat(dollars) * 100),
})

Hash Key Paths for Record Types

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

// _() marks a segment as a hash key for Record-typed paths
const itemPath = `catalog.categories.${_('c1')}.items`
// -> typed string containing HASH_KEY marker

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

// Multiple hash keys for deeply nested Records
const nestedPath = `catalog.${_('c1')}.products.${_('p1')}.variants.${_('v1')}.price`

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')
})

Performance

Benchmarks run on Apple M4 Pro against a full e-commerce pipeline: 75 sync pairs, 40 flip pairs, 100 BoolLogic expressions, 85 listeners with real JS handlers.

| Scenario | Changes | Duration | |---|---|---| | Single field change | 1 | 1.4µs | | Order confirmation | 1 | 40µs | | Dashboard aggregation (10 orders) | 10 | 295µs | | Bulk price update (3 Record levels deep) | 60 | 7.2ms | | Full catalog refresh (everything fires) | 135 | 7.4ms |

Typical interactions are sub-millisecond. The stress case — 135 changes triggering cascading sync, flip, BoolLogic, and 85 listener callbacks across 3 Record levels — completes in 7.4ms.

Bundle: ~105 KB JS + ~697 KB WASM, loaded separately and cached by the browser. For eager loading:

import '@sladg/apex-state/preload'

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 | | withConcerns(selection) | { useFieldStore } | Scoped field store with selected concerns | | withMeta(presetMeta) | { useFieldStore } | Field store with preset META values |

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]")
  |
  v
[WASM/Rust] single processChanges() call:
  - shadow state update
  - aggregation → sync → flip
  - expression evaluation (BoolLogic + ValueLogic, unified)
  - listener waves + validator dispatch (via externref — no JS round-trips)
  |
  v
[JS] partition result changes by meta flag (state vs _concerns)
  |
  ├── applyBatch(stateChanges) → valtio state proxy
  └── applyConcernChanges(concernChanges) → valtio _concerns proxy
  |
  v
React re-render

Dual-layer design: JS/React owns reactivity and rendering. Rust/WASM owns the full pipeline — shadow state, sync/flip graphs, expression evaluation, and listener dispatch (via externref callbacks). The JS layer normalizes inputs, calls one WASM function, and applies the result. The boundary is thin: paths as strings, values as JSON.

Side effects vs. concerns: These are two distinct systems with different authority and different timing.

  • Side effects model causality — because X changed, Y must change. They enforce invariants in the data model (sync, flip, aggregate, listen). They run during the pipeline and can write to state.
  • Concerns model interpretation — given settled state, what does this field mean for the UI? They annotate fields with metadata (valid, disabled, visible, label). They run after the pipeline and can only write to _concerns.

Both use the same expression language (BoolLogic, ValueLogic) — that's just a shared way to write declarative conditions. What those conditions drive is determined by the registration context, not the expression itself.

See Architecture Guide and 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 | Detailed benchmark analysis 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) | | Debug Logging | Pipeline trace, console output, debug configuration | | 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