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

@api.global/typedsocket

v7.1.0

Published

A library for creating typed WebSocket connections, supporting bi-directional communication with type safety.

Readme

@api.global/typedsocket

Typed request/response communication over WebSockets with one peer-scoped transport for JSON RPC and ordered virtual-stream-v1 byte streams. TypedSocket 7 integrates TypedRequest 7 with SmartServe 5.1.1, enforces an exact package-major handshake, and binds every server operation to the physical peer and routing surface selected during upgrade.

Issue Reporting and Security

For reporting bugs, issues, or security vulnerabilities, please visit community.foss.global/. This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a code.foss.global/ account to submit Pull Requests directly.

Install

pnpm add @api.global/typedsocket @api.global/typedrequest @api.global/typedrequest-interfaces

Server applications also need SmartServe:

pnpm add @push.rocks/smartserve

TypedSocket 7 requires @api.global/typedrequest 7, @api.global/typedrequest-interfaces 7, and @push.rocks/smartserve 5.1.1 or newer within major 5. These package majors form one transport contract and must not be mixed with earlier router or stream APIs.

Version 7 transport model

Each physical WebSocket peer has one always-on TypedSocket transport:

  • text frames carry bidirectional TypedRequest envelopes;
  • binary frames carry the same peer's virtual-stream-v1 streams;
  • SmartServe fixes the peer's routingSurface and transportOwner during upgrade;
  • the client and server must complete the exact TypedSocket package-major handshake before application requests or streams are admitted;
  • client connection restoration runs after the handshake and before desired tags and the connected state are published.

There are no optional native-byte or native-message capability modes in version 7. The v6 nativeBytes, native-byte-v1, native-message-v1, binary-message channel, and capability-mode APIs are not part of the v7 public surface. There is also no TypedSocket.fromSmartServe() attachment shortcut: server composition must happen before SmartServe is constructed.

Define shared contracts

TypedSocket uses ordinary TypedRequest interfaces. VirtualStreams use the transport-neutral TypedRequest 7 types:

import type {
  ITypedRequest,
  TVirtualStream,
  implementsTR,
} from '@api.global/typedrequest-interfaces';

export interface IGreetRequest extends implementsTR<ITypedRequest, IGreetRequest> {
  method: 'greet';
  request: { name: string };
  response: { message: string };
}

export interface IUploadRequest extends implementsTR<ITypedRequest, IUploadRequest> {
  method: 'upload';
  request: {
    stream: TVirtualStream<'send'>;
  };
  response: {
    storedBytes: number;
  };
}

export interface IDownloadRequest extends implementsTR<ITypedRequest, IDownloadRequest> {
  method: 'download';
  request: { objectId: string };
  response: {
    // Direction is local to the requester. The server handler sees 'send'.
    stream: TVirtualStream<'receive'>;
  };
}

export interface IRestoreSessionRequest
  extends implementsTR<ITypedRequest, IRestoreSessionRequest> {
  method: 'restoreSession';
  request: { token: string };
  response: { restored: true };
}

TypedHandler reverses stream directions at the handler boundary. An upload declared as requester-local send reaches the server handler as local receive; a download declared as requester-local receive is created by the handler as local send.

Server setup with SmartServe 5.1.1

Construction order is part of the transport contract:

  1. Create and populate the application TypedRouter.
  2. Call TypedSocket.createServer().
  3. Obtain the generated transport routing surface with getServerRoutingSurface().
  4. Construct SmartServe with that routing surface and the exact webSocketTransportOwner object.
  5. Call attachSmartServe().
  6. Start SmartServe.
import { TypedSocket } from '@api.global/typedsocket';
import { TypedHandler, TypedRouter } from '@api.global/typedrequest';
import { SmartServe } from '@push.rocks/smartserve';

const applicationRouter = new TypedRouter();

applicationRouter.addTypedHandler(
  new TypedHandler<IGreetRequest>('greet', async ({ name }) => ({
    message: `Hello, ${name}!`,
  })),
);

const typedSocket = TypedSocket.createServer(applicationRouter, {
  onServerConnectionReady: (connection) => {
    typedSocket.setServerTag(connection, 'application-client');
    return undefined;
  },
});

const smartServe = new SmartServe({
  port: 3000,
  websocket: {
    typedRouter: typedSocket.getServerRoutingSurface(applicationRouter),
    transportOwner: typedSocket.webSocketTransportOwner,
  },
});

typedSocket.attachSmartServe(smartServe);
await smartServe.start();

Do not pass applicationRouter directly to websocket.typedRouter. createServer() creates a distinct routing surface that composes the private TypedSocket protocol before the application router. SmartServe must bind that returned surface and the exact transport-owner identity to the peer.

