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

@ctxlyr/react

v0.1.0

Published

React state management library for untangling complex optimistic updates & providing app users with legendary error recovery paths.

Readme

Type-Driven State Modeling in React.js with Powerful TypeScript Generics

React state management library for untangling complex optimistic updates & providing app users with legendary error recovery paths.

API Documentation | Code Example

npm install @ctxlyr/react

Features @ctxlyr/react

  • Auto-generate hooks useStore(Chat)

  • Path-based selectors return slice specific scoped context & action handlers
    const { context, action } = useStore(Chat, "Generate.Stream")

  • Fine-grain reactivity with observables
    useSelect(context, $("newMessage", "list"))

  • Exhaustive action reducers
    Action.when("retry", () => to.slice("Generate.Stream"))

  • Strongly-typed context-aware transitions
    to.slice("Generate.Error", {...contextTransitionDelta})

  • DX: Your type declarations flow into each component import { Chat, useStore } from "@ctxlyr/react/use"

  • Built-in dependency injection through auto-generated Layer providers

Describe UI Behavior in a Compact, Readable Structure

Example of defining app behavior using utility type $

import type { $ } from "@ctxlyr/react"

type Store = $.Store<{
  Compose: $.Slice<
    [
      $.Context<{ list: Array<Message> draft: string }>,
      $.Action<{ updateDraft: string sendMessage: void }>,
    ]
  >
  Generate: $.Slice<
    [
      $.Context<{
        list: Array<Message>
        newMessage: Message
        responseBuffer: string
      }>,
      $.SubSlice<{
        Stream: $.Slice<[$.OnEntry]>
        Error: $.Slice<[$.Action<{ retry: void }>]>
      }>,
    ]
  >
}>

Instead of piecing together state and behavior from scattered files, you get a comprehensive understanding of your application's flow without overloading your context window.

Each slice defines:

  1. The guaranteed shape of context data
  2. When an action can mutate state

Less cognitive load → Architect new features without drowning in implementation details

✨ Every Component Binds to an Explicitly Typed Contract

Change how the UI flows & your IDE pinpoints the exact files that must be updated.

Because the entire UI is wired through these path-scoped contracts, any change in the model instantly ripples through your editor.

Path-based selectors return slice specific scoped context & action handlers

const { context, action } = useStore(Chat, "Generate.Stream")
/*    └─────┬─────────────────────────────────────────────┘
            │
            └─ context   : { list$: Message[] newMessage$: Message responseBuffer$: string }
               action    : {}  // "Stream" slice has no actions
*/
const { context, action } = useStore(Chat, "Generate.Error")
/*    └──────┬─────────────────────────────────────────────┘
             │
             └─ context : { list$: Message[] newMessage$: Message responseBuffer$: string }
                action  : { retry(): void }
*/

By splitting app state into tagged slices, you enable bullet-proof runtime confidence and next-level dev ergonomics.

⚡Observable Fine-grain Reactivity

Under the hood, @ctxlyr/react leverages the incredibly fast Legend-State.

This means you get the maintainability benefits global state through React.useContext without the downsides of wasteful re-renders on each state update.

📖 Usage

$ Type Utilities

The $ namespace provides type utilities for defining your store.

import type { $ } from "@ctxlyr/react"

| $ Type Utility | Description | | --------------- | -------------------------------------------------------------------------------- | | $.Model<T, U> | Resolved store model, optimized to flow all declared types into React components | | $.Store<T> | Structure for naming each slice & defining the state tree | | $.Slice<T> | Single store slice containing context, actions, and optional sub-slices | | $.Context<T> | Shape of data available within a slice | | $.Action<T> | Actions that can be dispatched from a slice | | $.SubSlice<T> | Nested slices that recursively inherit context & actions from parent | | $.OnEntry | Marker indicating a slice executes logic when entered | | $.Promise<T> | Promises accessible via Action.onEntry handler |

Store Builder

Runtime utilities for creating and configuring a store.

import { Store } from "@ctxlyr/react"

| Store Method | Description | | ------------------------------------------------- | ------------------------------------------------------------------- | | type<Model>() | Provides TypeScript inference for the store model | | type<T>().make(initial, layerService?, actions) | Builds a useable store for generating React hooks | | initial(slicePath) | Sets the initial slice that the store starts in | | layer(serviceMap) | Provide services into components through the store hook | | actions(...handlers) | Exhaustively, builds the action reducer for each defined $.Action |

Action Reducer

Utilities for building exhaustive action handlers that respond to user interactions and slice transitions.

import { Action } from "@ctxlyr/react"

| Action Builder | Description | | ------------------------------------ | --------------------------------------------------- | | Action.when(actionName, handler) | Handle a specific action dispatch from a React.FC | | Action.onEntry(slicePath, handler) | Execute side effects when entering a slice | | Action.exhaustive | Required marker ensuring all actions are handled |

