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

@skiesjs/socketio

v0.1.0

Published

Explicit Socket.IO event contracts and Result acknowledgements for Skies Node.js applications.

Readme

@skiesjs/socketio

Explicit Socket.IO contracts and registration for Result-returning Skies handlers. The adapter is opt-in, uses plain TypeScript objects and functions, and has no decorators, discovery, DI container, or base class.

Install

npm install @skiesjs/socketio socket.io zod

socket.io is a peer dependency (^4.8.3). The package runs on Node.js 24 or newer and is strict NodeNext ESM.

Define and register an event

import { createServer } from "node:http";
import { AccessTokens } from "@skiesjs/auth";
import { Result } from "@skiesjs/core";
import {
  accessTokenAuthentication,
  createSocketIoAdapter,
  defineSocketEvent,
} from "@skiesjs/socketio";
import { Server } from "socket.io";
import { z } from "zod";

const updateWallet = defineSocketEvent({
  operationId: "UpdateWallet",
  event: "wallet:update",
  auth: "required",
  payload: z.object({ walletId: z.string().uuid(), label: z.string().trim().min(1) }),
  output: z.object({ walletId: z.string().uuid(), label: z.string() }),
});

const http = createServer();
const io = new Server(http);
const accessTokens = new AccessTokens(process.env.JWT_SECRET!, "my-app", "my-app-api");
const adapter = createSocketIoAdapter(io, {
  authentication: accessTokenAuthentication(accessTokens),
  onError: (error, context) => logger.error({ error, ...context }),
});

const registration = adapter.register(updateWallet, async (input, { currentUser, signal }) => {
  // input is Zod-decoded, currentUser is non-optional, and no Socket.IO or HTTP object enters the domain.
  return updateWalletLabel(input, currentUser, signal); // Result<{ walletId: string; label: string }>
});

Registration is explicit and rejects duplicate operationId or event values on the same Socket.IO server. Both identifiers are released by registration.remove(). adapter.remove() removes all event listeners, aborts in-flight handlers, releases collision keys, and makes the installed authentication middleware inert. Both removal methods are idempotent; neither closes the application-owned server.

Authentication

Every contract declares auth: "optional" | "required". Authentication is not inferred from a marker: a required contract cannot register unless the adapter receives accessTokenAuthentication(accessTokens). That factory installs real Socket.IO connection middleware which verifies a provided JWT with AccessTokens.verify.

Clients send the raw token in the handshake:

import { io } from "socket.io-client";

const socket = io(url, { auth: { accessToken } });

A missing token keeps the connection anonymous so optional and required events can share a namespace. An optional handler receives CurrentUser | undefined; a required handler receives CurrentUser and a missing identity is acknowledged with the canonical auth.invalid_access_token failure before payload validation. A credential that is present but blank, malformed, expired, or invalid rejects the handshake; connect_error.data is the same failure envelope. Identity lives only in the authentication boundary and handler context—never in request/response globals.

Payload and acknowledgement contract

Each event accepts exactly one payload and one acknowledgement callback. Zod 4 parses the inbound value; issues become Errors.validation(...) field failures such as payload.walletId and validation.invalid_format. A custom Zod issue can supply params.skiesCode to override the default code. Fields are sorted for a deterministic wire response.

Handlers return Result<T> (or a promise of one). The acknowledgement preserves that discriminated union:

// success
{ ok: true, value: { walletId: "...", label: "Primary" } }

// expected or validation failure
{
  ok: false,
  error: {
    kind: "Validation",
    code: "validation.failed",
    message: "Validation failed",
    fields: [{ field: "payload.label", code: "validation.too_small", message: "..." }]
  }
}

Successful values are validated and encoded through the contract's output schema, including Zod codecs. The adapter owns the acknowledgement and wraps it as once-only, so domain handlers cannot double-send. Events without an acknowledgement are reported to onError; extra arguments receive a canonical arity validation failure.

Errors, disconnects, and ownership

Expected domain failures stay in Result.fail. Unexpected handler throws, invalid success output, transport acknowledgement failures, and protocol errors are passed to the required onError boundary with only operationId, event, and socketId; they are not mislabeled as expected Internal results.

Every invocation receives its own AbortSignal. A socket disconnect or explicit removal aborts all of that registration's in-flight handlers. Completion after disconnect is never acknowledged. Throwing the signal's AbortError is treated as cancellation; other unexpected errors still reach onError.

The application owns the HTTP server and Socket.IO Server. Removing this adapter does not close either.