onServerConnectionReady(connection) may synchronously assign protected tags or other connection-local state after the exact handshake response has been settled. It must return undefined; returning any other value, including a Promise or custom thenable, or throwing closes the connection before readiness is published.

Multiple isolated routing surfaces

One TypedSocket can compose multiple application routers without making them reachable from one another. Resolve the corresponding generated surface during upgrade:

const publicRouter = new TypedRouter();
const adminRouter = new TypedRouter();
const typedSocket = TypedSocket.createServer([publicRouter, adminRouter]);

const smartServe = new SmartServe({
  port: 3000,
  authorityValidation: 'strict',
  websocket: {
    resolveTypedRouter: (context) => {
      if (context.url.hostname === 'api.example.com') {
        return typedSocket.getServerRoutingSurface(publicRouter);
      }
      if (context.url.hostname === 'admin.example.com') {
        return typedSocket.getServerRoutingSurface(adminRouter);
      }
      return undefined;
    },
    transportOwner: typedSocket.webSocketTransportOwner,
  },
});

typedSocket.attachSmartServe(smartServe);
await smartServe.start();

SmartServe rejects an upgrade when resolveTypedRouter() returns undefined. typedRouter and resolveTypedRouter are mutually exclusive, as are transportOwner and resolveTransportOwner.

Client setup

The client router handles server-initiated requests. createClient() resolves only after the package-major handshake, optional connection restoration, and desired-tag reconciliation succeed.

import { TypedHandler, TypedRouter } from '@api.global/typedrequest';
import { TypedSocket } from '@api.global/typedsocket';

const clientRouter = new TypedRouter();

clientRouter.addTypedHandler(
  new TypedHandler<IGreetRequest>('greet', async ({ name }) => ({
    message: `Hello from the client, ${name}!`,
  })),
);

const client = await TypedSocket.createClient(
  clientRouter,
  'https://api.example.com',
  {
    autoReconnect: true,
    maxRetries: 20,
    initialBackoffMs: 1_000,
    maxBackoffMs: 30_000,
  },
);

const response = await client
  .createTypedRequest<IGreetRequest>('greet')
  .fire({ name: 'Ada' });

Use TypedSocket.useWindowLocationOriginUrl() for same-origin browser connections. Remote connections must use https: or wss:. Plain http: and ws: are restricted to loopback hosts. URLs containing credentials or fragments are rejected, and lifecycle logs redact paths and query strings.

Restoring authenticated connection state

restoreConnection runs after the version handshake and before tags or readiness. Its request factory is deadline-bound and becomes invalid when the callback finishes:

declare const serverUrl: string;
declare const currentSessionToken: string;

const client = await TypedSocket.createClient(clientRouter, serverUrl, {
  restoreConnection: async ({ createTypedRequest, abortSignal }) => {
    if (abortSignal.aborted) return;
    await createTypedRequest<IRestoreSessionRequest>('restoreSession').fire(
      { token: currentSessionToken },
    );
  },
});

A TypedSocketHandshakeError is terminal for that client startup. A package-major mismatch, malformed handshake envelope, handshake timeout, or binary frame before handshake completion closes the connection instead of falling back to a reduced transport.

Explicit server targets

Client requests target their server implicitly because the client owns one current physical connection. Server-initiated requests always require an explicit ISmartServeConnectionWrapper:

const target = await typedSocket.findTargetConnectionByTag('account', {
  accountId: 'account-123',
});

if (target) {
  const response = await typedSocket
    .createTypedRequest<IGreetRequest>('greet', target, {
      timeoutMs: 15_000,
    })
    .fire({ name: 'server push' });
}

Inside a server handler, bind follow-up work to the request's exact trusted peer:

applicationRouter.addTypedHandler(
  new TypedHandler<IGreetRequest>('greet', async ({ name }, tools) => {
    const target = typedSocket.getServerConnectionForRequest(tools);
    typedSocket.setServerTag(target, 'authenticated', { subject: 'user-123' });
    return { message: `Hello, ${name}!` };
  }),
);

findTargetConnection(), findAllTargetConnections(), and their tag variants return only live peers attached to this TypedSocket's generated routing surfaces. There is no implicit single-peer server fallback in v7.

VirtualStreams

TypedSocket 7 supplies TypedRequest 7's IVirtualStreamTransport for each handshake-ready physical peer. TypedRequest serializes only the JSON-compatible descriptor in the parent envelope; ordered Uint8Array chunks travel as bounded binary frames on that exact peer.

All stream facades expose protocol, direction, streamId, optional contentType and integrity, opened, completion, closed, and abort(). Senders add send(), writable, and close(). Receivers add receive(), readable, accept(), and reject().