Action Handler Function

| Fn Param | Description | | --------- | ---------------------------------------------------------- | | to | Builder for transitioning to other slices | | context | Observable context for the current slice | | payload | Data passed from the component when dispatching the action | | slice | String value of current slice path | | layer | Object map of provided services |

Action.when("sendMessage", ({ to, payload }) => {
  return to.slice("Generate.Stream", { newMessage: payload })
})

| onEntry Fn Param | Description | | ------------------ | ----------------------------------------------- | | ... | Includes all standard params | | promise | Object map of promises | | async (modifier) | Can await a promise created in a previous slice |

Action.onEntry("DocumentUpload.Optimistic", async ({ to, promise }) => {
  const doc = await promise.processDocument
  return to.slice("DocumentUpload.Confirmed", { doc })
})

Transition with to.slice

The to parameter provides strongly-typed utilities for transitioning between slices while maintaining context inheritance and type safety.

// ✅ Valid: newMessage is required for Generate.Stream
to.slice("Generate.Stream", { newMessage: "Hello", responseBuffer: "" })

// ❌ TypeScript Error: missing required newMessage property
to.slice("Generate.Stream", { responseBuffer: "" })

[!caution] > to.slice Must be returned in order to be applied.

Context Transition Rules

When transitioning between slices, TypeScript automatically determines which context properties are required, optional, or inherited based on the type differences between source and destination slices.

| Transition Type | Source Context | Target Context | Required in to.slice() | | ----------------------- | --------------------------- | ------------------------------- | ---------------------------- | | Type Change | { foo: number } | { foo: string } | { foo: string } | | Property Added | { foo: number } | { foo: number bar: boolean } | { bar: boolean } | | Required → Optional | { foo: number } | { foo?: number } | Nothing required | | Optional → Required | { foo?: number } | { foo: number } | { foo: number } | | Type Narrowing | { foo: string \| number } | { foo: string } | { foo: string } | | Type Widening | { foo: string } | { foo: string \| number } | Nothing required |

Optional Context Properties

When transitioning to a slice where inherited properties become optional, you can explicitly override them using withOptional.

// Current slice has { abc: string }
// Target slice has { abc?: string, xyz: boolean }

to.slice("TargetSlice", { xyz: true })
  // Optionally override inherited value
  .withOptional({ abc: "override" })
Fine-grain Optimistic UI

When transitioning to a slice defined to expect a promise using $.Promise, use to.withPromise to pass async operations that the target slice will await.

// Target slice expects: $.Promise<{ uploadResult: Promise<Document> }>

to.slice(
  "Upload.Optimistic",
  { uploadedAt: new Date() }, // if context required
  to.withPromise({
    uploadResult: api.upload(file),
  }),
)

The promise becomes available in the target slice's onEntry handler.

Dispatched Action Payload

Actions receive typed payloads from components based on your $.Action declarations.

/*    
  $.Action<{
    send: { text: string attachments?: File[] }
  }>
*/

Action.when("send", ({ payload }) => {
  // payload: { text: string attachments?: File[] }
  const newMessage = createMessage(payload.text, payload.attachments)
})

Update Slice Context

Use $set within action handlers to update the current slice's context without transitioning to a different slice.

Action.when("updateDraft", ({ context, payload }) =>
  $set(context.draft$, payload),
)

[!caution] > $set Must be returned in order to be applied.

Access Layer Service Dependencies

Inject services through Store.layer to access them in any action handler via the layer parameter.

/*    
  Store.layer({
    analytics: new Analytics(),
  })
*/

Action.when("send", ({ layer }) => {
  // Access services in action handlers
  layer.analytics.track("message_sent")
})

Async State Resolution

When an onEntry handler is marked async, it can await promises created during slice transitions, enabling powerful async state coordination.

/*    
  $.Promise<{
    chargeCard: ReturnType<typeof chargeCard>
  }>
*/

Action.onEntry("Payment.Processing", async ({ promise }) => {
  const result = await promise.chargeCard
})

Usage in React

Export Stores

The Store.type().make & Layer.makeProvider functions transforms your strongly-typed store definitions into a complete React integration layer.

export * from "@ctxlyr/react/hooks"
export { Chat, ChatLayer } from "./chat/store"
export { Auth, AuthLayer } from "./auth/store"

Config @ctxlyr/react/use Import Path

For the ultimate developer experience, configure your project to import the generated utilities from a custom path. This technique transforms verbose imports into a clean, project-specific API that feels native to your codebase.

// ❌ Before: Repetitive, fragile imports
import { Chat, useStore } from "../../../app/ctxlyr.ts"

// ✅ After: Clean, maintainable import
import { Chat, useStore } from "@ctxlyr/react/use"

Configure TypeScript to resolve the custom import path:

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@ctxlyr/react/use": ["./src/app/ctxlyr.ts"]
    }
  }
}

