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

@blujosi/rivetkit-solid

v2.3.13

Published

A SolidJS integration for [RivetKit](https://rivet.dev) that provides reactive actor connections using SolidJS signals, a Provider/Context architecture for SSR-safe client injection, and a SolidStart handler for serverless deployment.

Readme

@blujosi/rivetkit-solid

A SolidJS integration for RivetKit that provides reactive actor connections using SolidJS signals, a Provider/Context architecture for SSR-safe client injection, and a SolidStart handler for serverless deployment.

Installation

pnpm add @blujosi/rivetkit-solid rivetkit

# or

npm i @blujosi/rivetkit-solid rivetkit

Overview

@blujosi/rivetkit-solid provides three main pieces:

  1. SolidJS client (@blujosi/rivetkit-solid) — RivetProvider, useActorFromContext, and useActor hook for reactive actor connections with real-time events
  2. SolidStart handler (@blujosi/rivetkit-solid/solidstart) — createRivetKitHandler to serve RivetKit as a SolidStart API route
  3. SSR transport (@blujosi/rivetkit-solid/solidstart) — useRivetQuery for server-fetched data that upgrades to live subscriptions on the client

Features

  • Provider + Context — SSR-safe client injection via <RivetProvider>, no module-level singletons
  • SolidJS Signals — Built on createSignal, createEffect, createResource, and getter-based reactivity
  • Real-time Actor Connections — Connect to RivetKit actors with automatic state sync via WebSocket
  • SSR → Live UpgradeuseRivetQuery uses createResource for automatic SSR serialization, then upgrades to live WebSocket on the client
  • Event HandlinguseEvent with automatic cleanup via onCleanup
  • Type Safety — Full TypeScript support with registry type inference
  • SolidStart Handler — Run RivetKit serverless inside your SolidStart app

Package Entry Points

| Import path | Purpose | |---|---| | @blujosi/rivetkit-solid | Client-side: RivetProvider, useRivet, useActorFromContext, createClient, createRivetKit, createRivetKitWithClient, CRUD transforms | | @blujosi/rivetkit-solid/solid | SolidJS-specific: full client exports (re-exported from main) | | @blujosi/rivetkit-solid/solidstart | Server + SSR: createRivetKitHandler, useRivetQuery, createRivetQuery |


Quick Start

1. Define Your Actors & Registry

// backend/registry.ts
import { actor, setup } from "rivetkit";

export const counter = actor({
  state: { count: 0, countDouble: 0 },
  actions: {
    increment: (c, x: number) => {
      c.state.count += x;
      c.broadcast("newCount", c.state.count);
      return c.state.count;
    },
    getCount: (c) => c.state.count,
    getCountDouble: (c) => c.state.countDouble,
    doubleIncrement: (c, y: number) => {
      c.state.countDouble += y;
      c.broadcast("newDoubleCount", c.state.countDouble);
      return c.state.countDouble;
    },
    reset: (c) => {
      c.state.count = 0;
      c.state.countDouble = 0;
      c.broadcast("newCount", c.state.count);
      c.broadcast("newDoubleCount", c.state.countDouble);
      return c.state.count;
    },
  },
});

export const registry = setup({
  use: { counter },
});

export type Registry = typeof registry;

2. Set Up the SolidStart Handler

Create a catch-all API route to proxy RivetKit requests through SolidStart:

// src/routes/api/rivet/[...rest].ts
import { createRivetKitHandler } from "@blujosi/rivetkit-solid/solidstart";
import { registry } from "~backend/registry";

export const { GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS } =
  createRivetKitHandler({
    isDev: !!import.meta.env.DEV,
    registry,
    rivetSiteUrl: "http://localhost:3000",
  });

Handler Options

| Option | Type | Description | |---|---|---| | registry | Registry | Your RivetKit registry instance | | isDev | boolean | Enables auto-engine spawn and runner pool config | | rivetSiteUrl | string | Base URL for the site | | headers | Record<string, string>? | Static headers added to every request | | getHeaders | (request: Request) => Record<string, string>? | Dynamic per-request headers | | runtime | "default" \| "cloudflare"? | Runtime to use. "default" uses the built-in registry handler; "cloudflare" delegates to @rivetkit/cloudflare-workers. Defaults to "default" |

Cloudflare Workers Runtime

To deploy your SolidStart app on Cloudflare Workers, set runtime: "cloudflare" and install the Cloudflare Workers adapter:

npm install @rivetkit/cloudflare-workers

Then update your handler:

// src/routes/api/rivet/[...rest].ts
import { createRivetKitHandler } from "@blujosi/rivetkit-solid/solidstart";
import { registry } from "~backend/registry";

export const { GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS } =
  createRivetKitHandler({
    isDev: !!import.meta.env.DEV,
    registry,
    rivetSiteUrl: "http://localhost:3000",
    runtime: "cloudflare",
  });

When runtime: "cloudflare" is set, the handler dynamically imports @rivetkit/cloudflare-workers and uses its createHandler to process requests via Cloudflare's Durable Objects. The @rivetkit/cloudflare-workers package is an optional peer dependency — it only needs to be installed when using the Cloudflare runtime.

3. Create the Client & Wrap with Provider

// src/lib/actor.client.ts
import { createClient } from "@blujosi/rivetkit-solid";
import type { Client } from "rivetkit/client";
import type { Registry } from "~backend/registry";

const IS_BROWSER = typeof globalThis.document !== "undefined";

const endpoint = IS_BROWSER
  ? `${location.origin}/api/rivet`
  : "http://localhost:3000/api/rivet";

export const rivetClient: Client<Registry> = createClient<Registry>(endpoint);

Then wrap your app with <RivetProvider>:

// src/app.tsx
import { Router } from "@solidjs/router";
import { FileRoutes } from "@solidjs/start/router";
import { RivetProvider } from "@blujosi/rivetkit-solid";
import { rivetClient } from "~/lib/actor.client";

export default function App() {
  return (
    <RivetProvider client={rivetClient}>
      <Router root={(props) => <>{props.children}</>}>
        <FileRoutes />
      </Router>
    </RivetProvider>
  );
}

4. Use Actors in Components (Client-Side)

Use useActorFromContext — it pulls the client from the provider automatically:

// src/routes/index.tsx
import { useActorFromContext } from "@blujosi/rivetkit-solid";
import { Show } from "solid-js";

export default function Home() {
  const counterActor = useActorFromContext({
    name: "counter",
    key: ["test-counter"],
  });

  const countQuery = counterActor?.useActionQuery({
    action: "getCount",
    event: "newCount",
    initialValue: 0,
  });

  const increment = async () => {
    await counterActor?.current?.connection?.increment(1);
  };
  const reset = async () => {
    await counterActor?.current?.connection?.reset();
  };

  return (
    <Show
      when={!countQuery?.isLoading}
      fallback={<p>Loading...</p>}
    >
      <div>
        <h1>Counter: {countQuery?.value}</h1>
        <button onClick={increment}>Increment</button>
        <button onClick={reset}>Reset</button>
      </div>
    </Show>
  );
}

5. SSR with Live Upgrade

Use useRivetQuery to fetch actor collections server-side and automatically upgrade to live WebSocket subscriptions on the client. T is the item type; data is always T[]:

// src/routes/todos.tsx
import { useRivetQuery } from "@blujosi/rivetkit-solid/solidstart";
import { useActorFromContext } from "@blujosi/rivetkit-solid";
import { Show, For } from "solid-js";

interface Todo { id: string; title: string; done: boolean }

export default function TodosPage() {
  // SSR query — T is the item type, data() returns Todo[]
  const todos = useRivetQuery<Todo>({
    actor: "todoList",
    key: ["my-list"],
    action: "getTodos",
    event: "todoListUpdate",
  });

  // Actor connection for calling mutations
  const todoActor = useActorFromContext({
    name: "todoList",
    key: ["my-list"],
  });

  const addTodo = async (title: string) => {
    await todoActor?.current?.connection?.addTodo(title);
  };

  return (
    <div>
      <h2>Todos</h2>
      <Show when={!todos.isLoading()} fallback={<p>Loading...</p>}>
        <For each={todos.data() ?? []}>
          {(todo) => <p>{todo.title}</p>}
        </For>
      </Show>
    </div>
  );
}

For simple scalar values (counters etc.), use useActorFromContext + useActionQuery instead:

const counterActor = useActorFromContext({
  name: "counter",
  key: ["test-counter"],
});

const count = counterActor?.useActionQuery({
  action: "getCount",
  event: "newCount",
  initialValue: 0,
});

// count?.value is a number

How SSR → Live works:

  1. On the server, useRivetQuery uses createResource to call the action — SolidStart serializes the result automatically
  2. On the client (hydration), a createEffect waits for the resource to resolve, then creates a live WebSocket subscription
  3. onCleanup disconnects the WebSocket and unsubscribes events when the component unmounts
  4. The returned RivetQueryResult<T> exposes reactive accessors (data(), isLoading(), error(), isConnected(), refetch())

API Reference

Provider & Context (@blujosi/rivetkit-solid)

<RivetProvider client={client}>

Provides the RivetKit client to all descendant components via SolidJS context. Required for useActorFromContext and useRivetQuery.

import { RivetProvider } from "@blujosi/rivetkit-solid";

<RivetProvider client={rivetClient}>
  {/* All children can use useActorFromContext and useRivetQuery */}
</RivetProvider>

| Prop | Type | Description | |---|---|---| | client | Client<any> | The RivetKit client instance |

useRivet<Registry>()

Returns the RivetKit context value ({ client }). Throws if called outside a <RivetProvider>.

import { useRivet } from "@blujosi/rivetkit-solid";
const { client } = useRivet<Registry>();

useActorFromContext(options)

Connects to a RivetKit actor using the client from <RivetProvider>. Returns the same API as useActor.

import { useActorFromContext } from "@blujosi/rivetkit-solid";

const actor = useActorFromContext({
  name: "counter",
  key: ["test-counter"],
});

Client Exports (@blujosi/rivetkit-solid)

createClient<Registry>(url)

Creates a RivetKit client connection.

import { createClient } from "@blujosi/rivetkit-solid";
const client = createClient<Registry>("http://localhost:3000/api/rivet");

createRivetKit<Registry>(url, opts?)

Shorthand that creates both the client and the useActor hook.

import { createRivetKit } from "@blujosi/rivetkit-solid";
export const { useActor } = createRivetKit<Registry>("http://localhost:3000/api/rivet");

createRivetKitWithClient<Registry>(client, opts?)

Creates the useActor hook from an existing client instance.

import { createClient, createRivetKitWithClient } from "@blujosi/rivetkit-solid";
const client = createClient<Registry>(url);
export const { useActor } = createRivetKitWithClient(client);

useActor(options)

Connects to a RivetKit actor and returns reactive state plus helper methods. Uses SolidJS signals internally for fine-grained reactivity.

const actor = useActor({
  name: "counter",             // Actor name from your registry
  key: ["test-counter"],       // Unique key for this instance
  params: { /* ... */ },       // Optional connection parameters
  createInRegion: "us-east-1", // Optional region
  createWithInput: { /* */ },  // Optional input data
  enabled: true,               // Optional, defaults to true
});

Returns:

| Property | Type | Description | |---|---|---| | current.connection | ActorConn | Call actions on the actor | | current.handle | ActorHandle | Advanced actor operations | | current.isConnected | boolean | Whether the actor is connected | | current.isConnecting | boolean | Whether a connection is in progress | | current.isError | boolean | Whether there's an error | | current.error | Error \| null | The error object, if any | | useEvent(name, handler) | function | Listen for actor events | | useQuery(opts) | object | Reactive query with transform — see below | | useActionQuery(opts) | object | Re-fetch query (event = invalidation signal) — see below |


useEvent(eventName, handler)

Registers an event listener with automatic cleanup via onCleanup.

counterActor?.useEvent("newCount", (value: number) => {
  console.log("Count updated:", value);
});

useQuery(options)

Creates a reactive query that fetches an initial value by calling an actor action, then subscribes to an event to keep the value updated in real-time. The event data is used directly to update the value (optionally via a transform function).

const count = counterActor?.useQuery({
  action: "getCount",
  event: "newCount",
  initialValue: 0,
});

Options:

| Option | Type | Description | |---|---|---| | action | string | The action name to call for the initial value | | args | any[]? | Optional arguments passed to the action | | event | string | The event name to subscribe to for real-time updates | | initialValue | T | The value to use before the action resolves | | transform | (current: T, incoming: any) => T? | Optional function to merge incoming event data with the current value |

Default transform behavior:

  • Plain objects — shallow merge: { ...current, ...incoming }
  • Primitives & arrays — full replacement

Returns:

| Property | Type | Description | |---|---|---| | value | T | The current reactive value (getter backed by signal) | | isLoading | boolean | true until the initial action resolves | | error | Error \| null | Error from the action call, if any |


useActionQuery(options)

Creates a reactive query that fetches a value by calling an actor action, then re-fetches whenever a specified event fires. Unlike useQuery, the event data is not used — it's purely an invalidation signal that triggers a fresh action call.

const count = counterActor?.useActionQuery({
  action: "getCount",
  event: "newCount",
  initialValue: 0,
});

Options:

| Option | Type | Description | |---|---|---| | action | string | The action name to call (and re-call on event) | | args | Accessor<any[]>? | Optional reactive accessor for arguments | | event | string \| string[] | Event name(s) that trigger a re-fetch | | initialValue | T | The value to use before the action resolves |

Returns:

| Property | Type | Description | |---|---|---| | value | T | The current reactive value (getter backed by signal) | | isLoading | boolean | true during initial fetch and re-fetches | | error | Error \| null | Error from the action call, if any | | refetch | () => void | Manually trigger a re-fetch |

When to use useQuery vs useActionQuery:

| | useQuery | useActionQuery | |---|---|---| | Event data used? | Yes — event payload updates the value | No — event is just an invalidation signal | | Re-fetches action? | No — only on initial connect | Yes — every time the event fires | | Best for | Simple values broadcast via events | Computed/derived values that need a fresh server call |


CRUD Transforms (@blujosi/rivetkit-solid)

Pre-built transform factories for managing lists of items via create/update/delete events. Use with useQuery, useActionQuery, or useRivetQuery.

import {
  crudTransform,
  createTransform,
  updateTransform,
  deleteTransform,
} from "@blujosi/rivetkit-solid";

crudTransform<T>(opts?)

A unified transform that handles all three CRUD operations in a single function. The actor broadcasts events wrapped in a CrudEvent<T>:

// In the actor:
c.broadcast("taskChanged", { type: "created", data: newTask });
c.broadcast("taskChanged", { type: "updated", data: updatedTask });
c.broadcast("taskChanged", { type: "deleted", data: deletedTask });
// In the component:
interface Task { id: string; title: string; done: boolean }

// With useRivetQuery (SSR + live) — T is the item type, data() returns Task[]
const tasks = useRivetQuery<Task>({
  actor: "taskList",
  key: ["my-list"],
  action: "getTasks",
  event: "taskChanged",
  transform: crudTransform<Task>({ key: "id" }),
});

// With useQuery (client-side) — T is the full state type
const tasks = actor?.useQuery({
  action: "getTasks",
  event: "taskChanged",
  initialValue: [] as Task[],
  transform: crudTransform<Task>({ key: "id" }),
});

If the incoming payload is not wrapped in { type, data }, crudTransform falls back to an upsert — it updates the item if it exists, or appends it otherwise.

Options (CrudTransformOptions<T>):

| Option | Type | Default | Description | |---|---|---|---| | key | keyof T \| (item: T) => unknown | "id" | Property name or accessor used to uniquely identify items |

CrudEvent<T> shape:

| Field | Type | Description | |---|---|---| | type | "created" \| "updated" \| "deleted" | The operation type | | data | T | The item (or key value for deletes) |

Individual Transforms

Use these when you have separate events for each operation:

createTransform<T>(opts?)

Appends the incoming item to the list. Duplicates (same key) are ignored.

actor?.useQuery({
  action: "getTasks",
  event: "taskCreated",
  initialValue: [],
  transform: createTransform<Task>({ key: "id" }),
});
updateTransform<T>(opts?)

Replaces the matching item in-place. If no match is found, the list is returned unchanged.

actor?.useQuery({
  action: "getTasks",
  event: "taskUpdated",
  initialValue: [],
  transform: updateTransform<Task>({ key: "id" }),
});
deleteTransform<T>(opts?)

Removes the matching item. Accepts either a full object or a raw key value.

actor?.useQuery({
  action: "getTasks",
  event: "taskDeleted",
  initialValue: [],
  transform: deleteTransform<Task>({ key: "id" }),
});

// The actor can broadcast just the key:
c.broadcast("taskDeleted", taskId);
// Or the full object:
c.broadcast("taskDeleted", task);

Custom Key Functions

For items keyed by something other than a single property:

const items = useRivetQuery<Item>({
  actor: "items",
  key: ["all"],
  action: "getItems",
  event: "itemChanged",
  transform: crudTransform<Item>({
    key: (item) => `${item.type}:${item.slug}`,
  }),
});

SolidStart Exports (@blujosi/rivetkit-solid/solidstart)

createRivetKitHandler(opts)

Creates SolidStart API route handlers for a catch-all route.

import { createRivetKitHandler } from "@blujosi/rivetkit-solid/solidstart";
import { registry } from "~backend/registry";

export const { GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS } =
  createRivetKitHandler({
    isDev: !!import.meta.env.DEV,
    registry,
    rivetSiteUrl: "http://localhost:3000",
  });

useRivetQuery<T>(options) (recommended)

Fetches actor collection data with SSR support and automatic live upgrade. T is the item type — data() returns T[]. Uses createResource for SSR serialization and createEffect + onCleanup for live WebSocket upgrade. Must be called inside a component wrapped in <RivetProvider>.

Defaults to crudTransform<T>() when no transform is provided.

import { useRivetQuery } from "@blujosi/rivetkit-solid/solidstart";

const todos = useRivetQuery<Todo>({
  actor: "todoList",
  key: ["my-list"],
  action: "getTodos",
  event: "todoListUpdate",
});

// Access values via signal accessors
todos.data()         // Todo[] | undefined
todos.isLoading()    // boolean
todos.error()        // Error | undefined
todos.isConnected()  // boolean
todos.refetch()      // manually re-fetch

createRivetQuery<T>(client, options)

Same as useRivetQuery but takes an explicit client — doesn't require <RivetProvider>.

import { createRivetQuery } from "@blujosi/rivetkit-solid/solidstart";

const todos = createRivetQuery<Todo>(myClient, {
  actor: "todoList",
  key: ["my-list"],
  action: "getTodos",
  event: "todoListUpdate",
});

Options (RivetQueryOptions<T>):

| Option | Type | Description | |---|---|---| | actor | string | Actor name from the registry | | key | string \| string[] | Unique key for the actor instance | | action | string | Action name to call for the initial collection | | args | unknown[]? | Arguments to pass to the action | | event | string \| string[] | Event name(s) to subscribe to for live updates | | params | Record<string, string>? | Connection params (e.g. auth tokens) | | createInRegion | string? | Region to create the actor in | | createWithInput | unknown? | Input data for actor creation | | transform | (current: T[], incoming: CrudEvent<T>) => T[]? | Custom transform. Default: crudTransform<T>() |

RivetQueryResult<T> — return type:

| Accessor | Type | Description | |---|---|---| | data() | T[] \| undefined | The current collection (live data preferred over initial fetch) | | isLoading() | boolean | Whether the initial resource is loading | | error() | Error \| undefined | Any error from fetch or WebSocket | | isConnected() | boolean | Whether the live WebSocket is connected | | refetch() | void | Manually re-fetch the action value |


Connection Parameters

You can pass params when connecting to actors — both via useActorFromContext and useRivetQuery. These are Record<string, string> values sent with the connection handshake, typically used for authentication:

// Client-side via useActorFromContext
const actor = useActorFromContext({
  name: "counter",
  key: ["test-counter"],
  params: { token: "user-auth-token" },
});

// SSR via useRivetQuery — T is the item type
const todos = useRivetQuery<Todo>({
  actor: "todoList",
  key: ["my-list"],
  action: "getTodos",
  event: "todoListUpdate",
  params: { token: "user-auth-token" },
});

Inside the actor, params are available in the onBeforeConnect lifecycle (for validation/rejection) and on the connection context in actions:

export const counter = actor({
  state: { count: 0 },

  onBeforeConnect: (c, params) => {
    if (!params.token || !isValidToken(params.token)) {
      throw new Error("Unauthorized");
    }
  },

  actions: {
    getCount: (c) => {
      const token = c.conn.params.token;
      return c.state.count;
    },
  },
});

Project Structure

A typical SolidStart + RivetKit project looks like this:

├── backend/
│   └── registry.ts          # Actor definitions & registry
├── src/
│   ├── lib/
│   │   ├── actor.client.ts  # RivetKit client export
│   │   └── index.ts         # Re-exports
│   ├── routes/
│   │   ├── index.tsx         # Client-side page (useActorFromContext)
│   │   ├── ssr.tsx           # SSR page with useRivetQuery
│   │   └── api/
│   │       └── rivet/
│   │           └── [...rest].ts  # Catch-all RivetKit handler
│   ├── entry-client.tsx
│   ├── entry-server.tsx
│   └── app.tsx               # <RivetProvider> wraps the Router
├── app.config.ts             # SolidStart config
├── package.json
└── tsconfig.json

TypeScript Configuration

Ensure your tsconfig.json includes Vite client types for import.meta.env:

{
  "compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "jsx": "preserve",
    "jsxImportSource": "solid-js",
    "types": ["vite/client"]
  }
}

SolidStart Configuration

Example app.config.ts with path aliases for the backend directory:

import { defineConfig } from "@solidjs/start/config";

export default defineConfig({
  server: {
    preset: "node-server",
  },
  vite: {
    resolve: {
      alias: {
        "~backend": "./backend",
      },
    },
  },
});