receive() returns one complete logical chunk at a time and undefined at graceful EOF. The receiver must call accept() after draining EOF. completion resolves with the shared acceptance receipt; abnormal termination rejects it. Direct receive() and readable consumption are mutually exclusive.

Client-created streams with manager registrations

Application-level client streams use the advanced manager registration API, then bind the registration to TypedRequest's public facade:

import { VirtualStream } from '@api.global/typedrequest';

const transport = client.virtualStreams.getClientTransport();
if (!transport) {
  throw new Error('TypedSocket client transport is not connected');
}

const registration = client.virtualStreams.createRegistration({
  creatorDirection: 'send',
  contentType: 'application/octet-stream',
});

const stream = VirtualStream.fromRegistration({
  transport,
  registration,
});

const request = client.createTypedRequest<IUploadRequest>('upload');
const responsePromise = request.fire({ stream });

await stream.opened;
await stream.send(new Uint8Array([1, 2, 3]));
await stream.close();

const response = await responsePromise;

Client registrations do not take a peer target: the manager binds them to the current handshake-ready client generation. Registration is synchronous and silent. Its descriptor capability expires if it is not consumed, and TypedRequest owns disposal after the facade is created. Do not hand-build descriptors or reuse them across connections.

The matching server handler receives a requester-local send stream as local receive:

applicationRouter.addTypedHandler(
  new TypedHandler<IUploadRequest>('upload', async ({ stream }) => {
    let storedBytes = 0;
    while (true) {
      const chunk = await stream.receive();
      if (chunk === undefined) break;
      storedBytes += chunk.byteLength;
    }
    await stream.accept();
    return { storedBytes };
  }),
);

Server-created streams and the authorization facade

Server application code should create streams through TypedSocket.createVirtualStream(). This facade requires an exact attached target and a configured virtualStreamAuthorizationAdapter; it synchronously binds application authorization before publishing a descriptor.

interface IStreamAuthorization {
  subject: string;
  objectId: string;
  revision: string;
}

declare function isStreamAuthorityCurrent(
  authority: IStreamAuthorization,
  operation: 'open' | 'chunk' | 'accept' | 'reject',
): Promise<boolean>;

const typedSocket = TypedSocket.createServer(applicationRouter, {
  virtualStreamAuthorizationAdapter: {
    bind: (authorization, context) => {
      const authority = authorization as IStreamAuthorization;
      if (!authority.subject || !authority.objectId || !authority.revision) {
        throw new Error('Invalid stream authorization');
      }
      const target = context.target;

      return {
        revalidate: async ({ operation, connection, abortSignal }) => {
          if (
            abortSignal.aborted
            || connection.side !== 'server'
            || connection.peer !== target
          ) return false;
          return await isStreamAuthorityCurrent(authority, operation);
        },
      };
    },
  },
});

bind() must return synchronously and must provide revalidate(context). Revalidation runs with the exact connection binding, operation (open, chunk, accept, or reject), deadline, and abort signal. Return literal true only while the application authority remains current.

declare function loadBoundedObjectChunks(
  objectId: string,
): AsyncIterable<Uint8Array>;

applicationRouter.addTypedHandler(
  new TypedHandler<IDownloadRequest>('download', async ({ objectId }, tools) => {
    const target = typedSocket.getServerConnectionForRequest(tools);
    const stream = typedSocket.createVirtualStream({
      target,
      creatorDirection: 'send',
      contentType: 'application/octet-stream',
      authorization: {
        subject: 'user-123',
        objectId,
        revision: 'revision-7',
      } satisfies IStreamAuthorization,
    });

    const production = (async () => {
      await stream.opened;
      for await (const chunk of loadBoundedObjectChunks(objectId)) {
        await stream.send(chunk);
      }
      await stream.close();
    })();
    void production.catch((error) => stream.abort(error).catch(() => undefined));

    return { stream };
  }),
);

Finite streams may include { algorithm: 'sha256', byteLength, digest } integrity metadata. Open-ended streams omit integrity. Capabilities are opaque, single-use, peer-scoped, generation-scoped, and short-lived.

Connection tags

Client tag mutation is default-deny. Declare exact rules on the server:

const typedSocket = TypedSocket.createServer(applicationRouter, {
  clientTagPolicy: {
    authorizationTimeoutMs: 2_000,
    rules: [{
      name: 'workspace',
      owner: 'client',
      validateAndAuthorize: ({ payload, operation, abortSignal }) => {
        if (abortSignal.aborted) return false;
        if (operation === 'remove') return true;
        return typeof payload === 'object'
          && payload !== null
          && typeof Reflect.get(payload, 'workspaceId') === 'string';
      },
    }],
  },
});
await client.setTag('workspace', { workspaceId: 'workspace-123' });
await client.removeTag('workspace');