For Vite projects, ensure the bundler uses TypeScript paths at runtime:

import { defineConfig } from "vite"
import tsconfigPaths from "vite-tsconfig-paths"

export default defineConfig({
  plugins: [tsconfigPaths()],
})

Context Layer Provider

Every store automatically generates a corresponding Layer provider component that initializes the store context and manages the state lifecycle for its component tree.

<ChatLayer context={{ list: [], draft: "" }}>
  <ThreadView />
</ChatLayer>

The Layer provider serves as the dependency injection boundary for your store, ensuring that all child components have access to the typed context and actions defined in your model.

Store Layer Props

The Layer component accepts props based on your store's type definitions, with TypeScript ensuring you provide all required initial values & promises.

| Layer Prop | Description | | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | context | Initial values for the store's context. Optional properties can be omitted or explicitly set. | | promise | When your store includes $.Promise declarations. Accepts an object mapping promise keys to actual Promise instances that will be available in onEntry handlers for async state coordination. |

useStore Hook

The useStore hook is your primary interface for accessing typed context and actions within components. This auto-generated hook provides slice-specific context access with compile-time guarantees about what data and actions are available.

// Access any slice
const { slice$, context, action, layer } = useStore(Chat)

// Scope to a specific slice path
const { context, action } = useStore(Chat, "Generate.Stream")

// Scope to any sub slice path
const { context, action } = useStore(Chat, "Generate.*")

Returned Properties

| Property | Description | | --------- | ---------------------------------- | | slice$ | Current slice path as observable | | context | Slice-specific data as observables | | action | Available actions for the slice | | layer | Injected service dependencies |

Runtime Slice Validation

The hook includes runtime validation to ensure components are rendered within the correct slice. This catches routing errors early in development:

// If current slice is "Compose" but component expects "Generate.Stream"
const StreamView: React.FC = () => {
  const { context } = useStore(Chat, "Generate.Stream")
  // 🚨 Throws: Component rendered in wrong slice: 'Compose' does not match selection 'Generate.Stream'
}

useWatch Hook

The useWatch hook bridges the gap between observable state and React's rendering lifecycle. When you pass an observable to useWatch, it automatically subscribes to changes and triggers component re-renders only when that specific value updates.

import { useWatch } from "@ctxlyr/react/use"

Observable Syntax

The $ suffix convention signals that a value is observable, providing a visual cue in your code about which values can be watched.

const { slice$, context } = useStore(Chat)

const currentSlice = useWatch(slice$)
const draftMessage = useWatch(context.draft$)

useSelect vs useWatch

Both useSelect and useWatch enable fine-grained reactivity, but they serve different ergonomic needs when building components. Understanding when to use each pattern will help you write cleaner, more maintainable React components.

The useSelect Advantage: Bulk Property Access

When your component needs multiple context properties, useSelect dramatically reduces boilerplate by unwrapping all selected observables in a single declaration.

// ❌ Before: Verbose useWatch for each property
const { context } = useStore(Chat, "Generate.Error")

const errorMsg = useWatch(context.errorMsg$)
const responseBuffer = useWatch(context.responseBuffer$)
const retryCount = useWatch(context.retryCount$)
const lastAttempt = useWatch(context.lastAttempt$)
const errorCode = useWatch(context.errorCode$)
const errorStack = useWatch(context.errorStack$)
const userMessage = useWatch(context.userMessage$)
const debugInfo = useWatch(context.debugInfo$)
const timestamp = useWatch(context.timestamp$)
const sessionId = useWatch(context.sessionId$)

// ✅ After: Clean, maintainable with useSelect
const { context } = useStore(Chat, "Generate.Error")

const ctx = useSelect(
  context,
  $(
    "errorMsg",
    "responseBuffer",
    "retryCount",
    "lastAttempt",
    "errorCode",
    "errorStack",
    "userMessage",
    "debugInfo",
    "timestamp",
    "sessionId",
  ),
)

// All selected properties available on ctx object
return (
  <ErrorReport>
    <h2>
      Error {ctx.errorCode}: {ctx.errorMsg}
    </h2>
    <p>
      Attempt {ctx.retryCount} at {ctx.lastAttempt}
    </p>
    <details>
      <summary>Debug Info (Session: {ctx.sessionId})</summary>
      <pre>{ctx.errorStack}</pre>
      <code>{JSON.stringify(ctx.debugInfo, null, 2)}</code>
    </details>
    <output>{ctx.responseBuffer}</output>
  </ErrorReport>
)

useSelect makes it easy to extend and refactor components.

[!important] The $ utility is required to work with useSelect

import { $, useSelect } from "@ctxlyr/react/use"

When to Use useWatch: Nested Property Access

While useSelect excels at bulk property selection, useWatch shines when you just need to access one property or want to declare as it's own variable.

const name = useWatch(context.profile$.firstName$)