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

@pydantic-socketio/codegen

v0.1.0

Published

Generate native Socket.IO TypeScript event types from Pydantic-SocketIO contracts

Readme

TypeScript types for Pydantic-SocketIO

@pydantic-socketio/codegen turns a Pydantic-SocketIO AsyncAPI contract into event types for the official Socket.IO TypeScript client or server. Event names, namespace paths, payloads, handler argument names, and ACKs come from the Python registrations. The generated file contains types; it does not replace Socket.IO.

Requires Pydantic-SocketIO with asyncapi_schema() and Node.js 18 or newer.

Python server → TypeScript client

Register both incoming and outgoing events, then export the contract:

# contract.py
import json
from pathlib import Path

from pydantic import BaseModel
import pydantic_socketio


class Ask(BaseModel):
    text: str


class Answer(BaseModel):
    accepted: bool


class Notice(BaseModel):
    text: str


server = pydantic_socketio.Server(async_mode="threading")
server.register_emit("notice", Notice, namespace="/chat")


@server.on("ask", namespace="/chat")
def ask(sid: str, request: Ask) -> Answer:
    server.emit("notice", Notice(text=request.text), to=sid, namespace="/chat")
    return Answer(accepted=True)


schema = pydantic_socketio.asyncapi_schema(server, title="Chat API", version="1.0.0")
Path("asyncapi.json").write_text(json.dumps(schema, indent=2))

Run python contract.py. In the TypeScript project, generate and use the types:

npm install --save-dev @pydantic-socketio/codegen
npx @pydantic-socketio/codegen asyncapi.json -o src/socketio.generated.ts
import { io, type Socket } from "socket.io-client";
import { Chat, type Notice, type Ask, type Answer } from "./socketio.generated";

const socket: Socket<Chat.ServerToClientEvents, Chat.ClientToServerEvents> = io(Chat.path);
// /chat becomes Chat; Chat.path is "/chat".

socket.on("notice", (notice: Notice) => console.log(notice.text));
socket.emit("ask", { text: "Hello" } satisfies Ask, (answer: Answer) => {
  console.log(answer.accepted);
});

The server's ask handler supplies ClientToServerEvents, its Answer return annotation supplies the ACK type, and register_emit("notice", ...) supplies ServerToClientEvents.

Python client → TypeScript server

The same command works when Python is the client. Its outgoing registrations become ClientToServerEvents; its handlers become ServerToClientEvents:

# client_contract.py
import json
from pathlib import Path

from pydantic import BaseModel
import pydantic_socketio


class Ask(BaseModel):
    text: str


class Answer(BaseModel):
    accepted: bool


class Notice(BaseModel):
    text: str


client = pydantic_socketio.Client()
client.register_emit("ask", Ask, namespace="/chat", ack_type=Answer)


@client.on("notice", namespace="/chat")
def notice(message: Notice) -> Answer:
    print(message.text)
    return Answer(accepted=True)


schema = pydantic_socketio.asyncapi_schema(client, title="Chat API", version="1.0.0")
Path("asyncapi.json").write_text(json.dumps(schema, indent=2))

Run python client_contract.py, then run the same npx @pydantic-socketio/codegen asyncapi.json -o src/socketio.generated.ts command. On the TypeScript side, use the generated interfaces with the official server:

import { Server, type Namespace } from "socket.io";
import { Chat } from "./socketio.generated";

const server = new Server(3000);
const chat: Namespace<Chat.ClientToServerEvents, Chat.ServerToClientEvents> =
  server.of(Chat.path);
chat.on("connection", (socket) => {
  socket.on("ask", (request, ack) => {
    console.log(request.text); // Ask.text is a string
    ack?.({ accepted: true }); // Answer
    socket.emit("notice", { text: "Received" }, (answer) => {
      console.log(answer.accepted); // boolean
    });
  });
});

A Python client can connect to that server and call ask with response_model=Answer to validate the ACK at runtime. The generated TypeScript describes both directions; no second generator or wrapper is needed.

Namespaces and regeneration

/ becomes Root, /chat becomes Chat, and /admin becomes Admin. These are generated TypeScript namespaces containing path, ClientToServerEvents, and ServerToClientEvents; they are not Socket.IO server instances.

On a TypeScript client, io(Root.path) and io(Chat.path) return separate namespace sockets, which normally share one underlying connection. On a TypeScript server, use one Server and obtain namespace handles with server.of(path). If the Python contract covers both / and /chat, the server can type them separately:

import { Server, type Namespace } from "socket.io";
import { Root, Chat } from "./socketio.generated";

const server = new Server<Root.ClientToServerEvents, Root.ServerToClientEvents>(3000);
server.on("connection", (socket) => { /* events in / */ });
const chat: Namespace<Chat.ClientToServerEvents, Chat.ServerToClientEvents> =
  server.of(Chat.path);
chat.on("connection", (socket) => { /* events in /chat */ });

Unscoped outgoing registrations also appear under Unscoped; a registration for a specific namespace overrides that fallback there.

Named Pydantic models are exported as named TypeScript declarations such as Ask, Answer, and Notice. Primitive event arguments stay inline, and Python handler argument names appear in tuple labels. If two different models share a name, codegen adds event context so their types remain distinct. The JSON contract is sufficient to understand the generated file without Python source access.

After changing event names, models, namespaces, or ACK types, export asyncapi.json again, rerun codegen, and type-check the TypeScript project. Use --help for CLI syntax.