Use setServerTag() and removeServerTag() for authentication, roles, registration state, and other server-owned metadata. A server-owned name remains protected from client overwrite after removal. Desired client tags are reconciled after reconnect only after restoreConnection succeeds.

Do not use a universal allClients broadcast tag. Assign a dedicated application tag and target only clients that implement the corresponding server-initiated method.

Lifecycle, limits, and diagnostics

  • statusSubject publishes new, connecting, connected, disconnected, and reconnecting transitions.
  • diagnosticsSubject publishes bounded structured events for invariant closes, peer rejection, reconnect scheduling or exhaustion, and tag denial. Subscribers own unsubscription; the subject does not complete.
  • stop() disables client reconnect, rejects pending work, closes streams, and releases router registrations. Server stop() detaches TypedSocket state and composition but does not stop SmartServe.
  • Request timeoutMs and abortSignal are supported on both sides. Server requests are cancelled on target disconnect or server stop.
  • Client limits may lower package ceilings but cannot raise them. Untrusted network deployments should lower text-frame and queue ceilings to match the application protocol.
  • The stream transport bounds connections, active streams, logical chunk size, queued chunks and bytes, raw frames, outbound frames, revalidations, arrival accounting, tombstones, capability lifetime, progress time, and cleanup time.
  • Invalid framing, overflow, integrity failure, authority revocation, handshake failure, and timeout fail closed. Physical-peer identity and raw-frame settlement identity are never inferred from caller-controlled payloads.

Selected stream defaults are 32 KiB physical frames, 4 MiB logical chunks, 32 active streams per connection, a 10-second handshake and capability deadline, a 30-second progress deadline, and a 5-second revalidation deadline. Root exports provide the principal package ceilings and timeout constants.

Public API summary

TypedSocket

| API | Side | Purpose | | --- | --- | --- | | TypedSocket.createClient(router, url, options?) | client | Connects, handshakes, restores connection state, and reconciles tags. | | TypedSocket.createServer(routerOrRouters, options?) | server | Composes private protocol and application routers before SmartServe construction. | | getServerRoutingSurface(applicationRouter?) | server | Returns the exact generated router SmartServe must bind during upgrade. | | attachSmartServe(smartServe) | server | Attaches lifecycle, authority guards, and peer-scoped stream resolvers. | | createTypedRequest(method, target?, options?) | both | Creates a TypedRequest; server calls require an explicit target. | | createVirtualStream(options) | server | Creates an exact authorized stream facade for one attached peer. | | getServerConnectionForRequest(tools) | server | Resolves the exact trusted physical peer for an incoming request. | | setTag() / removeTag() | client | Mutates an explicitly allowed client-owned tag. | | setServerTag() / removeServerTag() | server | Maintains protected server-owned peer metadata. | | findTargetConnection*() / findAllTargetConnections*() | server | Finds live attached targets by predicate or tag. | | getStatus() | both | Returns the current connection status. | | stop() | both | Releases all TypedSocket-owned lifecycle state. |

virtualStreams

VirtualStreamManager is the peer-scoped transport manager. Client applications may use getClientTransport() and createRegistration() for explicit creator registrations. getStats() exposes bounded transport accounting. Server registration is not exposed on the manager; server applications must use the authorization-enforcing TypedSocket.createVirtualStream() facade.

Migration from version 6

  • Replace TypedRequest 5 and SmartServe 4 with TypedRequest 7 and SmartServe 5.1.1.
  • Remove nativeByteCapabilityMode, nativeMessageCapabilityMode, nativeBytes, message-channel APIs, and native-specific authorization adapters.
  • Replace native stream DTOs with TVirtualStream<'send' | 'receive'> from @api.global/typedrequest-interfaces.
  • Replace fromSmartServe() with the required createServer() → SmartServe construction → attachSmartServe() order.
  • Pass getServerRoutingSurface(applicationRouter) to SmartServe, not the application router itself.
  • Always pass an explicit server target to createTypedRequest().
  • Configure virtualStreamAuthorizationAdapter and use createVirtualStream() for server-created streams.
  • Treat a package-major handshake failure as terminal; there is no JSON-only or capability-disabled fallback.

License and Legal Information

This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the license.md file.

Please note: The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.

Trademarks

This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.

Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.

Company Information

Task Venture Capital GmbH Registered at District Court Bremen HRB 35230 HB, Germany

For any legal inquiries or further information, please contact us via email at [email protected].

By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.