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

@ivancerovina/contracts-react

v1.0.0

Published

React hooks for @ivancerovina/contracts WebSocket namespaces — typed Socket.IO via Jotai atom families

Downloads

10

Readme

@ivancerovina/contracts-react

React hooks for @ivancerovina/contracts WebSocket namespaces. Typed Socket.IO connections managed by Jotai atom families — connections open when the first component subscribes and close when the last one unmounts.

Install

pnpm add @ivancerovina/contracts-react @ivancerovina/contracts jotai socket.io-client react

react and jotai must be wrapped in a Provider at the app root.

Setup

Call createContractSocket once with your server URL. This returns the hooks bound to that URL.

import { createContractSocket } from "@ivancerovina/contracts-react";

export const { useContractSocket, useContractSocketEvent } =
  createContractSocket("http://localhost:3000");

Defining events in contracts

Events are defined in the ws array of a contract. Each event is either a bare Zod schema (no ack) or { data, ack } for acknowledgement support.

import { createContract } from "@ivancerovina/contracts";
import { z } from "zod";

const taskSchema = z.object({
  id: z.string().uuid(),
  title: z.string(),
  status: z.enum(["todo", "in_progress", "done"]),
});

export const TaskContract = createContract({
  name: "Tasks",
  description: "Task management",
  baseRoute: "/tasks",
  errors: {},
  routes: { /* ... */ },
  ws: [
    {
      namespace: "/tasks",
      serverEvents: {
        // Bare schema — no ack
        taskCreated: taskSchema,

        // Ack without args
        taskUpdated: { data: taskSchema, ack: z.undefined() },

        // Ack with typed response
        requestSync: {
          data: z.object({ since: z.string().datetime() }),
          ack: z.object({ synced: z.boolean(), count: z.number() }),
        },
      },
      clientEvents: {
        subscribeToProject: z.object({ projectId: z.string().uuid() }),
        unsubscribeFromProject: z.object({ projectId: z.string().uuid() }),
      },
    },
  ],
});

useContractSocket

Low-level hook. Returns a typed socket handle with on, emit, connected, and the raw socket.

function TaskList() {
  const tasks = useContractSocket(TaskContract, "/tasks");
  //                                            ^ autocompletes to contract namespace strings

  useEffect(() => {
    const unsub = tasks.on("taskCreated", (event) => {
      //                    ^ autocompletes to server event names
      console.log(event.data.title);
      //          ^ typed: { id: string; title: string; status: ... }
    });
    return unsub;
  }, [tasks.on]);

  // Emit client events — payload is typed
  tasks.emit("subscribeToProject", { projectId: "abc-123" });

  // Connection state
  if (tasks.connected) { /* ... */ }

  return null;
}

on() handler argument

The handler receives an object, not raw data:

| Event definition | Handler argument | |-----------------|-----------------| | taskCreated: taskSchema | { data: Task } | | taskUpdated: { data: taskSchema, ack: z.undefined() } | { data: Task; ack: () => void } | | requestSync: { data: ..., ack: responseSchema } | { data: ...; ack: (response: Response) => void } |

ack only exists on the object when the event definition includes it.

useContractSocketEvent

Subscribes to a single server event with automatic lifecycle management and Zod validation. No useEffect boilerplate needed.

function TaskFeed() {
  // No ack — event has { data }
  useContractSocketEvent(TaskContract, "/tasks", "taskCreated", (event) => {
    console.log(event.data.title);
    //          ^ Zod-validated and typed
  });

  return <div>...</div>;
}

Ack support

// Ack without params
useContractSocketEvent(TaskContract, "/tasks", "taskUpdated", (event) => {
  console.log(event.data.title);
  event.ack(); // no args
});

// Ack with typed params
useContractSocketEvent(TaskContract, "/tasks", "requestSync", (event) => {
  console.log(event.data.since);
  event.ack({ synced: true, count: 42 });
  //         ^ typed: { synced: boolean; count: number }
});

Emitting from the handler

The handler receives event.socket — the full typed socket for the namespace. Use it to emit client events without needing a separate useContractSocket call.

useContractSocketEvent(TaskContract, "/tasks", "taskCreated", (event) => {
  console.log(event.data.title);

  // Emit from inside the handler
  event.socket.emit("subscribeToProject", { projectId: "abc-123" });
  //                 ^ autocompletes to client event names

  // Full socket access
  event.socket.connected;
  event.socket.on("taskUpdated", (e) => { /* ... */ });
});

Connection lifecycle

Connections are managed by a Jotai atomFamily keyed by namespace:

  • First component subscribing to a namespace opens the WebSocket connection
  • Multiple components using the same namespace share one connection
  • Last component unmounting disconnects the socket and removes it from the cache

No manual connect/disconnect needed.

Exported types

| Type | Description | |------|-------------| | WsNamespaces<C> | Union of namespace strings from a contract | | FindNamespace<C, N> | Extract a specific NamespaceDefinition by namespace string | | ServerEvents<NS> | Union of server event names from a namespace | | EventHandlerArg<E> | The typed { data, ack? } object for an event | | TypedSocket<NS> | The socket handle with typed on, emit, connected, socket |

Scripts

pnpm build       # Build with tsdown
pnpm dev         # Watch mode
pnpm lint        # Biome check