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

@sqlrooms/room-store

v0.29.0

Published

Low-level state management primitives for SQLRooms, built on Zustand.

Readme

Low-level state management primitives for SQLRooms, built on Zustand.

Use this package when you want to build custom room state from scratch.
If you want DuckDB + layout + room shell out of the box, use @sqlrooms/room-shell.

Installation

npm install @sqlrooms/room-store

What this package provides

  • createRoomStore() and createRoomStoreCreator()
  • base lifecycle slice: createBaseRoomSlice()
  • generic slice helper: createSlice()
  • React context/hooks: RoomStateProvider, useBaseRoomStore, useRoomStoreApi
  • persistence helpers: persistSliceConfigs(), createPersistHelpers()
  • room-store persistence glue: createRoomStorePersistence()
  • persistence controller: createPersistenceController()

Quick start

import {
  BaseRoomStoreState,
  createBaseRoomSlice,
  createRoomStore,
  createSlice,
  type StateCreator,
} from '@sqlrooms/room-store';

type CounterSliceState = {
  counter: {
    value: number;
    increment: () => void;
  };
};

function createCounterSlice(): StateCreator<CounterSliceState> {
  return createSlice<CounterSliceState>((set, get) => ({
    counter: {
      value: 0,
      increment: () =>
        set((state) => ({
          counter: {
            ...state.counter,
            value: get().counter.value + 1,
          },
        })),
    },
  }));
}

type RoomState = BaseRoomStoreState & CounterSliceState;

export const {roomStore, useRoomStore} = createRoomStore<RoomState>(
  (set, get, store) => ({
    ...createBaseRoomSlice()(set, get, store),
    ...createCounterSlice()(set, get, store),
  }),
);

React integration

import {RoomStateProvider} from '@sqlrooms/room-store';
import {roomStore} from './store';

export function App() {
  return (
    <RoomStateProvider roomStore={roomStore}>
      <Dashboard />
    </RoomStateProvider>
  );
}
import {useRoomStore} from './store';
import {Button} from '@sqlrooms/ui';

function Dashboard() {
  const value = useRoomStore((state) => state.counter.value);
  const increment = useRoomStore((state) => state.counter.increment);

  return <Button onClick={increment}>Count: {value}</Button>;
}

Imperative access

Use roomStore.getState() for non-reactive code (events, timers, async jobs).

import {roomStore} from './store';

export function incrementLater() {
  setTimeout(() => {
    roomStore.getState().counter.increment();
  }, 500);
}

Guarded command invocation

External and agent-facing integrations must use invokeCommandWithPolicy() instead of calling roomStore.getState().commands.invokeCommand() directly. The guarded helper re-checks that the command exists and is enabled immediately before execution. It also blocks high-risk or requiresConfirmation commands unless the caller supplies confirmation obtained from the user.

import {invokeCommandWithPolicy} from '@sqlrooms/room-store';

const result = await invokeCommandWithPolicy(
  roomStore,
  'workspace.refresh',
  undefined,
  {
    surface: 'mcp',
    actor: 'assistant',
    traceId: requestId,
    metadata: {clientName: 'Example client'},
    signal: abortController.signal,
  },
  {confirmed: false},
);

Set confirmed: true only after explicit user confirmation. Omitting it, or passing false, fails closed with command-confirmation-required when the command requires confirmation. createCommandCliAdapter() and createCommandMcpAdapter() use the same guard and therefore have the same execution semantics.

Persistence

For a Zustand room store with host-owned storage, prefer createRoomStorePersistence(). It composes createPersistHelpers() with a controller-backed PersistStorage, rehydrate saved-snapshot marking, optional room-store subscription, autosave, and final flush helpers. This is the default entry point for SQLRooms apps that persist room state to DuckDB, files, or another project-owned store. See the Persistence developer guide for the full integration model, data flow, and examples.

persistSliceConfigs() defaults to browser localStorage. If browser storage is null, cannot be accessed, or a raw storage operation fails, the room state continues to work in memory and persistence is skipped. Malformed persisted JSON and read failures from an explicit custom storage adapter still propagate through Zustand's onRehydrateStorage callback so hosts can distinguish a failed load from an empty store; custom write and removal failures are logged and skipped.

Use the lower-level createPersistenceController() only when you need the same persistence policy outside a room store or Zustand persist. The controller is storage-agnostic: hosts provide load() and save() adapter functions, while SQLRooms handles hydration state, dirty tracking, scheduled saves, final flush, in-flight save coalescing, and observable save status.

createPersistHelpers() still only handles schema-based partialization and rehydrate merging. Let createRoomStorePersistence() combine those helpers with save policy unless you have a custom integration that does not fit the room-store helper.

import {createRoomStorePersistence} from '@sqlrooms/room-store';

const persistence = createRoomStorePersistence({
  partialize: (state) => ({room: state.room.config}),
  autosaveDelayMs: 300,
  load: async () => loadProjectSnapshot(),
  save: async (snapshot, metadata) => {
    await saveProjectSnapshot(snapshot, metadata?.reason);
  },
});

await persistence.hydrate();
await persistence.flush('final-flush');

Inside components, useRoomStoreApi() gives you the raw store API:

import {useRoomStoreApi} from '@sqlrooms/room-store';
import {Button} from '@sqlrooms/ui';

function ResetButton() {
  const store = useRoomStoreApi();
  return (
    <Button
      onClick={() => {
        // Example: imperative read from store
        const current = store.getState().room.initialized;
        console.log('initialized', current);
      }}
    >
      Inspect store
    </Button>
  );
}